blob: ef3ca39047889f6d94f80f37bbcac9745ada91c0 [file] [log] [blame]
/**
* @license
* Copyright (C) 2015 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 {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
import './gr-diff.js';
import {GrDiffBuilderImage} from '../gr-diff-builder/gr-diff-builder-image.js';
import {getComputedStyleValue} from '../../../utils/dom-util.js';
import {_setHiddenScroll} from '../../../scripts/hiddenscroll.js';
import {runA11yAudit} from '../../../test/a11y-test-utils.js';
import '@polymer/paper-button/paper-button.js';
import {Side} from '../../../api/diff.js';
import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-diff');
suite('gr-diff a11y test', () => {
test('audit', async () => {
await runA11yAudit(basicFixture);
});
});
suite('gr-diff tests', () => {
let element;
const MINIMAL_PREFS = {tab_size: 2, line_length: 80, font_size: 12};
setup(() => {
});
suite('selectionchange event handling', () => {
const emulateSelection = function() {
document.dispatchEvent(new CustomEvent('selectionchange'));
};
setup(() => {
element = basicFixture.instantiate();
sinon.stub(element.$.highlights, 'handleSelectionChange');
});
test('enabled if logged in', async () => {
element.loggedIn = true;
emulateSelection();
await flush();
assert.isTrue(element.$.highlights.handleSelectionChange.called);
});
test('ignored if logged out', async () => {
element.loggedIn = false;
emulateSelection();
await flush();
assert.isFalse(element.$.highlights.handleSelectionChange.called);
});
});
test('cancel', () => {
element = basicFixture.instantiate();
const cancelStub = sinon.stub(element.$.diffBuilder, 'cancel');
element.cancel();
assert.isTrue(cancelStub.calledOnce);
});
test('line limit with line_wrapping', () => {
element = basicFixture.instantiate();
element.prefs = {...MINIMAL_PREFS, line_wrapping: true};
flush();
assert.equal(getComputedStyleValue('--line-limit-marker', element), '80ch');
});
test('line limit without line_wrapping', () => {
element = basicFixture.instantiate();
element.prefs = {...MINIMAL_PREFS, line_wrapping: false};
flush();
assert.equal(getComputedStyleValue('--line-limit-marker', element), '-1px');
});
suite('FULL_RESPONSIVE mode', () => {
setup(() => {
element = basicFixture.instantiate();
element.prefs = {...MINIMAL_PREFS};
element.renderPrefs = {responsive_mode: 'FULL_RESPONSIVE'};
});
test('line limit is based on line_length', () => {
element.prefs = {...element.prefs, line_length: 100};
flush();
assert.equal(getComputedStyleValue('--line-limit-marker', element),
'100ch');
});
test('content-width should not be defined', () => {
flush();
assert.equal(getComputedStyleValue('--content-width', element), 'none');
});
});
suite('SHRINK_ONLY mode', () => {
setup(() => {
element = basicFixture.instantiate();
element.prefs = {...MINIMAL_PREFS};
element.renderPrefs = {responsive_mode: 'SHRINK_ONLY'};
});
test('content-width should not be defined', () => {
flush();
assert.equal(getComputedStyleValue('--content-width', element), 'none');
});
test('max-width considers two content columns in side-by-side', () => {
element.viewMode = 'SIDE_BY_SIDE';
flush();
assert.equal(getComputedStyleValue('--diff-max-width', element),
'calc(2 * 80ch + 2 * 48px + 1px + 2px)');
});
test('max-width considers one content column in unified', () => {
element.viewMode = 'UNIFIED_DIFF';
flush();
assert.equal(getComputedStyleValue('--diff-max-width', element),
'calc(1 * 80ch + 2 * 48px + 1px + 2px)');
});
test('max-width considers font-size', () => {
element.prefs = {...element.prefs, font_size: 13};
flush();
// Each line number column: 4 * 13 = 52px
assert.equal(getComputedStyleValue('--diff-max-width', element),
'calc(2 * 80ch + 2 * 52px + 1px + 2px)');
});
});
suite('not logged in', () => {
setup(() => {
const getLoggedInPromise = Promise.resolve(false);
stubRestApi('getLoggedIn').returns(getLoggedInPromise);
element = basicFixture.instantiate();
return getLoggedInPromise;
});
test('toggleLeftDiff', () => {
element.toggleLeftDiff();
assert.isTrue(element.classList.contains('no-left'));
element.toggleLeftDiff();
assert.isFalse(element.classList.contains('no-left'));
});
test('view does not start with displayLine classList', () => {
assert.isFalse(
element.shadowRoot
.querySelector('.diffContainer')
.classList
.contains('displayLine'));
});
test('displayLine class added called when displayLine is true', () => {
const spy = sinon.spy(element, '_computeContainerClass');
element.displayLine = true;
assert.isTrue(spy.called);
assert.isTrue(
element.shadowRoot
.querySelector('.diffContainer')
.classList
.contains('displayLine'));
});
test('thread groups', () => {
const contentEl = document.createElement('div');
element.changeNum = 123;
element.patchRange = {basePatchNum: 1, patchNum: 2};
element.path = 'file.txt';
element.$.diffBuilder.diff = getMockDiffResponse();
element.$.diffBuilder.prefs = {...MINIMAL_PREFS};
element.$.diffBuilder._builder = element.$.diffBuilder._getDiffBuilder();
// No thread groups.
assert.isNotOk(element._getThreadGroupForLine(contentEl));
// A thread group gets created.
const threadGroupEl = element._getOrCreateThreadGroup(contentEl);
assert.isOk(threadGroupEl);
// The new thread group can be fetched.
assert.isOk(element._getThreadGroupForLine(contentEl));
assert.equal(contentEl.querySelectorAll('.thread-group').length, 1);
});
suite('image diffs', () => {
let mockFile1;
let mockFile2;
setup(() => {
mockFile1 = {
body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
'wsAAAAAAAAAAAAAAAAA/w==',
type: 'image/bmp',
};
mockFile2 = {
body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' +
'wsAAAAAAAAAAAAA/////w==',
type: 'image/bmp',
};
element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
element.isImageDiff = true;
element.prefs = {
context: 10,
cursor_blink_rate: 0,
font_size: 12,
ignore_whitespace: 'IGNORE_NONE',
line_length: 100,
line_wrapping: false,
show_line_endings: true,
show_tabs: true,
show_whitespace_errors: true,
syntax_highlighting: true,
tab_size: 8,
theme: 'DEFAULT',
};
});
test('renders image diffs with same file name', async () => {
const leftRendered = mockPromise();
const rightRendered = mockPromise();
const rendered = () => {
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
assert.instanceOf(
element.$.diffBuilder._builder, GrDiffBuilderImage);
// Left image rendered with the parent commit's version of the file.
const leftImage = element.$.diffTable.querySelector('td.left img');
const leftLabel =
element.$.diffTable.querySelector('td.left label');
const leftLabelContent = leftLabel.querySelector('.label');
const leftLabelName = leftLabel.querySelector('.name');
const rightImage =
element.$.diffTable.querySelector('td.right img');
const rightLabel = element.$.diffTable.querySelector(
'td.right label');
const rightLabelContent = rightLabel.querySelector('.label');
const rightLabelName = rightLabel.querySelector('.name');
assert.isNotOk(rightLabelName);
assert.isNotOk(leftLabelName);
leftImage.addEventListener('load', () => {
assert.isOk(leftImage);
assert.equal(leftImage.getAttribute('src'),
'data:image/bmp;base64,' + mockFile1.body);
assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
leftRendered.resolve();
});
rightImage.addEventListener('load', () => {
assert.isOk(rightImage);
assert.equal(rightImage.getAttribute('src'),
'data:image/bmp;base64,' + mockFile2.body);
assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
rightRendered.resolve();
});
};
element.addEventListener('render', rendered);
element.baseImage = mockFile1;
element.revisionImage = mockFile2;
element.diff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
intraline_status: 'OK',
change_type: 'MODIFIED',
diff_header: [
'diff --git a/carrot.jpg b/carrot.jpg',
'index 2adc47d..f9c2f2c 100644',
'--- a/carrot.jpg',
'+++ b/carrot.jpg',
'Binary files differ',
],
content: [{skip: 66}],
binary: true,
};
await Promise.all([leftRendered, rightRendered]);
element.removeEventListener('render', rendered);
});
test('renders image diffs with a different file name', async () => {
const mockDiff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
lines: 560},
intraline_status: 'OK',
change_type: 'MODIFIED',
diff_header: [
'diff --git a/carrot.jpg b/carrot2.jpg',
'index 2adc47d..f9c2f2c 100644',
'--- a/carrot.jpg',
'+++ b/carrot2.jpg',
'Binary files differ',
],
content: [{skip: 66}],
binary: true,
};
const leftRendered = mockPromise();
const rightRendered = mockPromise();
const rendered = () => {
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
assert.instanceOf(
element.$.diffBuilder._builder, GrDiffBuilderImage);
// Left image rendered with the parent commit's version of the file.
const leftImage = element.$.diffTable.querySelector('td.left img');
const leftLabel =
element.$.diffTable.querySelector('td.left label');
const leftLabelContent = leftLabel.querySelector('.label');
const leftLabelName = leftLabel.querySelector('.name');
const rightImage =
element.$.diffTable.querySelector('td.right img');
const rightLabel = element.$.diffTable.querySelector(
'td.right label');
const rightLabelContent = rightLabel.querySelector('.label');
const rightLabelName = rightLabel.querySelector('.name');
assert.isOk(rightLabelName);
assert.isOk(leftLabelName);
assert.equal(leftLabelName.textContent, mockDiff.meta_a.name);
assert.equal(rightLabelName.textContent, mockDiff.meta_b.name);
leftImage.addEventListener('load', () => {
assert.isOk(leftImage);
assert.equal(leftImage.getAttribute('src'),
'data:image/bmp;base64,' + mockFile1.body);
assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
leftRendered.resolve();
});
rightImage.addEventListener('load', () => {
assert.isOk(rightImage);
assert.equal(rightImage.getAttribute('src'),
'data:image/bmp;base64,' + mockFile2.body);
assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
rightRendered.resolve();
});
};
element.addEventListener('render', rendered);
element.baseImage = mockFile1;
element.baseImage._name = mockDiff.meta_a.name;
element.revisionImage = mockFile2;
element.revisionImage._name = mockDiff.meta_b.name;
element.diff = mockDiff;
await Promise.all([leftRendered, rightRendered]);
element.removeEventListener('render', rendered);
});
test('renders added image', async () => {
const mockDiff = {
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
intraline_status: 'OK',
change_type: 'ADDED',
diff_header: [
'diff --git a/carrot.jpg b/carrot.jpg',
'index 0000000..f9c2f2c 100644',
'--- /dev/null',
'+++ b/carrot.jpg',
'Binary files differ',
],
content: [{skip: 66}],
binary: true,
};
const promise = mockPromise();
function rendered() { promise.resolve(); }
element.addEventListener('render', rendered);
element.revisionImage = mockFile2;
element.diff = mockDiff;
await promise;
element.removeEventListener('render', rendered);
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
assert.instanceOf(
element.$.diffBuilder._builder, GrDiffBuilderImage);
const leftImage = element.$.diffTable.querySelector('td.left img');
const rightImage = element.$.diffTable.querySelector('td.right img');
assert.isNotOk(leftImage);
assert.isOk(rightImage);
});
test('renders removed image', async () => {
const mockDiff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
intraline_status: 'OK',
change_type: 'DELETED',
diff_header: [
'diff --git a/carrot.jpg b/carrot.jpg',
'index f9c2f2c..0000000 100644',
'--- a/carrot.jpg',
'+++ /dev/null',
'Binary files differ',
],
content: [{skip: 66}],
binary: true,
};
const promise = mockPromise();
function rendered() { promise.resolve(); }
element.addEventListener('render', rendered);
element.baseImage = mockFile1;
element.diff = mockDiff;
await promise;
element.removeEventListener('render', rendered);
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
assert.instanceOf(
element.$.diffBuilder._builder, GrDiffBuilderImage);
const leftImage = element.$.diffTable.querySelector('td.left img');
const rightImage = element.$.diffTable.querySelector('td.right img');
assert.isOk(leftImage);
assert.isNotOk(rightImage);
});
test('does not render disallowed image type', async () => {
const mockDiff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
lines: 560},
intraline_status: 'OK',
change_type: 'DELETED',
diff_header: [
'diff --git a/carrot.jpg b/carrot.jpg',
'index f9c2f2c..0000000 100644',
'--- a/carrot.jpg',
'+++ /dev/null',
'Binary files differ',
],
content: [{skip: 66}],
binary: true,
};
mockFile1.type = 'image/jpeg-evil';
const promise = mockPromise();
function rendered() { promise.resolve(); }
element.addEventListener('render', rendered);
element.baseImage = mockFile1;
element.diff = mockDiff;
await promise;
element.removeEventListener('render', rendered);
// Recognizes that it should be an image diff.
assert.isTrue(element.isImageDiff);
assert.instanceOf(
element.$.diffBuilder._builder, GrDiffBuilderImage);
const leftImage = element.$.diffTable.querySelector('td.left img');
assert.isNotOk(leftImage);
});
});
test('_handleTap lineNum', async () => {
const addDraftStub = sinon.stub(element, 'addDraftAtLine');
const el = document.createElement('div');
el.className = 'lineNum';
const promise = mockPromise();
el.addEventListener('click', e => {
element._handleTap(e);
assert.isTrue(addDraftStub.called);
assert.equal(addDraftStub.lastCall.args[0], el);
promise.resolve();
});
el.click();
await promise;
});
test('_handleTap content', async () => {
const content = document.createElement('div');
const lineEl = document.createElement('div');
lineEl.className = 'lineNum';
const row = document.createElement('div');
row.appendChild(lineEl);
row.appendChild(content);
const selectStub = sinon.stub(element, '_selectLine');
content.className = 'content';
const promise = mockPromise();
content.addEventListener('click', e => {
element._handleTap(e);
assert.isTrue(selectStub.called);
assert.equal(selectStub.lastCall.args[0], lineEl);
promise.resolve();
});
content.click();
await promise;
});
suite('getCursorStops', () => {
function setupDiff() {
element.diff = getMockDiffResponse();
element.prefs = {
context: 10,
tab_size: 8,
font_size: 12,
line_length: 100,
cursor_blink_rate: 0,
line_wrapping: false,
show_line_endings: true,
show_tabs: true,
show_whitespace_errors: true,
syntax_highlighting: true,
theme: 'DEFAULT',
ignore_whitespace: 'IGNORE_NONE',
};
element._renderDiffTable();
element._setLoading(false);
flush();
}
test('getCursorStops returns [] when hidden and noAutoRender', () => {
element.noAutoRender = true;
setupDiff();
element.hidden = true;
assert.equal(element.getCursorStops().length, 0);
});
test('getCursorStops', () => {
setupDiff();
const ROWS = 48;
const FILE_ROW = 1;
assert.equal(element.getCursorStops().length, ROWS + FILE_ROW);
});
});
test('adds .hiddenscroll', () => {
_setHiddenScroll(true);
element.displayLine = true;
assert.include(element.shadowRoot
.querySelector('.diffContainer').className, 'hiddenscroll');
});
});
suite('logged in', () => {
let fakeLineEl;
setup(() => {
element = basicFixture.instantiate();
element.loggedIn = true;
element.patchRange = {};
fakeLineEl = {
getAttribute: sinon.stub().returns(42),
classList: {
contains: sinon.stub().returns(true),
},
};
});
test('addDraftAtLine', () => {
sinon.stub(element, '_selectLine');
sinon.stub(element, '_createComment');
element.addDraftAtLine(fakeLineEl);
assert.isTrue(element._createComment
.calledWithExactly(fakeLineEl, 42));
});
test('adds long range comment hint', async () => {
const range = {
start_line: 1,
end_line: 12,
start_character: 0,
end_character: 0,
};
const threadEl = document.createElement('div');
threadEl.className = 'comment-thread';
threadEl.setAttribute('diff-side', 'right');
threadEl.setAttribute('line-num', 1);
threadEl.setAttribute('range', JSON.stringify(range));
threadEl.setAttribute('slot', 'right-1');
const content = [{
a: [],
b: [],
}, {
ab: Array(13).fill('text'),
}];
setupSampleDiff({content});
element.appendChild(threadEl);
await flush();
assert.deepEqual(
element.querySelector('gr-ranged-comment-hint').range, range);
});
test('no duplicate range hint for same thread', async () => {
const range = {
start_line: 1,
end_line: 12,
start_character: 0,
end_character: 0,
};
const threadEl = document.createElement('div');
threadEl.className = 'comment-thread';
threadEl.setAttribute('diff-side', 'right');
threadEl.setAttribute('line-num', 1);
threadEl.setAttribute('range', JSON.stringify(range));
threadEl.setAttribute('slot', 'right-1');
const firstHint = document.createElement('gr-ranged-comment-hint');
firstHint.range = range;
firstHint.setAttribute('threadElRootId', threadEl.rootId);
firstHint.setAttribute('slot', 'right-1');
const content = [{
a: [],
b: [],
}, {
ab: Array(13).fill('text'),
}];
setupSampleDiff({content});
element.appendChild(firstHint);
await flush();
element._handleRenderContent();
await flush();
element.appendChild(threadEl);
await flush();
assert.equal(
element.querySelectorAll('gr-ranged-comment-hint').length, 1);
});
test('removes long range comment hint when comment is discarded',
async () => {
const range = {
start_line: 1,
end_line: 7,
start_character: 0,
end_character: 0,
};
const threadEl = document.createElement('div');
threadEl.className = 'comment-thread';
threadEl.setAttribute('diff-side', 'right');
threadEl.setAttribute('line-num', 1);
threadEl.setAttribute('range', JSON.stringify(range));
threadEl.setAttribute('slot', 'right-1');
const content = [{
a: [],
b: [],
}, {
ab: Array(8).fill('text'),
}];
setupSampleDiff({content});
element.appendChild(threadEl);
await flush();
threadEl.remove();
await flush();
assert.isEmpty(element.querySelectorAll('gr-ranged-comment-hint'));
});
suite('change in preferences', () => {
setup(() => {
element.diff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
diff_header: [],
intraline_status: 'OK',
change_type: 'MODIFIED',
content: [{skip: 66}],
};
element.renderDiffTableTask.flush();
});
test('change in preferences re-renders diff', () => {
sinon.stub(element, '_renderDiffTable');
element.prefs = {
...MINIMAL_PREFS, time_format: 'HHMM_12'};
element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
});
test('adding/removing property in preferences re-renders diff', () => {
const stub = sinon.stub(element, '_renderDiffTable');
const newPrefs1 = {...MINIMAL_PREFS,
line_wrapping: true};
element.prefs = newPrefs1;
element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
stub.reset();
const newPrefs2 = {...newPrefs1};
delete newPrefs2.line_wrapping;
element.prefs = newPrefs2;
element.renderDiffTableTask.flush();
assert.isTrue(element._renderDiffTable.called);
});
test('change in preferences does not re-renders diff with ' +
'noRenderOnPrefsChange', () => {
sinon.stub(element, '_renderDiffTable');
element.noRenderOnPrefsChange = true;
element.prefs = {
...MINIMAL_PREFS, time_format: 'HHMM_12'};
element.renderDiffTableTask.flush();
assert.isFalse(element._renderDiffTable.called);
});
});
});
suite('diff header', () => {
setup(() => {
element = basicFixture.instantiate();
element.diff = {
meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
lines: 560},
diff_header: [],
intraline_status: 'OK',
change_type: 'MODIFIED',
content: [{skip: 66}],
};
});
test('hidden', () => {
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', 'index 2adc47d..f9c2f2c 100644');
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', '--- a/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', '+++ b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', 'test');
assert.equal(element._diffHeaderItems.length, 1);
flush();
assert.equal(element.$.diffHeader.textContent.trim(), 'test');
});
test('binary files', () => {
element.diff.binary = true;
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', 'diff --git a/test.jpg b/test.jpg');
assert.equal(element._diffHeaderItems.length, 0);
element.push('diff.diff_header', 'test');
assert.equal(element._diffHeaderItems.length, 1);
element.push('diff.diff_header', 'Binary files differ');
assert.equal(element._diffHeaderItems.length, 1);
});
});
suite('safety and bypass', () => {
let renderStub;
setup(() => {
element = basicFixture.instantiate();
renderStub = sinon.stub(element.$.diffBuilder, 'render').callsFake(
() => {
element.$.diffBuilder.dispatchEvent(
new CustomEvent('render', {bubbles: true, composed: true}));
return Promise.resolve({});
});
sinon.stub(element, 'getDiffLength').returns(10000);
element.diff = getMockDiffResponse();
element.noRenderOnPrefsChange = true;
});
test('large render w/ context = 10', async () => {
element.prefs = {...MINIMAL_PREFS, context: 10};
const promise = mockPromise();
function rendered() {
assert.isTrue(renderStub.called);
assert.isFalse(element._showWarning);
promise.resolve();
element.removeEventListener('render', rendered);
}
element.addEventListener('render', rendered);
element._renderDiffTable();
await promise;
});
test('large render w/ whole file and bypass', async () => {
element.prefs = {...MINIMAL_PREFS, context: -1};
element._safetyBypass = 10;
const promise = mockPromise();
function rendered() {
assert.isTrue(renderStub.called);
assert.isFalse(element._showWarning);
promise.resolve();
element.removeEventListener('render', rendered);
}
element.addEventListener('render', rendered);
element._renderDiffTable();
await promise;
});
test('large render w/ whole file and no bypass', async () => {
element.prefs = {...MINIMAL_PREFS, context: -1};
const promise = mockPromise();
function rendered() {
assert.isFalse(renderStub.called);
assert.isTrue(element._showWarning);
promise.resolve();
element.removeEventListener('render', rendered);
}
element.addEventListener('render', rendered);
element._renderDiffTable();
await promise;
});
test('toggles expand context using bypass', async () => {
element.prefs = {...MINIMAL_PREFS, context: 3};
element.toggleAllContext();
element._renderDiffTable();
await flush();
assert.equal(element.prefs.context, 3);
assert.equal(element._safetyBypass, -1);
assert.equal(element.$.diffBuilder.prefs.context, -1);
});
test('toggles collapse context from bypass', async () => {
element.prefs = {...MINIMAL_PREFS, context: 3};
element._safetyBypass = -1;
element.toggleAllContext();
element._renderDiffTable();
await flush();
assert.equal(element.prefs.context, 3);
assert.isNull(element._safetyBypass);
assert.equal(element.$.diffBuilder.prefs.context, 3);
});
test('toggles collapse context from pref using default', async () => {
element.prefs = {...MINIMAL_PREFS, context: -1};
element.toggleAllContext();
element._renderDiffTable();
await flush();
assert.equal(element.prefs.context, -1);
assert.equal(element._safetyBypass, 10);
assert.equal(element.$.diffBuilder.prefs.context, 10);
});
});
suite('blame', () => {
setup(() => {
element = basicFixture.instantiate();
});
test('unsetting', () => {
element.blame = [];
const setBlameSpy = sinon.spy(element.$.diffBuilder, 'setBlame');
element.classList.add('showBlame');
element.blame = null;
assert.isTrue(setBlameSpy.calledWithExactly(null));
assert.isFalse(element.classList.contains('showBlame'));
});
test('setting', () => {
element.blame = [{id: 'commit id', ranges: [{start: 1, end: 2}]}];
assert.isTrue(element.classList.contains('showBlame'));
});
});
suite('trailing newline warnings', () => {
const NO_NEWLINE_BASE = 'No newline at end of base file.';
const NO_NEWLINE_REVISION = 'No newline at end of revision file.';
const getWarning = element =>
element.shadowRoot.querySelector('.newlineWarning').textContent;
setup(() => {
element = basicFixture.instantiate();
element.showNewlineWarningLeft = false;
element.showNewlineWarningRight = false;
});
test('shows combined warning if both sides set to warn', () => {
element.showNewlineWarningLeft = true;
element.showNewlineWarningRight = true;
assert.include(getWarning(element),
NO_NEWLINE_BASE + ' \u2014 ' + NO_NEWLINE_REVISION);// \u2014 - '—'
});
suite('showNewlineWarningLeft', () => {
test('show warning if true', () => {
element.showNewlineWarningLeft = true;
assert.include(getWarning(element), NO_NEWLINE_BASE);
});
test('hide warning if false', () => {
element.showNewlineWarningLeft = false;
assert.notInclude(getWarning(element), NO_NEWLINE_BASE);
});
test('hide warning if undefined', () => {
element.showNewlineWarningLeft = undefined;
assert.notInclude(getWarning(element), NO_NEWLINE_BASE);
});
});
suite('showNewlineWarningRight', () => {
test('show warning if true', () => {
element.showNewlineWarningRight = true;
assert.include(getWarning(element), NO_NEWLINE_REVISION);
});
test('hide warning if false', () => {
element.showNewlineWarningRight = false;
assert.notInclude(getWarning(element), NO_NEWLINE_REVISION);
});
test('hide warning if undefined', () => {
element.showNewlineWarningRight = undefined;
assert.notInclude(getWarning(element), NO_NEWLINE_REVISION);
});
});
test('_computeNewlineWarningClass', () => {
const hidden = 'newlineWarning hidden';
const shown = 'newlineWarning';
assert.equal(element._computeNewlineWarningClass(null, true), hidden);
assert.equal(element._computeNewlineWarningClass('foo', true), hidden);
assert.equal(element._computeNewlineWarningClass(null, false), hidden);
assert.equal(element._computeNewlineWarningClass('foo', false), shown);
});
test('_prefsEqual', () => {
element = basicFixture.instantiate();
assert.isTrue(element._prefsEqual(null, null));
assert.isTrue(element._prefsEqual({}, {}));
assert.isTrue(element._prefsEqual({x: 1}, {x: 1}));
assert.isTrue(
element._prefsEqual({x: 1, abc: 'def'}, {x: 1, abc: 'def'}));
const somePref = {abc: 'def', p: true};
assert.isTrue(element._prefsEqual(somePref, somePref));
assert.isFalse(element._prefsEqual({}, null));
assert.isFalse(element._prefsEqual(null, {}));
assert.isFalse(element._prefsEqual({x: 1}, {x: 2}));
assert.isFalse(element._prefsEqual({x: 1, y: 'abc'}, {x: 1, y: 'abcd'}));
assert.isFalse(element._prefsEqual({x: 1, y: 'abc'}, {x: 1}));
assert.isFalse(element._prefsEqual({x: 1}, {x: 1, y: 'abc'}));
});
});
suite('key locations', () => {
let renderStub;
setup(() => {
element = basicFixture.instantiate();
element.prefs = {};
renderStub = sinon.stub(element.$.diffBuilder, 'render')
.returns(new Promise(() => {}));
});
test('lineOfInterest is a key location', () => {
element.lineOfInterest = {lineNum: 789, side: Side.LEFT};
element._renderDiffTable();
assert.isTrue(renderStub.called);
assert.deepEqual(renderStub.lastCall.args[0], {
left: {789: true},
right: {},
});
});
test('line comments are key locations', () => {
const threadEl = document.createElement('div');
threadEl.className = 'comment-thread';
threadEl.setAttribute('diff-side', 'right');
threadEl.setAttribute('line-num', 3);
element.appendChild(threadEl);
flush();
element._renderDiffTable();
assert.isTrue(renderStub.called);
assert.deepEqual(renderStub.lastCall.args[0], {
left: {},
right: {3: true},
});
});
test('file comments are key locations', () => {
const threadEl = document.createElement('div');
threadEl.className = 'comment-thread';
threadEl.setAttribute('diff-side', 'left');
element.appendChild(threadEl);
flush();
element._renderDiffTable();
assert.isTrue(renderStub.called);
assert.deepEqual(renderStub.lastCall.args[0], {
left: {FILE: true},
right: {},
});
});
});
const setupSampleDiff = function(params) {
const {ignore_whitespace, content} = params;
// binary can't be undefined, use false if not set
const binary = params.binary || false;
element = basicFixture.instantiate();
element.prefs = {
ignore_whitespace: ignore_whitespace || 'IGNORE_ALL',
context: 10,
cursor_blink_rate: 0,
font_size: 12,
line_length: 100,
line_wrapping: false,
show_line_endings: true,
show_tabs: true,
show_whitespace_errors: true,
syntax_highlighting: true,
tab_size: 8,
theme: 'DEFAULT',
};
element.diff = {
intraline_status: 'OK',
change_type: 'MODIFIED',
diff_header: [
'diff --git a/carrot.js b/carrot.js',
'index 2adc47d..f9c2f2c 100644',
'--- a/carrot.js',
'+++ b/carrot.jjs',
'file differ',
],
content,
binary,
};
element._renderDiffTable();
flush();
};
test('clear diff table content as soon as diff changes', () => {
const content = [{
a: ['all work and no play make andybons a dull boy'],
}, {
b: [
'Non eram nescius, Brute, cum, quae summis ingeniis ',
],
}];
function assertDiffTableWithContent() {
assert.isTrue(element.$.diffTable.innerText.includes(content[0].a));
}
setupSampleDiff({content});
assertDiffTableWithContent();
element.diff = {...element.diff};
// immediately cleaned up
assert.equal(element.$.diffTable.innerHTML, '');
element._renderDiffTable();
flush();
// rendered again
assertDiffTableWithContent();
});
suite('selection test', () => {
test('user-select set correctly on side-by-side view', () => {
const 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',
],
}];
setupSampleDiff({content});
flush();
const diffLine = element.shadowRoot.querySelectorAll('.contentText')[2];
assert.equal(getComputedStyle(diffLine).userSelect, 'none');
// click to mark it as selected
MockInteractions.tap(diffLine);
assert.equal(getComputedStyle(diffLine).userSelect, 'text');
});
test('user-select set correctly on unified view', () => {
const 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',
],
}];
setupSampleDiff({content});
element.viewMode = 'UNIFIED_DIFF';
flush();
const diffLine = element.shadowRoot.querySelectorAll('.contentText')[2];
assert.equal(getComputedStyle(diffLine).userSelect, 'none');
MockInteractions.tap(diffLine);
assert.equal(getComputedStyle(diffLine).userSelect, 'text');
});
});
suite('whitespace changes only message', () => {
test('show the message if ignore_whitespace is criteria matches', () => {
setupSampleDiff({content: [{skip: 100}]});
assert.isTrue(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
element._diffLength,
element.diff
));
});
test('do not show the message for binary files', () => {
setupSampleDiff({content: [{skip: 100}], binary: true});
assert.isFalse(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
element._diffLength,
element.diff
));
});
test('do not show the message if still loading', () => {
setupSampleDiff({content: [{skip: 100}]});
assert.isFalse(element.showNoChangeMessage(
/* loading= */ true,
element.prefs,
element._diffLength,
element.diff
));
});
test('do not show the message if contains valid changes', () => {
const 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',
],
}];
setupSampleDiff({content});
assert.equal(element._diffLength, 3);
assert.isFalse(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
element._diffLength,
element.diff
));
});
test('do not show message if ignore whitespace is disabled', () => {
const 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',
],
}];
setupSampleDiff({ignore_whitespace: 'IGNORE_NONE', content});
assert.isFalse(element.showNoChangeMessage(
/* loading= */ false,
element.prefs,
element._diffLength,
element.diff
));
});
});
test('getDiffLength', () => {
const diff = getMockDiffResponse();
assert.equal(element.getDiffLength(diff), 52);
});
test('`render` event has contentRendered field in detail', async () => {
element = basicFixture.instantiate();
element.prefs = {};
sinon.stub(element.$.diffBuilder, 'render')
.returns(Promise.resolve());
const promise = mockPromise();
element.addEventListener('render', event => {
assert.isTrue(event.detail.contentRendered);
promise.resolve();
});
element._renderDiffTable();
await promise;
});
test('_prefsEqual', () => {
element = basicFixture.instantiate();
assert.isTrue(element._prefsEqual(null, null));
assert.isTrue(element._prefsEqual({}, {}));
assert.isTrue(element._prefsEqual({x: 1}, {x: 1}));
assert.isTrue(element._prefsEqual({x: 1, abc: 'def'}, {x: 1, abc: 'def'}));
const somePref = {abc: 'def', p: true};
assert.isTrue(element._prefsEqual(somePref, somePref));
assert.isFalse(element._prefsEqual({}, null));
assert.isFalse(element._prefsEqual(null, {}));
assert.isFalse(element._prefsEqual({x: 1}, {x: 2}));
assert.isFalse(element._prefsEqual({x: 1, y: 'abc'}, {x: 1, y: 'abcd'}));
assert.isFalse(element._prefsEqual({x: 1, y: 'abc'}, {x: 1}));
assert.isFalse(element._prefsEqual({x: 1}, {x: 1, y: 'abc'}));
});
});