blob: 69fea31cafd812513240786e52dbfec0d0bb44dc [file] [log] [blame]
<!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-builder</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../scripts/util.js"></script>
<script src="../gr-diff/gr-diff-line.js"></script>
<script src="../gr-diff/gr-diff-group.js"></script>
<script src="../gr-diff-highlight/gr-annotation.js"></script>
<script src="gr-diff-builder.js"></script>
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<link rel="import" href="gr-diff-builder.html">
<test-fixture id="basic">
<template>
<gr-diff-builder>
<table id="diffTable"></table>
</gr-diff-builder>
</template>
</test-fixture>
<test-fixture id="div-with-text">
<template>
<div>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</div>
</template>
</test-fixture>
<test-fixture id="mock-diff">
<template>
<gr-diff-builder view-mode="SIDE_BY_SIDE">
<table id="diffTable"></table>
</gr-diff-builder>
</template>
</test-fixture>
<script>
suite('gr-diff-builder tests', function() {
var element;
var builder;
setup(function() {
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(false); },
});
var prefs = {
line_length: 10,
show_tabs: true,
tab_size: 4,
};
builder = new GrDiffBuilder({content: []}, {left: [], right: []}, prefs);
});
test('context control buttons', function() {
var section = {};
var line = {contextGroup: {lines: []}};
// Create 10 lines.
for (var i = 0; i < 10; i++) {
line.contextGroup.lines.push('lorem upsum');
}
// Does not include +10 buttons when there are fewer than 11 lines.
var td = builder._createContextControl(section, line);
var buttons = td.querySelectorAll('gr-button.showContext');
assert.equal(buttons.length, 1);
assert.equal(buttons[0].textContent, 'Show 10 common lines');
// Add another line.
line.contextGroup.lines.push('lorem upsum');
// Includes +10 buttons when there are at least 11 lines.
td = builder._createContextControl(section, line);
buttons = td.querySelectorAll('gr-button.showContext');
assert.equal(buttons.length, 3);
assert.equal(buttons[0].textContent, '+10↑');
assert.equal(buttons[1].textContent, 'Show 11 common lines');
assert.equal(buttons[2].textContent, '+10↓');
});
test('newlines', function() {
var text = 'abcdef';
assert.equal(builder._addNewlines(text, text), text);
text = 'a'.repeat(20);
assert.equal(builder._addNewlines(text, text),
'a'.repeat(10) +
GrDiffBuilder.LINE_FEED_HTML +
'a'.repeat(10));
text = '<span class="thumbsup">👍</span>';
var html = '&lt;span class=&quot;thumbsup&quot;&gt;👍&lt;&#x2F;span&gt;';
assert.equal(builder._addNewlines(text, html),
'&lt;span clas' +
GrDiffBuilder.LINE_FEED_HTML +
's=&quot;thumbsu' +
GrDiffBuilder.LINE_FEED_HTML +
'p&quot;&gt;👍&lt;&#x2F;spa' +
GrDiffBuilder.LINE_FEED_HTML +
'n&gt;');
text = '01234\t56789';
assert.equal(builder._addNewlines(text, text),
'01234\t5' +
GrDiffBuilder.LINE_FEED_HTML +
'6789');
});
test('text length with tabs and unicode', function() {
assert.equal(builder._textLength('12345', 4), 5);
assert.equal(builder._textLength('\t\t12', 4), 10);
assert.equal(builder._textLength('abc💢123', 4), 7);
assert.equal(builder._textLength('abc\t', 8), 8);
assert.equal(builder._textLength('abc\t\t', 10), 20);
assert.equal(builder._textLength('', 10), 0);
assert.equal(builder._textLength('', 10), 0);
assert.equal(builder._textLength('abc\tde', 10), 12);
assert.equal(builder._textLength('abc\tde\t', 10), 20);
assert.equal(builder._textLength('\t\t\t\t\t', 20), 100);
});
test('tab wrapper insertion', function() {
var html = 'abc\tdef';
var wrapper = builder._getTabWrapper(
builder._prefs.tab_size - 3,
builder._prefs.show_tabs);
assert.ok(wrapper);
assert.isAbove(wrapper.length, 0);
assert.equal(builder._addTabWrappers(html, builder._prefs.tab_size),
'abc' + wrapper + 'def');
assert.throws(builder._getTabWrapper.bind(
builder,
// using \x3c instead of < in string so gjslint can parse
'">\x3cimg src="/" onerror="alert(1);">\x3cspan class="',
true));
});
test('comments', function() {
var line = new GrDiffLine(GrDiffLine.Type.BOTH);
line.beforeNumber = 3;
line.afterNumber = 5;
var comments = {left: [], right: []};
assert.deepEqual(builder._getCommentsForLine(comments, line), []);
assert.deepEqual(builder._getCommentsForLine(comments, line,
GrDiffBuilder.Side.LEFT), []);
assert.deepEqual(builder._getCommentsForLine(comments, line,
GrDiffBuilder.Side.RIGHT), []);
comments = {
left: [
{id: 'l3', line: 3},
{id: 'l5', line: 5},
],
right: [
{id: 'r3', line: 3},
{id: 'r5', line: 5},
],
};
assert.deepEqual(builder._getCommentsForLine(comments, line),
[{id: 'l3', line: 3}, {id: 'r5', line: 5}]);
assert.deepEqual(builder._getCommentsForLine(comments, line,
GrDiffBuilder.Side.LEFT), [{id: 'l3', line: 3}]);
assert.deepEqual(builder._getCommentsForLine(comments, line,
GrDiffBuilder.Side.RIGHT), [{id: 'r5', line: 5}]);
});
test('comment thread creation', function() {
var l3 = {id: 'l3', line: 3, updated: '2016-08-09 00:42:32.000000000'};
var l5 = {id: 'l5', line: 5, updated: '2016-08-09 00:42:32.000000000'};
var r5 = {id: 'r5', line: 5, updated: '2016-08-09 00:42:32.000000000'};
builder._comments = {
meta: {
changeNum: '42',
patchRange: {
basePatchNum: 'PARENT',
patchNum: '3',
},
path: '/path/to/foo',
projectConfig: {foo: 'bar'},
},
left: [l3, l5],
right: [r5],
};
function checkThreadProps(threadEl, patchNum, side, comments) {
assert.equal(threadEl.changeNum, '42');
assert.equal(threadEl.patchNum, patchNum);
assert.equal(threadEl.path, '/path/to/foo');
assert.equal(threadEl.side, side);
assert.deepEqual(threadEl.projectConfig, {foo: 'bar'});
assert.deepEqual(threadEl.comments, comments);
}
var line = new GrDiffLine(GrDiffLine.Type.BOTH);
line.beforeNumber = 5;
line.afterNumber = 5;
var threadEl = builder._commentThreadForLine(line);
checkThreadProps(threadEl, '3', 'REVISION', [l5, r5]);
threadEl = builder._commentThreadForLine(line, GrDiffBuilder.Side.RIGHT);
checkThreadProps(threadEl, '3', 'REVISION', [r5]);
threadEl = builder._commentThreadForLine(line, GrDiffBuilder.Side.LEFT);
checkThreadProps(threadEl, '3', 'PARENT', [l5]);
builder._comments.meta.patchRange.basePatchNum = '1';
threadEl = builder._commentThreadForLine(line);
checkThreadProps(threadEl, '3', 'REVISION', [l5, r5]);
threadEl = builder._commentThreadForLine(line, GrDiffBuilder.Side.LEFT);
checkThreadProps(threadEl, '1', 'REVISION', [l5]);
threadEl = builder._commentThreadForLine(line, GrDiffBuilder.Side.RIGHT);
checkThreadProps(threadEl, '3', 'REVISION', [r5]);
builder._comments.meta.patchRange.basePatchNum = 'PARENT';
line = new GrDiffLine(GrDiffLine.Type.REMOVE);
line.beforeNumber = 5;
line.afterNumber = 5;
threadEl = builder._commentThreadForLine(line);
checkThreadProps(threadEl, '3', 'PARENT', [l5, r5]);
line = new GrDiffLine(GrDiffLine.Type.ADD);
line.beforeNumber = 3;
line.afterNumber = 5;
threadEl = builder._commentThreadForLine(line);
checkThreadProps(threadEl, '3', 'REVISION', [l3, r5]);
});
suite('intraline differences', function() {
var el;
var str;
var annotateElementSpy;
var layer;
function slice(str, start, end) {
return Array.from(str).slice(start, end).join('');
}
setup(function() {
el = fixture('div-with-text');
str = el.textContent;
annotateElementSpy = sinon.spy(GrAnnotation, 'annotateElement');
layer = document.createElement('gr-diff-builder')
._createIntralineLayer();
});
teardown(function() {
annotateElementSpy.restore();
});
test('annotate no highlights', function() {
var line = {
text: str,
highlights: [],
};
layer.annotate(el, line);
// The content is unchanged.
assert.isFalse(annotateElementSpy.called);
assert.equal(el.childNodes.length, 1);
assert.instanceOf(el.childNodes[0], Text);
assert.equal(str, el.childNodes[0].textContent);
});
test('annotate with highlights', function() {
var line = {
text: str,
highlights: [
{startIndex: 6, endIndex: 12},
{startIndex: 18, endIndex: 22},
],
};
var str0 = slice(str, 0, 6);
var str1 = slice(str, 6, 12);
var str2 = slice(str, 12, 18);
var str3 = slice(str, 18, 22);
var str4 = slice(str, 22);
layer.annotate(el, line);
assert.isTrue(annotateElementSpy.called);
assert.equal(el.childNodes.length, 5);
assert.instanceOf(el.childNodes[0], Text);
assert.equal(el.childNodes[0].textContent, str0);
assert.notInstanceOf(el.childNodes[1], Text);
assert.equal(el.childNodes[1].textContent, str1);
assert.instanceOf(el.childNodes[2], Text);
assert.equal(el.childNodes[2].textContent, str2);
assert.notInstanceOf(el.childNodes[3], Text);
assert.equal(el.childNodes[3].textContent, str3);
assert.instanceOf(el.childNodes[4], Text);
assert.equal(el.childNodes[4].textContent, str4);
});
test('annotate without endIndex', function() {
var line = {
text: str,
highlights: [
{startIndex: 28},
],
};
var str0 = slice(str, 0, 28);
var str1 = slice(str, 28);
layer.annotate(el, line);
assert.isTrue(annotateElementSpy.called);
assert.equal(el.childNodes.length, 2);
assert.instanceOf(el.childNodes[0], Text);
assert.equal(el.childNodes[0].textContent, str0);
assert.notInstanceOf(el.childNodes[1], Text);
assert.equal(el.childNodes[1].textContent, str1);
});
test('annotate ignores empty highlights', function() {
var line = {
text: str,
highlights: [
{startIndex: 28, endIndex: 28},
],
};
layer.annotate(el, line);
assert.isFalse(annotateElementSpy.called);
assert.equal(el.childNodes.length, 1);
});
test('annotate handles unicode', function() {
// Put some unicode into the string:
str = str.replace(/\s/g, '💢');
el.textContent = str;
var line = {
text: str,
highlights: [
{startIndex: 6, endIndex: 12},
],
};
var str0 = slice(str, 0, 6);
var str1 = slice(str, 6, 12);
var str2 = slice(str, 12);
layer.annotate(el, line);
assert.isTrue(annotateElementSpy.called);
assert.equal(el.childNodes.length, 3);
assert.instanceOf(el.childNodes[0], Text);
assert.equal(el.childNodes[0].textContent, str0);
assert.notInstanceOf(el.childNodes[1], Text);
assert.equal(el.childNodes[1].textContent, str1);
assert.instanceOf(el.childNodes[2], Text);
assert.equal(el.childNodes[2].textContent, str2);
});
test('annotate handles unicode w/o endIndex', function() {
// Put some unicode into the string:
str = str.replace(/\s/g, '💢');
el.textContent = str;
var line = {
text: str,
highlights: [
{startIndex: 6},
],
};
var str0 = slice(str, 0, 6);
var str1 = slice(str, 6);
layer.annotate(el, line);
assert.isTrue(annotateElementSpy.called);
assert.equal(el.childNodes.length, 2);
assert.instanceOf(el.childNodes[0], Text);
assert.equal(el.childNodes[0].textContent, str0);
assert.notInstanceOf(el.childNodes[1], Text);
assert.equal(el.childNodes[1].textContent, str1);
});
});
suite('tab indicators', function() {
var sandbox;
var element;
var layer;
setup(function() {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element._showTabs = true;
layer = element._createTabIndicatorLayer();
});
teardown(function() {
sandbox.restore();
});
test('does nothing with empty line', function() {
var line = {text: ''};
var el = document.createElement('div');
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('does nothing with no tabs', function() {
var str = 'lorem ipsum no tabs';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('annotates tab at beginning', function() {
var str = '\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 1);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 0, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
test('does not annotate when disabled', function() {
element._showTabs = false;
var str = '\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('annotates multiple in beginning', function() {
var str = '\t\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 2);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 0, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
args = annotateElementStub.getCalls()[1].args;
assert.equal(args[0], el);
assert.equal(args[1], 1, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
test('annotates intermediate tabs', function() {
var str = 'lorem\tupsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 1);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 5, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
});
suite('rendering', function() {
var content;
var outputEl;
setup(function(done) {
var prefs = {
line_length: 10,
show_tabs: true,
tab_size: 4,
context: -1,
syntax_highlighting: true,
};
content = [
{
a: ['all work and no play make andybons a dull boy'],
b: ['elgoog elgoog elgoog']
},
{
ab: [
'Non eram nescius, Brute, cum, quae summis ingeniis ',
'exquisitaque doctrina philosophi Graeco sermone tractavissent',
]
},
];
stub('gr-reporting', {
time: sinon.stub(),
timeEnd: sinon.stub(),
});
element = fixture('basic');
outputEl = element.queryEffectiveChildren('#diffTable');
var renderHandler = function() {
element.removeEventListener('render', renderHandler);
done();
};
element.addEventListener('render', renderHandler);
sinon.stub(element, '_getDiffBuilder', function() {
var builder = new GrDiffBuilder(
{content: content}, {left: [], right: []}, prefs, outputEl);
builder.buildSectionElement = function(group) {
var section = document.createElement('stub');
section.textContent = group.lines.reduce(function(acc, line) {
return acc + line.text;
}, '');
return section;
};
return builder;
});
element.diff = {content: content};
element.render({left: [], right: []}, prefs);
});
test('reporting', function(done) {
var timeStub = element.$.reporting.time;
var timeEndStub = element.$.reporting.timeEnd;
flush(function() {
assert.isTrue(timeStub.calledWithExactly('Diff Total Render'));
assert.isTrue(timeStub.calledWithExactly('Diff Content Render'));
assert.isTrue(timeStub.calledWithExactly('Diff Syntax Render'));
assert.isTrue(timeEndStub.calledWithExactly('Diff Total Render'));
assert.isTrue(timeEndStub.calledWithExactly('Diff Content Render'));
assert.isTrue(timeEndStub.calledWithExactly('Diff Syntax Render'));
done();
});
});
test('renderSection', function() {
var section = outputEl.querySelector('stub:nth-of-type(2)');
var prevInnerHTML = section.innerHTML;
section.innerHTML = 'wiped';
element._builder.renderSection(section);
section = outputEl.querySelector('stub:nth-of-type(2)');
assert.equal(section.innerHTML, prevInnerHTML);
});
test('getSectionsByLineRange one line', function() {
var section = outputEl.querySelector('stub:nth-of-type(2)');
var sections = element._builder.getSectionsByLineRange(1, 1, 'left');
assert.equal(sections.length, 1);
assert.strictEqual(sections[0], section);
});
test('getSectionsByLineRange over diff', function() {
var section = [
outputEl.querySelector('stub:nth-of-type(2)'),
outputEl.querySelector('stub:nth-of-type(3)'),
];
var sections = element._builder.getSectionsByLineRange(1, 2, 'left');
assert.equal(sections.length, 2);
assert.strictEqual(sections[0], section[0]);
assert.strictEqual(sections[1], section[1]);
});
test('render-start and render are fired', function(done) {
var fireStub = sinon.stub(element, 'fire');
element.render({left: [], right: []}, {});
flush(function() {
assert.isTrue(fireStub.calledWithExactly('render-start'));
assert.isTrue(fireStub.calledWithExactly('render'));
done();
});
});
test('rendering normal-sized diff does not disable syntax', function() {
assert.isTrue(element.$.syntaxLayer.enabled);
});
test('rendering large diff disables syntax', function(done) {
// Before it renders, set the first diff line to 500 '*' characters.
element.diff.content[0].a = [new Array(501).join('*')];
element.addEventListener('render', function() {
assert.isFalse(element.$.syntaxLayer.enabled);
done();
});
var prefs = {
line_length: 10,
show_tabs: true,
tab_size: 4,
context: -1,
syntax_highlighting: true,
};
element.render({left: [], right: []}, prefs);
});
});
suite('mock-diff', function() {
var element;
var builder;
var diff;
var prefs;
setup(function(done) {
element = fixture('mock-diff');
diff = document.createElement('mock-diff-response').diffResponse;
element.diff = diff;
prefs = {
line_length: 80,
show_tabs: true,
tab_size: 4,
};
element.render({left: [], right: []}, prefs).then(function() {
builder = element._builder;
done();
});
});
test('getContentByLine', function() {
var actual;
actual = builder.getContentByLine(2, 'left');
assert.equal(actual.textContent, diff.content[0].ab[1]);
actual = builder.getContentByLine(2, 'right');
assert.equal(actual.textContent, diff.content[0].ab[1]);
actual = builder.getContentByLine(5, 'left');
assert.equal(actual.textContent, diff.content[2].ab[0]);
actual = builder.getContentByLine(5, 'right');
assert.equal(actual.textContent, diff.content[1].b[0]);
});
test('findLinesByRange', function() {
var lines = [];
var elems = [];
var start = 6;
var end = 10;
var count = end - start + 1;
builder.findLinesByRange(start, end, 'right', lines, elems);
assert.equal(lines.length, count);
assert.equal(elems.length, count);
for (var i = 0; i < 5; i++) {
assert.instanceOf(lines[i], GrDiffLine);
assert.equal(lines[i].afterNumber, start + i);
assert.instanceOf(elems[i], HTMLElement);
assert.equal(lines[i].text, elems[i].textContent);
}
});
test('_renderContentByRange', function() {
var spy = sinon.spy(builder, '_createTextEl');
var start = 9;
var end = 14;
var count = end - start + 1;
builder._renderContentByRange(start, end, 'left');
assert.equal(spy.callCount, count);
spy.getCalls().forEach(function(call, i) {
assert.equal(call.args[0].beforeNumber, start + i);
});
spy.restore();
});
test('_getNextContentOnSide side-by-side left', function() {
var startElem = builder.getContentByLine(5, 'left',
element.$.diffTable);
var expectedStartString = diff.content[2].ab[0];
var expectedNextString = diff.content[2].ab[1];
assert.equal(startElem.textContent, expectedStartString);
var nextElem = builder._getNextContentOnSide(startElem,
'left');
assert.equal(nextElem.textContent, expectedNextString);
});
test('_getNextContentOnSide side-by-side right', function() {
var startElem = builder.getContentByLine(5, 'right',
element.$.diffTable);
var expectedStartString = diff.content[1].b[0];
var expectedNextString = diff.content[1].b[1];
assert.equal(startElem.textContent, expectedStartString);
var nextElem = builder._getNextContentOnSide(startElem,
'right');
assert.equal(nextElem.textContent, expectedNextString);
});
test('_getNextContentOnSide unified left', function(done) {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
element.render({left: [], right: []}, prefs).then(function() {
builder = element._builder;
var startElem = builder.getContentByLine(5, 'left',
element.$.diffTable);
var expectedStartString = diff.content[2].ab[0];
var expectedNextString = diff.content[2].ab[1];
assert.equal(startElem.textContent, expectedStartString);
var nextElem = builder._getNextContentOnSide(startElem,
'left');
assert.equal(nextElem.textContent, expectedNextString);
done();
});
});
test('_getNextContentOnSide unified right', function(done) {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
element.render({left: [], right: []}, prefs).then(function() {
builder = element._builder;
var startElem = builder.getContentByLine(5, 'right',
element.$.diffTable);
var expectedStartString = diff.content[1].b[0];
var expectedNextString = diff.content[1].b[1];
assert.equal(startElem.textContent, expectedStartString);
var nextElem = builder._getNextContentOnSide(startElem,
'right');
assert.equal(nextElem.textContent, expectedNextString);
done();
});
});
});
});
</script>