
It was possible to cause a JS error when creating a ranged comment that started at the very end of the first line (selecting no content on that line). The relevant null-guard needed an additional set of parens to avoid evaluating the second OR operand with a bad argument in this case. Addresses the null-guard boolean expressions in `_normalizeStart` and `_normalizeEnd` and reduces the number of calls to `_getLength` from thrice to once per iteration. Adds a relevant unit test. Change-Id: I98848f9f6089fd3240bda175765770c9f9c5ba30
894 lines
34 KiB
HTML
894 lines
34 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
Copyright (C) 2016 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-highlight</title>
|
|
|
|
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
|
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
|
|
|
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
|
<link rel="import" href="gr-diff-highlight.html">
|
|
|
|
<test-fixture id="basic">
|
|
<template>
|
|
<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">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">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">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 <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl>udiam, <hl>quid</hl> sit, <span class="tab withIndicator" style="tab-size:8;"></span>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="120">120</td>
|
|
<!-- Next tag is formatted to eliminate zero-length text nodes. -->
|
|
<td class="content add lightHighlight">nacti , <hl>,</hl> sumus <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl> otiosum, <span class="tab withIndicator" style="tab-size:8;"></span> 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="141"></td>
|
|
<td class="content both darkHighlight">nam et<hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl>complectitur<span class="tab withIndicator" style="tab-size:8;"> </span>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>
|
|
|
|
<tbody class="section contextControl">
|
|
<tr class="diff-row side-by-side" left-type="contextControl" right-type="contextControl">
|
|
<td class="left contextLineNum" data-value="@@"></td>
|
|
<td>
|
|
<gr-button>+10↑</gr-button>
|
|
-
|
|
<gr-button>Show 21 common lines</gr-button>
|
|
-
|
|
<gr-button>+10↓</gr-button>
|
|
</td>
|
|
<td class="right contextLineNum" data-value="@@"></td>
|
|
<td>
|
|
<gr-button>+10↑</gr-button>
|
|
-
|
|
<gr-button>Show 21 common lines</gr-button>
|
|
-
|
|
<gr-button>+10↓</gr-button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody class="section delta">
|
|
<tr class="diff-row side-by-side" left-type="blank" right-type="add">
|
|
<td class="left"></td>
|
|
<td class="blank darkHighlight"></td>
|
|
<td class="right lineNum" data-value="146"></td>
|
|
<td class="content add darkHighlight">[17] Quid igitur est? inquit; audire enim cupio, quid non probes. Principio, inquam,</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="165"></td>
|
|
<td class="content both darkHighlight">in physicis, quibus maxime gloriatur, primum totus est alienus. Democritea dicit</td>
|
|
<td class="right lineNum" data-value="147"></td>
|
|
<td class="content both darkHighlight">in physicis, <hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</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('_enabledListeners', function() {
|
|
var listeners = element._enabledListeners;
|
|
for (var eventName in listeners) {
|
|
sandbox.stub(element, listeners[eventName]);
|
|
}
|
|
// Enable all the listeners.
|
|
element.enabled = true;
|
|
for (var eventName in listeners) {
|
|
var methodName = listeners[eventName];
|
|
var stub = element[methodName];
|
|
element.fire(eventName);
|
|
assert.isTrue(stub.called);
|
|
stub.reset();
|
|
}
|
|
// Disable all the listeners.
|
|
element.enabled = false;
|
|
for (var eventName in listeners) {
|
|
var methodName = listeners[eventName];
|
|
var stub = element[methodName];
|
|
element.fire(eventName);
|
|
assert.isFalse(stub.called);
|
|
}
|
|
});
|
|
|
|
test('does not listen to selectionchange when disabled', function() {
|
|
sandbox.stub(element, '_handleSelection');
|
|
sandbox.stub(element, '_removeActionBox');
|
|
element.enabled = false;
|
|
document.dispatchEvent(new CustomEvent('selectionchange'));
|
|
element.flushDebouncer('selectionChange');
|
|
assert.isFalse(element._handleSelection.called);
|
|
element.flushDebouncer('removeActionBox');
|
|
assert.isFalse(element._removeActionBox.called);
|
|
});
|
|
|
|
test('listens to selectionchange when enabled', function() {
|
|
sandbox.stub(element, '_handleSelection');
|
|
sandbox.stub(element, '_removeActionBox');
|
|
element.enabled = true;
|
|
document.dispatchEvent(new CustomEvent('selectionchange'));
|
|
element.flushDebouncer('selectionChange');
|
|
assert.isTrue(element._handleSelection.called);
|
|
element.flushDebouncer('removeActionBox');
|
|
assert.isTrue(element._removeActionBox.called);
|
|
});
|
|
|
|
suite('comment events', function() {
|
|
var builder;
|
|
|
|
setup(function() {
|
|
builder = {
|
|
getContentsByLineRange: sandbox.stub().returns([]),
|
|
getLineElByChild: sandbox.stub().returns({}),
|
|
getSideByLineEl: sandbox.stub().returns('other-side'),
|
|
renderLineRange: sandbox.stub(),
|
|
};
|
|
element._cachedDiffBuilder = builder;
|
|
element.enabled = true;
|
|
});
|
|
|
|
test('ignores thread discard for line comment', function(done) {
|
|
element.fire('thread-discard', {lastComment: {}});
|
|
flush(function() {
|
|
assert.isFalse(builder.renderLineRange.called);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('ignores comment discard for line comment', function(done) {
|
|
element.fire('comment-discard', {comment: {}});
|
|
flush(function() {
|
|
assert.isFalse(builder.renderLineRange.called);
|
|
done();
|
|
});
|
|
});
|
|
|
|
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');
|
|
element.fire('comment-mouse-over', {comment: {}});
|
|
assert.isFalse(element._applyRangedHighlight.called);
|
|
});
|
|
|
|
test('comment-mouse-out from line comments is ignored', function() {
|
|
element.fire('comment-mouse-over', {comment: {}});
|
|
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: {
|
|
range: {},
|
|
},
|
|
});
|
|
assert.isTrue(element._removeActionBox.called);
|
|
});
|
|
});
|
|
|
|
test('apply multiline highlight', 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;
|
|
element.enabled = true;
|
|
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, 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.');
|
|
|
|
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;
|
|
element.enabled = true;
|
|
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 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, 5);
|
|
assert.equal(content.firstChild.textContent,
|
|
'na💢ti te, inquit, sumus aliquando otiosum, certe a udiam, ' +
|
|
'quid sit, quod Epicurum');
|
|
var tabs = content.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(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, 5);
|
|
assert.equal(content.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 = 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.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 = content.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 = content.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 = 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.textContent, 'te, inquit, sumus aliquando');
|
|
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 right 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 left 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);
|
|
});
|
|
|
|
test('splitting hl containing a tab', function() {
|
|
element._applyRangedHighlight('some', 140, 63, 140, 72, 'left');
|
|
assert.equal(content.querySelector('hl.some').textContent, 'sit, quod');
|
|
element._applyRangedHighlight('another', 140, 66, 140, 81, 'left');
|
|
assert.equal(content.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() {
|
|
element.enabled = true;
|
|
sandbox.stub(element, '_applyAllHighlights');
|
|
element.fire('render');
|
|
assert.isTrue(element._applyAllHighlights.called);
|
|
});
|
|
|
|
test('apply comment ranges on context expand', function() {
|
|
element.enabled = true;
|
|
sandbox.stub(element, '_applyAllHighlights');
|
|
element.fire('show-context');
|
|
assert.isTrue(element._applyAllHighlights.called);
|
|
});
|
|
|
|
test('ignores render when disabled', function() {
|
|
element.enabled = false;
|
|
sandbox.stub(element, '_applyAllHighlights');
|
|
element.fire('render');
|
|
assert.isFalse(element._applyAllHighlights.called);
|
|
});
|
|
|
|
test('ignores context expand when disabled', function() {
|
|
element.enabled = false;
|
|
sandbox.stub(element, '_applyAllHighlights');
|
|
element.fire('show-context');
|
|
assert.isFalse(element._applyAllHighlights.called);
|
|
});
|
|
|
|
suite('selection', function() {
|
|
var diff;
|
|
var builder;
|
|
var contentStubs;
|
|
|
|
var stubContent = function(line, side, opt_child) {
|
|
var content = diff.querySelector(
|
|
'.' + side + '.lineNum[data-value="' + line + '"] ~ .content');
|
|
var lineEl = diff.querySelector(
|
|
'.' + side + '.lineNum[data-value="' + line + '"]');
|
|
contentStubs.push({
|
|
lineEl: lineEl,
|
|
content: content,
|
|
});
|
|
builder.getContentByLineEl.withArgs(lineEl).returns(content);
|
|
builder.getLineNumberByChild.withArgs(lineEl).returns(line);
|
|
builder.getContentByLine.withArgs(line, side).returns(content);
|
|
builder.getSideByLineEl.withArgs(lineEl).returns(side);
|
|
return content;
|
|
};
|
|
|
|
var emulateSelection = function(
|
|
startNode, startOffset, endNode, endOffset) {
|
|
var selection = window.getSelection();
|
|
var range = document.createRange();
|
|
range.setStart(startNode, startOffset);
|
|
range.setEnd(endNode, endOffset);
|
|
selection.addRange(range);
|
|
element._handleSelection();
|
|
};
|
|
|
|
var getActionRange = function() {
|
|
return Polymer.dom(element.root).querySelector(
|
|
'gr-selection-action-box').range;
|
|
};
|
|
|
|
var getActionSide = function() {
|
|
return Polymer.dom(element.root).querySelector(
|
|
'gr-selection-action-box').side;
|
|
};
|
|
|
|
var getLineElByChild = function(node) {
|
|
var stubs = contentStubs.find(function(stub) {
|
|
return stub.content.contains(node);
|
|
});
|
|
return stubs && stubs.lineEl;
|
|
};
|
|
|
|
setup(function() {
|
|
contentStubs = [];
|
|
stub('gr-selection-action-box', {
|
|
placeAbove: sandbox.stub(),
|
|
});
|
|
diff = element.querySelector('#diffTable');
|
|
builder = {
|
|
getContentByLine: sandbox.stub(),
|
|
getContentByLineEl: sandbox.stub(),
|
|
getLineElByChild: getLineElByChild,
|
|
getLineNumberByChild: sandbox.stub(),
|
|
getSideByLineEl: sandbox.stub(),
|
|
};
|
|
element._cachedDiffBuilder = builder;
|
|
element.enabled = true;
|
|
});
|
|
|
|
teardown(function() {
|
|
contentStubs = null;
|
|
window.getSelection().removeAllRanges();
|
|
});
|
|
|
|
test('single line', function() {
|
|
var content = stubContent(138, 'left');
|
|
emulateSelection(content.firstChild, 5, content.firstChild, 12);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 138,
|
|
startChar: 5,
|
|
endLine: 138,
|
|
endChar: 12,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('multiline', function() {
|
|
var startContent = stubContent(119, 'right');
|
|
var endContent = stubContent(120, 'right');
|
|
emulateSelection(
|
|
startContent.firstChild, 10, endContent.lastChild, 7);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 119,
|
|
startChar: 10,
|
|
endLine: 120,
|
|
endChar: 34,
|
|
});
|
|
assert.equal(getActionSide(), 'right');
|
|
});
|
|
|
|
test('multiline grow end highlight over tabs', function() {
|
|
var startContent = stubContent(119, 'right');
|
|
var endContent = stubContent(120, 'right');
|
|
emulateSelection(startContent.firstChild, 10, endContent.firstChild, 2);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 119,
|
|
startChar: 10,
|
|
endLine: 120,
|
|
endChar: 2,
|
|
});
|
|
assert.equal(getActionSide(), 'right');
|
|
});
|
|
|
|
test('collapsed', function() {
|
|
var content = stubContent(138, 'left');
|
|
emulateSelection(content.firstChild, 5, content.firstChild, 5);
|
|
assert.isOk(window.getSelection().getRangeAt(0).startContainer);
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('starts inside hl', function() {
|
|
var content = stubContent(140, 'left');
|
|
var hl = content.querySelector('.foo');
|
|
emulateSelection(hl.firstChild, 2, hl.nextSibling, 7);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 8,
|
|
endLine: 140,
|
|
endChar: 23,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('ends inside hl', function() {
|
|
var content = stubContent(140, 'left');
|
|
var hl = content.querySelector('.bar');
|
|
emulateSelection(hl.previousSibling, 2, hl.firstChild, 3);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 18,
|
|
endLine: 140,
|
|
endChar: 27,
|
|
});
|
|
});
|
|
|
|
test('multiple hl', function() {
|
|
var content = stubContent(140, 'left');
|
|
var hl = content.querySelectorAll('hl')[4];
|
|
emulateSelection(content.firstChild, 2, hl.firstChild, 2);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 2,
|
|
endLine: 140,
|
|
endChar: 60,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('starts outside of diff', function() {
|
|
var content = stubContent(140, 'left');
|
|
emulateSelection(content.previousElementSibling.firstChild, 2,
|
|
content.firstChild, 2);
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('ends outside of diff', function() {
|
|
var content = stubContent(140, 'left');
|
|
emulateSelection(content.nextElementSibling.firstChild, 2,
|
|
content.firstChild, 2);
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('starts and ends on different sides', function() {
|
|
var startContent = stubContent(140, 'left');
|
|
var endContent = stubContent(130, 'right');
|
|
emulateSelection(startContent.firstChild, 2, endContent.firstChild, 2);
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('starts in comment thread element', function() {
|
|
var startContent = stubContent(140, 'left');
|
|
var comment = startContent.querySelector('gr-diff-comment-thread');
|
|
var endContent = stubContent(141, 'left');
|
|
emulateSelection(comment.firstChild, 2, endContent.firstChild, 4);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 81,
|
|
endLine: 141,
|
|
endChar: 4,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('ends in comment thread element', function() {
|
|
var content = stubContent(140, 'left');
|
|
var comment = content.querySelector('gr-diff-comment-thread');
|
|
emulateSelection(content.firstChild, 4, comment.firstChild, 1);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 4,
|
|
endLine: 140,
|
|
endChar: 81,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('starts in context element', function() {
|
|
var contextControl = diff.querySelector('.contextControl');
|
|
var content = stubContent(146, 'right');
|
|
emulateSelection(contextControl, 0, content.firstChild, 7);
|
|
// TODO (viktard): Select nearest line.
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('ends in context element', function() {
|
|
var contextControl = diff.querySelector('.contextControl');
|
|
var content = stubContent(141, 'left');
|
|
emulateSelection(content.firstChild, 2, contextControl, 0);
|
|
// TODO (viktard): Select nearest line.
|
|
assert.isFalse(element.isRangeSelected());
|
|
});
|
|
|
|
test('selection containing context element', function() {
|
|
var startContent = stubContent(130, 'right');
|
|
var endContent = stubContent(146, 'right');
|
|
emulateSelection(startContent.firstChild, 3, endContent.firstChild, 14);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 130,
|
|
startChar: 3,
|
|
endLine: 146,
|
|
endChar: 14,
|
|
});
|
|
assert.equal(getActionSide(), 'right');
|
|
});
|
|
|
|
test('ends at a tab', function() {
|
|
var content = stubContent(140, 'left');
|
|
emulateSelection(
|
|
content.firstChild, 1, content.querySelector('span'), 0);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 1,
|
|
endLine: 140,
|
|
endChar: 51,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
test('starts at a tab', function() {
|
|
var content = stubContent(140, 'left');
|
|
emulateSelection(
|
|
content.querySelectorAll('hl')[3], 0,
|
|
content.querySelectorAll('span')[1], 0);
|
|
assert.isTrue(element.isRangeSelected());
|
|
assert.deepEqual(getActionRange(), {
|
|
startLine: 140,
|
|
startChar: 51,
|
|
endLine: 140,
|
|
endChar: 68,
|
|
});
|
|
assert.equal(getActionSide(), 'left');
|
|
});
|
|
|
|
// TODO (viktard): Selection starts in line number.
|
|
// TODO (viktard): Empty lines in selection start.
|
|
// TODO (viktard): Empty lines in selection end.
|
|
// TODO (viktard): Only empty lines selected.
|
|
// TODO (viktard): Unified mode.
|
|
});
|
|
});
|
|
</script>
|