Merge changes I58da4785,I6facd64e,Ifdb5da86

* changes:
  Replace gr-diff-comment-thread-group with div
  Move gr-diff-comment-thread factory to diff-host
  Move the actual comment element creation to host
This commit is contained in:
Ole Rehmsen
2018-11-12 07:56:41 +00:00
committed by Gerrit Code Review
19 changed files with 554 additions and 733 deletions

View File

@@ -20,10 +20,10 @@
// Prevent redefinition.
if (window.GrDiffBuilderBinary) { return; }
function GrDiffBuilderBinary(diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl) {
GrDiffBuilder.call(this, diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl);
function GrDiffBuilderBinary(diff, patchRange, commentThreadEls, prefs,
outputEl) {
GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
outputEl);
}
GrDiffBuilderBinary.prototype = Object.create(GrDiffBuilder.prototype);

View File

@@ -22,10 +22,10 @@
const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|jpeg|jpg|png|tiff|webp)$/;
function GrDiffBuilderImage(diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, baseImage, revisionImage) {
GrDiffBuilderSideBySide.call(this, diff, comments, parentIndex, changeNum,
path, projectName, prefs, outputEl, []);
function GrDiffBuilderImage(diff, patchRange, commentThreadEls, prefs,
outputEl, baseImage, revisionImage) {
GrDiffBuilderSideBySide.call(this, diff, patchRange, commentThreadEls,
prefs, outputEl, []);
this._baseImage = baseImage;
this._revisionImage = revisionImage;
}

View File

@@ -20,10 +20,10 @@
// Prevent redefinition.
if (window.GrDiffBuilderSideBySide) { return; }
function GrDiffBuilderSideBySide(diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, layers) {
GrDiffBuilder.call(this, diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, layers);
function GrDiffBuilderSideBySide(diff, patchRange, commentThreadEls,
prefs, outputEl, layers) {
GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
outputEl, layers);
}
GrDiffBuilderSideBySide.prototype = Object.create(GrDiffBuilder.prototype);
GrDiffBuilderSideBySide.prototype.constructor = GrDiffBuilderSideBySide;

View File

@@ -20,10 +20,10 @@
// Prevent redefinition.
if (window.GrDiffBuilderUnified) { return; }
function GrDiffBuilderUnified(diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, layers) {
GrDiffBuilder.call(this, diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, layers);
function GrDiffBuilderUnified(diff, patchRange, commentThreadEls, prefs,
outputEl, layers) {
GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
outputEl, layers);
}
GrDiffBuilderUnified.prototype = Object.create(GrDiffBuilder.prototype);
GrDiffBuilderUnified.prototype.constructor = GrDiffBuilderUnified;

View File

@@ -16,9 +16,10 @@ limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-comment-thread-group/gr-diff-comment-thread-group.html">
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
<link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
<link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html">
@@ -131,6 +132,10 @@ limitations under the License.
return this.queryEffectiveChildren('#diffTable');
},
get _commentThreadElements() {
return this.queryAllEffectiveChildren('.comment-thread');
},
observers: [
'_groupsChanged(_groups.splices)',
],
@@ -166,7 +171,8 @@ limitations under the License.
// Stop the processor and syntax layer (if they're running).
this.cancel();
this._builder = this._getDiffBuilder(this.diff, comments, prefs);
this._builder = this._getDiffBuilder(
this.diff, comments.meta.patchRange, prefs);
this.$.processor.context = prefs.context;
this.$.processor.keyLocations = this._getKeyLocations(comments,
@@ -290,7 +296,7 @@ limitations under the License.
throw Error(`Invalid preference value: ${pref}`);
},
_getDiffBuilder(diff, comments, prefs) {
_getDiffBuilder(diff, patchRange, prefs) {
if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
this._handlePreferenceError('tab size');
return;
@@ -303,22 +309,21 @@ limitations under the License.
let builder = null;
if (this.isImageDiff) {
builder = new GrDiffBuilderImage(diff, comments, this.parentIndex,
this.changeNum, this.path, this.projectName, prefs,
this.diffElement, this.baseImage, this.revisionImage);
builder = new GrDiffBuilderImage(diff, patchRange,
this._commentThreadElements, prefs, this.diffElement,
this.baseImage, this.revisionImage);
} else if (diff.binary) {
// If the diff is binary, but not an image.
return new GrDiffBuilderBinary(diff, comments, this.parentIndex,
this.changeNum, this.path, this.projectName, prefs,
this.diffElement);
return new GrDiffBuilderBinary(diff, patchRange,
this._commentThreadElements, prefs, this.diffElement);
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
builder = new GrDiffBuilderSideBySide(diff, comments,
this.parentIndex, this.changeNum, this.path, this.projectName,
prefs, this.diffElement, this._layers);
builder = new GrDiffBuilderSideBySide(diff, patchRange,
this._commentThreadElements, prefs, this.diffElement,
this._layers);
} else if (this.viewMode === DiffViewMode.UNIFIED) {
builder = new GrDiffBuilderUnified(diff, comments, this.parentIndex,
this.changeNum, this.path, this.projectName, prefs,
this.diffElement, this._layers);
builder = new GrDiffBuilderUnified(diff, patchRange,
this._commentThreadElements, prefs, this.diffElement,
this._layers);
}
if (!builder) {
throw Error('Unsupported diff view mode: ' + this.viewMode);

View File

@@ -96,14 +96,10 @@
*/
const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
function GrDiffBuilder(diff, comments, parentIndex, changeNum, path,
projectName, prefs, outputEl, layers) {
function GrDiffBuilder(diff, patchRange, commentThreadEls, prefs,
outputEl, layers) {
this._diff = diff;
this._comments = comments;
this._parentIndex = parentIndex;
this._changeNum = changeNum;
this._path = path;
this._projectName = projectName;
this._patchRange = patchRange;
this._prefs = prefs;
this._outputEl = outputEl;
this.groups = [];
@@ -125,25 +121,7 @@
}
}
if (!this._comments) {
this._threadEls = [];
return;
}
const allComments = [];
for (const side of [GrDiffBuilder.Side.LEFT, GrDiffBuilder.Side.RIGHT]) {
// This is needed by the threading.
for (const comment of this._comments[side]) {
comment.__commentSide = side;
}
allComments.push(...this._comments[side]);
}
const threads = this._createThreads(allComments);
this._threadEls = threads.map(thread => {
return Gerrit.createThreadElement(
thread, this._parentIndex, this._changeNum,
this._path, this._projectName);
});
this._threadEls = commentThreadEls;
}
GrDiffBuilder.GroupType = {
@@ -396,88 +374,27 @@
return button;
};
/**
* @param {Array<Object>} comments
*/
GrDiffBuilder.prototype._createThreads = function(comments) {
const sortedComments = comments.slice(0).sort((a, b) => {
if (b.__draft && !a.__draft ) { return 0; }
if (a.__draft && !b.__draft ) { return 1; }
return util.parseDate(a.updated) - util.parseDate(b.updated);
});
const threads = [];
for (const comment of sortedComments) {
// If the comment is in reply to another comment, find that comment's
// thread and append to it.
if (comment.in_reply_to) {
const thread = threads.find(thread =>
thread.comments.some(c => c.id === comment.in_reply_to));
if (thread) {
thread.comments.push(comment);
continue;
}
}
// Otherwise, this comment starts its own thread.
const newThread = {
start_datetime: comment.updated,
comments: [comment],
commentSide: comment.__commentSide,
patchNum: comment.patch_set,
rootId: comment.id || comment.__draftID,
lineNum: comment.line,
isOnParent: comment.side === 'PARENT',
};
if (comment.range) {
newThread.range = Object.assign({}, comment.range);
}
threads.push(newThread);
}
return threads;
};
/**
* Returns the patch number that new comment threads should be attached to.
*
* @param {GrDiffLine} line The line new thread will be attached to.
* @param {string=} opt_side Set to LEFT to force adding it to the LEFT side -
* will be ignored if the left is a parent or a merge parent
* @return {number} Patch set to attach the new thread to
*/
GrDiffBuilder.prototype._determinePatchNumForNewThreads = function(
patchRange, line, opt_side) {
if ((line.type === GrDiffLine.Type.REMOVE ||
opt_side === GrDiffBuilder.Side.LEFT) &&
patchRange.basePatchNum !== 'PARENT' &&
!Gerrit.PatchSetBehavior.isMergeParent(patchRange.basePatchNum)) {
return patchRange.basePatchNum;
} else {
return patchRange.patchNum;
}
};
/**
* @param {!GrDiffLine} line
* @param {!GrDiffBuilder.Side=} side The side (LEFT, RIGHT, BOTH) for which to return
* the thread group (default: BOTH).
* @param {!GrDiffBuilder.Side=} side The side (LEFT, RIGHT, BOTH) for which
* to return the thread group (default: BOTH).
* @return {!Object}
*/
GrDiffBuilder.prototype._commentThreadGroupForLine = function(
line, side = GrDiffBuilder.Side.BOTH) {
line, commentSide = GrDiffBuilder.Side.BOTH) {
const threadElsForGroup =
Gerrit.filterThreadElsForLocation(this._threadEls, line, side);
Gerrit.filterThreadElsForLocation(this._threadEls, line, commentSide);
if (!threadElsForGroup || threadElsForGroup.length === 0) {
return null;
}
const threadGroupEl =
document.createElement('gr-diff-comment-thread-group');
const threadGroupEl = document.createElement('div');
threadGroupEl.className = 'thread-group';
for (const threadEl of threadElsForGroup) {
Polymer.dom(threadGroupEl).appendChild(threadEl);
}
if (side) {
threadGroupEl.setAttribute('data-side', side);
if (commentSide) {
threadGroupEl.setAttribute('data-side', commentSide);
}
return threadGroupEl;
};

View File

@@ -62,10 +62,6 @@ limitations under the License.
let builder;
let sandbox;
const LINE_FEED_HTML = '<span class="style-scope gr-diff br"></span>';
const parentIndex = 3;
const changeNum = 4;
const path = 'some/path.ext';
const projectName = 'Awesome Project';
setup(() => {
sandbox = sinon.sandbox.create();
@@ -80,8 +76,7 @@ limitations under the License.
tab_size: 4,
};
builder = new GrDiffBuilder(
{content: []}, {left: [], right: []}, parentIndex, changeNum, path,
projectName, prefs);
{content: []}, {left: [], right: []}, [], prefs);
});
teardown(() => { sandbox.restore(); });
@@ -145,153 +140,6 @@ limitations under the License.
Gerrit.DiffSide.RIGHT), [r]);
});
test('_createThreads', () => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
line: 1,
__commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
__commentSide: 'left',
line: 1,
in_reply_to: 'sallys_confession',
},
{
id: 'new_draft',
message: 'i do not like either of you',
__commentSide: 'left',
__draft: true,
updated: '2015-12-20 15:01:20.396000000',
},
];
const actualThreads = builder._createThreads(comments);
assert.equal(actualThreads.length, 2);
assert.equal(
actualThreads[0].start_datetime, '2015-12-23 15:00:20.396000000');
assert.equal(actualThreads[0].commentSide, 'left');
assert.equal(actualThreads[0].comments.length, 2);
assert.deepEqual(actualThreads[0].comments[0], comments[0]);
assert.deepEqual(actualThreads[0].comments[1], comments[1]);
assert.equal(actualThreads[0].patchNum, undefined);
assert.equal(actualThreads[0].rootId, 'sallys_confession');
assert.equal(actualThreads[0].lineNum, 1);
assert.equal(
actualThreads[1].start_datetime, '2015-12-20 15:01:20.396000000');
assert.equal(actualThreads[1].commentSide, 'left');
assert.equal(actualThreads[1].comments.length, 1);
assert.deepEqual(actualThreads[1].comments[0], comments[2]);
assert.equal(actualThreads[1].patchNum, undefined);
assert.equal(actualThreads[1].rootId, 'new_draft');
assert.equal(actualThreads[1].lineNum, undefined);
});
test('_createThreads inherits patchNum and range', () => {
const comments = [{
id: 'betsys_confession',
message: 'i like you, jack',
updated: '2015-12-24 15:00:10.396000000',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
patch_set: 5,
__commentSide: 'left',
line: 1,
}];
expectedThreads = [
{
start_datetime: '2015-12-24 15:00:10.396000000',
commentSide: 'left',
comments: [{
id: 'betsys_confession',
message: 'i like you, jack',
updated: '2015-12-24 15:00:10.396000000',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
patch_set: 5,
__commentSide: 'left',
line: 1,
}],
patchNum: 5,
rootId: 'betsys_confession',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
lineNum: 1,
isOnParent: false,
},
];
assert.deepEqual(
builder._createThreads(comments),
expectedThreads);
});
test('_createThreads does not thread unrelated comments at same location',
() => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
__commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
__commentSide: 'left',
},
];
assert.equal(builder._createThreads(comments).length, 2);
});
test('_createThreads derives isOnParent using side from first comment',
() => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
// line: 1,
// __commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
// __commentSide: 'left',
// line: 1,
in_reply_to: 'sallys_confession',
},
];
assert.equal(builder._createThreads(comments)[0].isOnParent, false);
comments[0].side = 'REVISION';
assert.equal(builder._createThreads(comments)[0].isOnParent, false);
comments[0].side = 'PARENT';
assert.equal(builder._createThreads(comments)[0].isOnParent, true);
});
test('_createElement classStr applies all classes', () => {
const node = builder._createElement('div', 'test classes');
assert.isTrue(node.classList.contains('gr-diff'));
@@ -466,42 +314,32 @@ limitations under the License.
});
test('comment thread group creation', () => {
const l3 = {id: 'l3', line: 3, updated: '2016-08-09 00:42:32.000000000',
__commentSide: 'left', side: 'PARENT'};
const l5 = {id: 'l5', line: 5, updated: '2016-08-09 00:42:32.000000000',
__commentSide: 'left', patch_set: '2', side: 'REVISION'};
const r5 = {id: 'r5', line: 5, updated: '2016-08-09 00:42:32.000000000',
__commentSide: 'right', patch_set: '3'};
const l3 = document.createElement('div');
l3.className = 'comment-thread';
l3.setAttribute('comment-side', 'left');
l3.setAttribute('line-num', 3);
const l5 = document.createElement('div');
l5.className = 'comment-thread';
l5.setAttribute('comment-side', 'left');
l5.setAttribute('line-num', 5);
const r5 = document.createElement('div');
r5.className = 'comment-thread';
r5.setAttribute('comment-side', 'right');
r5.setAttribute('line-num', 5);
builder = new GrDiffBuilder(
{content: []}, {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: '3',
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [l3, l5],
right: [r5],
}, parentIndex, changeNum, path, projectName, prefs);
{content: []}, {basePatchNum: 'PARENT', patchNum: '3'}, [l3, l5, r5],
prefs);
function checkThreadGroupProps(threadGroupEl, patchNum,
comments) {
function checkThreadGroupProps(threadGroupEl,
expectedThreadEls) {
const threadEls = Polymer.dom(threadGroupEl).queryDistributedElements(
'gr-diff-comment-thread');
assert.equal(threadEls.length, comments.length);
for (let i=0; i < comments.length; i++) {
const threadEl = threadEls[i];
const comment = comments[i];
assert.equal(threadEl.patchNum, comment.patch_set);
assert.equal(threadEl.commentSide, comment.__commentSide);
assert.equal(threadEl.comments.length, 1);
assert.equal(threadEl.comments[0], comment);
assert.equal(threadEl.rootId, comment.id);
'.comment-thread');
assert.equal(threadEls.length, expectedThreadEls.length);
for (let i=0; i<expectedThreadEls.length; i++) {
assert.equal(threadEls[i], expectedThreadEls[i]);
}
}
@@ -509,42 +347,42 @@ limitations under the License.
line.beforeNumber = 5;
line.afterNumber = 5;
let threadGroupEl = builder._commentThreadGroupForLine(line);
checkThreadGroupProps(threadGroupEl, '3', [l5, r5]);
checkThreadGroupProps(threadGroupEl, [l5, r5]);
threadGroupEl =
builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.RIGHT);
checkThreadGroupProps(threadGroupEl, '3', [r5]);
checkThreadGroupProps(threadGroupEl, [r5]);
threadGroupEl =
builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.LEFT);
checkThreadGroupProps(threadGroupEl, '3', [l5]);
checkThreadGroupProps(threadGroupEl, [l5]);
builder._comments.meta.patchRange.basePatchNum = '1';
builder._patchRange.basePatchNum = '1';
threadGroupEl = builder._commentThreadGroupForLine(line);
checkThreadGroupProps(threadGroupEl, '3', [l5, r5]);
checkThreadGroupProps(threadGroupEl, [l5, r5]);
threadEl =
builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.LEFT);
checkThreadGroupProps(threadEl, '1', [l5]);
checkThreadGroupProps(threadEl, [l5]);
threadGroupEl =
builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.RIGHT);
checkThreadGroupProps(threadGroupEl, '3', [r5]);
checkThreadGroupProps(threadGroupEl, [r5]);
builder._comments.meta.patchRange.basePatchNum = 'PARENT';
builder._patchRange.basePatchNum = 'PARENT';
line = new GrDiffLine(GrDiffLine.Type.REMOVE);
line.beforeNumber = 5;
line.afterNumber = 5;
threadGroupEl = builder._commentThreadGroupForLine(line);
checkThreadGroupProps(threadGroupEl, '3', [l5, r5]);
checkThreadGroupProps(threadGroupEl, [l5, r5]);
line = new GrDiffLine(GrDiffLine.Type.ADD);
line.beforeNumber = 3;
line.afterNumber = 5;
threadGroupEl = builder._commentThreadGroupForLine(line);
checkThreadGroupProps(threadGroupEl, '3', [l3, r5]);
checkThreadGroupProps(threadGroupEl, [l3, r5]);
});
@@ -578,16 +416,18 @@ limitations under the License.
});
const lineOfInterest = {number: 789, leftSide: true};
assert.deepEqual(element._getKeyLocations(comments, lineOfInterest), {
left: {FILE: true, 123: true, 789: true},
right: {456: true},
});
assert.deepEqual(
element._getKeyLocations(comments, lineOfInterest), {
left: {FILE: true, 123: true, 789: true},
right: {456: true},
});
delete lineOfInterest.leftSide;
assert.deepEqual(element._getKeyLocations(comments, lineOfInterest), {
left: {FILE: true, 123: true},
right: {456: true, 789: true},
});
assert.deepEqual(
element._getKeyLocations(comments, lineOfInterest), {
left: {FILE: true, 123: true},
right: {456: true, 789: true},
});
});
suite('_isTotal', () => {
@@ -1029,7 +869,7 @@ limitations under the License.
processStub = sandbox.stub(element.$.processor, 'process')
.returns(Promise.resolve());
sandbox.stub(element, '_anyLineTooLong').returns(true);
comments = {left: [], right: []};
comments = {left: [], right: [], meta: {patchRange: undefined}};
prefs = {
line_length: 10,
show_tabs: true,
@@ -1077,6 +917,7 @@ limitations under the License.
suite('rendering', () => {
let content;
let outputEl;
let comments;
setup(done => {
const prefs = {
@@ -1104,10 +945,10 @@ limitations under the License.
});
element = fixture('basic');
outputEl = element.queryEffectiveChildren('#diffTable');
comments = {left: [], right: [], meta: {patchRange: undefined}};
sandbox.stub(element, '_getDiffBuilder', () => {
const builder = new GrDiffBuilder(
{content}, {left: [], right: []}, parentIndex, changeNum, path,
projectName, prefs, outputEl);
{content}, undefined, [], prefs, outputEl);
sandbox.stub(builder, 'addColumns');
builder.buildSectionElement = function(group) {
const section = document.createElement('stub');
@@ -1119,7 +960,7 @@ limitations under the License.
return builder;
});
element.diff = {content};
element.render({left: [], right: []}, prefs).then(done);
element.render(comments, prefs).then(done);
});
test('reporting', done => {
@@ -1144,7 +985,7 @@ limitations under the License.
});
test('addColumns is called', done => {
element.render({left: [], right: []}, {}).then(done);
element.render(comments, {}).then(done);
assert.isTrue(element._builder.addColumns.called);
});
@@ -1168,7 +1009,7 @@ limitations under the License.
test('render-start and render are fired', done => {
const dispatchEventStub = sandbox.stub(element, 'dispatchEvent');
element.render({left: [], right: []}, {}).then(() => {
element.render(comments, {}).then(() => {
const firedEventTypes = dispatchEventStub.getCalls()
.map(c => { return c.args[0].type; });
assert.include(firedEventTypes, 'render-start');
@@ -1196,7 +1037,7 @@ limitations under the License.
context: -1,
syntax_highlighting: true,
};
element.render({left: [], right: []}, prefs);
element.render(comments, prefs);
});
test('cancel', () => {
@@ -1213,6 +1054,7 @@ limitations under the License.
let builder;
let diff;
let prefs;
let comments;
setup(done => {
element = fixture('mock-diff');
@@ -1224,12 +1066,12 @@ limitations under the License.
show_tabs: true,
tab_size: 4,
};
comments = {left: [], right: [], meta: {patchRange: undefined}};
element.render(
undefined, prefs).then(() => {
builder = element._builder;
done();
});
element.render(comments, prefs).then(() => {
builder = element._builder;
done();
});
});
test('getDiffLength', () => {
@@ -1336,7 +1178,7 @@ limitations under the License.
test('_getNextContentOnSide unified left', done => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
element.render({left: [], right: []}, prefs).then(() => {
element.render(comments, prefs).then(() => {
builder = element._builder;
const startElem = builder.getContentByLine(5, 'left',
@@ -1356,7 +1198,7 @@ limitations under the License.
test('_getNextContentOnSide unified right', done => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
element.render({left: [], right: []}, prefs).then(() => {
element.render(comments, prefs).then(() => {
builder = element._builder;
const startElem = builder.getContentByLine(5, 'right',

View File

@@ -1,37 +0,0 @@
<!--
@license
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-diff-comment-thread-group">
<template>
<style include="shared-styles">
:host {
display: block;
max-width: var(--content-width, 80ch);
white-space: normal;
}
gr-diff-comment-thread + gr-diff-comment-thread {
margin-top: .2em;
}
</style>
<slot></slot>
</template>
<script src="gr-diff-comment-thread-group.js"></script>
</dom-module>

View File

@@ -1,118 +0,0 @@
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
window.Gerrit = window.Gerrit || {};
// This method will eventually move to gr-diff-host (so that gr-diff and it's
// descendants, including gr-diff-comment-thread-group, do not depend on a
// specific comment thread implementation, but can instead be used with other
// comment widgets). I cannot move it there yet, because it is still called
// from this file, and this file cannot depend on gr-diff-host. I decided to
// make it a function on the global Gerrit namespace, so that
// 1) I can move the call-side in the next change without moving this code,
// and thereby reduce the number of moving parts per commit.
// 2) To already now cut the ties to the this object - if it was an element
// method, I would probably want to use isOnParent etc. from `this`, and
// thus be required to change the code when I move it to gr-diff host.
// Making it a free function first requires me to catch any references to
// `this` and instead pass those in as parameter, which then allows me
// to move it later without any other changes, which makes the diff
// easier to read.
/**
* @param {Object} thread
* @param {number} parentIndex
* @param {number} changeNum
* @param {string} path
* @param {string} projectName
*/
window.Gerrit.createThreadElement = function(
thread, parentIndex, changeNum, path, projectName) {
const threadEl = document.createElement('gr-diff-comment-thread');
threadEl.comments = thread.comments;
threadEl.commentSide = thread.commentSide;
threadEl.isOnParent = thread.isOnParent;
threadEl.parentIndex = parentIndex;
threadEl.changeNum = changeNum;
threadEl.patchNum = thread.patchNum;
threadEl.lineNum = thread.lineNum;
const rootIdChangedListener = changeEvent => {
thread.rootId = changeEvent.detail.value;
};
threadEl.addEventListener('root-id-changed', rootIdChangedListener);
threadEl.path = path;
threadEl.projectName = projectName;
threadEl.range = thread.range;
const threadDiscardListener = e => {
const threadEl = /** @type {!Node} */ (e.currentTarget);
const parent = Polymer.dom(threadEl).parentNode;
threadEl.removeEventListener('root-id-changed', rootIdChangedListener);
threadEl.removeEventListener('thread-discard', threadDiscardListener);
Polymer.dom(parent).removeChild(threadEl);
};
threadEl.addEventListener('thread-discard', threadDiscardListener);
return threadEl;
};
Polymer({
is: 'gr-diff-comment-thread-group',
properties: {
},
get threadEls() {
return Polymer.dom(this).queryDistributedElements(
'gr-diff-comment-thread');
},
/**
* Fetch the thread element at the given range, or the range-less thread
* element on the line if no range is provided, lineNum, and side.
*
* @param {string} side
* @param {!Object=} opt_range
* @return {!Object|undefined}
*/
getThreadEl(side, opt_range) {
const threads = [].filter.call(this.threadEls,
thread => this._rangesEqual(thread.range, opt_range))
.filter(thread => thread.commentSide === side);
if (threads.length === 1) {
return threads[0];
}
},
/**
* Compare two ranges. Either argument may be falsy, but will only return
* true if both are falsy or if neither are falsy and have the same position
* values.
*
* @param {Object=} a range 1
* @param {Object=} b range 2
* @return {boolean}
*/
_rangesEqual(a, b) {
if (!a && !b) { return true; }
if (!a || !b) { return false; }
return a.startLine === b.startLine &&
a.startChar === b.startChar &&
a.endLine === b.endLine &&
a.endChar === b.endChar;
},
});
})();

View File

@@ -1,165 +0,0 @@
<!DOCTYPE html>
<!--
@license
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-comment-thread-group</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-diff-comment-thread-group.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-diff-comment-thread-group></gr-diff-comment-thread-group>
</template>
</test-fixture>
<script>
suite('gr-diff-comment-thread-group tests', () => {
let element;
let sandbox;
setup(() => {
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn() { return Promise.resolve(false); },
});
element = fixture('basic');
});
teardown(() => {
sandbox.restore();
});
test('getThreadEl', () => {
const range = {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
};
const threads = [
{
rootId: 'sallys_confession',
commentSide: 'left',
comments: [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
__commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
__commentSide: 'left',
in_reply_to: 'sallys_confession',
}, {
id: 'new_draft',
message: 'i do not like either of you',
__commentSide: 'left',
__draft: true,
in_reply_to: 'sallys_confession',
updated: '2015-12-20 15:01:20.396000000',
},
],
},
{
rootId: 'right_side_comment',
commentSide: 'right',
comments: [
{
id: 'right_side_comment',
message: 'right side comment',
__commentSide: 'right',
__draft: true,
updated: '2015-12-20 15:01:20.396000000',
},
],
}, {
rootId: 'betsys_confession',
commentSide: 'left',
range,
comments: [
{
id: 'betsys_confession',
message: 'i like you more, jack',
updated: '2015-12-24 15:00:10.396000000',
range,
__commentSide: 'left',
},
],
},
];
for (const thread of threads) {
Polymer.dom(element).appendChild(Gerrit.createThreadElement(
thread, 1, '2', 'some/path', 'some_project'));
}
flushAsynchronousOperations();
assert.deepEqual(
element.getThreadEl('right').rootId, 'right_side_comment');
assert.deepEqual(element.getThreadEl('right').comments.length, 1);
assert.deepEqual(element.getThreadEl('left').rootId, 'sallys_confession');
assert.deepEqual(element.getThreadEl('left').comments.length, 3);
assert.deepEqual(element.getThreadEl('left', range).rootId,
'betsys_confession');
assert.deepEqual(element.getThreadEl('left', range).comments.length, 1);
});
test('thread-discard handling', () => {
const threads = [
{comments: [{id: 4711}]},
{comments: [{id: 42}]},
];
for (const thread of threads) {
const threadEl = Gerrit.createThreadElement(
thread, 1, 2, 'some/path', 'Some Project');
Polymer.dom(element).appendChild(threadEl);
}
element.threadEls[0].dispatchEvent(
new CustomEvent('thread-discard', {detail: {rootId: 4711}}));
assert.equal(element.threadEls.length, 1);
assert.equal(element.threadEls[0].rootId, 42);
});
test('_rangesEqual', () => {
const range1 =
{startLine: 123, startChar: 345, endLine: 234, endChar: 456};
const range2 =
{startLine: 1, startChar: 2, endLine: 3, endChar: 4};
assert.isTrue(element._rangesEqual(null, null));
assert.isTrue(element._rangesEqual(null, undefined));
assert.isTrue(element._rangesEqual(undefined, null));
assert.isTrue(element._rangesEqual(undefined, undefined));
assert.isFalse(element._rangesEqual(range1, null));
assert.isFalse(element._rangesEqual(null, range1));
assert.isFalse(element._rangesEqual(range1, range2));
assert.isTrue(element._rangesEqual(range1, Object.assign({}, range1)));
});
});
</script>

View File

@@ -60,7 +60,11 @@ limitations under the License.
diffElement.loggedIn = false;
diffElement.patchRange = {basePatchNum: 1, patchNum: 2};
diffElement.comments = {left: [], right: []};
diffElement.comments = {
left: [],
right: [],
meta: {patchRange: undefined},
};
const setupDone = () => {
cursorElement._updateStops();
cursorElement.moveToFirstChunk();

View File

@@ -19,6 +19,7 @@ limitations under the License.
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff/gr-diff.html">
<dom-module id="gr-diff-host">
@@ -46,8 +47,7 @@ limitations under the License.
base-image="[[_baseImage]]"
revision-image=[[_revisionImage]]
blame="[[_blame]]"
diff="[[_diff]]"
parent-index="[[_parentIndex]]"></gr-diff>
diff="[[_diff]]"></gr-diff>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-reporting id="reporting" category="diff"></gr-reporting>
</template>

View File

@@ -45,13 +45,35 @@
return !!(diff.binary && (isA || isB));
}
/** @typedef {{startLine: number, startChar: number,
* endLine: number, endChar: number}} */
Gerrit.Range;
/**
* Compare two ranges. Either argument may be falsy, but will only return
* true if both are falsy or if neither are falsy and have the same position
* values.
*
* @param {Gerrit.Range=} a range 1
* @param {Gerrit.Range=} b range 2
* @return {boolean}
*/
function rangesEqual(a, b) {
if (!a && !b) { return true; }
if (!a || !b) { return false; }
return a.startLine === b.startLine &&
a.startChar === b.startChar &&
a.endLine === b.endLine &&
a.endChar === b.endChar;
}
/**
* Wrapper around gr-diff.
*
* Webcomponent fetching diffs and related data from restAPI and passing them
* to the presentational gr-diff for rendering.
*/
// TODO(oler): Move all calls to restAPI from gr-diff here.
Polymer({
is: 'gr-diff-host',
@@ -108,7 +130,10 @@
type: Boolean,
value: false,
},
comments: Object,
comments: {
type: Object,
observer: '_commentsChanged',
},
lineWrapping: {
type: Boolean,
value: false,
@@ -176,6 +201,11 @@
type: Number,
computed: '_computeParentIndex(patchRange.*)',
},
_threadEls: {
type: Array,
value: [],
},
},
behaviors: [
@@ -310,7 +340,7 @@
* @return {!Array<!HTMLElement>}
*/
getThreadEls() {
return this.$.diff.getThreadEls();
return this._threadEls;
},
/** @param {HTMLElement} el */
@@ -437,6 +467,70 @@
return isImageDiff(diff);
},
_commentsChanged(newComments) {
const allComments = [];
for (const side of [GrDiffBuilder.Side.LEFT, GrDiffBuilder.Side.RIGHT]) {
// This is needed by the threading.
for (const comment of newComments[side]) {
comment.__commentSide = side;
}
allComments.push(...newComments[side]);
}
// Currently, the only way this is ever changed here is when the initial
// comments are loaded, so it's okay performance wise to clear the threads
// and recreate them. If this changes in future, we might want to reuse
// some DOM nodes here.
this._clearThreads();
const threads = this._createThreads(allComments);
for (const thread of threads) {
const threadEl = this._createThreadElement(thread);
this._attachThreadElement(threadEl);
}
},
/**
* @param {!Array<!Object>} comments
* @return {!Array<!Object>} Threads for the given comments.
*/
_createThreads(comments) {
const sortedComments = comments.slice(0).sort((a, b) => {
if (b.__draft && !a.__draft ) { return 0; }
if (a.__draft && !b.__draft ) { return 1; }
return util.parseDate(a.updated) - util.parseDate(b.updated);
});
const threads = [];
for (const comment of sortedComments) {
// If the comment is in reply to another comment, find that comment's
// thread and append to it.
if (comment.in_reply_to) {
const thread = threads.find(thread =>
thread.comments.some(c => c.id === comment.in_reply_to));
if (thread) {
thread.comments.push(comment);
continue;
}
}
// Otherwise, this comment starts its own thread.
const newThread = {
start_datetime: comment.updated,
comments: [comment],
commentSide: comment.__commentSide,
patchNum: comment.patch_set,
rootId: comment.id || comment.__draftID,
lineNum: comment.line,
isOnParent: comment.side === 'PARENT',
};
if (comment.range) {
newThread.range = Object.assign({}, comment.range);
}
threads.push(newThread);
}
return threads;
},
/**
* @param {Object} blame
* @return {boolean}
@@ -456,44 +550,117 @@
/** @param {CustomEvent} e */
_handleCreateComment(e) {
const {threadGroupEl, lineNum, side, range, isOnParent,
patchForNewThreads} = e.detail;
const threadEl = this._getOrCreateThread(threadGroupEl, side, range,
isOnParent, patchForNewThreads);
const {lineNum, side, patchNum, isOnParent, range} = e.detail;
const threadEl = this._getOrCreateThread(patchNum, lineNum, side, range,
isOnParent);
threadEl.addOrEditDraft(lineNum, range);
this.$.reporting.recordDraftInteraction();
},
/**
* Gets or creates a comment thread from a specific thread group.
* May include a range, if the comment is a range comment.
* Gets or creates a comment thread at a given location.
* May provide a range, to get/create a range comment.
*
* @param {!Node} threadGroupEl
* @param {string} patchNum
* @param {?number} lineNum
* @param {string} commentSide
* @param {?Object} range
* @param {Gerrit.Range|undefined} range
* @param {boolean} isOnParent
* @param {string} patchForNewThreads
* @return {!Object}
*/
_getOrCreateThread(threadGroupEl, commentSide, range, isOnParent,
patchForNewThreads) {
let threadEl = threadGroupEl.getThreadEl(commentSide, range);
_getOrCreateThread(patchNum, lineNum, commentSide, range, isOnParent) {
let threadEl = this._getThreadEl(lineNum, commentSide, range);
if (!threadEl) {
threadEl = Gerrit.createThreadElement(
{
comments: [],
commentSide,
patchNum: patchForNewThreads,
range,
isOnParent,
}, this._parentIndex, this.changeNum,
this.path, this.projectName);
Polymer.dom(threadGroupEl).appendChild(threadEl);
threadEl = this._createThreadElement({
comments: [],
commentSide,
patchNum,
lineNum,
range,
isOnParent,
});
this._attachThreadElement(threadEl);
}
return threadEl;
},
_attachThreadElement(threadEl) {
this._threadEls.push(threadEl);
Polymer.dom(this.$.diff).appendChild(threadEl);
},
_clearThreads() {
for (const threadEl of this._threadEls) {
const parent = Polymer.dom(threadEl).parentNode;
Polymer.dom(parent).removeChild(threadEl);
}
this._threadEls = [];
},
_createThreadElement(thread) {
const threadEl = document.createElement('gr-diff-comment-thread');
threadEl.className = 'comment-thread';
threadEl.comments = thread.comments;
threadEl.commentSide = thread.commentSide;
threadEl.isOnParent = !!thread.isOnParent;
threadEl.parentIndex = this._parentIndex;
threadEl.changeNum = this.changeNum;
threadEl.patchNum = thread.patchNum;
threadEl.lineNum = thread.lineNum;
const rootIdChangedListener = changeEvent => {
thread.rootId = changeEvent.detail.value;
};
threadEl.addEventListener('root-id-changed', rootIdChangedListener);
threadEl.path = this.path;
threadEl.projectName = this.projectName;
threadEl.range = thread.range;
const threadDiscardListener = e => {
const threadEl = /** @type {!Node} */ (e.currentTarget);
const parent = Polymer.dom(threadEl).parentNode;
Polymer.dom(parent).removeChild(threadEl);
const i = this._threadEls.findIndex(
threadEl => threadEl.rootId == e.detail.rootId);
this._threadEls.splice(i, 1);
threadEl.removeEventListener('root-id-changed', rootIdChangedListener);
threadEl.removeEventListener('thread-discard', threadDiscardListener);
};
threadEl.addEventListener('thread-discard', threadDiscardListener);
return threadEl;
},
/**
* Gets a comment thread element at a given location.
* May provide a range, to get a range comment.
*
* @param {?number} lineNum
* @param {string} commentSide
* @param {!Gerrit.Range=} range
* @return {?Node}
*/
_getThreadEl(lineNum, commentSide, range=undefined) {
let line;
if (commentSide === GrDiffBuilder.Side.LEFT) {
line = {beforeNumber: lineNum};
} else if (commentSide === GrDiffBuilder.Side.RIGHT) {
line = {afterNumber: lineNum};
} else {
throw new Error(`Unknown side: ${commentSide}`);
}
function matchesRange(threadEl) {
const threadRange = /** @type {!Gerrit.Range} */(
JSON.parse(threadEl.getAttribute('range')));
return rangesEqual(threadRange, range);
}
const filteredThreadEls = Gerrit.filterThreadElsForLocation(
this._threadEls, line, commentSide).filter(matchesRange);
return filteredThreadEls.length ? filteredThreadEls[0] : null;
},
/**
* Take a diff that was loaded with a ignore-whitespace other than
* IGNORE_NONE, and convert delta chunks labeled as common into shared

View File

@@ -46,12 +46,41 @@ limitations under the License.
async getLoggedIn() { return getLoggedIn; },
});
element = fixture('basic');
// For reasons beyond me, fixture reuses elements, cleans out some
// stuff but not that list.
element._threadEls = [];
});
teardown(() => {
sandbox.restore();
});
test('thread-discard handling', () => {
const threads = [
{comments: [{id: 4711}]},
{comments: [{id: 42}]},
];
element._parentIndex = 1;
element.changeNum = '2';
element.path = 'some/path';
element.projectName = 'Some project';
const threadEls = threads.map(
thread => element._createThreadElement(thread));
assert.equal(threadEls.length, 2);
assert.equal(threadEls[0].rootId, 4711);
assert.equal(threadEls[1].rootId, 42);
for (const threadEl of threadEls) {
Polymer.dom(element).appendChild(threadEl);
}
threadEls[0].dispatchEvent(
new CustomEvent('thread-discard', {detail: {rootId: 4711}}));
const attachedThreads = element.queryAllEffectiveChildren(
'gr-diff-comment-thread');
assert.equal(attachedThreads.length, 1);
assert.equal(attachedThreads[0].rootId, 42);
});
test('reload() cancels before network resolves', () => {
const cancelStub = sandbox.stub(element.$.diff, 'cancel');
@@ -182,7 +211,11 @@ limitations under the License.
});
element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
element.comments = {left: [], right: []};
element.comments = {
left: [],
right: [],
meta: {patchRange: element.patchRange},
};
});
test('renders image diffs with same file name', done => {
@@ -556,13 +589,10 @@ limitations under the License.
});
});
test('delegates getThreadEls()', () => {
test('getThreadEls() returns _threadEls', () => {
const returnValue = [document.createElement('b')];
const stub = sandbox.stub(element.$.diff, 'getThreadEls')
.returns(returnValue);
element._threadEls = returnValue;
assert.equal(element.getThreadEls(), returnValue);
assert.isTrue(stub.calledOnce);
assert.equal(stub.lastCall.args.length, 0);
});
test('delegates addDraftAtLine(el)', () => {
@@ -771,15 +801,160 @@ limitations under the License.
});
});
test('_createThreads', () => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
line: 1,
__commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
__commentSide: 'left',
line: 1,
in_reply_to: 'sallys_confession',
},
{
id: 'new_draft',
message: 'i do not like either of you',
__commentSide: 'left',
__draft: true,
updated: '2015-12-20 15:01:20.396000000',
},
];
const actualThreads = element._createThreads(comments);
assert.equal(actualThreads.length, 2);
assert.equal(
actualThreads[0].start_datetime, '2015-12-23 15:00:20.396000000');
assert.equal(actualThreads[0].commentSide, 'left');
assert.equal(actualThreads[0].comments.length, 2);
assert.deepEqual(actualThreads[0].comments[0], comments[0]);
assert.deepEqual(actualThreads[0].comments[1], comments[1]);
assert.equal(actualThreads[0].patchNum, undefined);
assert.equal(actualThreads[0].rootId, 'sallys_confession');
assert.equal(actualThreads[0].lineNum, 1);
assert.equal(
actualThreads[1].start_datetime, '2015-12-20 15:01:20.396000000');
assert.equal(actualThreads[1].commentSide, 'left');
assert.equal(actualThreads[1].comments.length, 1);
assert.deepEqual(actualThreads[1].comments[0], comments[2]);
assert.equal(actualThreads[1].patchNum, undefined);
assert.equal(actualThreads[1].rootId, 'new_draft');
assert.equal(actualThreads[1].lineNum, undefined);
});
test('_createThreads inherits patchNum and range', () => {
const comments = [{
id: 'betsys_confession',
message: 'i like you, jack',
updated: '2015-12-24 15:00:10.396000000',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
patch_set: 5,
__commentSide: 'left',
line: 1,
}];
expectedThreads = [
{
start_datetime: '2015-12-24 15:00:10.396000000',
commentSide: 'left',
comments: [{
id: 'betsys_confession',
message: 'i like you, jack',
updated: '2015-12-24 15:00:10.396000000',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
patch_set: 5,
__commentSide: 'left',
line: 1,
}],
patchNum: 5,
rootId: 'betsys_confession',
range: {
start_line: 1,
start_character: 1,
end_line: 1,
end_character: 2,
},
lineNum: 1,
isOnParent: false,
},
];
assert.deepEqual(
element._createThreads(comments),
expectedThreads);
});
test('_createThreads does not thread unrelated comments at same location',
() => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
__commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
__commentSide: 'left',
},
];
assert.equal(element._createThreads(comments).length, 2);
});
test('_createThreads derives isOnParent using side from first comment',
() => {
const comments = [
{
id: 'sallys_confession',
message: 'i like you, jack',
updated: '2015-12-23 15:00:20.396000000',
// line: 1,
// __commentSide: 'left',
}, {
id: 'jacks_reply',
message: 'i like you, too',
updated: '2015-12-24 15:01:20.396000000',
// __commentSide: 'left',
// line: 1,
in_reply_to: 'sallys_confession',
},
];
assert.equal(element._createThreads(comments)[0].isOnParent, false);
comments[0].side = 'REVISION';
assert.equal(element._createThreads(comments)[0].isOnParent, false);
comments[0].side = 'PARENT';
assert.equal(element._createThreads(comments)[0].isOnParent, true);
});
test('_getOrCreateThread', () => {
const threadGroupEl =
document.createElement('gr-diff-comment-thread-group');
const commentSide = 'left';
assert.isOk(element._getOrCreateThread(threadGroupEl,
commentSide, undefined, false, '2'));
assert.isOk(element._getOrCreateThread('2', 3,
commentSide, undefined, false));
let threads = Polymer.dom(threadGroupEl)
let threads = Polymer.dom(element.$.diff)
.queryDistributedElements('gr-diff-comment-thread');
assert.equal(threads.length, 1);
@@ -798,9 +973,9 @@ limitations under the License.
};
assert.isOk(element._getOrCreateThread(
threadGroupEl, commentSide, range, true, '3'));
'3', 1, commentSide, range, true));
threads = Polymer.dom(threadGroupEl)
threads = Polymer.dom(element.$.diff)
.queryDistributedElements('gr-diff-comment-thread');
assert.equal(threads.length, 2);

View File

@@ -332,7 +332,6 @@ limitations under the License.
patch-range="[[_patchRange]]"
path="[[_path]]"
prefs="[[_prefs]]"
project-config="[[_projectConfig]]"
project-name="[[_change.project]]"
view-mode="[[_diffMode]]"
is-blame-loaded="{{_isBlameLoaded}}"

View File

@@ -20,7 +20,7 @@ limitations under the License.
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../gr-diff-builder/gr-diff-builder.html">
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-comment-thread-group/gr-diff-comment-thread-group.html">
<link rel="import" href="../gr-diff-highlight/gr-diff-highlight.html">
<link rel="import" href="../gr-diff-selection/gr-diff-selection.html">
<link rel="import" href="../gr-syntax-themes/gr-syntax-theme.html">
@@ -36,6 +36,11 @@ limitations under the License.
:host(.no-left) .sideBySide ::content .right:not([data-value]) + td {
display: none;
}
::slotted(*) .thread-group {
display: block;
max-width: var(--content-width, 80ch);
white-space: normal;
}
.diffContainer {
display: flex;
font-family: var(--monospace-font-family);
@@ -290,9 +295,8 @@ limitations under the License.
is-image-diff="[[isImageDiff]]"
base-image="[[baseImage]]"
revision-image="[[revisionImage]]"
parentIndex="[[parentIndex]]"
path="[[path]]"
line-of-interest="[[lineOfInterest]]">
<slot></slot>
<table
id="diffTable"
class$="[[_diffTableClass]]"

View File

@@ -180,6 +180,9 @@
},
_diffLength: Number,
/** @type {?PolymerDomApi.ObserveHandle} */
_nodeObserver: Object,
},
behaviors: [
@@ -191,6 +194,11 @@
'comment-update': '_handleCommentUpdate',
'comment-save': '_handleCommentSave',
'create-range-comment': '_handleCreateRangeComment',
'render-content': '_handleRenderContent',
},
detached() {
this._unobserveNodes();
},
/** Cancel any remaining diff builder rendering work. */
@@ -230,17 +238,6 @@
{bubbles: true}));
},
/** @return {!Array<!HTMLElement>} */
getThreadEls() {
let threads = [];
const threadGroupEls = Polymer.dom(this.root)
.querySelectorAll('gr-diff-comment-thread-group');
for (const threadGroupEl of threadGroupEls) {
threads = threads.concat(threadGroupEl.threadEls);
}
return threads;
},
/** @return {string} */
_computeContainerClass(loggedIn, viewMode, displayLine) {
const classes = ['diffContainer'];
@@ -354,42 +351,40 @@
const patchForNewThreads = this._getPatchNumByLineAndContent(
lineEl, contentEl);
const isOnParent =
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
const threadGroupEl = this._getOrCreateThreadGroup(contentEl);
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
this.dispatchEvent(new CustomEvent('create-comment', {
bubbles: true,
detail: {
threadGroupEl,
lineNum,
side,
range,
patchNum: patchForNewThreads,
isOnParent,
patchForNewThreads,
range,
},
}));
},
_getThreadGroupForLine(contentEl) {
return contentEl.querySelector('gr-diff-comment-thread-group');
return contentEl.querySelector('.thread-group');
},
/**
* Gets or creates a comment thread group for a specific line and side on a
* diff.
* @param {!Object} contentEl
* @return {!Object}
* @return {!Node}
*/
_getOrCreateThreadGroup(contentEl) {
// Check if thread group exists.
let threadGroupEl = this._getThreadGroupForLine(contentEl);
if (!threadGroupEl) {
threadGroupEl = document.createElement('gr-diff-comment-thread-group');
threadGroupEl = document.createElement('div');
threadGroupEl.className = 'thread-group';
contentEl.appendChild(threadGroupEl);
}
return threadGroupEl;
},
/**
* The value to be used for the patch number of new comments created at the
* given line and content elements.
@@ -578,6 +573,7 @@
},
_renderDiffTable() {
this._unobserveNodes();
if (!this.prefs) {
this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
return;
@@ -594,6 +590,34 @@
this.$.diffBuilder.render(this.comments, this._getBypassPrefs());
},
_handleRenderContent() {
this._nodeObserver = Polymer.dom(this).observeNodes(info => {
const addedThreadEls = info.addedNodes.filter(
node => node.nodeType === Node.ELEMENT_NODE);
// In principal we should also handle removed nodes, but I have not
// figured out how to do that yet without also catching all the removals
// caused by further redistribution. Right now, comments are never
// removed by no longer slotting them in, so I decided to not handle
// this situation until it occurs.
for (const threadEl of addedThreadEls) {
const lineNum = Number(threadEl.getAttribute('line-num'));
const commentSide = threadEl.getAttribute('comment-side');
const lineEl = this.$.diffBuilder.getLineElByNumber(
lineNum, commentSide);
const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
const contentEl = contentText.parentElement;
const threadGroupEl = this._getOrCreateThreadGroup(contentEl);
Polymer.dom(threadGroupEl).appendChild(threadEl);
}
});
},
_unobserveNodes() {
if (this._nodeObserver) {
Polymer.dom(this).unobserveNodes(this._nodeObserver);
}
},
/**
* Get the preferences object including the safety bypass context (if any).
*/
@@ -605,6 +629,7 @@
},
clearDiffContent() {
this._unobserveNodes();
this.$.diffTable.innerHTML = null;
},

View File

@@ -315,8 +315,7 @@ limitations under the License.
// The new thread group can be fetched.
assert.isOk(element._getThreadGroupForLine(contentEl));
assert.equal(contentEl.querySelectorAll(
'gr-diff-comment-thread-group').length, 1);
assert.equal(contentEl.querySelectorAll('.thread-group').length, 1);
});
suite('image diffs', () => {
@@ -335,7 +334,11 @@ limitations under the License.
};
element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
element.comments = {left: [], right: []};
element.comments = {
left: [],
right: [],
meta: {patchRange: undefined},
};
element.isImageDiff = true;
element.prefs = {
auto_hide_diff_table_header: true,
@@ -664,6 +667,7 @@ limitations under the License.
element.comments = {
left: [],
right: [],
meta: {patchRange: undefined},
};
element.prefs = {
context: 10,

View File

@@ -103,7 +103,6 @@ limitations under the License.
'core/gr-smart-search/gr-smart-search_test.html',
'diff/gr-comment-api/gr-comment-api_test.html',
'diff/gr-diff-builder/gr-diff-builder_test.html',
'diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html',
'diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
'diff/gr-diff-comment/gr-diff-comment_test.html',
'diff/gr-diff-cursor/gr-diff-cursor_test.html',