
The gr-diff element was already capable of rendering diffs in either side-by-side or unified style, but was configured by default to use side-by-side. The GWT diff UI included an icon button toggle to switch between these two styles above and to the right of the diff near the "Diff View Preferences" button. This change introduces a selector in the PolyGerrit UI to allow similar switching behavior. Previously, the diff mode was encoded as a private property of the gr-diff element. Now that the switcher is introduced at the same level as the "Diff View Preferences" button (one level up from the gr-diff element) it is changed to be a public property of the element so that it can be changed through the parent view (in this case the gr-diff-view element). A dropdown is added to the gr-diff-view based in style on the gr-patch-range-select element (which plays a similar role). This dropdown has options for either of the two diff modes and controls the diff display through a property on the view. The view gets a new, computed property called _diffMode which encodes which diff state should be displayed based on the changeViewState and the user's preferences. The user's choice of diff view is persisted across diff views, but is reverted to the preference when the change view is visited. A test is added to the gr-diff-view element. Bug: Issue 3909 Change-Id: I00d93500f8d210394acc9c8f3398c9406ae74276
393 lines
15 KiB
HTML
393 lines
15 KiB
HTML
<!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-diff-view</title>
|
|
|
|
<script src="../../../bower_components/webcomponentsjs/webcomponents.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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
|
<link rel="import" href="gr-diff-view.html">
|
|
|
|
<test-fixture id="basic">
|
|
<template>
|
|
<gr-diff-view></gr-diff-view>
|
|
</template>
|
|
</test-fixture>
|
|
|
|
<script>
|
|
suite('gr-diff-view tests', function() {
|
|
var element;
|
|
|
|
setup(function() {
|
|
stub('gr-rest-api-interface', {
|
|
getLoggedIn: function() { return Promise.resolve(false); },
|
|
getProjectConfig: function() { return Promise.resolve({}); },
|
|
getDiffChangeDetail: function() { return Promise.resolve(null); },
|
|
getChangeFiles: function() { return Promise.resolve({}); },
|
|
saveFileReviewed: function() { return Promise.resolve(); },
|
|
});
|
|
element = fixture('basic');
|
|
});
|
|
|
|
test('keyboard shortcuts', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
patchNum: '10',
|
|
};
|
|
element._change = {
|
|
revisions: {
|
|
a: {_number: 10},
|
|
},
|
|
};
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
element.changeViewState.selectedFileIndex = 1;
|
|
|
|
var showStub = sinon.stub(page, 'show');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/'),
|
|
'Should navigate to /c/42/');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'),
|
|
'Should navigate to /c/42/10/wheatley.md');
|
|
element._path = 'wheatley.md';
|
|
assert.equal(element.changeViewState.selectedFileIndex, 2);
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'),
|
|
'Should navigate to /c/42/10/glados.txt');
|
|
element._path = 'glados.txt';
|
|
assert.equal(element.changeViewState.selectedFileIndex, 1);
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'),
|
|
'Should navigate to /c/42/10/chell.go');
|
|
element._path = 'chell.go';
|
|
assert.equal(element.changeViewState.selectedFileIndex, 0);
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/'),
|
|
'Should navigate to /c/42/');
|
|
assert.equal(element.changeViewState.selectedFileIndex, 0);
|
|
|
|
var showPrefsStub = sinon.stub(element.$.diff, 'showDiffPreferences');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 188); // ','
|
|
assert(showPrefsStub.calledOnce);
|
|
|
|
var scrollStub = sinon.stub(element.$.diff, 'scrollToNextDiffChunk');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 78); // 'n'
|
|
assert(scrollStub.calledOnce);
|
|
scrollStub.restore();
|
|
|
|
scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousDiffChunk');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 80); // 'p'
|
|
assert(scrollStub.calledOnce);
|
|
scrollStub.restore();
|
|
|
|
scrollStub = sinon.stub(element.$.diff, 'scrollToNextCommentThread');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']); // 'N'
|
|
assert(scrollStub.calledOnce);
|
|
scrollStub.restore();
|
|
|
|
scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousCommentThread');
|
|
MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']); // 'P'
|
|
assert(scrollStub.calledOnce);
|
|
scrollStub.restore();
|
|
|
|
showPrefsStub.restore();
|
|
showStub.restore();
|
|
});
|
|
|
|
test('keyboard shortcuts with patch range', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
basePatchNum: '5',
|
|
patchNum: '10',
|
|
};
|
|
element._change = {
|
|
revisions: {
|
|
a: {_number: 10},
|
|
},
|
|
};
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
|
|
var showStub = sinon.stub(page, 'show');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
|
|
'only work when the user is logged in.');
|
|
assert.isNull(window.sessionStorage.getItem(
|
|
'changeView.showReplyDialog'));
|
|
|
|
element._loggedIn = true;
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(element.changeViewState.showReplyDialog);
|
|
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/'),
|
|
'Should navigate to /c/42/');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/'),
|
|
'Should navigate to /c/42/');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'),
|
|
'Should navigate to /c/42/5..10/wheatley.md');
|
|
element._path = 'wheatley.md';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'),
|
|
'Should navigate to /c/42/5..10/glados.txt');
|
|
element._path = 'glados.txt';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'),
|
|
'Should navigate to /c/42/5..10/chell.go');
|
|
element._path = 'chell.go';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/'),
|
|
'Should navigate to /c/42/');
|
|
|
|
showStub.restore();
|
|
});
|
|
|
|
test('keyboard shortcuts with old patch number', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
patchNum: '1',
|
|
};
|
|
element._change = {
|
|
revisions: {
|
|
a: {_number: 1},
|
|
b: {_number: 2},
|
|
},
|
|
};
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
|
|
var showStub = sinon.stub(page, 'show');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
|
|
'only work when the user is logged in.');
|
|
assert.isNull(window.sessionStorage.getItem(
|
|
'changeView.showReplyDialog'));
|
|
|
|
element._loggedIn = true;
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(element.changeViewState.showReplyDialog);
|
|
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
|
|
'Should navigate to /c/42/1/wheatley.md');
|
|
element._path = 'wheatley.md';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
|
|
'Should navigate to /c/42/1/glados.txt');
|
|
element._path = 'glados.txt';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
|
|
'Should navigate to /c/42/1/chell.go');
|
|
element._path = 'chell.go';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
showStub.restore();
|
|
});
|
|
|
|
test('go up to change via kb without change loaded', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
patchNum: '1',
|
|
};
|
|
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
|
|
var showStub = sinon.stub(page, 'show');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
|
|
'only work when the user is logged in.');
|
|
assert.isNull(window.sessionStorage.getItem(
|
|
'changeView.showReplyDialog'));
|
|
|
|
element._loggedIn = true;
|
|
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
|
|
assert.isTrue(element.changeViewState.showReplyDialog);
|
|
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
|
|
'Should navigate to /c/42/1/wheatley.md');
|
|
element._path = 'wheatley.md';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
|
|
'Should navigate to /c/42/1/glados.txt');
|
|
element._path = 'glados.txt';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
|
|
'Should navigate to /c/42/1/chell.go');
|
|
element._path = 'chell.go';
|
|
|
|
MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
|
|
assert(showStub.lastCall.calledWithExactly('/c/42/1'),
|
|
'Should navigate to /c/42/1');
|
|
|
|
showStub.restore();
|
|
});
|
|
|
|
test('jump to file dropdown', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
patchNum: '10',
|
|
};
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
flushAsynchronousOperations();
|
|
var linkEls =
|
|
Polymer.dom(element.root).querySelectorAll('.dropdown-content > a');
|
|
assert.equal(linkEls.length, 3);
|
|
assert.isFalse(linkEls[0].hasAttribute('selected'));
|
|
assert.isTrue(linkEls[1].hasAttribute('selected'));
|
|
assert.isFalse(linkEls[2].hasAttribute('selected'));
|
|
assert.equal(linkEls[0].getAttribute('data-key-nav'), '[');
|
|
assert.equal(linkEls[1].getAttribute('data-key-nav'), '');
|
|
assert.equal(linkEls[2].getAttribute('data-key-nav'), ']');
|
|
assert.equal(linkEls[0].getAttribute('href'), '/c/42/10/chell.go');
|
|
assert.equal(linkEls[1].getAttribute('href'), '/c/42/10/glados.txt');
|
|
assert.equal(linkEls[2].getAttribute('href'), '/c/42/10/wheatley.md');
|
|
|
|
assert.equal(element._computeFileDisplayName('/foo/bar/baz'),
|
|
'/foo/bar/baz');
|
|
assert.equal(element._computeFileDisplayName('/COMMIT_MSG'),
|
|
'Commit message');
|
|
});
|
|
|
|
test('jump to file dropdown with patch range', function() {
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
basePatchNum: '5',
|
|
patchNum: '10',
|
|
};
|
|
element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
|
|
element._path = 'glados.txt';
|
|
flushAsynchronousOperations();
|
|
var linkEls =
|
|
Polymer.dom(element.root).querySelectorAll('.dropdown-content > a');
|
|
assert.equal(linkEls.length, 3);
|
|
assert.isFalse(linkEls[0].hasAttribute('selected'));
|
|
assert.isTrue(linkEls[1].hasAttribute('selected'));
|
|
assert.isFalse(linkEls[2].hasAttribute('selected'));
|
|
assert.equal(linkEls[0].getAttribute('data-key-nav'), '[');
|
|
assert.equal(linkEls[1].getAttribute('data-key-nav'), '');
|
|
assert.equal(linkEls[2].getAttribute('data-key-nav'), ']');
|
|
assert.equal(linkEls[0].getAttribute('href'), '/c/42/5..10/chell.go');
|
|
assert.equal(linkEls[1].getAttribute('href'), '/c/42/5..10/glados.txt');
|
|
assert.equal(linkEls[2].getAttribute('href'), '/c/42/5..10/wheatley.md');
|
|
});
|
|
|
|
test('file review status', function(done) {
|
|
element._loggedIn = true;
|
|
element._changeNum = '42';
|
|
element._patchRange = {
|
|
basePatchNum: '1',
|
|
patchNum: '2',
|
|
};
|
|
element._fileList = ['/COMMIT_MSG'];
|
|
element._path = '/COMMIT_MSG';
|
|
var saveReviewedStub = sinon.stub(element, '_saveReviewedState',
|
|
function() { return Promise.resolve(); });
|
|
|
|
flush(function() {
|
|
var commitMsg = Polymer.dom(element.root).querySelector(
|
|
'input[type="checkbox"]');
|
|
|
|
assert.isTrue(commitMsg.checked);
|
|
MockInteractions.tap(commitMsg);
|
|
assert.isFalse(commitMsg.checked);
|
|
assert.isTrue(saveReviewedStub.lastCall.calledWithExactly(false));
|
|
|
|
MockInteractions.tap(commitMsg);
|
|
assert.isTrue(commitMsg.checked);
|
|
assert.isTrue(saveReviewedStub.lastCall.calledWithExactly(true));
|
|
|
|
saveReviewedStub.restore();
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('diff mode selector correctly toggles the diff', function() {
|
|
var select = element.$.modeSelect;
|
|
var diffDisplay = element.$.diff;
|
|
|
|
element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
|
|
|
|
// The mode selected in the view state reflects the selected option.
|
|
assert.equal(element._getDiffViewMode(), select.value);
|
|
|
|
// The mode selected in the view state reflects the view rednered in the
|
|
// diff.
|
|
assert.equal(select.value, diffDisplay.viewMode);
|
|
|
|
// We will simulate a user change of the selected mode.
|
|
var newMode = 'UNIFIED_DIFF';
|
|
|
|
// Listen to the change handler.
|
|
var eventStub = sinon.spy(element, '_handleModeChange');
|
|
|
|
// Set the actual value of the select, and simulate the change event.
|
|
select.value = newMode;
|
|
element.fire('change', {}, { node: select });
|
|
|
|
// Make sure the handler was called and the state is still coherent.
|
|
assert.isTrue(eventStub.called);
|
|
assert.equal(element._getDiffViewMode(), newMode);
|
|
assert.equal(element._getDiffViewMode(), select.value);
|
|
assert.equal(element._getDiffViewMode(), diffDisplay.viewMode);
|
|
|
|
eventStub.restore();
|
|
});
|
|
});
|
|
</script>
|