blob: e022a747042a84cd93c275923bbe8b40757403fc [file] [log] [blame]
<!DOCTYPE html>
<!--
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.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-file-list</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-file-list.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-file-list></gr-file-list>
</template>
</test-fixture>
<test-fixture id="blank">
<template>
<div></div>
</template>
</test-fixture>
<script>
suite('gr-file-list tests', () => {
let element;
let sandbox;
let saveStub;
setup(() => {
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn() { return Promise.resolve(true); },
getPreferences() { return Promise.resolve({}); },
fetchJSON() { return Promise.resolve({}); },
});
stub('gr-date-formatter', {
_loadTimeFormat() { return Promise.resolve(''); },
});
stub('gr-diff', {
reload() { return Promise.resolve(); },
});
element = fixture('basic');
element.numFilesShown = 200;
saveStub = sandbox.stub(element, '_saveReviewedState',
() => { return Promise.resolve(); });
});
teardown(() => {
sandbox.restore();
});
test('correct number of files are shown', () => {
element._files = _.times(500, i => {
return {__path: '/file' + i, lines_inserted: 9};
});
flushAsynchronousOperations();
assert.equal(
Polymer.dom(element.root).querySelectorAll('.file-row').length,
element.numFilesShown);
});
test('get file list', done => {
const getChangeFilesStub = sandbox.stub(element.$.restAPI, 'getChangeFiles',
() => {
return Promise.resolve({
'/COMMIT_MSG': {lines_inserted: 9},
'tags.html': {lines_deleted: 123},
'about.txt': {},
});
});
element._getFiles().then(files => {
const filenames = files.map(f => { return f.__path; });
assert.deepEqual(filenames, ['/COMMIT_MSG', 'about.txt', 'tags.html']);
assert.deepEqual(files[0], {
lines_inserted: 9,
lines_deleted: 0,
__path: '/COMMIT_MSG',
});
assert.deepEqual(files[1], {
lines_inserted: 0,
lines_deleted: 0,
__path: 'about.txt',
});
assert.deepEqual(files[2], {
lines_inserted: 0,
lines_deleted: 123,
__path: 'tags.html',
});
getChangeFilesStub.restore();
done();
});
});
test('calculate totals for patch number', () => {
element._files = [
{__path: '/COMMIT_MSG', lines_inserted: 9},
{
__path: 'file_added_in_rev2.txt',
lines_inserted: 1,
lines_deleted: 1,
size_delta: 10,
size: 100,
},
{
__path: 'myfile.txt',
lines_inserted: 1,
lines_deleted: 1,
size_delta: 10,
size: 100,
},
];
assert.deepEqual(element._patchChange, {
inserted: 2,
deleted: 2,
size_delta_inserted: 0,
size_delta_deleted: 0,
total_size: 0,
});
assert.isTrue(element._hideBinaryChangeTotals);
assert.isFalse(element._hideChangeTotals);
// Test with a commit message that isn't the first file.
element._files = [
{__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
{__path: '/COMMIT_MSG', lines_inserted: 9},
{__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
];
assert.deepEqual(element._patchChange, {
inserted: 2,
deleted: 2,
size_delta_inserted: 0,
size_delta_deleted: 0,
total_size: 0,
});
assert.isTrue(element._hideBinaryChangeTotals);
assert.isFalse(element._hideChangeTotals);
// Test with no commit message.
element._files = [
{__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1},
{__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1},
];
assert.deepEqual(element._patchChange, {
inserted: 2,
deleted: 2,
size_delta_inserted: 0,
size_delta_deleted: 0,
total_size: 0,
});
assert.isTrue(element._hideBinaryChangeTotals);
assert.isFalse(element._hideChangeTotals);
// Test with files missing either lines_inserted or lines_deleted.
element._files = [
{__path: 'file_added_in_rev2.txt', lines_inserted: 1},
{__path: 'myfile.txt', lines_deleted: 1},
];
assert.deepEqual(element._patchChange, {
inserted: 1,
deleted: 1,
size_delta_inserted: 0,
size_delta_deleted: 0,
total_size: 0,
});
assert.isTrue(element._hideBinaryChangeTotals);
assert.isFalse(element._hideChangeTotals);
});
test('binary only files', () => {
element._files = [
{__path: '/COMMIT_MSG', lines_inserted: 9},
{__path: 'file_binary', binary: true, size_delta: 10, size: 100},
{__path: 'file_binary', binary: true, size_delta: -5, size: 120},
];
assert.deepEqual(element._patchChange, {
inserted: 0,
deleted: 0,
size_delta_inserted: 10,
size_delta_deleted: -5,
total_size: 220,
});
assert.isFalse(element._hideBinaryChangeTotals);
assert.isTrue(element._hideChangeTotals);
});
test('binary and regular files', () => {
element._files = [
{__path: '/COMMIT_MSG', lines_inserted: 9},
{__path: 'file_binary', binary: true, size_delta: 10, size: 100},
{__path: 'file_binary', binary: true, size_delta: -5, size: 120},
{__path: 'myfile.txt', lines_deleted: 5, size_delta: -10, size: 100},
{__path: 'myfile2.txt', lines_inserted: 10},
];
assert.deepEqual(element._patchChange, {
inserted: 10,
deleted: 5,
size_delta_inserted: 10,
size_delta_deleted: -5,
total_size: 220,
});
assert.isFalse(element._hideBinaryChangeTotals);
assert.isFalse(element._hideChangeTotals);
});
test('_formatBytes function', () => {
const table = {
'64': '+64 B',
'1023': '+1023 B',
'1024': '+1 KiB',
'4096': '+4 KiB',
'1073741824': '+1 GiB',
'-64': '-64 B',
'-1023': '-1023 B',
'-1024': '-1 KiB',
'-4096': '-4 KiB',
'-1073741824': '-1 GiB',
'0': '+/-0 B',
};
for (const bytes in table) {
if (table.hasOwnProperty(bytes)) {
assert.equal(element._formatBytes(bytes), table[bytes]);
}
}
});
test('_formatPercentage function', () => {
const table = [
{size: 100,
delta: 100,
display: '',
},
{size: 195060,
delta: 64,
display: '(+0%)',
},
{size: 195060,
delta: -64,
display: '(-0%)',
},
{size: 394892,
delta: -7128,
display: '(-2%)',
},
{size: 90,
delta: -10,
display: '(-10%)',
},
{size: 110,
delta: 10,
display: '(+10%)',
},
];
for (const item of table) {
assert.equal(element._formatPercentage(
item.size, item.delta), item.display);
}
});
suite('keyboard shortcuts', () => {
setup(() => {
element._files = [
{__path: '/COMMIT_MSG'},
{__path: 'file_added_in_rev2.txt'},
{__path: 'myfile.txt'},
];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
element.$.fileCursor.setCursorAtIndex(0);
});
test('toggle left diff via shortcut', () => {
const toggleLeftDiffStub = sandbox.stub();
// Property getter cannot be stubbed w/ sandbox due to a bug in Sinon.
// https://github.com/sinonjs/sinon/issues/781
const diffsStub = sinon.stub(element, 'diffs', {
get() {
return [{toggleLeftDiff: toggleLeftDiffStub}];
},
});
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce);
diffsStub.restore();
});
test('keyboard shortcuts', () => {
flushAsynchronousOperations();
const items = Polymer.dom(element.root).querySelectorAll('.file-row');
element.$.fileCursor.stops = items;
element.$.fileCursor.setCursorAtIndex(0);
assert.equal(items.length, 3);
assert.isTrue(items[0].classList.contains('selected'));
assert.isFalse(items[1].classList.contains('selected'));
assert.isFalse(items[2].classList.contains('selected'));
// j with a modifier should not move the cursor.
MockInteractions.pressAndReleaseKeyOn(element, 74, 'shift', 'j');
assert.equal(element.$.fileCursor.index, 0);
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.$.fileCursor.index, 1);
assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
const showStub = sandbox.stub(page, 'show');
assert.equal(element.$.fileCursor.index, 2);
assert.equal(element.selectedIndex, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
'Should navigate to /c/42/2/myfile.txt');
// k with a modifier should not move the cursor.
MockInteractions.pressAndReleaseKeyOn(element, 75, 'shift', 'k');
assert.equal(element.$.fileCursor.index, 2);
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.$.fileCursor.index, 1);
assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 79, null, 'o');
assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
'Should navigate to /c/42/2/file_added_in_rev2.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.$.fileCursor.index, 0);
assert.equal(element.selectedIndex, 0);
});
test('i key shows/hides selected inline diff', () => {
sandbox.stub(element, '_expandedPathsChanged');
flushAsynchronousOperations();
const files = Polymer.dom(element.root).querySelectorAll('.file-row');
element.$.fileCursor.stops = files;
element.$.fileCursor.setCursorAtIndex(0);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.include(element._expandedFilePaths, element.diffs[0].path);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.notInclude(element._expandedFilePaths, element.diffs[0].path);
element.$.fileCursor.setCursorAtIndex(1);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.include(element._expandedFilePaths, element.diffs[1].path);
MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (const index in element.diffs) {
if (!element.diffs.hasOwnProperty(index)) { continue; }
assert.include(element._expandedFilePaths, element.diffs[index].path);
}
MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (const index in element.diffs) {
if (!element.diffs.hasOwnProperty(index)) { continue; }
assert.notInclude(element._expandedFilePaths,
element.diffs[index].path);
}
});
suite('_handleEnterKey', () => {
let interact;
setup(() => {
sandbox.stub(element, 'shouldSuppressKeyboardShortcut')
.returns(false);
sandbox.stub(element, 'modifierPressed').returns(false);
const openCursorStub = sandbox.stub(element, '_openCursorFile');
const openSelectedStub = sandbox.stub(element, '_openSelectedFile');
const expandStub = sandbox.stub(element, '_togglePathExpanded');
interact = function(opt_payload) {
openCursorStub.reset();
openSelectedStub.reset();
expandStub.reset();
const e = new CustomEvent('fake-keyboard-event', opt_payload);
sinon.stub(e, 'preventDefault');
element._handleEnterKey(e);
assert.isTrue(e.preventDefault.called);
const result = {};
if (openCursorStub.called) {
result.opened_cursor = true;
}
if (openSelectedStub.called) {
result.opened_selected = true;
}
if (expandStub.called) {
result.expanded = true;
}
return result;
};
});
test('open from selected file', () => {
element._showInlineDiffs = false;
assert.deepEqual(interact(), {opened_selected: true});
});
test('open from diff cursor', () => {
element._showInlineDiffs = true;
assert.deepEqual(interact(), {opened_cursor: true});
// "Show diffs" mode overrides userPrefs.expand_inline_diffs
element._userPrefs = {expand_inline_diffs: true};
assert.deepEqual(interact(), {opened_cursor: true});
});
test('expand when user prefers', () => {
element._showInlineDiffs = false;
assert.deepEqual(interact(), {opened_selected: true});
element._userPrefs = {};
assert.deepEqual(interact(), {opened_selected: true});
element._userPrefs.expand_inline_diffs = true;
assert.deepEqual(interact(), {expanded: true});
});
test('noop when anchor focused', () => {
const e = new CustomEvent('fake-keyboard-event',
{detail: {keyboardEvent: {target: document.createElement('a')}}});
sinon.stub(e, 'preventDefault');
element._handleEnterKey(e);
assert.isFalse(e.preventDefault.called);
});
});
});
test('comment filtering', () => {
const comments = {
'/COMMIT_MSG': [
{patch_set: 1, message: 'Done', updated: '2017-02-08 16:40:49'},
{patch_set: 1, message: 'oh hay', updated: '2017-02-09 16:40:49'},
{patch_set: 2, message: 'hello', updated: '2017-02-10 16:40:49'},
],
'myfile.txt': [
{patch_set: 1, message: 'good news!', updated: '2017-02-08 16:40:49'},
{patch_set: 2, message: 'wat!?', updated: '2017-02-09 16:40:49'},
{patch_set: 2, message: 'hi', updated: '2017-02-10 16:40:49'},
],
'unresolved.file': [
{
patch_set: 2,
message: 'wat!?',
updated: '2017-02-09 16:40:49',
id: '1',
unresolved: true,
},
{
patch_set: 2,
message: 'hi',
updated: '2017-02-10 16:40:49',
id: '2',
in_reply_to: '1',
unresolved: false,
},
{
patch_set: 2,
message: 'good news!',
updated: '2017-02-08 16:40:49',
id: '3',
unresolved: true,
},
],
};
const drafts = {
'unresolved.file': [
{
patch_set: 2,
message: 'hi',
updated: '2017-02-11 16:40:49',
id: '4',
in_reply_to: '3',
unresolved: false,
},
],
};
assert.equal(
element._computeCountString(comments, '1', '/COMMIT_MSG', 'comment'),
'2 comments');
assert.equal(
element._computeCommentsStringMobile(comments, '1', '/COMMIT_MSG'),
'2c');
assert.equal(
element._computeDraftsStringMobile(comments, '1', '/COMMIT_MSG'),
'2d');
assert.equal(
element._computeCountString(comments, '1', 'myfile.txt', 'comment'),
'1 comment');
assert.equal(
element._computeCommentsStringMobile(comments, '1', 'myfile.txt'),
'1c');
assert.equal(
element._computeDraftsStringMobile(comments, '1', 'myfile.txt'),
'1d');
assert.equal(
element._computeCountString(comments, '1',
'file_added_in_rev2.txt', 'comment'), '');
assert.equal(
element._computeCommentsStringMobile(comments, '1',
'file_added_in_rev2.txt'), '');
assert.equal(
element._computeDraftsStringMobile(comments, '1',
'file_added_in_rev2.txt'), '');
assert.equal(
element._computeCountString(comments, '2', '/COMMIT_MSG', 'comment'),
'1 comment');
assert.equal(
element._computeCommentsStringMobile(comments, '2', '/COMMIT_MSG'),
'1c');
assert.equal(
element._computeDraftsStringMobile(comments, '2', '/COMMIT_MSG'),
'1d');
assert.equal(
element._computeCountString(comments, '2', 'myfile.txt', 'comment'),
'2 comments');
assert.equal(
element._computeCommentsStringMobile(comments, '2', 'myfile.txt'),
'2c');
assert.equal(
element._computeDraftsStringMobile(comments, '2', 'myfile.txt'),
'2d');
assert.equal(
element._computeCountString(comments, '2',
'file_added_in_rev2.txt', 'comment'), '');
assert.equal(element._computeCountString(comments, '2',
'unresolved.file', 'comment'), '3 comments');
assert.equal(
element._computeUnresolvedString(comments, [], 2, 'myfile.txt'), '');
assert.equal(
element.computeUnresolvedNum(comments, [], 2, 'myfile.txt'), 0);
assert.equal(
element._computeUnresolvedString(comments, [], 2, 'unresolved.file'),
'(1 unresolved)');
assert.equal(
element.computeUnresolvedNum(comments, [], 2, 'unresolved.file'), 1);
assert.equal(
element._computeUnresolvedString(comments, drafts, 2,
'unresolved.file'), '');
});
test('computed properties', () => {
assert.equal(element._computeFileStatus('A'), 'A');
assert.equal(element._computeFileStatus(undefined), 'M');
assert.equal(element._computeFileStatus(null), 'M');
assert.equal(element._computeFileDisplayName('/foo/bar/baz'),
'/foo/bar/baz');
assert.equal(element._computeFileDisplayName('/COMMIT_MSG'),
'Commit message');
assert.equal(element._computeClass('clazz', '/foo/bar/baz'), 'clazz');
assert.equal(element._computeClass('clazz', '/COMMIT_MSG'),
'clazz invisible');
assert.equal(element._computeExpandInlineClass(
{expand_inline_diffs: true}), 'expandInline');
assert.equal(element._computeExpandInlineClass(
{expand_inline_diffs: false}), '');
});
test('file review status', () => {
element._files = [
{__path: '/COMMIT_MSG'},
{__path: 'file_added_in_rev2.txt'},
{__path: 'myfile.txt'},
];
element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
element._loggedIn = true;
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
const fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
const commitMsg = fileRows[0].querySelector(
'input.reviewed[type="checkbox"]');
const fileAdded = fileRows[1].querySelector(
'input.reviewed[type="checkbox"]');
const myFile = fileRows[2].querySelector(
'input.reviewed[type="checkbox"]');
assert.isTrue(commitMsg.checked);
assert.isFalse(fileAdded.checked);
assert.isTrue(myFile.checked);
MockInteractions.tap(commitMsg);
assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', false));
MockInteractions.tap(commitMsg);
assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', true));
});
test('patch set from revisions', () => {
const expected = [
{num: 1, desc: 'test'},
{num: 2, desc: 'test'},
{num: 3, desc: 'test'},
{num: 4, desc: 'test'},
];
const patchNums = element.computeAllPatchSets({
revisions: {
rev3: {_number: 3, description: 'test'},
rev1: {_number: 1, description: 'test'},
rev4: {_number: 4, description: 'test'},
rev2: {_number: 2, description: 'test'},
},
});
assert.equal(patchNums.length, expected.length);
for (let i = 0; i < expected.length; i++) {
assert.deepEqual(patchNums[i], expected[i]);
}
});
test('patch range string', () => {
assert.equal(
element._patchRangeStr({basePatchNum: 'PARENT', patchNum: '1'}),
'1');
assert.equal(
element._patchRangeStr({basePatchNum: '1', patchNum: '3'}),
'1..3');
});
test('diff against dropdown', done => {
const showStub = sandbox.stub(page, 'show');
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '3',
};
element.change = {
revisions: {
rev1: {_number: 1},
rev2: {_number: 2},
rev3: {_number: 3},
},
};
flush(() => {
const selectEl = element.$.patchChange;
assert.equal(selectEl.value, 'PARENT');
assert.isTrue(element.$$('option[value="3"]').hasAttribute('disabled'));
selectEl.addEventListener('change', () => {
assert.equal(selectEl.value, '2');
assert(showStub.lastCall.calledWithExactly('/c/42/2..3'),
'Should navigate to /c/42/2..3');
showStub.restore();
done();
});
selectEl.value = '2';
element.fire('change', {}, {node: selectEl});
});
});
test('checkbox shows/hides diff inline', () => {
element._files = [
{__path: 'myfile.txt'},
];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
element.$.fileCursor.setCursorAtIndex(0);
sandbox.stub(element, '_expandedPathsChanged');
flushAsynchronousOperations();
const fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
// Because the label surrounds the input, the tap event is triggered
// there first.
const showHideLabel = fileRows[0].querySelector('label.show-hide');
const showHideCheck = fileRows[0].querySelector(
'input.show-hide[type="checkbox"]');
assert.isNotOk(showHideCheck.checked);
MockInteractions.tap(showHideLabel);
assert.isOk(showHideCheck.checked);
assert.notEqual(element._expandedFilePaths.indexOf('myfile.txt'), -1);
});
test('path should be properly escaped', () => {
element._files = [
{__path: 'foo bar/my+file.txt%'},
];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
flushAsynchronousOperations();
// Slashes should be preserved, and spaces should be translated to `+`.
// @see Issue 4255 regarding double-encoding.
// @see Issue 4577 regarding more readable URLs.
assert.equal(
element.$$('a').getAttribute('href'),
'/c/42/2/foo+bar/my%252Bfile.txt%2525');
});
test('diff mode correctly toggles the diffs', () => {
element._files = [
{__path: 'myfile.txt'},
];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
sandbox.spy(element, '_updateDiffPreferences');
element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
// Tap on a file to generate the diff.
const row = Polymer.dom(element.root)
.querySelectorAll('.row:not(.header) label.show-hide')[0];
MockInteractions.tap(row);
flushAsynchronousOperations();
const diffDisplay = element.diffs[0];
element._userPrefs = {default_diff_view: 'SIDE_BY_SIDE'};
assert.equal(element.diffViewMode, 'SIDE_BY_SIDE');
assert.equal(diffDisplay.viewMode, 'SIDE_BY_SIDE');
element.set('diffViewMode', 'UNIFIED_DIFF');
assert.equal(diffDisplay.viewMode, 'UNIFIED_DIFF');
assert.isTrue(element._updateDiffPreferences.called);
});
test('diff mode selector initializes from preferences', () => {
let resolvePrefs;
const prefsPromise = new Promise(resolve => {
resolvePrefs = resolve;
});
sandbox.stub(element, '_getPreferences').returns(prefsPromise);
// Attach a new gr-file-list so we can intercept the preferences fetch.
const view = document.createElement('gr-file-list');
const select = view.$.modeSelect;
fixture('blank').appendChild(view);
flushAsynchronousOperations();
// At this point the diff mode doesn't yet have the user's preference.
assert.equal(select.value, 'SIDE_BY_SIDE');
// Receive the overriding preference.
resolvePrefs({default_diff_view: 'UNIFIED'});
flushAsynchronousOperations();
assert.equal(select.value, 'SIDE_BY_SIDE');
document.getElementById('blank').restore();
});
test('show/hide diffs disabled for large amounts of files', done => {
const computeSpy = sandbox.spy(element, '_fileListActionsVisible');
element._files = [];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
element.$.fileCursor.setCursorAtIndex(0);
flush(() => {
assert.isTrue(computeSpy.lastCall.returnValue);
const arr = [];
_.times(element._maxFilesForBulkActions + 1, () => {
arr.push({__path: 'myfile.txt'});
});
element._files = arr;
element.numFilesShown = arr.length;
assert.isFalse(computeSpy.lastCall.returnValue);
done();
});
});
test('expanded attribute not set on path when not expanded', () => {
element._files = [
{__path: '/COMMIT_MSG'},
];
assert.isNotOk(element.$$('.expanded'));
});
test('_getDiffViewMode', () => {
// No user prefs or diff view mode set.
assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
// User prefs but no diff view mode set.
element.diffViewMode = null;
element._userPrefs = {default_diff_view: 'UNIFIED_DIFF'};
assert.equal(
element._getDiffViewMode(null, element._userPrefs), 'UNIFIED_DIFF');
// User prefs and diff view mode set.
element.diffViewMode = 'SIDE_BY_SIDE';
assert.equal(element._getDiffViewMode(
element.diffViewMode, element._userPrefs), 'SIDE_BY_SIDE');
});
test('expand_inline_diffs user preference', () => {
element._files = [
{__path: '/COMMIT_MSG'},
];
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
sandbox.stub(element, '_expandedPathsChanged');
flushAsynchronousOperations();
const commitMsgFile = Polymer.dom(element.root)
.querySelectorAll('.row:not(.header) a')[0];
// Remove href attribute so the app doesn't route to a diff view
commitMsgFile.removeAttribute('href');
const togglePathSpy = sandbox.spy(element, '_togglePathExpanded');
MockInteractions.tap(commitMsgFile);
flushAsynchronousOperations();
assert(togglePathSpy.notCalled, 'file is opened as diff view');
assert.isNotOk(element.$$('.expanded'));
element._userPrefs = {expand_inline_diffs: true};
flushAsynchronousOperations();
MockInteractions.tap(commitMsgFile);
flushAsynchronousOperations();
assert(togglePathSpy.calledOnce, 'file is expanded');
assert.isOk(element.$$('.expanded'));
});
test('_togglePathExpanded', () => {
const path = 'path/to/my/file.txt';
element.files = [{__path: path}];
const renderStub = sandbox.stub(element, '_renderInOrder')
.returns(Promise.resolve());
assert.equal(element._expandedFilePaths.length, 0);
element._togglePathExpanded(path);
flushAsynchronousOperations();
assert.equal(renderStub.callCount, 1);
assert.include(element._expandedFilePaths, path);
element._togglePathExpanded(path);
flushAsynchronousOperations();
assert.equal(renderStub.callCount, 2);
assert.notInclude(element._expandedFilePaths, path);
});
test('_expandedPathsChanged', done => {
sandbox.stub(element, '_reviewFile');
const path = 'path/to/my/file.txt';
const diffs = [{
path,
reload() {
done();
},
}];
sinon.stub(element, 'diffs', {
get() { return diffs; },
});
element.push('_expandedFilePaths', path);
});
suite('_handleFileListTap', () => {
function testForModifier(modifier) {
const e = {preventDefault() {}};
e.detail = {sourceEvent: {}};
e.target = {
dataset: {path: '/test'},
classList: element.classList,
};
e.detail.sourceEvent[modifier] = true;
const togglePathStub = sandbox.stub(element, '_togglePathExpanded');
element._userPrefs = {expand_inline_diffs: true};
element._handleFileListTap(e);
assert.isFalse(togglePathStub.called);
e.detail.sourceEvent[modifier] = false;
element._handleFileListTap(e);
assert.equal(togglePathStub.callCount, 1);
element._userPrefs = {expand_inline_diffs: false};
element._handleFileListTap(e);
assert.equal(togglePathStub.callCount, 1);
}
test('_handleFileListTap meta', () => {
testForModifier('metaKey');
});
test('_handleFileListTap ctrl', () => {
testForModifier('ctrlKey');
});
});
test('_renderInOrder', done => {
const reviewStub = sandbox.stub(element, '_reviewFile');
let callCount = 0;
const diffs = [{
path: 'p0',
reload() {
assert.equal(callCount++, 2);
return Promise.resolve();
},
}, {
path: 'p1',
reload() {
assert.equal(callCount++, 1);
return Promise.resolve();
},
}, {
path: 'p2',
reload() {
assert.equal(callCount++, 0);
return Promise.resolve();
},
}];
element._renderInOrder(['p2', 'p1', 'p0'], diffs, 3)
.then(() => {
assert.isFalse(reviewStub.called);
done();
});
});
test('_renderInOrder logged in', done => {
element._isLoggedIn = true;
const reviewStub = sandbox.stub(element, '_reviewFile');
let callCount = 0;
const diffs = [{
path: 'p0',
reload() {
assert.equal(reviewStub.callCount, 2);
assert.equal(callCount++, 2);
return Promise.resolve();
},
}, {
path: 'p1',
reload() {
assert.equal(reviewStub.callCount, 1);
assert.equal(callCount++, 1);
return Promise.resolve();
},
}, {
path: 'p2',
reload() {
assert.equal(reviewStub.callCount, 0);
assert.equal(callCount++, 0);
return Promise.resolve();
},
}];
element._renderInOrder(['p2', 'p1', 'p0'], diffs, 3)
.then(() => {
assert.equal(reviewStub.callCount, 3);
done();
});
});
});
suite('gr-file-list inline diff tests', () => {
let element;
let sandbox;
const setupDiff = function(diff) {
const mock = document.createElement('mock-diff-response');
diff._diff = mock.diffResponse;
diff._comments = {
left: [],
right: [],
};
diff.prefs = {
context: 10,
tab_size: 8,
font_size: 12,
line_length: 100,
cursor_blink_rate: 0,
line_wrapping: false,
intraline_difference: true,
show_line_endings: true,
show_tabs: true,
show_whitespace_errors: true,
syntax_highlighting: true,
auto_hide_diff_table_header: true,
theme: 'DEFAULT',
ignore_whitespace: 'IGNORE_NONE',
};
diff._renderDiffTable();
};
const renderAndGetNewDiffs = function(index) {
const diffs =
Polymer.dom(element.root).querySelectorAll('gr-diff');
for (let i = index; i < diffs.length; i++) {
setupDiff(diffs[i]);
}
element._updateDiffCursor();
element.$.diffCursor.handleDiffUpdate();
return diffs;
};
setup(() => {
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn() { return Promise.resolve(true); },
getPreferences() { return Promise.resolve({}); },
});
stub('gr-date-formatter', {
_loadTimeFormat() { return Promise.resolve(''); },
});
stub('gr-diff', {
reload() { return Promise.resolve(); },
});
element = fixture('basic');
element.numFilesShown = 75;
element.selectedIndex = 0;
element._files = [
{__path: '/COMMIT_MSG', lines_inserted: 9},
{
__path: 'file_added_in_rev2.txt',
lines_inserted: 1,
lines_deleted: 1,
size_delta: 10,
size: 100,
},
{
__path: 'myfile.txt',
lines_inserted: 1,
lines_deleted: 1,
size_delta: 10,
size: 100,
},
];
element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
element._loggedIn = true;
element.changeNum = '42';
element.patchRange = {
basePatchNum: 'PARENT',
patchNum: '2',
};
sandbox.stub(window, 'fetch', () => {
return Promise.resolve();
});
flushAsynchronousOperations();
});
teardown(() => {
sandbox.restore();
});
test('cursor with individually opened files', () => {
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
let diffs = renderAndGetNewDiffs(0);
const diffStops = diffs[0].getCursorStops();
// 1 diff should be rendered.
assert.equal(diffs.length, 1);
// No line number is selected.
assert.isFalse(diffStops[10].classList.contains('target-row'));
// Tapping content on a line selects the line number.
MockInteractions.tap(Polymer.dom(
diffStops[10]).querySelectorAll('.contentText')[0]);
flushAsynchronousOperations();
assert.isTrue(diffStops[10].classList.contains('target-row'));
// Keyboard shortcuts are still moving the file cursor, not the diff
// cursor.
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
flushAsynchronousOperations();
assert.isTrue(diffStops[10].classList.contains('target-row'));
assert.isFalse(diffStops[11].classList.contains('target-row'));
// The file cusor is now at 1.
assert.equal(element.$.fileCursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
diffs = renderAndGetNewDiffs(1);
// Two diffs should be rendered.
assert.equal(diffs.length, 2);
const diffStopsFirst = diffs[0].getCursorStops();
const diffStopsSecond = diffs[1].getCursorStops();
// The line on the first diff is stil selected
assert.isTrue(diffStopsFirst[10].classList.contains('target-row'));
assert.isFalse(diffStopsSecond[10].classList.contains('target-row'));
});
test('cursor with toggle all files', () => {
MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
const diffs = renderAndGetNewDiffs(0);
const diffStops = diffs[0].getCursorStops();
// 1 diff should be rendered.
assert.equal(diffs.length, 3);
// No line number is selected.
assert.isFalse(diffStops[10].classList.contains('target-row'));
// Tapping content on a line selects the line number.
MockInteractions.tap(Polymer.dom(
diffStops[10]).querySelectorAll('.contentText')[0]);
flushAsynchronousOperations();
assert.isTrue(diffStops[10].classList.contains('target-row'));
// Keyboard shortcuts are still moving the file cursor, not the diff
// cursor.
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
flushAsynchronousOperations();
assert.isFalse(diffStops[10].classList.contains('target-row'));
assert.isTrue(diffStops[11].classList.contains('target-row'));
// The file cusor is still at 0.
assert.equal(element.$.fileCursor.index, 0);
});
suite('n key presses', () => {
let nKeySpy;
let nextCommentStub;
let nextChunkStub;
let fileRows;
setup(() => {
nKeySpy = sandbox.spy(element, '_handleNKey');
nextCommentStub = sandbox.stub(element.$.diffCursor,
'moveToNextCommentThread');
nextChunkStub = sandbox.stub(element.$.diffCursor,
'moveToNextChunk');
fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
});
test('n key with all files expanded and no shift key', () => {
MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i');
flushAsynchronousOperations();
// Handle N key should return before calling diff cursor functions.
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
assert.isTrue(nKeySpy.called);
assert.isFalse(nextCommentStub.called);
// This is also called in diffCursor.moveToFirstChunk.
assert.equal(nextChunkStub.callCount, 1);
assert.isFalse(!!element._showInlineDiffs);
});
test('n key with all files expanded and shift key', () => {
MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i');
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
assert.isTrue(nKeySpy.called);
assert.isFalse(nextCommentStub.called);
// This is also called in diffCursor.moveToFirstChunk.
assert.equal(nextChunkStub.callCount, 1);
assert.isFalse(!!element._showInlineDiffs);
});
test('n key without all files expanded and shift key', () => {
MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i');
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
assert.isTrue(nKeySpy.called);
assert.isFalse(nextCommentStub.called);
// This is also called in diffCursor.moveToFirstChunk.
assert.equal(nextChunkStub.callCount, 2);
assert.isTrue(element._showInlineDiffs);
});
test('n key without all files expanded and no shift key', () => {
MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i');
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
assert.isTrue(nKeySpy.called);
assert.isTrue(nextCommentStub.called);
// This is also called in diffCursor.moveToFirstChunk.
assert.equal(nextChunkStub.callCount, 1);
assert.isTrue(element._showInlineDiffs);
});
});
test('_openSelectedFile behavior', () => {
const _files = element._files;
element.set('_files', []);
const showStub = sandbox.stub(page, 'show');
// Noop when there are no files.
element._openSelectedFile();
assert.isFalse(showStub.called);
element.set('_files', _files);
flushAsynchronousOperations();
// Navigates when a file is selected.
element._openSelectedFile();
assert.isTrue(showStub.called);
});
});
</script>