Apply range comments and highlights
Utility methods for applying comment range highlights to diff, with tests including some of the corner cases. Feature: Issue 3910 Change-Id: Id7de2dd4ff027ce96479a2d596e9414a0cadd6bf
This commit is contained in:
@@ -14,6 +14,9 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
|
||||
var REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-diff-highlight',
|
||||
|
||||
@@ -44,13 +47,6 @@
|
||||
return this._cachedDiffBuilder;
|
||||
},
|
||||
|
||||
get diffElement() {
|
||||
if (!this._diffElement) {
|
||||
this._diffElement = Polymer.dom(this).querySelector('#diffTable');
|
||||
}
|
||||
return this._diffElement;
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
this.enabled = false;
|
||||
},
|
||||
@@ -76,5 +72,307 @@
|
||||
this._removeActionBox();
|
||||
}
|
||||
},
|
||||
|
||||
_removeActionBox: function() {
|
||||
var actionBox = this.$$('gr-selection-action-box');
|
||||
if (actionBox) {
|
||||
Polymer.dom(this.root).removeChild(actionBox);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Traverse diff content from right to left, call callback for each node.
|
||||
* Stops if callback returns true.
|
||||
*
|
||||
* @param {!Node} startNode
|
||||
* @param {function(Node):boolean} callback
|
||||
* @param {Object=} flags If flags.left is true, traverse left.
|
||||
*/
|
||||
_traverseContentSiblings: function(startNode, callback, opt_flags) {
|
||||
var travelLeft = opt_flags && opt_flags.left;
|
||||
var node = startNode;
|
||||
while (node) {
|
||||
if (node instanceof Element && node.tagName !== 'HL') {
|
||||
break;
|
||||
}
|
||||
var nextNode = travelLeft ? node.previousSibling : node.nextSibling;
|
||||
if (callback(node)) {
|
||||
break;
|
||||
}
|
||||
node = nextNode;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get length of a node. Traverses diff content siblings if required.
|
||||
*
|
||||
* @param {!Node} node
|
||||
* @return {number}
|
||||
*/
|
||||
_getLength: function(node) {
|
||||
if (node instanceof Element && node.classList.contains('content')) {
|
||||
node = node.firstChild;
|
||||
var length = 0;
|
||||
while (node) {
|
||||
// Only measure Text nodes and <hl>
|
||||
if (node instanceof Text || node.tagName == 'HL') {
|
||||
length += this._getLength(node);
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return length;
|
||||
} else {
|
||||
// DOM API for textConten.length is broken for Unicode:
|
||||
// https://mathiasbynens.be/notes/javascript-unicode
|
||||
return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Wraps node in hl tag with cssClass, replacing the node in DOM.
|
||||
*
|
||||
* @return {!Element} Wrapped node.
|
||||
*/
|
||||
_wrapInHighlight: function(node, cssClass) {
|
||||
var hl = document.createElement('hl');
|
||||
hl.className = cssClass;
|
||||
Polymer.dom(node.parentElement).replaceChild(hl, node);
|
||||
hl.appendChild(node);
|
||||
return hl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Node.prototype.splitText Unicode-valid alternative.
|
||||
*
|
||||
* @param {!Text} node
|
||||
* @param {number} offset
|
||||
* @return {!Text} Trailing Text Node.
|
||||
*/
|
||||
_splitText: function(node, offset) {
|
||||
if (node.textContent.match(REGEX_ASTRAL_SYMBOL)) {
|
||||
// DOM Api for splitText() is broken for Unicode:
|
||||
// https://mathiasbynens.be/notes/javascript-unicode
|
||||
// TODO (viktard): Polyfill Array.from for IE10.
|
||||
var head = Array.from(node.textContent);
|
||||
var tail = head.splice(offset);
|
||||
var parent = node.parentElement;
|
||||
var headNode = document.createTextNode(head.join(''));
|
||||
parent.replaceChild(headNode, node);
|
||||
var tailNode = document.createTextNode(tail.join(''));
|
||||
parent.insertBefore(tailNode, headNode.nextSibling);
|
||||
return tailNode;
|
||||
} else {
|
||||
return node.splitText(offset);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Split Text Node and wrap it in hl with cssClass.
|
||||
* Wraps trailing part after split, tailing one if opt_firstPart is true.
|
||||
*
|
||||
* @param {!Text} node
|
||||
* @param {number} offset
|
||||
* @param {string} cssClass
|
||||
* @param {boolean=} opt_firstPart
|
||||
*/
|
||||
_splitAndWrapInHighlight: function(node, offset, cssClass, opt_firstPart) {
|
||||
if (this._getLength(node) === offset || offset === 0) {
|
||||
return this._wrapInHighlight(node, cssClass);
|
||||
} else {
|
||||
if (opt_firstPart) {
|
||||
this._splitText(node, offset);
|
||||
// Node points to first part of the Text, second one is sibling.
|
||||
} else {
|
||||
node = this._splitText(node, offset);
|
||||
}
|
||||
return this._wrapInHighlight(node, cssClass);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hl tag with cssClass for starting side of range highlight.
|
||||
*
|
||||
* @param {!Element} startContent Range start diff content aka td.content.
|
||||
* @param {!Element} endContent Range end diff content aka td.content.
|
||||
* @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.
|
||||
while (startNode &&
|
||||
this._getLength(startNode) <= startOffset ||
|
||||
this._getLength(startNode) === 0) {
|
||||
startOffset -= this._getLength(startNode);
|
||||
startNode = startNode.nextSibling;
|
||||
}
|
||||
|
||||
// Split Text node.
|
||||
if (startNode instanceof Text) {
|
||||
startNode =
|
||||
this._splitAndWrapInHighlight(startNode, startOffset, cssClass);
|
||||
startContent.insertBefore(startNode, startNode.nextSibling);
|
||||
// Edge case: single line, text node wraps the highlight.
|
||||
if (isOneLine && this._getLength(startNode) > length) {
|
||||
var extra = this._splitText(startNode.firstChild, length);
|
||||
startContent.insertBefore(extra, startNode.nextSibling);
|
||||
startContent.normalize();
|
||||
}
|
||||
} else if (startNode.tagName == 'HL') {
|
||||
if (!startNode.classList.contains(cssClass)) {
|
||||
var hl = startNode;
|
||||
startNode = this._splitAndWrapInHighlight(
|
||||
startNode.firstChild, startOffset, cssClass);
|
||||
startContent.insertBefore(startNode, hl.nextSibling);
|
||||
// Edge case: single line, <hl> wraps the highlight.
|
||||
if (isOneLine && this._getLength(startNode) > length) {
|
||||
var trailingHl = hl.cloneNode(false);
|
||||
trailingHl.appendChild(
|
||||
this._splitText(startNode.firstChild, length));
|
||||
startContent.insertBefore(trailingHl, startNode.nextSibling);
|
||||
}
|
||||
if (hl.textContent.length === 0) {
|
||||
hl.remove();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startNode = null;
|
||||
}
|
||||
return startNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates hl tag with cssClass for ending side of range highlight.
|
||||
*
|
||||
* @param {!Element} startContent Range start diff content aka td.content.
|
||||
* @param {!Element} endContent Range end diff content aka td.content.
|
||||
* @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.
|
||||
while (endNode &&
|
||||
this._getLength(endNode) < endOffset ||
|
||||
this._getLength(endNode) === 0) {
|
||||
endOffset -= this._getLength(endNode);
|
||||
endNode = endNode.nextSibling;
|
||||
}
|
||||
|
||||
if (endNode instanceof Text) {
|
||||
endNode =
|
||||
this._splitAndWrapInHighlight(endNode, endOffset, cssClass, true);
|
||||
} else if (endNode.tagName == 'HL') {
|
||||
if (!endNode.classList.contains(cssClass)) {
|
||||
// Split text inside HL.
|
||||
var hl = endNode;
|
||||
endNode = this._splitAndWrapInHighlight(
|
||||
endNode.firstChild, endOffset, cssClass, true);
|
||||
endContent.insertBefore(endNode, hl);
|
||||
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 td.content.
|
||||
* @param {!Element} endContent Range end diff content aka td.content.
|
||||
* @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) {
|
||||
this._traverseContentSiblings(startNode.nextSibling, function(node) {
|
||||
startNode.textContent += node.textContent;
|
||||
node.remove();
|
||||
return node == endNode;
|
||||
});
|
||||
}
|
||||
|
||||
if (!isOneLine && endNode) {
|
||||
// Prepend text up to line start to the ending highlight.
|
||||
this._traverseContentSiblings(endNode.previousSibling, function(node) {
|
||||
endNode.textContent = node.textContent + endNode.textContent;
|
||||
node.remove();
|
||||
}, {left: true});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} cssClass
|
||||
* @param {number} startLine Range start code line number.
|
||||
* @param {number} startCol Range start column number.
|
||||
* @param {number} endCol Range end column number.
|
||||
* @param {number} endOffset Range end within end content.
|
||||
* @param {string=} opt_side Side selector (right or left).
|
||||
*/
|
||||
_applyRangedHighlight: function(
|
||||
cssClass, startLine, startCol, endLine, endCol, opt_side) {
|
||||
var side = 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);
|
||||
// Wrap contents in highlight.
|
||||
contents.forEach(function(content) {
|
||||
if (content.textContent.length === 0) {
|
||||
return;
|
||||
}
|
||||
var lineEl = this.diffBuilder.getLineElByChild(content);
|
||||
var line = lineEl.getAttribute('data-value');
|
||||
var threadEl =
|
||||
this.diffBuilder.getCommentThreadByContentEl(content);
|
||||
if (threadEl) {
|
||||
threadEl.remove();
|
||||
}
|
||||
var text = document.createTextNode(content.textContent);
|
||||
while (content.firstChild) {
|
||||
content.removeChild(content.firstChild);
|
||||
}
|
||||
content.appendChild(text);
|
||||
if (threadEl) {
|
||||
content.appendChild(threadEl);
|
||||
}
|
||||
this._wrapInHighlight(text, cssClass);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -26,18 +26,72 @@ limitations under the License.
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-diff-highlight></gr-diff-highlight>
|
||||
<gr-diff-highlight>
|
||||
<table id="diffTable">
|
||||
|
||||
<tbody class="section both">
|
||||
<tr class="diff-row side-by-side" left-type="both" right-type="both">
|
||||
<td class="left lineNum" data-value="138"></td>
|
||||
<td class="content both darkHighlight">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</td>
|
||||
<td class="right lineNum" data-value="119"></td>
|
||||
<td class="content both darkHighlight">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody class="section delta">
|
||||
<tr class="diff-row side-by-side" left-type="remove" right-type="add">
|
||||
<td class="left lineNum" data-value="140"></td>
|
||||
<!-- Next tag is formatted to eliminate zero-length text nodes. -->
|
||||
<td class="content remove lightHighlight">na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a udiam, <hl>quid</hl> sit, quod <hl>Epicurum</hl><gr-diff-comment-thread>
|
||||
[Yet another random diff thread content here]
|
||||
</gr-diff-comment-thread></td>
|
||||
<td class="right lineNum" data-value="121"></td>
|
||||
<td class="content add lightHighlight">
|
||||
nacti ,
|
||||
<hl>,</hl>
|
||||
sumus otiosum, audiam, sit, quod
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody class="section both">
|
||||
<tr class="diff-row side-by-side" left-type="both" right-type="both">
|
||||
<td class="left lineNum" data-value="149"></td>
|
||||
<td class="content both darkHighlight">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</td>
|
||||
<td class="right lineNum" data-value="130"></td>
|
||||
<td class="content both darkHighlight">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</gr-diff-highlight>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<test-fixture id="highlighted">
|
||||
<template>
|
||||
<div>
|
||||
<hl class="rangeHighlight">foo</hl>
|
||||
bar
|
||||
<hl class="rangeHighlight">baz</hl>
|
||||
</div>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-diff-highlight', function() {
|
||||
var element;
|
||||
var sandbox;
|
||||
|
||||
setup(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('handles down only when enabled ', function() {
|
||||
sinon.stub(element, '_handleDown');
|
||||
MockInteractions.down(element);
|
||||
@@ -47,5 +101,196 @@ limitations under the License.
|
||||
assert.isTrue(element._handleDown.called);
|
||||
element._handleDown.restore();
|
||||
});
|
||||
|
||||
test('apply multiline highlight', function() {
|
||||
var diff = element.querySelector('#diffTable');
|
||||
var startContent =
|
||||
diff.querySelector('.left.lineNum[data-value="138"] ~ .content');
|
||||
var endContent =
|
||||
diff.querySelector('.left.lineNum[data-value="149"] ~ .content');
|
||||
var betweenContent =
|
||||
diff.querySelector('.left.lineNum[data-value="140"] ~ .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;
|
||||
element.enabled = true;
|
||||
builder.getContentByLine.withArgs(138, 'left').returns(
|
||||
startContent);
|
||||
builder.getContentByLine.withArgs(149, 'left').returns(
|
||||
endContent);
|
||||
element._applyRangedHighlight('some', 138, 4, 149, 8, '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(endContent.childNodes[0], Element);
|
||||
assert.equal(endContent.childNodes[0].textContent, 'nam et c');
|
||||
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,
|
||||
'omplectitur verbis, quod vult, et dicit plane, quod intellegam;');
|
||||
|
||||
assert.instanceOf(betweenContent.firstChild, Element);
|
||||
assert.equal(betweenContent.firstChild.tagName, 'HL');
|
||||
assert.equal(betweenContent.firstChild.className, 'some');
|
||||
assert.equal(betweenContent.childNodes.length, 2);
|
||||
assert.equal(betweenContent.firstChild.childNodes.length, 1);
|
||||
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.strictEqual(betweenContent.querySelector('gr-diff-comment-thread'),
|
||||
commentThread, 'Comment threads should be preserved.');
|
||||
});
|
||||
|
||||
suite('single line ranges', function() {
|
||||
var diff;
|
||||
var content;
|
||||
var commentThread;
|
||||
var builder;
|
||||
|
||||
setup(function() {
|
||||
diff = element.querySelector('#diffTable');
|
||||
content =
|
||||
diff.querySelector('.left.lineNum[data-value="140"] ~ .content');
|
||||
commentThread = diff.querySelector('gr-diff-comment-thread');
|
||||
builder = {
|
||||
getCommentThreadByContentEl: sandbox.stub().returns(commentThread),
|
||||
getContentByLine: sandbox.stub().returns(content),
|
||||
getContentsByLineRange: sandbox.stub().returns([]),
|
||||
getLineElByChild: sandbox.stub().returns(
|
||||
{getAttribute: sandbox.stub()}),
|
||||
};
|
||||
element._cachedDiffBuilder = builder;
|
||||
element.enabled = true;
|
||||
});
|
||||
|
||||
test('whole line range', function() {
|
||||
element._applyRangedHighlight('some', 140, 0, 140, 81, 'left');
|
||||
assert.instanceOf(content.firstChild, Element);
|
||||
assert.equal(content.firstChild.tagName, 'HL');
|
||||
assert.equal(content.firstChild.className, 'some');
|
||||
assert.equal(content.childNodes.length, 2);
|
||||
assert.equal(content.firstChild.childNodes.length, 1);
|
||||
assert.equal(content.firstChild.textContent,
|
||||
'na💢ti te, inquit, sumus aliquando otiosum, certe a udiam, ' +
|
||||
'quid sit, quod Epicurum');
|
||||
});
|
||||
|
||||
test('merging multiple other hls', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 80, 'left');
|
||||
assert.instanceOf(content.firstChild, Text);
|
||||
assert.equal(content.childNodes.length, 4);
|
||||
var hl = content.querySelector('hl.some');
|
||||
assert.strictEqual(content.firstChild, hl.previousSibling);
|
||||
assert.equal(hl.childNodes.length, 1);
|
||||
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 = content.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 = content.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 = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">quit, sum</hl>');
|
||||
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 = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">e, in</hl>');
|
||||
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 = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">, inquit, sumus ali</hl>');
|
||||
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 = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">💢ti te, inquit, sum</hl>');
|
||||
assert.notOk(content.querySelector('.foo'));
|
||||
});
|
||||
|
||||
test('hl starting and ending in boundaries', function() {
|
||||
element._applyRangedHighlight('some', 140, 6, 140, 33, 'left');
|
||||
var hl = content.querySelector('hl.some');
|
||||
assert.equal(
|
||||
hl.outerHTML, '<hl class="some">te, inquit, sumus aliquando</hl>');
|
||||
assert.notOk(content.querySelector('.foo'));
|
||||
assert.notOk(content.querySelector('.bar'));
|
||||
});
|
||||
|
||||
test('overlapping hls', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 3, 'left');
|
||||
element._applyRangedHighlight('some', 140, 2, 140, 4, 'left');
|
||||
assert.equal(content.querySelectorAll('hl.some').length, 1);
|
||||
var hl = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">a💢t</hl>');
|
||||
});
|
||||
|
||||
test('growing hl left including another hl', function() {
|
||||
element._applyRangedHighlight('some', 140, 1, 140, 4, 'left');
|
||||
element._applyRangedHighlight('some', 140, 3, 140, 10, 'left');
|
||||
assert.equal(content.querySelectorAll('hl.some').length, 1);
|
||||
var hl = content.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 right to start of line', function() {
|
||||
element._applyRangedHighlight('some', 140, 2, 140, 5, 'left');
|
||||
element._applyRangedHighlight('some', 140, 0, 140, 3, 'left');
|
||||
assert.equal(content.querySelectorAll('hl.some').length, 1);
|
||||
var hl = content.querySelector('hl.some');
|
||||
assert.equal(hl.outerHTML, '<hl class="some">na💢ti</hl>');
|
||||
assert.strictEqual(content.firstChild, hl);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user