Establish annotation pipeline
Apply diff annotations (intraline differences and comment ranges) by executing the annotation layers in order to each line. The diff builder maintains an ordered array of annotation layers which are communicated to GrDiffBuilder subclass instances. The builder also listens to each layer for notifications that annotations have changed for some line range and re-renders (i.e. re-applies the pipeline on DIV.contentText elements) accordingly. Change-Id: Iea0599d4869cafaadc0974158153a91d927913e8
This commit is contained in:
@@ -24,11 +24,11 @@ limitations under the License.
|
||||
position: relative;
|
||||
}
|
||||
.contentWrapper ::content .range {
|
||||
background-color: #ffd500 !important;
|
||||
background-color: rgba(255,213,0,0.5);
|
||||
display: inline;
|
||||
}
|
||||
.contentWrapper ::content .rangeHighlight {
|
||||
background-color: #ff0 !important;
|
||||
background-color: rgba(255,255,0,0.5);
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var RANGE_HIGHLIGHT = 'range';
|
||||
var HOVER_HIGHLIGHT = 'rangeHighlight';
|
||||
|
||||
Polymer({
|
||||
is: 'gr-diff-highlight',
|
||||
|
||||
@@ -27,13 +24,9 @@
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'comment-discard': '_handleCommentDiscard',
|
||||
'comment-mouse-out': '_handleCommentMouseOut',
|
||||
'comment-mouse-over': '_handleCommentMouseOver',
|
||||
'create-comment': '_createComment',
|
||||
'render': '_handleRender',
|
||||
'show-context': '_handleShowContext',
|
||||
'thread-discard': '_handleThreadDiscard',
|
||||
},
|
||||
|
||||
get diffBuilder() {
|
||||
@@ -56,21 +49,6 @@
|
||||
return !!this.$$('gr-selection-action-box');
|
||||
},
|
||||
|
||||
_handleThreadDiscard: function(e) {
|
||||
var comment = e.detail.lastComment;
|
||||
// Comment Element was removed from DOM already.
|
||||
if (comment.range) {
|
||||
this._renderCommentRange(comment, e.target);
|
||||
}
|
||||
},
|
||||
|
||||
_handleCommentDiscard: function(e) {
|
||||
var comment = e.detail.comment;
|
||||
if (comment.range) {
|
||||
this._renderCommentRange(comment, e.target);
|
||||
}
|
||||
},
|
||||
|
||||
_handleSelectionChange: function() {
|
||||
// Can't use up or down events to handle selection started and/or ended in
|
||||
// in comment threads or outside of diff.
|
||||
@@ -79,45 +57,36 @@
|
||||
this.debounce('selectionChange', this._handleSelection, 200);
|
||||
},
|
||||
|
||||
_handleRender: function() {
|
||||
this._applyAllHighlights();
|
||||
},
|
||||
|
||||
_handleShowContext: function() {
|
||||
// TODO (viktard): Re-render expanded sections only.
|
||||
this._applyAllHighlights();
|
||||
},
|
||||
|
||||
_handleCommentMouseOver: function(e) {
|
||||
var comment = e.detail.comment;
|
||||
var range = comment.range;
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
if (!comment.range) { return; }
|
||||
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
||||
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
||||
this._applyRangedHighlight(
|
||||
HOVER_HIGHLIGHT, range.start_line, range.start_character,
|
||||
range.end_line, range.end_character, side);
|
||||
var index = this._indexOfComment(side, comment);
|
||||
if (index !== undefined) {
|
||||
this.set(['comments', side, index, '__hovering'], true);
|
||||
}
|
||||
},
|
||||
|
||||
_handleCommentMouseOut: function(e) {
|
||||
var comment = e.detail.comment;
|
||||
var range = comment.range;
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
if (!comment.range) { return; }
|
||||
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
||||
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
||||
var contentEls = this.diffBuilder.getContentsByLineRange(
|
||||
range.start_line, range.end_line, side);
|
||||
contentEls.forEach(function(content) {
|
||||
Polymer.dom(content).querySelectorAll('.' + HOVER_HIGHLIGHT).forEach(
|
||||
function(el) {
|
||||
el.classList.remove(HOVER_HIGHLIGHT);
|
||||
el.classList.add(RANGE_HIGHLIGHT);
|
||||
});
|
||||
}, this);
|
||||
var index = this._indexOfComment(side, comment);
|
||||
if (index !== undefined) {
|
||||
this.set(['comments', side, index, '__hovering'], false);
|
||||
}
|
||||
},
|
||||
|
||||
_indexOfComment: function(side, comment) {
|
||||
var idProp = comment.id ? 'id' : '__draftID';
|
||||
for (var i = 0; i < this.comments[side].length; i++) {
|
||||
if (comment[idProp] &&
|
||||
this.comments[side][i][idProp] === comment[idProp]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -226,26 +195,8 @@
|
||||
}
|
||||
},
|
||||
|
||||
_renderCommentRange: function(comment, el) {
|
||||
var lineEl = this.diffBuilder.getLineElByChild(el);
|
||||
if (!lineEl) {
|
||||
return;
|
||||
}
|
||||
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
||||
this._rerenderByLines(
|
||||
comment.range.start_line, comment.range.end_line, side);
|
||||
},
|
||||
|
||||
_createComment: function(e) {
|
||||
this._removeActionBox();
|
||||
var side = e.detail.side;
|
||||
var range = e.detail.range;
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
this._applyRangedHighlight(
|
||||
RANGE_HIGHLIGHT, range.startLine, range.startChar,
|
||||
range.endLine, range.endChar, side);
|
||||
},
|
||||
|
||||
_removeActionBoxDebounced: function() {
|
||||
@@ -314,226 +265,5 @@
|
||||
return GrAnnotation.getLength(node);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hl tag with cssClass for starting side of range highlight.
|
||||
*
|
||||
* @param {!Element} startContent Range start diff content
|
||||
* aka div.contentText.
|
||||
* @param {!Element} endContent Range end diff content
|
||||
* aka div.contentText.
|
||||
* @param {number} startOffset Range start within start content.
|
||||
* @param {number} endOffset Range end within end content.
|
||||
* @param {string} cssClass
|
||||
* @return {!Element} Range start node.
|
||||
*/
|
||||
_normalizeStart: function(
|
||||
startContent, endContent, startOffset, endOffset, cssClass) {
|
||||
var isOneLine = startContent === endContent;
|
||||
var startNode = startContent.firstChild;
|
||||
var length = endOffset - startOffset;
|
||||
|
||||
if (!startNode) {
|
||||
return startNode;
|
||||
}
|
||||
|
||||
// Skip nodes before startOffset.
|
||||
var nodeLength = this._getLength(startNode);
|
||||
while (startNode && (nodeLength <= startOffset || nodeLength === 0)) {
|
||||
startOffset -= nodeLength;
|
||||
startNode = startNode.nextSibling;
|
||||
nodeLength = startNode && this._getLength(startNode);
|
||||
}
|
||||
if (!startNode) { return null; }
|
||||
|
||||
// Split Text node.
|
||||
if (startNode instanceof Text) {
|
||||
startNode = GrAnnotation.splitAndWrapInHighlight(
|
||||
startNode, startOffset, cssClass);
|
||||
// Edge case: single line, text node wraps the highlight.
|
||||
if (isOneLine && this._getLength(startNode) > length) {
|
||||
var extra = GrAnnotation.splitTextNode(startNode.firstChild, length);
|
||||
startContent.insertBefore(extra, startNode.nextSibling);
|
||||
startContent.normalize();
|
||||
}
|
||||
} else if (startNode.tagName == 'HL') {
|
||||
if (!startNode.classList.contains(cssClass)) {
|
||||
// Edge case: single line, <hl> wraps the highlight.
|
||||
// Should leave wrapping HL's content after the highlight.
|
||||
if (isOneLine && startOffset + length < this._getLength(startNode)) {
|
||||
GrAnnotation.splitNode(startNode, startOffset + length);
|
||||
}
|
||||
startNode = GrAnnotation.splitAndWrapInHighlight(
|
||||
startNode, startOffset, cssClass);
|
||||
}
|
||||
} else if (startNode.tagName == 'SPAN') {
|
||||
startNode = GrAnnotation.splitAndWrapInHighlight(
|
||||
startNode, startOffset, cssClass);
|
||||
} else {
|
||||
startNode = null;
|
||||
}
|
||||
return startNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hl tag with cssClass for ending side of range highlight.
|
||||
*
|
||||
* @param {!Element} startContent Range start diff content
|
||||
* aka div.contentText.
|
||||
* @param {!Element} endContent Range end diff content
|
||||
* aka div.contentText.
|
||||
* @param {number} startOffset Range start within start content.
|
||||
* @param {number} endOffset Range end within end content.
|
||||
* @param {string} cssClass
|
||||
* @return {!Element} Range start node.
|
||||
*/
|
||||
_normalizeEnd: function(
|
||||
startContent, endContent, startOffset, endOffset, cssClass) {
|
||||
var endNode = endContent.firstChild;
|
||||
|
||||
if (!endNode) {
|
||||
return endNode;
|
||||
}
|
||||
|
||||
// Find the node where endOffset points at.
|
||||
var nodeLength = this._getLength(endNode);
|
||||
while (endNode && (nodeLength < endOffset || nodeLength === 0)) {
|
||||
endOffset -= nodeLength;
|
||||
endNode = endNode.nextSibling;
|
||||
nodeLength = endNode && this._getLength(endNode);
|
||||
}
|
||||
if (!endNode) { return null; }
|
||||
|
||||
if (endNode instanceof Text) {
|
||||
endNode = GrAnnotation.splitAndWrapInHighlight(
|
||||
endNode, endOffset, cssClass, true);
|
||||
} else if (endNode.tagName == 'HL') {
|
||||
if (!endNode.classList.contains(cssClass)) {
|
||||
// Split text inside HL.
|
||||
var hl = endNode;
|
||||
endNode = GrAnnotation.splitAndWrapInHighlight(
|
||||
endNode, endOffset, cssClass, true);
|
||||
if (hl.textContent.length === 0) {
|
||||
hl.remove();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
endNode = null;
|
||||
}
|
||||
return endNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies highlight to first and last lines in range.
|
||||
*
|
||||
* @param {!Element} startContent Range start diff content
|
||||
* aka div.contentText.
|
||||
* @param {!Element} endContent Range end diff content
|
||||
* aka div.contentText.
|
||||
* @param {number} startOffset Range start within start content.
|
||||
* @param {number} endOffset Range end within end content.
|
||||
* @param {string} cssClass
|
||||
*/
|
||||
_highlightSides: function(
|
||||
startContent, endContent, startOffset, endOffset, cssClass) {
|
||||
var isOneLine = startContent === endContent;
|
||||
var startNode = this._normalizeStart(
|
||||
startContent, endContent, startOffset, endOffset, cssClass);
|
||||
var endNode = this._normalizeEnd(
|
||||
startContent, endContent, startOffset, endOffset, cssClass);
|
||||
|
||||
// Grow starting highlight until endNode or end of line.
|
||||
if (startNode && startNode != endNode) {
|
||||
var growStartHl = function(node) {
|
||||
if (node instanceof Text || node.tagName === 'SPAN') {
|
||||
startNode.appendChild(node);
|
||||
} else if (node.tagName === 'HL') {
|
||||
this._traverseContentSiblings(node.firstChild, growStartHl);
|
||||
node.remove();
|
||||
}
|
||||
return node == endNode;
|
||||
}.bind(this);
|
||||
this._traverseContentSiblings(startNode.nextSibling, growStartHl);
|
||||
startNode.normalize();
|
||||
}
|
||||
|
||||
if (!isOneLine && endNode) {
|
||||
var growEndHl = function(node) {
|
||||
if (node instanceof Text || node.tagName === 'SPAN') {
|
||||
endNode.insertBefore(node, endNode.firstChild);
|
||||
} else if (node.tagName === 'HL') {
|
||||
this._traverseContentSiblings(node.firstChild, growEndHl);
|
||||
node.remove();
|
||||
}
|
||||
}.bind(this);
|
||||
// Prepend text up to line start to the ending highlight.
|
||||
this._traverseContentSiblings(
|
||||
endNode.previousSibling, growEndHl, {left: true});
|
||||
endNode.normalize();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} cssClass
|
||||
* @param {number} startLine Range start code line number.
|
||||
* @param {number} startCol Range start column number.
|
||||
* @param {number} endLine Range end line number.
|
||||
* @param {number} endCol Range end column number.
|
||||
* @param {string=} opt_side Side selector (right or left).
|
||||
*/
|
||||
_applyRangedHighlight: function(
|
||||
cssClass, startLine, startCol, endLine, endCol, opt_side) {
|
||||
var startEl = this.diffBuilder.getContentByLine(startLine, opt_side);
|
||||
var endEl = this.diffBuilder.getContentByLine(endLine, opt_side);
|
||||
this._highlightSides(startEl, endEl, startCol, endCol, cssClass);
|
||||
if (endLine - startLine > 1) {
|
||||
// There is at least one line in between.
|
||||
var contents = this.diffBuilder.getContentsByLineRange(
|
||||
startLine + 1, endLine - 1, opt_side);
|
||||
contents.forEach(function(content) {
|
||||
if (!content.firstChild) {
|
||||
return;
|
||||
}
|
||||
// Wrap contents in highlight.
|
||||
var hl = GrAnnotation.wrapInHighlight(content.firstChild, cssClass);
|
||||
var wrapInHl = function(node) {
|
||||
if (node instanceof Text || node.tagName === 'SPAN') {
|
||||
hl.appendChild(node);
|
||||
} else if (node.tagName === 'HL') {
|
||||
this._traverseContentSiblings(node.firstChild, wrapInHl);
|
||||
node.remove();
|
||||
}
|
||||
return node === content.lastChild;
|
||||
}.bind(this);
|
||||
this._traverseContentSiblings(hl.nextSibling, wrapInHl);
|
||||
hl.normalize();
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
_applyAllHighlights: function() {
|
||||
var rangedLeft =
|
||||
this.comments.left.filter(function(item) { return !!item.range; });
|
||||
var rangedRight =
|
||||
this.comments.right.filter(function(item) { return !!item.range; });
|
||||
rangedLeft.forEach(function(item) {
|
||||
var range = item.range;
|
||||
this._applyRangedHighlight(
|
||||
RANGE_HIGHLIGHT, range.start_line, range.start_character,
|
||||
range.end_line, range.end_character, 'left');
|
||||
}, this);
|
||||
rangedRight.forEach(function(item) {
|
||||
var range = item.range;
|
||||
this._applyRangedHighlight(
|
||||
RANGE_HIGHLIGHT, range.start_line, range.start_character,
|
||||
range.end_line, range.end_character, 'right');
|
||||
}, this);
|
||||
},
|
||||
|
||||
_rerenderByLines: function(startLine, endLine, opt_side) {
|
||||
this.async(function() {
|
||||
this.diffBuilder.renderLineRange(startLine, endLine, opt_side);
|
||||
}, 1);
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -167,42 +167,17 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
test('renders lines in comment range on thread discard', function(done) {
|
||||
element.fire('thread-discard', {
|
||||
lastComment: {
|
||||
range: {
|
||||
start_line: 10,
|
||||
end_line: 24,
|
||||
},
|
||||
},
|
||||
});
|
||||
flush(function() {
|
||||
assert.isTrue(
|
||||
builder.renderLineRange.calledWithExactly(10, 24, 'other-side'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders lines in comment range on comment discard', function(done) {
|
||||
element.fire('comment-discard', {
|
||||
comment: {
|
||||
range: {
|
||||
start_line: 10,
|
||||
end_line: 24,
|
||||
},
|
||||
},
|
||||
});
|
||||
flush(function() {
|
||||
assert.isTrue(
|
||||
builder.renderLineRange.calledWithExactly(10, 24, 'other-side'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('comment-mouse-over from line comments is ignored', function() {
|
||||
sandbox.stub(element, '_applyRangedHighlight');
|
||||
sandbox.stub(element, 'set');
|
||||
element.fire('comment-mouse-over', {comment: {}});
|
||||
assert.isFalse(element._applyRangedHighlight.called);
|
||||
assert.isFalse(element.set.called);
|
||||
});
|
||||
|
||||
test('comment-mouse-over from ranged comment causes set', function() {
|
||||
sandbox.stub(element, 'set');
|
||||
sandbox.stub(element, '_indexOfComment').returns(0);
|
||||
element.fire('comment-mouse-over', {comment: {range:{}}});
|
||||
assert.isTrue(element.set.called);
|
||||
});
|
||||
|
||||
test('comment-mouse-out from line comments is ignored', function() {
|
||||
@@ -210,56 +185,7 @@ limitations under the License.
|
||||
assert.isFalse(builder.getContentsByLineRange.called);
|
||||
});
|
||||
|
||||
test('on comment-mouse-out highlight classes are removed', function() {
|
||||
var testEl = fixture('highlighted');
|
||||
builder.getContentsByLineRange.returns([testEl]);
|
||||
element.fire('comment-mouse-out', {
|
||||
comment: {
|
||||
range: {
|
||||
start_line: 3,
|
||||
start_character: 14,
|
||||
end_line: 10,
|
||||
end_character: 24,
|
||||
}
|
||||
}});
|
||||
assert.isTrue(builder.getContentsByLineRange.calledWithExactly(
|
||||
3, 10, 'other-side'));
|
||||
assert.equal(0, testEl.querySelectorAll('.rangeHighlight').length);
|
||||
assert.equal(2, testEl.querySelectorAll('.range').length);
|
||||
});
|
||||
|
||||
test('on comment-mouse-over range is highlighted', function() {
|
||||
sandbox.stub(element, '_applyRangedHighlight');
|
||||
element.fire('comment-mouse-over', {
|
||||
comment: {
|
||||
range: {
|
||||
start_line: 3,
|
||||
start_character: 14,
|
||||
end_line: 10,
|
||||
end_character: 24,
|
||||
},
|
||||
}});
|
||||
assert.isTrue(element._applyRangedHighlight.calledWithExactly(
|
||||
'rangeHighlight', 3, 14, 10, 24, 'other-side'));
|
||||
});
|
||||
|
||||
test('on create-comment range is highlighted', function() {
|
||||
sandbox.stub(element, '_applyRangedHighlight');
|
||||
element.fire('create-comment', {
|
||||
range: {
|
||||
startLine: 3,
|
||||
startChar: 14,
|
||||
endLine: 10,
|
||||
endChar: 24,
|
||||
},
|
||||
side: 'some-side',
|
||||
});
|
||||
assert.isTrue(element._applyRangedHighlight.calledWithExactly(
|
||||
'range', 3, 14, 10, 24, 'some-side'));
|
||||
});
|
||||
|
||||
test('on create-comment action box is removed', function() {
|
||||
sandbox.stub(element, '_applyRangedHighlight');
|
||||
sandbox.stub(element, '_removeActionBox');
|
||||
element.fire('create-comment', {
|
||||
comment: {
|
||||
@@ -270,290 +196,6 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
test('apply multiline highlight', function() {
|
||||
var diff = element.querySelector('#diffTable');
|
||||
var startContent = diff.querySelector(
|
||||
'.left.lineNum[data-value="138"] ~ .content .contentText');
|
||||
var betweenContent = diff.querySelector(
|
||||
'.left.lineNum[data-value="140"] ~ .content .contentText');
|
||||
var endContent = diff.querySelector(
|
||||
'.left.lineNum[data-value="141"] ~ .content .contentText');
|
||||
var builder = {
|
||||
getContentByLine: sandbox.stub().returns({}),
|
||||
getContentsByLineRange: sandbox.stub().returns([betweenContent]),
|
||||
getLineElByChild: sandbox.stub().returns(
|
||||
{getAttribute: sandbox.stub()}),
|
||||
};
|
||||
element._cachedDiffBuilder = builder;
|
||||
builder.getContentByLine.withArgs(138, 'left').returns(
|
||||
startContent);
|
||||
builder.getContentByLine.withArgs(141, 'left').returns(
|
||||
endContent);
|
||||
element._applyRangedHighlight('some', 138, 4, 141, 28, 'left');
|
||||
assert.instanceOf(startContent.childNodes[0], Text);
|
||||
assert.equal(startContent.childNodes[0].textContent, '[14]');
|
||||
assert.instanceOf(startContent.childNodes[1], Element);
|
||||
assert.equal(startContent.childNodes[1].textContent,
|
||||
' Nam cum ad me in Cumanum salutandi causa uterque venisset,');
|
||||
assert.equal(startContent.childNodes[1].tagName, 'HL');
|
||||
assert.equal(startContent.childNodes[1].className, 'some');
|
||||
|
||||
assert.instanceOf(betweenContent.firstChild, Element);
|
||||
assert.equal(betweenContent.firstChild.tagName, 'HL');
|
||||
assert.equal(betweenContent.firstChild.className, 'some');
|
||||
assert.equal(betweenContent.childNodes.length, 1);
|
||||
assert.equal(betweenContent.firstChild.childNodes.length, 5);
|
||||
assert.equal(betweenContent.firstChild.textContent,
|
||||
'na💢ti te, inquit, sumus aliquando otiosum, certe a udiam, ' +
|
||||
'quid sit, quod Epicurum');
|
||||
|
||||
assert.isNull(diff.querySelector('.right + .content .some'),
|
||||
'Highlight should be applied only to the left side content.');
|
||||
|
||||
assert.instanceOf(endContent.childNodes[0], Element);
|
||||
assert.equal(endContent.childNodes[0].textContent,
|
||||
'nam et\tcomplectitur\tverbis, ');
|
||||
assert.equal(endContent.childNodes[0].tagName, 'HL');
|
||||
assert.equal(endContent.childNodes[0].className, 'some');
|
||||
assert.instanceOf(endContent.childNodes[1], Text);
|
||||
assert.equal(endContent.childNodes[1].textContent,
|
||||
'quod vult, et dicit plane, quod intellegam;');
|
||||
var endHl = endContent.querySelector('hl.some');
|
||||
assert.equal(endHl.childNodes.length, 5);
|
||||
var tabs = endHl.querySelectorAll('span.tab');
|
||||
assert.equal(tabs.length, 2);
|
||||
assert.equal(tabs[0].previousSibling.textContent, 'nam et');
|
||||
assert.equal(tabs[1].previousSibling.textContent, 'complectitur');
|
||||
assert.equal(tabs[1].nextSibling.textContent, 'verbis, ');
|
||||
});
|
||||
|
||||
test('multiline highlight w/ start at end of 1st line', function() {
|
||||
var diff = element.querySelector('#diffTable');
|
||||
var startContent =
|
||||
diff.querySelector('.left.lineNum[data-value="138"] ~ .content');
|
||||
var betweenContent =
|
||||
diff.querySelector('.left.lineNum[data-value="140"] ~ .content');
|
||||
var endContent =
|
||||
diff.querySelector('.left.lineNum[data-value="141"] ~ .content');
|
||||
var commentThread =
|
||||
diff.querySelector('gr-diff-comment-thread');
|
||||
var builder = {
|
||||
getCommentThreadByContentEl: sandbox.stub().returns(commentThread),
|
||||
getContentByLine: sandbox.stub().returns({}),
|
||||
getContentsByLineRange: sandbox.stub().returns([betweenContent]),
|
||||
getLineElByChild: sandbox.stub().returns(
|
||||
{getAttribute: sandbox.stub()}),
|
||||
};
|
||||
element._cachedDiffBuilder = builder;
|
||||
builder.getContentByLine.withArgs(138, 'left').returns(
|
||||
startContent);
|
||||
builder.getContentByLine.withArgs(141, 'left').returns(
|
||||
endContent);
|
||||
|
||||
var expectedStartContentNodes = startContent.childNodes.length;
|
||||
|
||||
// The following should not cause an error.
|
||||
element._applyRangedHighlight(
|
||||
'some', 138, startContent.textContent.length, 141, 28, 'left');
|
||||
|
||||
assert.equal(startContent.childNodes.length, expectedStartContentNodes,
|
||||
'Should not add a highlight to the start content');
|
||||
});
|
||||
|
||||
suite('single line ranges', function() {
|
||||
var diff;
|
||||
var contentText;
|
||||
var commentThread;
|
||||
var builder;
|
||||
|
||||
setup(function() {
|
||||
diff = element.querySelector('#diffTable');
|
||||
var contentTd = diff.querySelector(
|
||||
'.left.lineNum[data-value="140"] ~ .content');
|
||||
contentText = contentTd.querySelector('.contentText');
|
||||
commentThread = diff.querySelector('gr-diff-comment-thread');
|
||||
builder = {
|
||||
getCommentThreadByContentEl: sandbox.stub().returns(commentThread),
|
||||
getContentByLine: sandbox.stub().returns(contentText),
|
||||
getContentsByLineRange: sandbox.stub().returns([]),
|
||||
getLineElByChild: sandbox.stub().returns(
|
||||
{getAttribute: sandbox.stub()}),
|
||||
};
|
||||
element._cachedDiffBuilder = builder;
|
||||
});
|
||||
|
||||
test('whole line range', function() {
|
||||
element._applyRangedHighlight('some', 140, 0, 140, 81, 'left');
|
||||
assert.equal(contentText.childNodes.length, 1);
|
||||
assert.instanceOf(contentText.firstChild, Element);
|
||||
assert.equal(contentText.firstChild.tagName, 'HL');
|
||||
assert.equal(contentText.firstChild.className, 'some');
|
||||
assert.equal(contentText.firstChild.childNodes.length, 5);
|
||||
assert.equal(contentText.firstChild.textContent,
|
||||
'na💢ti te, inquit, sumus aliquando otiosum, certe a udiam, ' +
|
||||
'quid sit, quod Epicurum');
|
||||
var tabs = contentText.querySelectorAll('span.tab');
|
||||
assert.equal(tabs.length, 2);
|
||||
assert.strictEqual(tabs[1].previousSibling, tabs[0].nextSibling);
|
||||
assert.equal(tabs[0].previousSibling.textContent,
|
||||
'na💢ti te, inquit, sumus aliquando otiosum, certe a ');
|
||||
assert.equal(tabs[1].previousSibling.textContent,
|
||||
'udiam, quid sit, ');
|
||||
assert.equal(tabs[1].nextSibling.textContent, 'quod Epicurum');
|
||||
});
|
||||
|
||||
test('merging multiple other hls', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 80, 'left');
|
||||
assert.instanceOf(contentText.firstChild, Text);
|
||||
assert.equal(contentText.childNodes.length, 3);
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.strictEqual(contentText.firstChild, hl.previousSibling);
|
||||
assert.equal(hl.childNodes.length, 5);
|
||||
assert.equal(contentText.querySelectorAll('span.tab').length, 2);
|
||||
assert.equal(hl.textContent,
|
||||
'a💢ti te, inquit, sumus aliquando otiosum, certe a udiam, ' +
|
||||
'quid sit, quod Epicuru');
|
||||
});
|
||||
|
||||
test('hl inside Text node', function() {
|
||||
// Before: na💢ti
|
||||
// After: n<hl class="some">a💢t</hl>i
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 4, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">a💢t</hl>');
|
||||
});
|
||||
|
||||
test('hl ending over different hl', function() {
|
||||
// Before: na💢ti <hl>te, inquit</hl>,
|
||||
// After: na💢<hl class="some">ti te</hl><hl class="foo">, inquit</hl>,
|
||||
element._applyRangedHighlight('some', 140, 3, 140, 8, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">ti te</hl>');
|
||||
assert.equal(hl.nextSibling.outerHTML,
|
||||
'<hl class="foo">, inquit</hl>');
|
||||
});
|
||||
|
||||
test('hl starting inside different hl', function() {
|
||||
// Before: na💢ti <hl>te, inquit</hl>, sumus
|
||||
// After: na💢ti <hl class="foo">te, in</hl><hl class="some">quit, ...
|
||||
element._applyRangedHighlight('some', 140, 12, 140, 21, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.textContent, 'quit, sum');
|
||||
assert.equal(
|
||||
hl.previousSibling.outerHTML, '<hl class="foo">te, in</hl>');
|
||||
});
|
||||
|
||||
test('hl inside different hl', function() {
|
||||
// Before: na💢ti <hl class="foo">te, inquit</hl>, sumus
|
||||
// After: <hl class="foo">t</hl><hl="some">e, i</hl><hl class="foo">n..
|
||||
element._applyRangedHighlight('some', 140, 7, 140, 12, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.textContent, 'e, in');
|
||||
assert.equal(hl.previousSibling.outerHTML, '<hl class="foo">t</hl>');
|
||||
assert.equal(hl.nextSibling.outerHTML, '<hl class="foo">quit</hl>');
|
||||
});
|
||||
|
||||
test('hl starts and ends in different hls', function() {
|
||||
element._applyRangedHighlight('some', 140, 8, 140, 27, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.textContent, ', inquit, sumus ali');
|
||||
assert.equal(hl.previousSibling.outerHTML, '<hl class="foo">te</hl>');
|
||||
assert.equal(hl.nextSibling.outerHTML, '<hl class="bar">quando</hl>');
|
||||
});
|
||||
|
||||
test('hl over different hl', function() {
|
||||
element._applyRangedHighlight('some', 140, 2, 140, 21, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">💢ti te, inquit, sum</hl>');
|
||||
assert.notOk(contentText.querySelector('.foo'));
|
||||
});
|
||||
|
||||
test('hl starting and ending in boundaries', function() {
|
||||
element._applyRangedHighlight('some', 140, 6, 140, 33, 'left');
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.textContent, 'te, inquit, sumus aliquando');
|
||||
assert.notOk(contentText.querySelector('.bar'));
|
||||
});
|
||||
|
||||
test('overlapping hls', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 3, 'left');
|
||||
element._applyRangedHighlight('some', 140, 2, 140, 4, 'left');
|
||||
assert.equal(contentText.querySelectorAll('hl.some').length, 1);
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">a💢t</hl>');
|
||||
});
|
||||
|
||||
test('growing hl right including another hl', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 4, 'left');
|
||||
element._applyRangedHighlight('some', 140, 3, 140, 10, 'left');
|
||||
assert.equal(contentText.querySelectorAll('hl.some').length, 1);
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">a💢ti te, </hl>');
|
||||
assert.equal(hl.nextSibling.outerHTML, '<hl class="foo">inquit</hl>');
|
||||
});
|
||||
|
||||
test('growing hl left to start of line', function() {
|
||||
element._applyRangedHighlight('some', 140, 2, 140, 5, 'left');
|
||||
element._applyRangedHighlight('some', 140, 0, 140, 3, 'left');
|
||||
assert.equal(contentText.querySelectorAll('hl.some').length, 1);
|
||||
var hl = contentText.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">na💢ti</hl>');
|
||||
assert.strictEqual(contentText.firstChild, hl);
|
||||
});
|
||||
|
||||
test('splitting hl containing a tab', function() {
|
||||
element._applyRangedHighlight('some', 140, 63, 140, 72, 'left');
|
||||
assert.equal(contentText.querySelector('hl.some').textContent,
|
||||
'sit, quod');
|
||||
element._applyRangedHighlight('another', 140, 66, 140, 81, 'left');
|
||||
assert.equal(contentText.querySelector('hl.another').textContent,
|
||||
', quod Epicurum');
|
||||
});
|
||||
});
|
||||
|
||||
test('_applyAllHighlights', function() {
|
||||
element.comments = {
|
||||
left: [
|
||||
{
|
||||
range: {
|
||||
start_line: 3,
|
||||
start_character: 14,
|
||||
end_line: 10,
|
||||
end_character: 24,
|
||||
},
|
||||
},
|
||||
],
|
||||
right: [
|
||||
{
|
||||
range: {
|
||||
start_line: 320,
|
||||
start_character: 200,
|
||||
end_line: 1024,
|
||||
end_character: 768,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
sandbox.stub(element, '_applyRangedHighlight');
|
||||
element._applyAllHighlights();
|
||||
sinon.assert.calledWith(element._applyRangedHighlight,
|
||||
'range', 3, 14, 10, 24, 'left');
|
||||
sinon.assert.calledWith(element._applyRangedHighlight,
|
||||
'range', 320, 200, 1024, 768, 'right');
|
||||
});
|
||||
|
||||
test('apply comment ranges on render', function() {
|
||||
sandbox.stub(element, '_applyAllHighlights');
|
||||
element.fire('render');
|
||||
assert.isTrue(element._applyAllHighlights.called);
|
||||
});
|
||||
|
||||
test('apply comment ranges on context expand', function() {
|
||||
sandbox.stub(element, '_applyAllHighlights');
|
||||
element.fire('show-context');
|
||||
assert.isTrue(element._applyAllHighlights.called);
|
||||
});
|
||||
|
||||
suite('selection', function() {
|
||||
var diff;
|
||||
var builder;
|
||||
|
||||
Reference in New Issue
Block a user