blob: e8b14530e256a391ffe60c8687e8eb20f3ab901b [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() {
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('rendering', function() {
var content;
var outputEl;
setup(function(done) {
var prefs = {
line_length: 10,
show_tabs: true,
tab_size: 4,
context: -1
};
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',
]
},
];
element = fixture('basic');
outputEl = element.queryEffectiveChildren('#diffTable');
element.addEventListener('render', function() {
done();
});
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('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]);
});
});
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>