| <!DOCTYPE html> |
| <!-- |
| @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. |
| --> |
| |
| <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-lite.min.js"></script> |
| <script src="../../../bower_components/web-component-tester/browser.js"></script> |
| <link rel="import" href="../../../test/common-test-setup.html"/> |
| <script src="../../../bower_components/page/page.js"></script> |
| <script src="../../../scripts/util.js"></script> |
| |
| <link rel="import" href="gr-diff-view.html"> |
| |
| <script>void(0);</script> |
| |
| <test-fixture id="basic"> |
| <template> |
| <gr-diff-view></gr-diff-view> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="blank"> |
| <template> |
| <div></div> |
| </template> |
| </test-fixture> |
| |
| <script> |
| suite('gr-diff-view tests', () => { |
| const kb = window.Gerrit.KeyboardShortcutBinder; |
| kb.bindShortcut(kb.Shortcut.LEFT_PANE, 'shift+left'); |
| kb.bindShortcut(kb.Shortcut.RIGHT_PANE, 'shift+right'); |
| kb.bindShortcut(kb.Shortcut.NEXT_LINE, 'j', 'down'); |
| kb.bindShortcut(kb.Shortcut.PREV_LINE, 'k', 'up'); |
| kb.bindShortcut(kb.Shortcut.NEXT_FILE_WITH_COMMENTS, 'shift+j'); |
| kb.bindShortcut(kb.Shortcut.PREV_FILE_WITH_COMMENTS, 'shift+k'); |
| kb.bindShortcut(kb.Shortcut.NEW_COMMENT, 'c'); |
| kb.bindShortcut(kb.Shortcut.SAVE_COMMENT, 'ctrl+s'); |
| kb.bindShortcut(kb.Shortcut.NEXT_FILE, ']'); |
| kb.bindShortcut(kb.Shortcut.PREV_FILE, '['); |
| kb.bindShortcut(kb.Shortcut.NEXT_CHUNK, 'n'); |
| kb.bindShortcut(kb.Shortcut.NEXT_COMMENT_THREAD, 'shift+n'); |
| kb.bindShortcut(kb.Shortcut.PREV_CHUNK, 'p'); |
| kb.bindShortcut(kb.Shortcut.PREV_COMMENT_THREAD, 'shift+p'); |
| kb.bindShortcut(kb.Shortcut.OPEN_REPLY_DIALOG, 'a'); |
| kb.bindShortcut(kb.Shortcut.TOGGLE_LEFT_PANE, 'shift+a'); |
| kb.bindShortcut(kb.Shortcut.UP_TO_CHANGE, 'u'); |
| kb.bindShortcut(kb.Shortcut.OPEN_DIFF_PREFS, ','); |
| kb.bindShortcut(kb.Shortcut.TOGGLE_DIFF_MODE, 'm'); |
| kb.bindShortcut(kb.Shortcut.TOGGLE_FILE_REVIEWED, 'r'); |
| kb.bindShortcut(kb.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x'); |
| kb.bindShortcut(kb.Shortcut.EXPAND_ALL_COMMENT_THREADS, 'e'); |
| kb.bindShortcut(kb.Shortcut.COLLAPSE_ALL_COMMENT_THREADS, 'shift+e'); |
| kb.bindShortcut(kb.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m'); |
| |
| let element; |
| let sandbox; |
| |
| const PARENT = 'PARENT'; |
| |
| setup(() => { |
| sandbox = sinon.sandbox.create(); |
| |
| stub('gr-rest-api-interface', { |
| getConfig() { return Promise.resolve({change: {}}); }, |
| getLoggedIn() { return Promise.resolve(false); }, |
| getProjectConfig() { return Promise.resolve({}); }, |
| getDiffChangeDetail() { return Promise.resolve({}); }, |
| getChangeFiles() { return Promise.resolve({}); }, |
| saveFileReviewed() { return Promise.resolve(); }, |
| getDiffComments() { return Promise.resolve({}); }, |
| getDiffRobotComments() { return Promise.resolve({}); }, |
| getDiffDrafts() { return Promise.resolve({}); }, |
| getReviewedFiles() { return Promise.resolve([]); }, |
| }); |
| element = fixture('basic'); |
| return element._loadComments(); |
| }); |
| |
| teardown(() => { |
| sandbox.restore(); |
| }); |
| |
| test('params change triggers diffViewDisplayed()', () => { |
| sandbox.stub(element.$.reporting, 'diffViewDisplayed'); |
| sandbox.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sandbox.spy(element, '_paramsChanged'); |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '2', |
| basePatchNum: '1', |
| path: '/COMMIT_MSG', |
| }; |
| |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isTrue(element.$.reporting.diffViewDisplayed.calledOnce); |
| }); |
| }); |
| |
| test('toggle left diff with a hotkey', () => { |
| const toggleLeftDiffStub = sandbox.stub( |
| element.$.diffHost, 'toggleLeftDiff'); |
| MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a'); |
| assert.isTrue(toggleLeftDiffStub.calledOnce); |
| }); |
| |
| test('keyboard shortcuts', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: '10', |
| }; |
| element._change = { |
| _number: 42, |
| revisions: { |
| a: {_number: 10, commit: {parents: []}}, |
| }, |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| element.changeViewState.selectedFileIndex = 1; |
| element._loggedIn = true; |
| |
| const diffNavStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); |
| const changeNavStub = sandbox.stub(Gerrit.Nav, 'navigateToChange'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u'); |
| assert(changeNavStub.lastCall.calledWith(element._change), |
| 'Should navigate to /c/42/'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']'); |
| assert(diffNavStub.lastCall.calledWith(element._change, 'wheatley.md', |
| '10', PARENT), 'Should navigate to /c/42/10/wheatley.md'); |
| element._path = 'wheatley.md'; |
| assert.equal(element.changeViewState.selectedFileIndex, 2); |
| assert.isTrue(element._loading); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(diffNavStub.lastCall.calledWith(element._change, 'glados.txt', |
| '10', PARENT), 'Should navigate to /c/42/10/glados.txt'); |
| element._path = 'glados.txt'; |
| assert.equal(element.changeViewState.selectedFileIndex, 1); |
| assert.isTrue(element._loading); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(diffNavStub.lastCall.calledWith(element._change, 'chell.go', '10', |
| PARENT), 'Should navigate to /c/42/10/chell.go'); |
| element._path = 'chell.go'; |
| assert.equal(element.changeViewState.selectedFileIndex, 0); |
| assert.isTrue(element._loading); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(changeNavStub.lastCall.calledWith(element._change), |
| 'Should navigate to /c/42/'); |
| assert.equal(element.changeViewState.selectedFileIndex, 0); |
| assert.isTrue(element._loading); |
| |
| const showPrefsStub = |
| sandbox.stub(element.$.diffPreferences.$.prefsOverlay, 'open', |
| () => Promise.resolve()); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 188, null, ','); |
| assert(showPrefsStub.calledOnce); |
| |
| element.disableDiffPrefs = true; |
| MockInteractions.pressAndReleaseKeyOn(element, 188, null, ','); |
| assert(showPrefsStub.calledOnce); |
| |
| let scrollStub = sandbox.stub(element.$.cursor, 'moveToNextChunk'); |
| MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sandbox.stub(element.$.cursor, 'moveToPreviousChunk'); |
| MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sandbox.stub(element.$.cursor, 'moveToNextCommentThread'); |
| MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sandbox.stub(element.$.cursor, |
| 'moveToPreviousCommentThread'); |
| MockInteractions.pressAndReleaseKeyOn(element, 80, 'shift', 'p'); |
| assert(scrollStub.calledOnce); |
| |
| const computeContainerClassStub = sandbox.stub(element.$.diffHost.$.diff, |
| '_computeContainerClass'); |
| MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j'); |
| assert(computeContainerClassStub.lastCall.calledWithExactly( |
| false, 'SIDE_BY_SIDE', true)); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'esc'); |
| assert(computeContainerClassStub.lastCall.calledWithExactly( |
| false, 'SIDE_BY_SIDE', false)); |
| |
| sandbox.stub(element, '_setReviewed'); |
| element.$.reviewed.checked = false; |
| MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift', 'r'); |
| assert.isFalse(element._setReviewed.called); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); |
| assert.isTrue(element._setReviewed.called); |
| assert.equal(element._setReviewed.lastCall.args[0], true); |
| }); |
| |
| test('shift+x shortcut expands all diff context', () => { |
| const expandStub = sandbox.stub(element.$.diffHost, 'expandAllContext'); |
| MockInteractions.pressAndReleaseKeyOn(element, 88, 'shift', 'x'); |
| flushAsynchronousOperations(); |
| assert.isTrue(expandStub.called); |
| }); |
| |
| test('keyboard shortcuts with patch range', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: '5', |
| patchNum: '10', |
| }; |
| element._change = { |
| _number: 42, |
| revisions: { |
| a: {_number: 10, commit: {parents: []}}, |
| b: {_number: 5, commit: {parents: []}}, |
| }, |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| |
| const diffNavStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); |
| const changeNavStub = sandbox.stub(Gerrit.Nav, 'navigateToChange'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a'); |
| assert.isTrue(changeNavStub.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, null, 'a'); |
| assert.isTrue(element.changeViewState.showReplyDialog); |
| |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '10', |
| '5'), 'Should navigate to /c/42/5..10'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u'); |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '10', |
| '5'), 'Should navigate to /c/42/5..10'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']'); |
| assert.isTrue(element._loading); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, |
| 'wheatley.md', '10', '5'), |
| 'Should navigate to /c/42/5..10/wheatley.md'); |
| element._path = 'wheatley.md'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert.isTrue(element._loading); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, |
| 'glados.txt', '10', '5'), |
| 'Should navigate to /c/42/5..10/glados.txt'); |
| element._path = 'glados.txt'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert.isTrue(element._loading); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, 'chell.go', |
| '10', '5'), |
| 'Should navigate to /c/42/5..10/chell.go'); |
| element._path = 'chell.go'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert.isTrue(element._loading); |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '10', |
| '5'), |
| 'Should navigate to /c/42/5..10'); |
| }); |
| |
| test('keyboard shortcuts with old patch number', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: '1', |
| }; |
| element._change = { |
| _number: 42, |
| revisions: { |
| a: {_number: 1, commit: {parents: []}}, |
| b: {_number: 2, commit: {parents: []}}, |
| }, |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| |
| const diffNavStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); |
| const changeNavStub = sandbox.stub(Gerrit.Nav, 'navigateToChange'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a'); |
| assert.isTrue(changeNavStub.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, null, 'a'); |
| assert.isTrue(element.changeViewState.showReplyDialog); |
| |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '1', |
| PARENT), 'Should navigate to /c/42/1'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u'); |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '1', |
| PARENT), 'Should navigate to /c/42/1'); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']'); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, |
| 'wheatley.md', '1', PARENT), |
| 'Should navigate to /c/42/1/wheatley.md'); |
| element._path = 'wheatley.md'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, |
| 'glados.txt', '1', PARENT), |
| 'Should navigate to /c/42/1/glados.txt'); |
| element._path = 'glados.txt'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(diffNavStub.lastCall.calledWithExactly(element._change, 'chell.go', |
| '1', PARENT), 'Should navigate to /c/42/1/chell.go'); |
| element._path = 'chell.go'; |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, '1', |
| PARENT), 'Should navigate to /c/42/1'); |
| }); |
| |
| suite('diff prefs hidden', () => { |
| test('when no prefs or logged out', () => { |
| element.disableDiffPrefs = false; |
| element._loggedIn = false; |
| flushAsynchronousOperations(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = true; |
| flushAsynchronousOperations(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = false; |
| element._prefs = {font_size: '12'}; |
| flushAsynchronousOperations(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = true; |
| flushAsynchronousOperations(); |
| assert.isFalse(element.$.diffPrefsContainer.hidden); |
| }); |
| |
| test('when disableDiffPrefs is set', () => { |
| element._loggedIn = true; |
| element._prefs = {font_size: '12'}; |
| element.disableDiffPrefs = false; |
| flushAsynchronousOperations(); |
| |
| assert.isFalse(element.$.diffPrefsContainer.hidden); |
| element.disableDiffPrefs = true; |
| flushAsynchronousOperations(); |
| |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| }); |
| }); |
| |
| test('prefsButton opens gr-diff-preferences', () => { |
| const handlePrefsTapSpy = sandbox.spy(element, '_handlePrefsTap'); |
| const overlayOpenStub = sandbox.stub(element.$.diffPreferences, |
| 'open'); |
| const prefsButton = |
| Polymer.dom(element.root).querySelector('.prefsButton'); |
| |
| MockInteractions.tap(prefsButton); |
| |
| assert.isTrue(handlePrefsTapSpy.called); |
| assert.isTrue(overlayOpenStub.called); |
| }); |
| |
| test('_computeCommentString', done => { |
| loadCommentSpy = sandbox.spy(element.$.commentAPI, 'loadAll'); |
| const path = '/test'; |
| element.$.commentAPI.loadAll().then(comments => { |
| const commentCountStub = |
| sandbox.stub(comments, 'computeCommentCount'); |
| const unresolvedCountStub = |
| sandbox.stub(comments, 'computeUnresolvedNum'); |
| commentCountStub.withArgs(1, path).returns(0); |
| commentCountStub.withArgs(2, path).returns(1); |
| commentCountStub.withArgs(3, path).returns(2); |
| commentCountStub.withArgs(4, path).returns(0); |
| unresolvedCountStub.withArgs(1, path).returns(1); |
| unresolvedCountStub.withArgs(2, path).returns(0); |
| unresolvedCountStub.withArgs(3, path).returns(2); |
| unresolvedCountStub.withArgs(4, path).returns(0); |
| |
| assert.equal(element._computeCommentString(comments, 1, path), |
| '1 unresolved'); |
| assert.equal(element._computeCommentString(comments, 2, path), |
| '1 comment'); |
| assert.equal(element._computeCommentString(comments, 3, path), |
| '2 comments, 2 unresolved'); |
| assert.equal(element._computeCommentString(comments, 4, path), ''); |
| done(); |
| }); |
| }); |
| |
| suite('url params', () => { |
| setup(() => { |
| sandbox.stub(Gerrit.Nav, 'getUrlForDiff', (c, p, pn, bpn) => { |
| return `${c._number}-${p}-${pn}-${bpn}`; |
| }); |
| sandbox.stub(Gerrit.Nav, 'getUrlForChange', (c, pn, bpn) => { |
| return `${c._number}-${pn}-${bpn}`; |
| }); |
| }); |
| |
| test('_formattedFiles', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: '10', |
| }; |
| element._change = {_number: 42}; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md', |
| '/COMMIT_MSG', '/MERGE_LIST']; |
| element._path = 'glados.txt'; |
| const expectedFormattedFiles = [ |
| { |
| text: 'chell.go', |
| mobileText: 'chell.go', |
| value: 'chell.go', |
| bottomText: '', |
| }, { |
| text: 'glados.txt', |
| mobileText: 'glados.txt', |
| value: 'glados.txt', |
| bottomText: '', |
| }, { |
| text: 'wheatley.md', |
| mobileText: 'wheatley.md', |
| value: 'wheatley.md', |
| bottomText: '', |
| }, |
| { |
| text: 'Commit message', |
| mobileText: 'Commit message', |
| value: '/COMMIT_MSG', |
| bottomText: '', |
| }, |
| { |
| text: 'Merge list', |
| mobileText: 'Merge list', |
| value: '/MERGE_LIST', |
| bottomText: '', |
| }, |
| ]; |
| |
| assert.deepEqual(element._formattedFiles, expectedFormattedFiles); |
| assert.equal(element._formattedFiles[1].value, element._path); |
| }); |
| |
| test('prev/up/next links', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: '10', |
| }; |
| element._change = { |
| _number: 42, |
| revisions: { |
| a: {_number: 10, commit: {parents: []}}, |
| }, |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| flushAsynchronousOperations(); |
| const linkEls = Polymer.dom(element.root).querySelectorAll('.navLink'); |
| assert.equal(linkEls.length, 3); |
| assert.equal(linkEls[0].getAttribute('href'), '42-chell.go-10-PARENT'); |
| assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined'); |
| assert.equal(linkEls[2].getAttribute('href'), |
| '42-wheatley.md-10-PARENT'); |
| element._path = 'wheatley.md'; |
| flushAsynchronousOperations(); |
| assert.equal(linkEls[0].getAttribute('href'), |
| '42-glados.txt-10-PARENT'); |
| assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined'); |
| assert.isFalse(linkEls[2].hasAttribute('href')); |
| element._path = 'chell.go'; |
| flushAsynchronousOperations(); |
| assert.isFalse(linkEls[0].hasAttribute('href')); |
| assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined'); |
| assert.equal(linkEls[2].getAttribute('href'), |
| '42-glados.txt-10-PARENT'); |
| element._path = 'not_a_real_file'; |
| flushAsynchronousOperations(); |
| assert.equal(linkEls[0].getAttribute('href'), |
| '42-wheatley.md-10-PARENT'); |
| assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined'); |
| assert.equal(linkEls[2].getAttribute('href'), '42-chell.go-10-PARENT'); |
| }); |
| |
| test('prev/up/next links with patch range', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: '5', |
| patchNum: '10', |
| }; |
| element._change = { |
| _number: 42, |
| revisions: { |
| a: {_number: 5, commit: {parents: []}}, |
| b: {_number: 10, commit: {parents: []}}, |
| }, |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| flushAsynchronousOperations(); |
| const linkEls = Polymer.dom(element.root).querySelectorAll('.navLink'); |
| assert.equal(linkEls.length, 3); |
| assert.equal(linkEls[0].getAttribute('href'), '42-chell.go-10-5'); |
| assert.equal(linkEls[1].getAttribute('href'), '42-10-5'); |
| assert.equal(linkEls[2].getAttribute('href'), '42-wheatley.md-10-5'); |
| element._path = 'wheatley.md'; |
| flushAsynchronousOperations(); |
| assert.equal(linkEls[0].getAttribute('href'), '42-glados.txt-10-5'); |
| assert.equal(linkEls[1].getAttribute('href'), '42-10-5'); |
| assert.isFalse(linkEls[2].hasAttribute('href')); |
| element._path = 'chell.go'; |
| flushAsynchronousOperations(); |
| assert.isFalse(linkEls[0].hasAttribute('href')); |
| assert.equal(linkEls[1].getAttribute('href'), '42-10-5'); |
| assert.equal(linkEls[2].getAttribute('href'), '42-glados.txt-10-5'); |
| }); |
| }); |
| |
| test('_handlePatchChange calls navigateToDiff correctly', () => { |
| const navigateStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); |
| element._change = {_number: 321, project: 'foo/bar'}; |
| element._path = 'path/to/file.txt'; |
| |
| element._patchRange = { |
| basePatchNum: 'PARENT', |
| patchNum: '3', |
| }; |
| |
| const detail = { |
| basePatchNum: 'PARENT', |
| patchNum: '1', |
| }; |
| |
| element.$.rangeSelect.dispatchEvent( |
| new CustomEvent('patch-range-change', {detail, bubbles: false})); |
| |
| assert(navigateStub.lastCall.calledWithExactly(element._change, |
| element._path, '1', 'PARENT')); |
| }); |
| |
| test('download link', () => { |
| element._change = {project: 'test'}, |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: '10', |
| }; |
| element._fileList = ['chell.go', 'glados.txt', 'wheatley.md']; |
| element._path = 'glados.txt'; |
| flushAsynchronousOperations(); |
| const link = element.$$('.downloadLink'); |
| assert.equal(link.getAttribute('href'), |
| '/changes/test~42/revisions/10/patch?zip&path=glados.txt'); |
| assert.isTrue(link.hasAttribute('download')); |
| }); |
| |
| test('_prefs.manual_review is respected', () => { |
| const saveReviewedStub = sandbox.stub(element, '_saveReviewedState', |
| () => Promise.resolve()); |
| const getReviewedStub = sandbox.stub(element, '_getReviewedStatus', |
| () => Promise.resolve()); |
| |
| sandbox.stub(element.$.diffHost, 'reload'); |
| element._loggedIn = true; |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '2', |
| basePatchNum: '1', |
| path: '/COMMIT_MSG', |
| }; |
| element._prefs = {manual_review: true}; |
| flushAsynchronousOperations(); |
| |
| assert.isFalse(saveReviewedStub.called); |
| assert.isTrue(getReviewedStub.called); |
| |
| element._prefs = {}; |
| flushAsynchronousOperations(); |
| |
| assert.isTrue(saveReviewedStub.called); |
| assert.isTrue(getReviewedStub.calledOnce); |
| }); |
| |
| test('file review status', () => { |
| const saveReviewedStub = sandbox.stub(element, '_saveReviewedState', |
| () => Promise.resolve()); |
| sandbox.stub(element.$.diffHost, 'reload'); |
| |
| element._loggedIn = true; |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '2', |
| basePatchNum: '1', |
| path: '/COMMIT_MSG', |
| }; |
| element._prefs = {}; |
| flushAsynchronousOperations(); |
| |
| const 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)); |
| const callCount = saveReviewedStub.callCount; |
| |
| element.set('params.view', Gerrit.Nav.View.CHANGE); |
| flushAsynchronousOperations(); |
| |
| // saveReviewedState observer observes params, but should not fire when |
| // view !== Gerrit.Nav.View.DIFF. |
| assert.equal(saveReviewedStub.callCount, callCount); |
| }); |
| |
| test('file review status with edit loaded', () => { |
| const saveReviewedStub = sandbox.stub(element, '_saveReviewedState'); |
| |
| element._patchRange = {patchNum: element.EDIT_NAME}; |
| flushAsynchronousOperations(); |
| |
| assert.isTrue(element._editMode); |
| element._setReviewed(); |
| assert.isFalse(saveReviewedStub.called); |
| }); |
| |
| test('hash is determined from params', done => { |
| sandbox.stub(element.$.diffHost, 'reload'); |
| sandbox.stub(element, '_initCursor'); |
| |
| element._loggedIn = true; |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '2', |
| basePatchNum: '1', |
| path: '/COMMIT_MSG', |
| hash: 10, |
| }; |
| |
| flush(() => { |
| assert.isTrue(element._initCursor.calledOnce); |
| done(); |
| }); |
| }); |
| |
| test('diff mode selector correctly toggles the diff', () => { |
| const select = element.$.modeSelect; |
| const diffDisplay = element.$.diffHost; |
| element._userPrefs = {default_diff_view: 'SIDE_BY_SIDE'}; |
| |
| // The mode selected in the view state reflects the selected option. |
| assert.equal(element._getDiffViewMode(), select.mode); |
| |
| // The mode selected in the view state reflects the view rednered in the |
| // diff. |
| assert.equal(select.mode, diffDisplay.viewMode); |
| |
| // We will simulate a user change of the selected mode. |
| const newMode = 'UNIFIED_DIFF'; |
| |
| // Set the mode, and simulate the change event. |
| element.set('changeViewState.diffMode', newMode); |
| |
| // Make sure the handler was called and the state is still coherent. |
| assert.equal(element._getDiffViewMode(), newMode); |
| assert.equal(element._getDiffViewMode(), select.mode); |
| assert.equal(element._getDiffViewMode(), diffDisplay.viewMode); |
| }); |
| |
| test('diff mode selector initializes from preferences', () => { |
| let resolvePrefs; |
| const prefsPromise = new Promise(resolve => { |
| resolvePrefs = resolve; |
| }); |
| sandbox.stub(element.$.restAPI, 'getPreferences', () => prefsPromise); |
| |
| // Attach a new gr-diff-view so we can intercept the preferences fetch. |
| const view = document.createElement('gr-diff-view'); |
| fixture('blank').appendChild(view); |
| flushAsynchronousOperations(); |
| |
| // At this point the diff mode doesn't yet have the user's preference. |
| assert.equal(view._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| |
| // Receive the overriding preference. |
| resolvePrefs({default_diff_view: 'UNIFIED'}); |
| flushAsynchronousOperations(); |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| }); |
| |
| suite('_commitRange', () => { |
| setup(() => { |
| sandbox.stub(element.$.diffHost, 'reload'); |
| sandbox.stub(element, '_initCursor'); |
| sandbox.stub(element, '_getChangeDetail').returns(Promise.resolve({ |
| _number: 42, |
| revisions: { |
| 'commit-sha-1': { |
| _number: 1, |
| commit: { |
| parents: [{commit: 'sha-1-parent'}], |
| }, |
| }, |
| 'commit-sha-2': {_number: 2}, |
| 'commit-sha-3': {_number: 3}, |
| 'commit-sha-4': {_number: 4}, |
| 'commit-sha-5': { |
| _number: 5, |
| commit: { |
| parents: [{commit: 'sha-5-parent'}], |
| }, |
| }, |
| }, |
| })); |
| }); |
| |
| test('uses the patchNum and basePatchNum ', done => { |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '4', |
| basePatchNum: '2', |
| path: '/COMMIT_MSG', |
| }; |
| flush(() => { |
| assert.deepEqual(element._commitRange, { |
| baseCommit: 'commit-sha-2', |
| commit: 'commit-sha-4', |
| }); |
| done(); |
| }); |
| }); |
| |
| test('uses the parent when there is no base patch num ', done => { |
| element.params = { |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: '42', |
| patchNum: '5', |
| path: '/COMMIT_MSG', |
| }; |
| flush(() => { |
| assert.deepEqual(element._commitRange, { |
| commit: 'commit-sha-5', |
| baseCommit: 'sha-5-parent', |
| }); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('_initCursor', () => { |
| assert.isNotOk(element.$.cursor.initialLineNumber); |
| |
| // Does nothing when params specify no cursor address: |
| element._initCursor({}); |
| assert.isNotOk(element.$.cursor.initialLineNumber); |
| |
| // Does nothing when params specify side but no number: |
| element._initCursor({leftSide: true}); |
| assert.isNotOk(element.$.cursor.initialLineNumber); |
| |
| // Revision hash: specifies lineNum but not side. |
| element._initCursor({lineNum: 234}); |
| assert.equal(element.$.cursor.initialLineNumber, 234); |
| assert.equal(element.$.cursor.side, 'right'); |
| |
| // Base hash: specifies lineNum and side. |
| element._initCursor({leftSide: true, lineNum: 345}); |
| assert.equal(element.$.cursor.initialLineNumber, 345); |
| assert.equal(element.$.cursor.side, 'left'); |
| |
| // Specifies right side: |
| element._initCursor({leftSide: false, lineNum: 123}); |
| assert.equal(element.$.cursor.initialLineNumber, 123); |
| assert.equal(element.$.cursor.side, 'right'); |
| }); |
| |
| test('_getLineOfInterest', () => { |
| assert.isNull(element._getLineOfInterest({})); |
| |
| let result = element._getLineOfInterest({lineNum: 12}); |
| assert.equal(result.number, 12); |
| assert.isNotOk(result.leftSide); |
| |
| result = element._getLineOfInterest({lineNum: 12, leftSide: true}); |
| assert.equal(result.number, 12); |
| assert.isOk(result.leftSide); |
| }); |
| |
| test('_onLineSelected', () => { |
| const getUrlStub = sandbox.stub(Gerrit.Nav, 'getUrlForDiffById'); |
| const replaceStateStub = sandbox.stub(history, 'replaceState'); |
| const moveStub = sandbox.stub(element.$.cursor, 'moveToLineNumber'); |
| sandbox.stub(element.$.cursor, 'getAddress') |
| .returns({number: 123, isLeftSide: false}); |
| |
| element._changeNum = 321; |
| element._change = {_number: 321, project: 'foo/bar'}; |
| element._patchRange = { |
| basePatchNum: '3', |
| patchNum: '5', |
| }; |
| const e = {}; |
| const detail = {number: 123, side: 'right'}; |
| |
| element._onLineSelected(e, detail); |
| |
| assert.isTrue(moveStub.called); |
| assert.equal(moveStub.lastCall.args[0], detail.number); |
| assert.equal(moveStub.lastCall.args[1], detail.side); |
| |
| assert.isTrue(replaceStateStub.called); |
| assert.isTrue(getUrlStub.called); |
| }); |
| |
| test('_onLineSelected w/o line address', () => { |
| const getUrlStub = sandbox.stub(Gerrit.Nav, 'getUrlForDiffById'); |
| sandbox.stub(history, 'replaceState'); |
| sandbox.stub(element.$.cursor, 'moveToLineNumber'); |
| sandbox.stub(element.$.cursor, 'getAddress').returns(null); |
| element._changeNum = 321; |
| element._change = {_number: 321, project: 'foo/bar'}; |
| element._patchRange = {basePatchNum: '3', patchNum: '5'}; |
| element._onLineSelected({}, {number: 123, side: 'right'}); |
| assert.isTrue(getUrlStub.calledOnce); |
| assert.isUndefined(getUrlStub.lastCall.args[5]); |
| assert.isUndefined(getUrlStub.lastCall.args[6]); |
| }); |
| |
| test('_getDiffViewMode', () => { |
| // No user prefs or change view state set. |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| |
| // User prefs but no change view state set. |
| element._userPrefs = {default_diff_view: 'UNIFIED_DIFF'}; |
| assert.equal(element._getDiffViewMode(), 'UNIFIED_DIFF'); |
| |
| // User prefs and change view state set. |
| element.changeViewState = {diffMode: 'SIDE_BY_SIDE'}; |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| }); |
| |
| test('_handleToggleDiffMode', () => { |
| sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const e = {preventDefault: () => {}}; |
| // Initial state. |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| |
| element._handleToggleDiffMode(e); |
| assert.equal(element._getDiffViewMode(), 'UNIFIED_DIFF'); |
| |
| element._handleToggleDiffMode(e); |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| }); |
| |
| suite('_loadComments', () => { |
| test('empty', done => { |
| element._loadComments().then(() => { |
| assert.equal(Object.keys(element._commentMap).length, 0); |
| done(); |
| }); |
| }); |
| |
| test('has paths', done => { |
| sandbox.stub(element, '_getPaths').returns({ |
| 'path/to/file/one.cpp': [{patch_set: 3, message: 'lorem'}], |
| 'path-to/file/two.py': [{patch_set: 5, message: 'ipsum'}], |
| }); |
| sandbox.stub(element, '_getCommentsForPath').returns({meta: {}}); |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: '3', |
| patchNum: '5', |
| }; |
| element._loadComments().then(() => { |
| assert.deepEqual(Object.keys(element._commentMap), |
| ['path/to/file/one.cpp', 'path-to/file/two.py']); |
| done(); |
| }); |
| }); |
| }); |
| |
| suite('_computeCommentSkips', () => { |
| test('empty file list', () => { |
| const commentMap = { |
| 'path/one.jpg': true, |
| 'path/three.wav': true, |
| }; |
| const path = 'path/two.m4v'; |
| const fileList = []; |
| const result = element._computeCommentSkips(commentMap, fileList, path); |
| assert.isNull(result.previous); |
| assert.isNull(result.next); |
| }); |
| |
| test('finds skips', () => { |
| const fileList = ['path/one.jpg', 'path/two.m4v', 'path/three.wav']; |
| let path = fileList[1]; |
| const commentMap = {}; |
| commentMap[fileList[0]] = true; |
| commentMap[fileList[1]] = false; |
| commentMap[fileList[2]] = true; |
| |
| let result = element._computeCommentSkips(commentMap, fileList, path); |
| assert.equal(result.previous, fileList[0]); |
| assert.equal(result.next, fileList[2]); |
| |
| commentMap[fileList[1]] = true; |
| |
| result = element._computeCommentSkips(commentMap, fileList, path); |
| assert.equal(result.previous, fileList[0]); |
| assert.equal(result.next, fileList[2]); |
| |
| path = fileList[0]; |
| |
| result = element._computeCommentSkips(commentMap, fileList, path); |
| assert.isNull(result.previous); |
| assert.equal(result.next, fileList[1]); |
| |
| path = fileList[2]; |
| |
| result = element._computeCommentSkips(commentMap, fileList, path); |
| assert.equal(result.previous, fileList[1]); |
| assert.isNull(result.next); |
| }); |
| |
| suite('skip next/previous', () => { |
| let navToChangeStub; |
| let navToDiffStub; |
| |
| setup(() => { |
| navToChangeStub = sandbox.stub(element, '_navToChangeView'); |
| navToDiffStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); |
| element._fileList = [ |
| 'path/one.jpg', 'path/two.m4v', 'path/three.wav', |
| ]; |
| element._patchRange = {patchNum: '2', basePatchNum: '1'}; |
| }); |
| |
| suite('_moveToPreviousFileWithComment', () => { |
| test('no skips', () => { |
| element._moveToPreviousFileWithComment(); |
| assert.isFalse(navToChangeStub.called); |
| assert.isFalse(navToDiffStub.called); |
| }); |
| |
| test('no previous', () => { |
| const commentMap = {}; |
| commentMap[element._fileList[0]] = false; |
| commentMap[element._fileList[1]] = false; |
| commentMap[element._fileList[2]] = true; |
| element._commentMap = commentMap; |
| element._path = element._fileList[1]; |
| |
| element._moveToPreviousFileWithComment(); |
| assert.isTrue(navToChangeStub.calledOnce); |
| assert.isFalse(navToDiffStub.called); |
| }); |
| |
| test('w/ previous', () => { |
| const commentMap = {}; |
| commentMap[element._fileList[0]] = true; |
| commentMap[element._fileList[1]] = false; |
| commentMap[element._fileList[2]] = true; |
| element._commentMap = commentMap; |
| element._path = element._fileList[1]; |
| |
| element._moveToPreviousFileWithComment(); |
| assert.isFalse(navToChangeStub.called); |
| assert.isTrue(navToDiffStub.calledOnce); |
| }); |
| }); |
| |
| suite('_moveToNextFileWithComment', () => { |
| test('no skips', () => { |
| element._moveToNextFileWithComment(); |
| assert.isFalse(navToChangeStub.called); |
| assert.isFalse(navToDiffStub.called); |
| }); |
| |
| test('no previous', () => { |
| const commentMap = {}; |
| commentMap[element._fileList[0]] = true; |
| commentMap[element._fileList[1]] = false; |
| commentMap[element._fileList[2]] = false; |
| element._commentMap = commentMap; |
| element._path = element._fileList[1]; |
| |
| element._moveToNextFileWithComment(); |
| assert.isTrue(navToChangeStub.calledOnce); |
| assert.isFalse(navToDiffStub.called); |
| }); |
| |
| test('w/ previous', () => { |
| const commentMap = {}; |
| commentMap[element._fileList[0]] = true; |
| commentMap[element._fileList[1]] = false; |
| commentMap[element._fileList[2]] = true; |
| element._commentMap = commentMap; |
| element._path = element._fileList[1]; |
| |
| element._moveToNextFileWithComment(); |
| assert.isFalse(navToChangeStub.called); |
| assert.isTrue(navToDiffStub.calledOnce); |
| }); |
| }); |
| }); |
| }); |
| |
| test('_computeEditMode', () => { |
| const callCompute = range => element._computeEditMode({base: range}); |
| assert.isFalse(callCompute({})); |
| assert.isFalse(callCompute({basePatchNum: 'PARENT', patchNum: 1})); |
| assert.isFalse(callCompute({basePatchNum: 'edit', patchNum: 1})); |
| assert.isTrue(callCompute({basePatchNum: 1, patchNum: 'edit'})); |
| }); |
| |
| test('_computeFileNum', () => { |
| assert.equal(element._computeFileNum('/foo', |
| [{value: '/foo'}, {value: '/bar'}]), 1); |
| assert.equal(element._computeFileNum('/bar', |
| [{value: '/foo'}, {value: '/bar'}]), 2); |
| }); |
| |
| test('_computeFileNumClass', () => { |
| assert.equal(element._computeFileNumClass(0, []), ''); |
| assert.equal(element._computeFileNumClass(1, |
| [{value: '/foo'}, {value: '/bar'}]), 'show'); |
| }); |
| |
| test('_getReviewedStatus', () => { |
| const promises = []; |
| element.$.restAPI.getReviewedFiles.restore(); |
| |
| sandbox.stub(element.$.restAPI, 'getReviewedFiles') |
| .returns(Promise.resolve(['path'])); |
| |
| promises.push(element._getReviewedStatus(true, null, null, 'path') |
| .then(reviewed => assert.isFalse(reviewed))); |
| |
| promises.push(element._getReviewedStatus(false, null, null, 'otherPath') |
| .then(reviewed => assert.isFalse(reviewed))); |
| |
| promises.push(element._getReviewedStatus(false, null, null, 'path') |
| .then(reviewed => assert.isTrue(reviewed))); |
| |
| return Promise.all(promises); |
| }); |
| |
| suite('editMode behavior', () => { |
| setup(() => { |
| element._loggedIn = true; |
| }); |
| |
| const isVisible = el => { |
| assert.ok(el); |
| return getComputedStyle(el).getPropertyValue('display') !== 'none'; |
| }; |
| |
| test('reviewed checkbox', () => { |
| sandbox.stub(element, '_handlePatchChange'); |
| element._patchRange = {patchNum: '1'}; |
| // Reviewed checkbox should be shown. |
| assert.isTrue(isVisible(element.$.reviewed)); |
| element.set('_patchRange.patchNum', element.EDIT_NAME); |
| flushAsynchronousOperations(); |
| |
| assert.isFalse(isVisible(element.$.reviewed)); |
| }); |
| }); |
| |
| test('_paramsChanged sets in projectLookup', () => { |
| sandbox.stub(element, '_getLineOfInterest'); |
| sandbox.stub(element, '_initCursor'); |
| const setStub = sandbox.stub(element.$.restAPI, 'setInProjectLookup'); |
| element._paramsChanged({ |
| view: Gerrit.Nav.View.DIFF, |
| changeNum: 101, |
| project: 'test-project', |
| path: '', |
| }); |
| assert.isTrue(setStub.calledOnce); |
| assert.isTrue(setStub.calledWith(101, 'test-project')); |
| }); |
| |
| test('shift+m navigates to next unreviewed file', () => { |
| element._fileList = ['file1', 'file2', 'file3']; |
| element._reviewedFiles = new Set(['file1', 'file2']); |
| element._path = 'file1'; |
| const reviewedStub = sandbox.stub(element, '_setReviewed'); |
| const navStub = sandbox.stub(element, '_navToFile'); |
| MockInteractions.pressAndReleaseKeyOn(element, 77, 'shift', 'm'); |
| flushAsynchronousOperations(); |
| |
| assert.isTrue(reviewedStub.lastCall.args[0]); |
| assert.deepEqual(navStub.lastCall.args, [ |
| 'file1', |
| ['file1', 'file3'], |
| 1, |
| ]); |
| }); |
| }); |
| </script> |