The "above" / "below" text of the current buttons refers to expansion of content above and below the expander, but when coming from other code review tools with different paradigms can be interpreted as "expand the hidden lines above the content below", i.e. the opposite of what the button does. Since both buttons are on the same line, there is no vertical association with the area of content they affect, which would otherwise avoid this confusion. This change moves each button into the section of code it will expand for clear mapping. It further horizontally centers the buttons on the divider and displays only a single set of buttons in side-by-side diff, removing the previous duplication of context controls. Screenshot: https://imgur.com/a/TIMMAMi Change-Id: I356c7af7b1da8883986dcb99b1e0295ea9f74220
1304 lines
44 KiB
JavaScript
1304 lines
44 KiB
JavaScript
/**
|
|
* @license
|
|
* 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.
|
|
*/
|
|
|
|
import '../../../test/common-test-setup-karma.js';
|
|
import '../gr-diff/gr-diff-group.js';
|
|
import './gr-diff-builder.js';
|
|
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
|
import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
|
|
import './gr-diff-builder-element.js';
|
|
import {stubBaseUrl} from '../../../test/test-utils.js';
|
|
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
|
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation.js';
|
|
import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
|
|
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group.js';
|
|
import {GrDiffBuilder} from './gr-diff-builder.js';
|
|
import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side.js';
|
|
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
|
|
|
const basicFixture = fixtureFromTemplate(html`
|
|
<gr-diff-builder>
|
|
<table id="diffTable"></table>
|
|
</gr-diff-builder>
|
|
`);
|
|
|
|
const divWithTextFixture = fixtureFromTemplate(html`
|
|
<div>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</div>
|
|
`);
|
|
|
|
const mockDiffFixture = fixtureFromTemplate(html`
|
|
<gr-diff-builder view-mode="SIDE_BY_SIDE">
|
|
<table id="diffTable"></table>
|
|
</gr-diff-builder>
|
|
`);
|
|
|
|
const DiffViewMode = {
|
|
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
|
|
UNIFIED: 'UNIFIED_DIFF',
|
|
};
|
|
|
|
suite('gr-diff-builder tests', () => {
|
|
let prefs;
|
|
let element;
|
|
let builder;
|
|
|
|
const LINE_FEED_HTML = '<span class="style-scope gr-diff br"></span>';
|
|
|
|
setup(() => {
|
|
element = basicFixture.instantiate();
|
|
stub('gr-rest-api-interface', {
|
|
getLoggedIn() { return Promise.resolve(false); },
|
|
getProjectConfig() { return Promise.resolve({}); },
|
|
});
|
|
stubBaseUrl('/r');
|
|
prefs = {
|
|
line_length: 10,
|
|
show_tabs: true,
|
|
tab_size: 4,
|
|
};
|
|
builder = new GrDiffBuilder({content: []}, prefs);
|
|
});
|
|
|
|
test('_createElement classStr applies all classes', () => {
|
|
const node = builder._createElement('div', 'test classes');
|
|
assert.isTrue(node.classList.contains('gr-diff'));
|
|
assert.isTrue(node.classList.contains('test'));
|
|
assert.isTrue(node.classList.contains('classes'));
|
|
});
|
|
|
|
suite('context control', () => {
|
|
function createContextGroups(options) {
|
|
const offset = options.offset || 0;
|
|
const numLines = options.count || 10;
|
|
const lines = [];
|
|
for (let i = 0; i < numLines; i++) {
|
|
const line = new GrDiffLine(GrDiffLineType.BOTH);
|
|
line.beforeNumber = offset + i + 1;
|
|
line.afterNumber = offset + i + 1;
|
|
line.text = 'lorem upsum';
|
|
lines.push(line);
|
|
}
|
|
|
|
return [new GrDiffGroup(GrDiffGroupType.BOTH, lines)];
|
|
}
|
|
|
|
function createContextSectionForGroups(options) {
|
|
const section = document.createElement('div');
|
|
builder._createContextControls(
|
|
section, createContextGroups(options), DiffViewMode.UNIFIED);
|
|
return section;
|
|
}
|
|
|
|
suite('old style', () => {
|
|
setup(() => {
|
|
builder = new GrDiffBuilder(
|
|
{content: []}, prefs, null, [], false /* useNewContextControls */);
|
|
});
|
|
|
|
test('no +10 buttons for 10 or less lines', () => {
|
|
const section = createContextSectionForGroups({count: 10});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 1);
|
|
assert.equal(buttons[0].textContent, 'Show 10 common lines');
|
|
});
|
|
|
|
test('context control at the top', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 0, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 2);
|
|
assert.equal(buttons[0].textContent, 'Show 20 common lines');
|
|
assert.equal(buttons[1].textContent, '+10 below');
|
|
});
|
|
|
|
test('context control in the middle', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 10, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 3);
|
|
assert.equal(buttons[0].textContent, '+10 above');
|
|
assert.equal(buttons[1].textContent, 'Show 20 common lines');
|
|
assert.equal(buttons[2].textContent, '+10 below');
|
|
});
|
|
|
|
test('context control at the bottom', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 30, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 2);
|
|
assert.equal(buttons[0].textContent, '+10 above');
|
|
assert.equal(buttons[1].textContent, 'Show 20 common lines');
|
|
});
|
|
});
|
|
|
|
suite('new style', () => {
|
|
setup(() => {
|
|
builder = new GrDiffBuilder(
|
|
{content: []}, prefs, null, [], true /* useNewContextControls */);
|
|
});
|
|
|
|
test('no +10 buttons for 10 or less lines', () => {
|
|
const section = createContextSectionForGroups({count: 10});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 1);
|
|
assert.equal(buttons[0].textContent, '+10 common lines');
|
|
});
|
|
|
|
test('context control at the top', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 0, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 2);
|
|
assert.equal(buttons[0].textContent, '+20 common lines');
|
|
assert.equal(buttons[1].textContent, '+10');
|
|
|
|
assert.include([...buttons[0].classList.values()], 'belowButton');
|
|
assert.include([...buttons[1].classList.values()], 'belowButton');
|
|
});
|
|
|
|
test('context control in the middle', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 10, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 3);
|
|
assert.equal(buttons[0].textContent, '+20 common lines');
|
|
assert.equal(buttons[1].textContent, '+10');
|
|
assert.equal(buttons[2].textContent, '+10');
|
|
|
|
assert.include([...buttons[0].classList.values()], 'centeredButton');
|
|
assert.include([...buttons[1].classList.values()], 'aboveButton');
|
|
assert.include([...buttons[2].classList.values()], 'belowButton');
|
|
});
|
|
|
|
test('context control at the bottom', () => {
|
|
builder._numLinesLeft = 50;
|
|
const section = createContextSectionForGroups({offset: 30, count: 20});
|
|
const buttons = section.querySelectorAll('gr-button.showContext');
|
|
|
|
assert.equal(buttons.length, 2);
|
|
assert.equal(buttons[0].textContent, '+20 common lines');
|
|
assert.equal(buttons[1].textContent, '+10');
|
|
|
|
assert.include([...buttons[0].classList.values()], 'aboveButton');
|
|
assert.include([...buttons[1].classList.values()], 'aboveButton');
|
|
});
|
|
});
|
|
});
|
|
|
|
test('newlines 1', () => {
|
|
let text = 'abcdef';
|
|
|
|
assert.equal(builder._formatText(text, 4, 10).innerHTML, text);
|
|
text = 'a'.repeat(20);
|
|
assert.equal(builder._formatText(text, 4, 10).innerHTML,
|
|
'a'.repeat(10) +
|
|
LINE_FEED_HTML +
|
|
'a'.repeat(10));
|
|
});
|
|
|
|
test('newlines 2', () => {
|
|
const text = '<span class="thumbsup">👍</span>';
|
|
assert.equal(builder._formatText(text, 4, 10).innerHTML,
|
|
'<span clas' +
|
|
LINE_FEED_HTML +
|
|
's="thumbsu' +
|
|
LINE_FEED_HTML +
|
|
'p">👍</span' +
|
|
LINE_FEED_HTML +
|
|
'>');
|
|
});
|
|
|
|
test('newlines 3', () => {
|
|
const text = '01234\t56789';
|
|
assert.equal(builder._formatText(text, 4, 10).innerHTML,
|
|
'01234' + builder._getTabWrapper(3).outerHTML + '56' +
|
|
LINE_FEED_HTML +
|
|
'789');
|
|
});
|
|
|
|
test('newlines 4', () => {
|
|
const text = '👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍';
|
|
assert.equal(builder._formatText(text, 4, 20).innerHTML,
|
|
'👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍' +
|
|
LINE_FEED_HTML +
|
|
'👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍' +
|
|
LINE_FEED_HTML +
|
|
'👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍');
|
|
});
|
|
|
|
test('line_length ignored if line_wrapping is true', () => {
|
|
builder._prefs = {line_wrapping: true, tab_size: 4, line_length: 50};
|
|
const text = 'a'.repeat(51);
|
|
|
|
const line = {text, highlights: []};
|
|
const result = builder._createTextEl(undefined, line).firstChild.innerHTML;
|
|
assert.equal(result, text);
|
|
});
|
|
|
|
test('line_length applied if line_wrapping is false', () => {
|
|
builder._prefs = {line_wrapping: false, tab_size: 4, line_length: 50};
|
|
const text = 'a'.repeat(51);
|
|
|
|
const line = {text, highlights: []};
|
|
const expected = 'a'.repeat(50) + LINE_FEED_HTML + 'a';
|
|
const result = builder._createTextEl(undefined, line).firstChild.innerHTML;
|
|
assert.equal(result, expected);
|
|
});
|
|
|
|
[DiffViewMode.UNIFIED, DiffViewMode.SIDE_BY_SIDE]
|
|
.forEach(mode => {
|
|
test(`line_length used for regular files under ${mode}`, () => {
|
|
element.path = '/a.txt';
|
|
element.viewMode = mode;
|
|
builder = element._getDiffBuilder(
|
|
{}, {tab_size: 4, line_length: 50}
|
|
);
|
|
assert.equal(builder._prefs.line_length, 50);
|
|
});
|
|
|
|
test(`line_length ignored for commit msg under ${mode}`, () => {
|
|
element.path = '/COMMIT_MSG';
|
|
element.viewMode = mode;
|
|
builder = element._getDiffBuilder(
|
|
{}, {tab_size: 4, line_length: 50}
|
|
);
|
|
assert.equal(builder._prefs.line_length, 72);
|
|
});
|
|
});
|
|
|
|
test('_createTextEl linewrap with tabs', () => {
|
|
const text = '\t'.repeat(7) + '!';
|
|
const line = {text, highlights: []};
|
|
const el = builder._createTextEl(undefined, line);
|
|
assert.equal(el.innerText, text);
|
|
// With line length 10 and tab size 2, there should be a line break
|
|
// after every two tabs.
|
|
const newlineEl = el.querySelector('.contentText > .br');
|
|
assert.isOk(newlineEl);
|
|
assert.equal(
|
|
el.querySelector('.contentText .tab:nth-child(2)').nextSibling,
|
|
newlineEl);
|
|
});
|
|
|
|
test('text length with tabs and unicode', () => {
|
|
function expectTextLength(text, tabSize, expected) {
|
|
// Formatting to |expected| columns should not introduce line breaks.
|
|
const result = builder._formatText(text, tabSize, expected);
|
|
assert.isNotOk(result.querySelector('.contentText > .br'),
|
|
` Expected the result of: \n` +
|
|
` _formatText(${text}', ${tabSize}, ${expected})\n` +
|
|
` to not contain a br. But the actual result HTML was:\n` +
|
|
` '${result.innerHTML}'\nwhereupon`);
|
|
|
|
// Increasing the line limit should produce the same markup.
|
|
assert.equal(builder._formatText(text, tabSize, Infinity).innerHTML,
|
|
result.innerHTML);
|
|
assert.equal(builder._formatText(text, tabSize, expected + 1).innerHTML,
|
|
result.innerHTML);
|
|
|
|
// Decreasing the line limit should introduce line breaks.
|
|
if (expected > 0) {
|
|
const tooSmall = builder._formatText(text, tabSize, expected - 1);
|
|
assert.isOk(tooSmall.querySelector('.contentText > .br'),
|
|
` Expected the result of: \n` +
|
|
` _formatText(${text}', ${tabSize}, ${expected - 1})\n` +
|
|
` to contain a br. But the actual result HTML was:\n` +
|
|
` '${tooSmall.innerHTML}'\nwhereupon`);
|
|
}
|
|
}
|
|
expectTextLength('12345', 4, 5);
|
|
expectTextLength('\t\t12', 4, 10);
|
|
expectTextLength('abc💢123', 4, 7);
|
|
expectTextLength('abc\t', 8, 8);
|
|
expectTextLength('abc\t\t', 10, 20);
|
|
expectTextLength('', 10, 0);
|
|
// 17 Thai combining chars.
|
|
expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
|
|
expectTextLength('abc\tde', 10, 12);
|
|
expectTextLength('abc\tde\t', 10, 20);
|
|
expectTextLength('\t\t\t\t\t', 20, 100);
|
|
});
|
|
|
|
test('tab wrapper insertion', () => {
|
|
const html = 'abc\tdef';
|
|
const tabSize = builder._prefs.tab_size;
|
|
const wrapper = builder._getTabWrapper(tabSize - 3);
|
|
assert.ok(wrapper);
|
|
assert.equal(wrapper.innerText, '\t');
|
|
assert.equal(
|
|
builder._formatText(html, tabSize, Infinity).innerHTML,
|
|
'abc' + wrapper.outerHTML + 'def');
|
|
});
|
|
|
|
test('tab wrapper style', () => {
|
|
const pattern = new RegExp('^<span class="style-scope gr-diff tab" ' +
|
|
'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$');
|
|
|
|
for (const size of [1, 3, 8, 55]) {
|
|
const html = builder._getTabWrapper(size).outerHTML;
|
|
expect(html).to.match(pattern);
|
|
assert.equal(html.match(pattern)[2], size);
|
|
}
|
|
});
|
|
|
|
test('_handlePreferenceError throws with invalid preference', () => {
|
|
const prefs = {tab_size: 0};
|
|
assert.throws(() => element._getDiffBuilder(element.diff, prefs));
|
|
});
|
|
|
|
test('_handlePreferenceError triggers alert and javascript error', () => {
|
|
const errorStub = sinon.stub();
|
|
element.addEventListener('show-alert', errorStub);
|
|
assert.throws(() => element._handlePreferenceError('tab size'));
|
|
assert.equal(errorStub.lastCall.args[0].detail.message,
|
|
`The value of the 'tab size' user preference is invalid. ` +
|
|
`Fix in diff preferences`);
|
|
});
|
|
|
|
suite('_isTotal', () => {
|
|
test('is total for add', () => {
|
|
const group = new GrDiffGroup(GrDiffGroupType.DELTA);
|
|
for (let idx = 0; idx < 10; idx++) {
|
|
group.addLine(new GrDiffLine(GrDiffLineType.ADD));
|
|
}
|
|
assert.isTrue(GrDiffBuilder.prototype._isTotal(group));
|
|
});
|
|
|
|
test('is total for remove', () => {
|
|
const group = new GrDiffGroup(GrDiffGroupType.DELTA);
|
|
for (let idx = 0; idx < 10; idx++) {
|
|
group.addLine(new GrDiffLine(GrDiffLineType.REMOVE));
|
|
}
|
|
assert.isTrue(GrDiffBuilder.prototype._isTotal(group));
|
|
});
|
|
|
|
test('not total for empty', () => {
|
|
const group = new GrDiffGroup(GrDiffGroupType.BOTH);
|
|
assert.isFalse(GrDiffBuilder.prototype._isTotal(group));
|
|
});
|
|
|
|
test('not total for non-delta', () => {
|
|
const group = new GrDiffGroup(GrDiffGroupType.DELTA);
|
|
for (let idx = 0; idx < 10; idx++) {
|
|
group.addLine(new GrDiffLine(GrDiffLineType.BOTH));
|
|
}
|
|
assert.isFalse(GrDiffBuilder.prototype._isTotal(group));
|
|
});
|
|
});
|
|
|
|
suite('intraline differences', () => {
|
|
let el;
|
|
let str;
|
|
let annotateElementSpy;
|
|
let layer;
|
|
const lineNumberEl = document.createElement('td');
|
|
|
|
function slice(str, start, end) {
|
|
return Array.from(str).slice(start, end)
|
|
.join('');
|
|
}
|
|
|
|
setup(() => {
|
|
el = divWithTextFixture.instantiate();
|
|
str = el.textContent;
|
|
annotateElementSpy = sinon.spy(GrAnnotation, 'annotateElement');
|
|
layer = document.createElement('gr-diff-builder')
|
|
._createIntralineLayer();
|
|
});
|
|
|
|
test('annotate no highlights', () => {
|
|
const line = {
|
|
text: str,
|
|
highlights: [],
|
|
};
|
|
|
|
layer.annotate(el, lineNumberEl, 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', () => {
|
|
const line = {
|
|
text: str,
|
|
highlights: [
|
|
{startIndex: 6, endIndex: 12},
|
|
{startIndex: 18, endIndex: 22},
|
|
],
|
|
};
|
|
const str0 = slice(str, 0, 6);
|
|
const str1 = slice(str, 6, 12);
|
|
const str2 = slice(str, 12, 18);
|
|
const str3 = slice(str, 18, 22);
|
|
const str4 = slice(str, 22);
|
|
|
|
layer.annotate(el, lineNumberEl, 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', () => {
|
|
const line = {
|
|
text: str,
|
|
highlights: [
|
|
{startIndex: 28},
|
|
],
|
|
};
|
|
|
|
const str0 = slice(str, 0, 28);
|
|
const str1 = slice(str, 28);
|
|
|
|
layer.annotate(el, lineNumberEl, 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', () => {
|
|
const line = {
|
|
text: str,
|
|
highlights: [
|
|
{startIndex: 28, endIndex: 28},
|
|
],
|
|
};
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.isFalse(annotateElementSpy.called);
|
|
assert.equal(el.childNodes.length, 1);
|
|
});
|
|
|
|
test('annotate handles unicode', () => {
|
|
// Put some unicode into the string:
|
|
str = str.replace(/\s/g, '💢');
|
|
el.textContent = str;
|
|
const line = {
|
|
text: str,
|
|
highlights: [
|
|
{startIndex: 6, endIndex: 12},
|
|
],
|
|
};
|
|
|
|
const str0 = slice(str, 0, 6);
|
|
const str1 = slice(str, 6, 12);
|
|
const str2 = slice(str, 12);
|
|
|
|
layer.annotate(el, lineNumberEl, 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', () => {
|
|
// Put some unicode into the string:
|
|
str = str.replace(/\s/g, '💢');
|
|
el.textContent = str;
|
|
|
|
const line = {
|
|
text: str,
|
|
highlights: [
|
|
{startIndex: 6},
|
|
],
|
|
};
|
|
|
|
const str0 = slice(str, 0, 6);
|
|
const str1 = slice(str, 6);
|
|
|
|
layer.annotate(el, lineNumberEl, 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', () => {
|
|
let element;
|
|
let layer;
|
|
const lineNumberEl = document.createElement('td');
|
|
|
|
setup(() => {
|
|
element = basicFixture.instantiate();
|
|
element._showTabs = true;
|
|
layer = element._createTabIndicatorLayer();
|
|
});
|
|
|
|
test('does nothing with empty line', () => {
|
|
const line = {text: ''};
|
|
const el = document.createElement('div');
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
|
|
test('does nothing with no tabs', () => {
|
|
const str = 'lorem ipsum no tabs';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
|
|
test('annotates tab at beginning', () => {
|
|
const str = '\tlorem upsum';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.equal(annotateElementStub.callCount, 1);
|
|
const 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', () => {
|
|
element._showTabs = false;
|
|
|
|
const str = '\tlorem upsum';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
|
|
test('annotates multiple in beginning', () => {
|
|
const str = '\t\tlorem upsum';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.equal(annotateElementStub.callCount, 2);
|
|
|
|
let 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', () => {
|
|
const str = 'lorem\tupsum';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
|
|
layer.annotate(el, lineNumberEl, line);
|
|
|
|
assert.equal(annotateElementStub.callCount, 1);
|
|
const 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('layers', () => {
|
|
let element;
|
|
let initialLayersCount;
|
|
let withLayerCount;
|
|
setup(() => {
|
|
const layers = [];
|
|
element = basicFixture.instantiate();
|
|
element.layers = layers;
|
|
element._showTrailingWhitespace = true;
|
|
element._setupAnnotationLayers();
|
|
initialLayersCount = element._layers.length;
|
|
});
|
|
|
|
test('no layers', () => {
|
|
element._setupAnnotationLayers();
|
|
assert.equal(element._layers.length, initialLayersCount);
|
|
});
|
|
|
|
suite('with layers', () => {
|
|
const layers = [{}, {}];
|
|
setup(() => {
|
|
element = basicFixture.instantiate();
|
|
element.layers = layers;
|
|
element._showTrailingWhitespace = true;
|
|
element._setupAnnotationLayers();
|
|
withLayerCount = element._layers.length;
|
|
});
|
|
test('with layers', () => {
|
|
element._setupAnnotationLayers();
|
|
assert.equal(element._layers.length, withLayerCount);
|
|
assert.equal(initialLayersCount + layers.length,
|
|
withLayerCount);
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('trailing whitespace', () => {
|
|
let element;
|
|
let layer;
|
|
const lineNumberEl = document.createElement('td');
|
|
|
|
setup(() => {
|
|
element = basicFixture.instantiate();
|
|
element._showTrailingWhitespace = true;
|
|
layer = element._createTrailingWhitespaceLayer();
|
|
});
|
|
|
|
test('does nothing with empty line', () => {
|
|
const line = {text: ''};
|
|
const el = document.createElement('div');
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
|
|
test('does nothing with no trailing whitespace', () => {
|
|
const str = 'lorem ipsum blah blah';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
|
|
test('annotates trailing spaces', () => {
|
|
const str = 'lorem ipsum ';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isTrue(annotateElementStub.called);
|
|
assert.equal(annotateElementStub.lastCall.args[1], 11);
|
|
assert.equal(annotateElementStub.lastCall.args[2], 3);
|
|
});
|
|
|
|
test('annotates trailing tabs', () => {
|
|
const str = 'lorem ipsum\t\t\t';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isTrue(annotateElementStub.called);
|
|
assert.equal(annotateElementStub.lastCall.args[1], 11);
|
|
assert.equal(annotateElementStub.lastCall.args[2], 3);
|
|
});
|
|
|
|
test('annotates mixed trailing whitespace', () => {
|
|
const str = 'lorem ipsum\t \t';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isTrue(annotateElementStub.called);
|
|
assert.equal(annotateElementStub.lastCall.args[1], 11);
|
|
assert.equal(annotateElementStub.lastCall.args[2], 3);
|
|
});
|
|
|
|
test('unicode preceding trailing whitespace', () => {
|
|
const str = '💢\t';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isTrue(annotateElementStub.called);
|
|
assert.equal(annotateElementStub.lastCall.args[1], 1);
|
|
assert.equal(annotateElementStub.lastCall.args[2], 1);
|
|
});
|
|
|
|
test('does not annotate when disabled', () => {
|
|
element._showTrailingWhitespace = false;
|
|
const str = 'lorem upsum\t \t ';
|
|
const line = {text: str};
|
|
const el = document.createElement('div');
|
|
el.textContent = str;
|
|
const annotateElementStub =
|
|
sinon.stub(GrAnnotation, 'annotateElement');
|
|
layer.annotate(el, lineNumberEl, line);
|
|
assert.isFalse(annotateElementStub.called);
|
|
});
|
|
});
|
|
|
|
suite('rendering text, images and binary files', () => {
|
|
let processStub;
|
|
let keyLocations;
|
|
let prefs;
|
|
let content;
|
|
|
|
setup(() => {
|
|
element = basicFixture.instantiate();
|
|
element.viewMode = 'SIDE_BY_SIDE';
|
|
processStub = sinon.stub(element.$.processor, 'process')
|
|
.returns(Promise.resolve());
|
|
keyLocations = {left: {}, right: {}};
|
|
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',
|
|
],
|
|
}];
|
|
});
|
|
|
|
test('text', () => {
|
|
element.diff = {content};
|
|
return element.render(keyLocations, prefs).then(() => {
|
|
assert.isTrue(processStub.calledOnce);
|
|
assert.isFalse(processStub.lastCall.args[1]);
|
|
});
|
|
});
|
|
|
|
test('image', () => {
|
|
element.diff = {content, binary: true};
|
|
element.isImageDiff = true;
|
|
return element.render(keyLocations, prefs).then(() => {
|
|
assert.isTrue(processStub.calledOnce);
|
|
assert.isTrue(processStub.lastCall.args[1]);
|
|
});
|
|
});
|
|
|
|
test('binary', () => {
|
|
element.diff = {content, binary: true};
|
|
return element.render(keyLocations, prefs).then(() => {
|
|
assert.isTrue(processStub.calledOnce);
|
|
assert.isTrue(processStub.lastCall.args[1]);
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('rendering', () => {
|
|
let content;
|
|
let outputEl;
|
|
let keyLocations;
|
|
|
|
setup(done => {
|
|
const 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',
|
|
],
|
|
},
|
|
];
|
|
element = basicFixture.instantiate();
|
|
outputEl = element.queryEffectiveChildren('#diffTable');
|
|
keyLocations = {left: {}, right: {}};
|
|
sinon.stub(element, '_getDiffBuilder').callsFake(() => {
|
|
const builder = new GrDiffBuilderSideBySide({content}, prefs, outputEl);
|
|
sinon.stub(builder, 'addColumns');
|
|
builder.buildSectionElement = function(group) {
|
|
const section = document.createElement('stub');
|
|
section.textContent = group.lines
|
|
.reduce((acc, line) => acc + line.text, '');
|
|
return section;
|
|
};
|
|
return builder;
|
|
});
|
|
element.diff = {content};
|
|
element.render(keyLocations, prefs).then(done);
|
|
});
|
|
|
|
test('addColumns is called', done => {
|
|
element.render(keyLocations, {}).then(done);
|
|
assert.isTrue(element._builder.addColumns.called);
|
|
});
|
|
|
|
test('getSectionsByLineRange one line', () => {
|
|
const section = outputEl.querySelector('stub:nth-of-type(2)');
|
|
const sections = element._builder.getSectionsByLineRange(1, 1, 'left');
|
|
assert.equal(sections.length, 1);
|
|
assert.strictEqual(sections[0], section);
|
|
});
|
|
|
|
test('getSectionsByLineRange over diff', () => {
|
|
const section = [
|
|
outputEl.querySelector('stub:nth-of-type(2)'),
|
|
outputEl.querySelector('stub:nth-of-type(3)'),
|
|
];
|
|
const 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-content are fired', done => {
|
|
const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
|
|
element.render(keyLocations, {}).then(() => {
|
|
const firedEventTypes = dispatchEventStub.getCalls()
|
|
.map(c => c.args[0].type);
|
|
assert.include(firedEventTypes, 'render-start');
|
|
assert.include(firedEventTypes, 'render-content');
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('cancel', () => {
|
|
const processorCancelStub = sinon.stub(element.$.processor, 'cancel');
|
|
element.cancel();
|
|
assert.isTrue(processorCancelStub.called);
|
|
});
|
|
});
|
|
|
|
suite('mock-diff', () => {
|
|
let element;
|
|
let builder;
|
|
let diff;
|
|
let prefs;
|
|
let keyLocations;
|
|
|
|
setup(done => {
|
|
element = mockDiffFixture.instantiate();
|
|
diff = getMockDiffResponse();
|
|
element.diff = diff;
|
|
|
|
prefs = {
|
|
line_length: 80,
|
|
show_tabs: true,
|
|
tab_size: 4,
|
|
};
|
|
keyLocations = {left: {}, right: {}};
|
|
|
|
element.render(keyLocations, prefs).then(() => {
|
|
builder = element._builder;
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('aria-labels on added line numbers', () => {
|
|
const deltaLineNumberButton = element.diffElement.querySelectorAll(
|
|
'.lineNumButton.right')[5];
|
|
|
|
assert.isOk(deltaLineNumberButton);
|
|
assert.equal(deltaLineNumberButton.getAttribute('aria-label'), '5 added');
|
|
});
|
|
|
|
test('aria-labels on removed line numbers', () => {
|
|
const deltaLineNumberButton = element.diffElement.querySelectorAll(
|
|
'.lineNumButton.left')[10];
|
|
|
|
assert.isOk(deltaLineNumberButton);
|
|
assert.equal(
|
|
deltaLineNumberButton.getAttribute('aria-label'), '10 removed');
|
|
});
|
|
|
|
test('getContentByLine', () => {
|
|
let 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('getContentTdByLineEl works both with button and td', () => {
|
|
const diffRow = element.diffElement.querySelectorAll('tr.diff-row')[2];
|
|
|
|
const lineNumTdLeft = diffRow.querySelector('td.lineNum.left');
|
|
const lineNumButtonLeft = lineNumTdLeft.querySelector('button');
|
|
const contentTdLeft = diffRow.querySelectorAll('.content')[0];
|
|
|
|
const lineNumTdRight = diffRow.querySelector('td.lineNum.right');
|
|
const lineNumButtonRight = lineNumTdRight.querySelector('button');
|
|
const contentTdRight = diffRow.querySelectorAll('.content')[1];
|
|
|
|
assert.equal(element.getContentTdByLineEl(lineNumTdLeft), contentTdLeft);
|
|
assert.equal(
|
|
element.getContentTdByLineEl(lineNumButtonLeft), contentTdLeft);
|
|
assert.equal(
|
|
element.getContentTdByLineEl(lineNumTdRight), contentTdRight);
|
|
assert.equal(
|
|
element.getContentTdByLineEl(lineNumButtonRight), contentTdRight);
|
|
});
|
|
|
|
test('findLinesByRange', () => {
|
|
const lines = [];
|
|
const elems = [];
|
|
const start = 6;
|
|
const end = 10;
|
|
const count = end - start + 1;
|
|
|
|
builder.findLinesByRange(start, end, 'right', lines, elems);
|
|
|
|
assert.equal(lines.length, count);
|
|
assert.equal(elems.length, count);
|
|
|
|
for (let 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', () => {
|
|
const spy = sinon.spy(builder, '_createTextEl');
|
|
const start = 9;
|
|
const end = 14;
|
|
const count = end - start + 1;
|
|
|
|
builder._renderContentByRange(start, end, 'left');
|
|
|
|
assert.equal(spy.callCount, count);
|
|
spy.getCalls().forEach((call, i) => {
|
|
assert.equal(call.args[1].beforeNumber, start + i);
|
|
});
|
|
});
|
|
|
|
test('_renderContentByRange notexistent elements', () => {
|
|
const spy = sinon.spy(builder, '_createTextEl');
|
|
|
|
sinon.stub(builder, '_getLineNumberEl').returns(
|
|
document.createElement('div')
|
|
);
|
|
sinon.stub(builder, 'findLinesByRange').callsFake(
|
|
(s, e, d, lines, elements) => {
|
|
// Add a line and a corresponding element.
|
|
lines.push(new GrDiffLine(GrDiffLineType.BOTH));
|
|
const tr = document.createElement('tr');
|
|
const td = document.createElement('td');
|
|
const el = document.createElement('div');
|
|
tr.appendChild(td);
|
|
td.appendChild(el);
|
|
elements.push(el);
|
|
|
|
// Add 2 lines without corresponding elements.
|
|
lines.push(new GrDiffLine(GrDiffLineType.BOTH));
|
|
lines.push(new GrDiffLine(GrDiffLineType.BOTH));
|
|
});
|
|
|
|
builder._renderContentByRange(1, 10, 'left');
|
|
// Should be called only once because only one line had a corresponding
|
|
// element.
|
|
assert.equal(spy.callCount, 1);
|
|
});
|
|
|
|
test('_getLineNumberEl side-by-side left', () => {
|
|
const contentEl = builder.getContentByLine(5, 'left',
|
|
element.$.diffTable);
|
|
const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
|
|
assert.isTrue(lineNumberEl.classList.contains('lineNum'));
|
|
assert.isTrue(lineNumberEl.classList.contains('left'));
|
|
});
|
|
|
|
test('_getLineNumberEl side-by-side right', () => {
|
|
const contentEl = builder.getContentByLine(5, 'right',
|
|
element.$.diffTable);
|
|
const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
|
|
assert.isTrue(lineNumberEl.classList.contains('lineNum'));
|
|
assert.isTrue(lineNumberEl.classList.contains('right'));
|
|
});
|
|
|
|
test('_getLineNumberEl unified left', done => {
|
|
// Re-render as unified:
|
|
element.viewMode = 'UNIFIED_DIFF';
|
|
element.render(keyLocations, prefs).then(() => {
|
|
builder = element._builder;
|
|
|
|
const contentEl = builder.getContentByLine(5, 'left',
|
|
element.$.diffTable);
|
|
const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
|
|
assert.isTrue(lineNumberEl.classList.contains('lineNum'));
|
|
assert.isTrue(lineNumberEl.classList.contains('left'));
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('_getLineNumberEl unified right', done => {
|
|
// Re-render as unified:
|
|
element.viewMode = 'UNIFIED_DIFF';
|
|
element.render(keyLocations, prefs).then(() => {
|
|
builder = element._builder;
|
|
|
|
const contentEl = builder.getContentByLine(5, 'right',
|
|
element.$.diffTable);
|
|
const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
|
|
assert.isTrue(lineNumberEl.classList.contains('lineNum'));
|
|
assert.isTrue(lineNumberEl.classList.contains('right'));
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('_getNextContentOnSide side-by-side left', () => {
|
|
const startElem = builder.getContentByLine(5, 'left',
|
|
element.$.diffTable);
|
|
const expectedStartString = diff.content[2].ab[0];
|
|
const expectedNextString = diff.content[2].ab[1];
|
|
assert.equal(startElem.textContent, expectedStartString);
|
|
|
|
const nextElem = builder._getNextContentOnSide(startElem,
|
|
'left');
|
|
assert.equal(nextElem.textContent, expectedNextString);
|
|
});
|
|
|
|
test('_getNextContentOnSide side-by-side right', () => {
|
|
const startElem = builder.getContentByLine(5, 'right',
|
|
element.$.diffTable);
|
|
const expectedStartString = diff.content[1].b[0];
|
|
const expectedNextString = diff.content[1].b[1];
|
|
assert.equal(startElem.textContent, expectedStartString);
|
|
|
|
const nextElem = builder._getNextContentOnSide(startElem,
|
|
'right');
|
|
assert.equal(nextElem.textContent, expectedNextString);
|
|
});
|
|
|
|
test('_getNextContentOnSide unified left', done => {
|
|
// Re-render as unified:
|
|
element.viewMode = 'UNIFIED_DIFF';
|
|
element.render(keyLocations, prefs).then(() => {
|
|
builder = element._builder;
|
|
|
|
const startElem = builder.getContentByLine(5, 'left',
|
|
element.$.diffTable);
|
|
const expectedStartString = diff.content[2].ab[0];
|
|
const expectedNextString = diff.content[2].ab[1];
|
|
assert.equal(startElem.textContent, expectedStartString);
|
|
|
|
const nextElem = builder._getNextContentOnSide(startElem,
|
|
'left');
|
|
assert.equal(nextElem.textContent, expectedNextString);
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('_getNextContentOnSide unified right', done => {
|
|
// Re-render as unified:
|
|
element.viewMode = 'UNIFIED_DIFF';
|
|
element.render(keyLocations, prefs).then(() => {
|
|
builder = element._builder;
|
|
|
|
const startElem = builder.getContentByLine(5, 'right',
|
|
element.$.diffTable);
|
|
const expectedStartString = diff.content[1].b[0];
|
|
const expectedNextString = diff.content[1].b[1];
|
|
assert.equal(startElem.textContent, expectedStartString);
|
|
|
|
const nextElem = builder._getNextContentOnSide(startElem,
|
|
'right');
|
|
assert.equal(nextElem.textContent, expectedNextString);
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('escaping HTML', () => {
|
|
let input = '<script>alert("XSS");<' + '/script>';
|
|
let expected = '<script>alert("XSS");</script>';
|
|
let result = builder._formatText(input, 1, Infinity).innerHTML;
|
|
assert.equal(result, expected);
|
|
|
|
input = '& < > " \' / `';
|
|
expected = '& < > " \' / `';
|
|
result = builder._formatText(input, 1, Infinity).innerHTML;
|
|
assert.equal(result, expected);
|
|
});
|
|
});
|
|
|
|
suite('blame', () => {
|
|
let mockBlame;
|
|
|
|
setup(() => {
|
|
mockBlame = [
|
|
{id: 'commit 1', ranges: [{start: 1, end: 2}, {start: 10, end: 16}]},
|
|
{id: 'commit 2', ranges: [{start: 4, end: 10}, {start: 17, end: 32}]},
|
|
];
|
|
});
|
|
|
|
test('setBlame attempts to render each blamed line', () => {
|
|
const getBlameStub = sinon.stub(builder, '_getBlameByLineNum')
|
|
.returns(null);
|
|
builder.setBlame(mockBlame);
|
|
assert.equal(getBlameStub.callCount, 32);
|
|
});
|
|
|
|
test('_getBlameCommitForBaseLine', () => {
|
|
sinon.stub(builder, '_getBlameByLineNum').returns(null);
|
|
builder.setBlame(mockBlame);
|
|
assert.isOk(builder._getBlameCommitForBaseLine(1));
|
|
assert.equal(builder._getBlameCommitForBaseLine(1).id, 'commit 1');
|
|
|
|
assert.isOk(builder._getBlameCommitForBaseLine(11));
|
|
assert.equal(builder._getBlameCommitForBaseLine(11).id, 'commit 1');
|
|
|
|
assert.isOk(builder._getBlameCommitForBaseLine(32));
|
|
assert.equal(builder._getBlameCommitForBaseLine(32).id, 'commit 2');
|
|
|
|
assert.isNull(builder._getBlameCommitForBaseLine(33));
|
|
});
|
|
|
|
test('_getBlameCommitForBaseLine w/o blame returns null', () => {
|
|
assert.isNull(builder._getBlameCommitForBaseLine(1));
|
|
assert.isNull(builder._getBlameCommitForBaseLine(11));
|
|
assert.isNull(builder._getBlameCommitForBaseLine(31));
|
|
});
|
|
|
|
test('_createBlameCell', () => {
|
|
const mocbBlameCell = document.createElement('span');
|
|
const getBlameStub = sinon.stub(builder, '_getBlameForBaseLine')
|
|
.returns(mocbBlameCell);
|
|
const line = new GrDiffLine(GrDiffLineType.BOTH);
|
|
line.beforeNumber = 3;
|
|
line.afterNumber = 5;
|
|
|
|
const result = builder._createBlameCell(line.beforeNumber);
|
|
|
|
assert.isTrue(getBlameStub.calledWithExactly(3));
|
|
assert.equal(result.getAttribute('data-line-number'), '3');
|
|
assert.equal(result.firstChild, mocbBlameCell);
|
|
});
|
|
|
|
test('_getBlameForBaseLine', () => {
|
|
const mockCommit = {
|
|
time: 1576105200,
|
|
id: 1234567890,
|
|
author: 'Clark Kent',
|
|
commit_msg: 'Testing Commit',
|
|
ranges: [1],
|
|
};
|
|
const blameNode = builder._getBlameForBaseLine(1, mockCommit);
|
|
|
|
const authors = blameNode.getElementsByClassName('blameAuthor');
|
|
assert.equal(authors.length, 1);
|
|
assert.equal(authors[0].innerText, ' Clark');
|
|
|
|
const date = (new Date(mockCommit.time * 1000)).toLocaleDateString();
|
|
flush();
|
|
const cards = blameNode.getElementsByClassName('blameHoverCard');
|
|
assert.equal(cards.length, 1);
|
|
assert.equal(cards[0].innerHTML,
|
|
`Commit 1234567890<br>Author: Clark Kent<br>Date: ${date}`
|
|
+ '<br><br>Testing Commit'
|
|
);
|
|
|
|
const url = blameNode.getElementsByClassName('blameDate');
|
|
assert.equal(url[0].getAttribute('href'), '/r/q/1234567890');
|
|
});
|
|
});
|
|
});
|
|
|