
Diffs in PolyGerrit apply two shades of highlight to changed lines (light and dark) to indicate the granularity of modifications and to distinguish intraline edits. However, the logic for choosing the background shade for diff lines would differ from that of GWT UI diffs subtly. +----------------------------------+----------------------------------+ | GWT UI Shading Logic | PG Shading Logic (incorrect) | +----------------------------------+----------------------------------+ | Diff lines get a dark background | Diff lines get a dark background | | IFF they appear in a delta chunk | IFF they do NOT contain any | | that is empty on the left OR | intraline differences. | | empty on the right. | | +----------------------------------+----------------------------------+ | Diff lines get a light background otherwise. | +---------------------------------------------------------------------+ With this change, the shading logic in PolyGerrit is modified to match that of the GWT UI. Bug: Issue 4219 Bug: Issue 5117 Change-Id: Ice24292df777118c08c3e73f771720f8a186a183
519 lines
19 KiB
HTML
519 lines
19 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"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
|
|
<td class="right lineNum" data-value="119">119</td>
|
|
<td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></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"><div class="contentText">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></div><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"><div class="contentText">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</div></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"><div class="contentText">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;</div></td>
|
|
<td class="right lineNum" data-value="130"></td>
|
|
<td class="content both"><div class="contentText">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</div></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 total">
|
|
<tr class="diff-row side-by-side" left-type="blank" right-type="add">
|
|
<td class="left"></td>
|
|
<td class="blank"></td>
|
|
<td class="right lineNum" data-value="146"></td>
|
|
<td class="content add"><div class="contentText">[17] Quid igitur est? inquit; audire enim cupio, quid non probes. Principio, inquam,</div></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"><div class="contentText">in physicis, quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
|
|
<td class="right lineNum" data-value="147"></td>
|
|
<td class="content both"><div class="contentText">in physicis, <hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></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();
|
|
});
|
|
|
|
suite('selectionchange event handling', function() {
|
|
var emulateSelection = function() {
|
|
document.dispatchEvent(new CustomEvent('selectionchange'));
|
|
element.flushDebouncer('selectionChange');
|
|
element.flushDebouncer('removeActionBox');
|
|
};
|
|
|
|
setup(function() {
|
|
sandbox.stub(element, '_handleSelection');
|
|
sandbox.stub(element, '_removeActionBox');
|
|
});
|
|
|
|
test('enabled if logged in', function() {
|
|
element.loggedIn = true;
|
|
emulateSelection();
|
|
assert.isTrue(element._handleSelection.called);
|
|
assert.isTrue(element._removeActionBox.called);
|
|
});
|
|
|
|
test('ignored if logged out', function() {
|
|
element.loggedIn = false;
|
|
emulateSelection();
|
|
assert.isFalse(element._handleSelection.called);
|
|
assert.isFalse(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;
|
|
});
|
|
|
|
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('comment-mouse-over from line comments is ignored', function() {
|
|
sandbox.stub(element, 'set');
|
|
element.fire('comment-mouse-over', {comment: {}});
|
|
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() {
|
|
element.fire('comment-mouse-over', {comment: {}});
|
|
assert.isFalse(builder.getContentsByLineRange.called);
|
|
});
|
|
|
|
test('on create-comment action box is removed', function() {
|
|
sandbox.stub(element, '_removeActionBox');
|
|
element.fire('create-comment', {
|
|
comment: {
|
|
range: {},
|
|
},
|
|
});
|
|
assert.isTrue(element._removeActionBox.called);
|
|
});
|
|
});
|
|
|
|
suite('selection', function() {
|
|
var diff;
|
|
var builder;
|
|
var contentStubs;
|
|
|
|
var stubContent = function(line, side, opt_child) {
|
|
var contentTd = diff.querySelector(
|
|
'.' + side + '.lineNum[data-value="' + line + '"] ~ .content');
|
|
var contentText = contentTd.querySelector('.contentText');
|
|
var lineEl = diff.querySelector(
|
|
'.' + side + '.lineNum[data-value="' + line + '"]');
|
|
contentStubs.push({
|
|
lineEl: lineEl,
|
|
contentTd: contentTd,
|
|
contentText: contentText,
|
|
});
|
|
builder.getContentByLineEl.withArgs(lineEl).returns(contentText);
|
|
builder.getLineNumberByChild.withArgs(lineEl).returns(line);
|
|
builder.getContentByLine.withArgs(line, side).returns(contentText);
|
|
builder.getSideByLineEl.withArgs(lineEl).returns(side);
|
|
return contentText;
|
|
};
|
|
|
|
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.contentTd.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;
|
|
});
|
|
|
|
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 contentText = stubContent(140, 'left');
|
|
var contentTd = contentText.parentElement;
|
|
|
|
emulateSelection(contentTd.previousElementSibling.firstChild, 2,
|
|
contentText.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.parentElement.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.parentElement.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');
|
|
});
|
|
|
|
test('properly accounts for syntax highlighting', function() {
|
|
var content = stubContent(140, 'left');
|
|
var spy = sinon.spy(element, '_normalizeRange');
|
|
emulateSelection(
|
|
content.querySelectorAll('hl')[3], 0,
|
|
content.querySelectorAll('span')[1], 0);
|
|
var spyCall = spy.getCall(0);
|
|
var range = window.getSelection().getRangeAt(0);
|
|
assert.notDeepEqual(spyCall.returnValue, range);
|
|
});
|
|
|
|
test('GrRangeNormalizer._getTextOffset computes text offset', function() {
|
|
var content = stubContent(140, 'left');
|
|
var child = content.lastChild.lastChild;
|
|
var result = GrRangeNormalizer._getTextOffset(content, child);
|
|
assert.equal(result, 73);
|
|
content = stubContent(146, 'right');
|
|
child = content.lastChild;
|
|
result = GrRangeNormalizer._getTextOffset(content, child);
|
|
assert.equal(result, 0);
|
|
});
|
|
// 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>
|