| /** |
| * @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 './gr-diff-view.js'; |
| import {GerritNav} from '../../core/gr-navigation/gr-navigation.js'; |
| import {ChangeStatus} from '../../../constants/constants.js'; |
| import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js'; |
| import {SPECIAL_PATCH_SET_NUM} from '../../../utils/patch-set-util.js'; |
| import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js'; |
| import {_testOnly_findCommentById} from '../gr-comment-api/gr-comment-api.js'; |
| import {appContext} from '../../../services/app-context.js'; |
| import {GerritView} from '../../core/gr-navigation/gr-navigation.js'; |
| import { |
| createChange, |
| createRevisions, |
| } from '../../../test/test-data-generators.js'; |
| |
| const basicFixture = fixtureFromElement('gr-diff-view'); |
| |
| const blankFixture = fixtureFromElement('div'); |
| |
| suite('gr-diff-view tests', () => { |
| suite('basic tests', () => { |
| let element; |
| let clock; |
| |
| suiteSetup(() => { |
| const kb = TestKeyboardShortcutBinder.push(); |
| kb.bindShortcut(Shortcut.LEFT_PANE, 'shift+left'); |
| kb.bindShortcut(Shortcut.RIGHT_PANE, 'shift+right'); |
| kb.bindShortcut(Shortcut.NEXT_LINE, 'j', 'down'); |
| kb.bindShortcut(Shortcut.PREV_LINE, 'k', 'up'); |
| kb.bindShortcut(Shortcut.NEXT_FILE_WITH_COMMENTS, 'shift+j'); |
| kb.bindShortcut(Shortcut.PREV_FILE_WITH_COMMENTS, 'shift+k'); |
| kb.bindShortcut(Shortcut.NEW_COMMENT, 'c'); |
| kb.bindShortcut(Shortcut.SAVE_COMMENT, 'ctrl+s'); |
| kb.bindShortcut(Shortcut.NEXT_FILE, ']'); |
| kb.bindShortcut(Shortcut.PREV_FILE, '['); |
| kb.bindShortcut(Shortcut.NEXT_CHUNK, 'n'); |
| kb.bindShortcut(Shortcut.NEXT_COMMENT_THREAD, 'shift+n'); |
| kb.bindShortcut(Shortcut.PREV_CHUNK, 'p'); |
| kb.bindShortcut(Shortcut.PREV_COMMENT_THREAD, 'shift+p'); |
| kb.bindShortcut(Shortcut.OPEN_REPLY_DIALOG, 'a'); |
| kb.bindShortcut(Shortcut.OPEN_DOWNLOAD_DIALOG, 'd'); |
| kb.bindShortcut(Shortcut.TOGGLE_LEFT_PANE, 'shift+a'); |
| kb.bindShortcut(Shortcut.UP_TO_CHANGE, 'u'); |
| kb.bindShortcut(Shortcut.OPEN_DIFF_PREFS, ','); |
| kb.bindShortcut(Shortcut.TOGGLE_DIFF_MODE, 'm'); |
| kb.bindShortcut(Shortcut.TOGGLE_FILE_REVIEWED, 'r'); |
| kb.bindShortcut(Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x'); |
| kb.bindShortcut(Shortcut.EXPAND_ALL_COMMENT_THREADS, 'e'); |
| kb.bindShortcut(Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, 'h'); |
| kb.bindShortcut(Shortcut.COLLAPSE_ALL_COMMENT_THREADS, 'shift+e'); |
| kb.bindShortcut(Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m'); |
| kb.bindShortcut(Shortcut.TOGGLE_BLAME, 'b'); |
| kb.bindShortcut(Shortcut.OPEN_FILE_LIST, 'f'); |
| }); |
| |
| suiteTeardown(() => { |
| TestKeyboardShortcutBinder.pop(); |
| }); |
| |
| const PARENT = 'PARENT'; |
| |
| function getFilesFromFileList(fileList) { |
| const changeFilesByPath = fileList.reduce((files, path) => { |
| files[path] = {}; |
| return files; |
| }, {}); |
| return { |
| sortedFileList: fileList, |
| changeFilesByPath, |
| }; |
| } |
| |
| setup(async () => { |
| clock = sinon.useFakeTimers(); |
| sinon.stub(appContext.flagsService, 'isEnabled').returns(true); |
| 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 = basicFixture.instantiate(); |
| element._changeNum = '42'; |
| element._path = 'some/path.txt'; |
| element._change = {}; |
| element._diff = {content: []}; |
| element._patchRange = { |
| patchNum: 77, |
| basePatchNum: 'PARENT', |
| }; |
| sinon.stub(element.$.commentAPI, 'loadAll').returns(Promise.resolve({ |
| _comments: {'/COMMIT_MSG': [ |
| { |
| id: 'c1', |
| line: 10, |
| patch_set: 2, |
| __commentSide: 'left', |
| path: '/COMMIT_MSG', |
| }, { |
| id: 'c3', |
| line: 10, |
| patch_set: 'PARENT', |
| __commentSide: 'left', |
| path: '/COMMIT_MSG', |
| }, |
| ]}, |
| computeCommentThreadCount: () => {}, |
| computeUnresolvedNum: () => {}, |
| getPaths: () => {}, |
| getCommentsBySideForPath: () => {}, |
| findCommentById: _testOnly_findCommentById, |
| })); |
| await element._loadComments(); |
| await flush(); |
| }); |
| |
| teardown(() => { |
| clock.restore(); |
| sinon.restore(); |
| }); |
| |
| test('params change triggers diffViewDisplayed()', () => { |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.stub(element, '_initPatchRange'); |
| sinon.stub(element, '_getFiles'); |
| sinon.spy(element, '_paramsChanged'); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 2, |
| basePatchNum: 1, |
| path: '/COMMIT_MSG', |
| }; |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isTrue(element.reporting.diffViewDisplayed.calledOnce); |
| }); |
| }); |
| |
| test('comment route', () => { |
| const initLineOfInterestAndCursorStub = |
| sinon.stub(element, '_initLineOfInterestAndCursor'); |
| const getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById'); |
| const replaceStateStub = sinon.stub(history, 'replaceState'); |
| sinon.stub(element, '_getFiles'); |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.spy(element, '_paramsChanged'); |
| sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({ |
| ...createChange(), |
| revisions: createRevisions(11), |
| })); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| commentLink: true, |
| commentId: 'c1', |
| }; |
| sinon.stub(element.$.diffHost, '_commentsChanged'); |
| sinon.stub(element, '_getCommentsForPath').returns({ |
| left: [{id: 'c1', __commentSide: 'left', line: 10}], |
| right: [{id: 'c2', __commentSide: 'right', line: 11}], |
| }); |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(11), |
| }; |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isTrue(initLineOfInterestAndCursorStub. |
| calledWithExactly(true)); |
| assert.equal(element._focusLineNum, 10); |
| assert.equal(element._patchRange.patchNum, 11); |
| assert.equal(element._patchRange.basePatchNum, 2); |
| |
| assert.isTrue(replaceStateStub.called); |
| assert.isTrue(getUrlStub.calledWithExactly('42', 'test-project', |
| '/COMMIT_MSG', 11, 2, 10, true)); |
| }); |
| }); |
| |
| test('params change causes blame to load if it was set to true', () => { |
| // Blame loads for subsequent files if it was loaded for one file |
| element._isBlameLoaded = true; |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element, '_loadBlame'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.spy(element, '_paramsChanged'); |
| sinon.stub(element, '_initPatchRange'); |
| sinon.stub(element, '_getFiles'); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 2, |
| basePatchNum: 1, |
| path: '/COMMIT_MSG', |
| }; |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isTrue(element._isBlameLoaded); |
| assert.isTrue(element._loadBlame.calledOnce); |
| }); |
| }); |
| |
| test('unchanged diff X vs latest from comment links navigates to base vs X' |
| , () => { |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element, '_loadBlame'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.stub(element, '_isFileUnchanged').returns(true); |
| sinon.spy(element, '_paramsChanged'); |
| sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({ |
| ...createChange(), |
| revisions: createRevisions(11), |
| })); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| path: '/COMMIT_MSG', |
| commentLink: true, |
| commentId: 'c1', |
| }; |
| sinon.stub(element.$.diffHost, '_commentsChanged'); |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(11), |
| }; |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isTrue(diffNavStub.lastCall.calledWithExactly( |
| element._change, '/COMMIT_MSG', 2, 'PARENT', 10)); |
| }); |
| }); |
| |
| test('unchanged diff Base vs latest from comment does not navigate' |
| , () => { |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element, '_loadBlame'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.stub(element, '_isFileUnchanged').returns(true); |
| sinon.spy(element, '_paramsChanged'); |
| sinon.stub(element, '_getChangeDetail').returns(Promise.resolve({ |
| ...createChange(), |
| revisions: createRevisions(11), |
| })); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| path: '/COMMIT_MSG', |
| commentLink: true, |
| commentId: 'c3', |
| }; |
| sinon.stub(element.$.diffHost, '_commentsChanged'); |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(11), |
| }; |
| return element._paramsChanged.returnValues[0].then(() => { |
| assert.isFalse(diffNavStub.called); |
| }); |
| }); |
| |
| test('_isFileUnchanged', () => { |
| let diff = { |
| content: [ |
| {a: 'abcd', ab: 'ef'}, |
| {b: 'ancd', a: 'xx'}, |
| ], |
| }; |
| assert.equal(element._isFileUnchanged(diff), false); |
| diff = { |
| content: [ |
| {ab: 'abcd'}, |
| {ab: 'ancd'}, |
| ], |
| }; |
| assert.equal(element._isFileUnchanged(diff), true); |
| diff = { |
| content: [ |
| {a: 'abcd', ab: 'ef', common: true}, |
| {b: 'ancd', ab: 'xx'}, |
| ], |
| }; |
| assert.equal(element._isFileUnchanged(diff), false); |
| diff = { |
| content: [ |
| {a: 'abcd', ab: 'ef', common: true}, |
| {b: 'ancd', ab: 'xx', common: true}, |
| ], |
| }; |
| assert.equal(element._isFileUnchanged(diff), true); |
| }); |
| |
| test('diff toast to go to latest is shown and not base', async () => { |
| sinon.stub(element.reporting, 'diffViewDisplayed'); |
| sinon.stub(element, '_loadBlame'); |
| sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve()); |
| sinon.spy(element, '_paramsChanged'); |
| element.$.restAPI.getDiffChangeDetail.restore(); |
| sinon.stub(element.$.restAPI, 'getDiffChangeDetail') |
| .returns( |
| Promise.resolve({ |
| ...createChange(), |
| revisions: createRevisions(11), |
| })); |
| element._patchRange = { |
| patchNum: 2, |
| basePatchNum: 1, |
| }; |
| sinon.stub(element, '_isFileUnchanged').returns(false); |
| const toastStub = |
| sinon.stub(element, '_displayDiffBaseAgainstLeftToast'); |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| project: 'p', |
| commentId: 'c1', |
| commentLink: true, |
| }; |
| await element._paramsChanged.returnValues[0]; |
| assert.isTrue(toastStub.called); |
| }); |
| |
| test('toggle left diff with a hotkey', () => { |
| const toggleLeftDiffStub = sinon.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._files = getFilesFromFileList( |
| ['chell.go', 'glados.txt', 'wheatley.md']); |
| element._path = 'glados.txt'; |
| element.changeViewState.selectedFileIndex = 1; |
| element._loggedIn = true; |
| |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| const changeNavStub = sinon.stub(GerritNav, '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 = |
| sinon.stub(element.$.diffPreferencesDialog, 'open').callsFake( |
| () => Promise.resolve()); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 188, null, ','); |
| assert(showPrefsStub.calledOnce); |
| |
| element.disableDiffPrefs = true; |
| MockInteractions.pressAndReleaseKeyOn(element, 188, null, ','); |
| assert(showPrefsStub.calledOnce); |
| |
| let scrollStub = sinon.stub(element.$.cursor, 'moveToNextChunk'); |
| MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sinon.stub(element.$.cursor, 'moveToPreviousChunk'); |
| MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sinon.stub(element.$.cursor, 'moveToNextCommentThread'); |
| MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n'); |
| assert(scrollStub.calledOnce); |
| |
| scrollStub = sinon.stub(element.$.cursor, |
| 'moveToPreviousCommentThread'); |
| MockInteractions.pressAndReleaseKeyOn(element, 80, 'shift', 'p'); |
| assert(scrollStub.calledOnce); |
| |
| const computeContainerClassStub = sinon.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)); |
| |
| sinon.stub(element, '_setReviewed'); |
| sinon.spy(element, '_handleToggleFileReviewed'); |
| element.$.reviewed.checked = false; |
| MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift', 'r'); |
| assert.isFalse(element._setReviewed.called); |
| assert.isTrue(element._handleToggleFileReviewed.calledOnce); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); |
| assert.isTrue(element._handleToggleFileReviewed.calledOnce); |
| |
| clock.tick(1000); |
| |
| MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); |
| assert.isTrue(element._handleToggleFileReviewed.calledTwice); |
| assert.isTrue(element._setReviewed.called); |
| assert.equal(element._setReviewed.lastCall.args[0], true); |
| }); |
| |
| test('shift+x shortcut expands all diff context', () => { |
| const expandStub = sinon.stub(element.$.diffHost, 'expandAllContext'); |
| MockInteractions.pressAndReleaseKeyOn(element, 88, 'shift', 'x'); |
| flush(); |
| assert.isTrue(expandStub.called); |
| }); |
| |
| test('diff against base', () => { |
| element._patchRange = { |
| basePatchNum: 5, |
| patchNum: 10, |
| }; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffAgainstBase(new CustomEvent('')); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 10); |
| assert.isNotOk(args[3]); |
| }); |
| |
| test('diff against latest', () => { |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(12), |
| }; |
| element._patchRange = { |
| basePatchNum: 5, |
| patchNum: 10, |
| }; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffAgainstLatest(new CustomEvent('')); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 12); |
| assert.equal(args[3], 5); |
| }); |
| |
| test('_handleDiffBaseAgainstLeft', () => { |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(10), |
| }; |
| element._patchRange = { |
| patchNum: 3, |
| basePatchNum: 1, |
| }; |
| element.params = {}; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffBaseAgainstLeft(new CustomEvent('')); |
| assert(diffNavStub.called); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 1); |
| assert.equal(args[3], 'PARENT'); |
| assert.isNotOk(args[4]); |
| }); |
| |
| test('_handleDiffBaseAgainstLeft when initially navigating to a comment', |
| () => { |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(10), |
| }; |
| element._patchRange = { |
| patchNum: 3, |
| basePatchNum: 1, |
| }; |
| sinon.stub(element, '_paramsChanged'); |
| element.params = {commentLink: true, view: GerritView.DIFF}; |
| element._focusLineNum = 10; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffBaseAgainstLeft(new CustomEvent('')); |
| assert(diffNavStub.called); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 1); |
| assert.equal(args[3], 'PARENT'); |
| assert.equal(args[4], 10); |
| }); |
| |
| test('_handleDiffRightAgainstLatest', () => { |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(10), |
| }; |
| element._patchRange = { |
| basePatchNum: 1, |
| patchNum: 3, |
| }; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffRightAgainstLatest(new CustomEvent('')); |
| assert(diffNavStub.called); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 10); |
| assert.equal(args[3], 3); |
| }); |
| |
| test('_handleDiffBaseAgainstLatest', () => { |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(10), |
| }; |
| element._patchRange = { |
| basePatchNum: 1, |
| patchNum: 3, |
| }; |
| sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false); |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._handleDiffBaseAgainstLatest(new CustomEvent('')); |
| assert(diffNavStub.called); |
| const args = diffNavStub.getCall(0).args; |
| assert.equal(args[2], 10); |
| assert.isNotOk(args[3]); |
| }); |
| |
| 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._files = getFilesFromFileList( |
| ['chell.go', 'glados.txt', 'wheatley.md']); |
| element._path = 'glados.txt'; |
| |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| const changeNavStub = sinon.stub(GerritNav, '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'); |
| |
| assert.isUndefined(element.changeViewState.showDownloadDialog); |
| MockInteractions.pressAndReleaseKeyOn(element, 68, null, 'd'); |
| assert.isTrue(element.changeViewState.showDownloadDialog); |
| }); |
| |
| 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._files = getFilesFromFileList( |
| ['chell.go', 'glados.txt', 'wheatley.md']); |
| element._path = 'glados.txt'; |
| |
| const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| const changeNavStub = sinon.stub(GerritNav, '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'; |
| |
| changeNavStub.reset(); |
| MockInteractions.pressAndReleaseKeyOn(element, 219, null, '['); |
| assert(changeNavStub.lastCall.calledWithExactly(element._change, 1, |
| PARENT), 'Should navigate to /c/42/1'); |
| assert.isTrue(changeNavStub.calledOnce); |
| }); |
| |
| test('edit should redirect to edit page', done => { |
| element._loggedIn = true; |
| element._path = 't.txt'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: 1, |
| }; |
| element._change = { |
| _number: 42, |
| project: 'gerrit', |
| status: ChangeStatus.NEW, |
| revisions: { |
| a: {_number: 1, commit: {parents: []}}, |
| b: {_number: 2, commit: {parents: []}}, |
| }, |
| }; |
| const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl'); |
| flush(() => { |
| const editBtn = element.shadowRoot |
| .querySelector('.editButton gr-button'); |
| assert.isTrue(!!editBtn); |
| MockInteractions.tap(editBtn); |
| assert.isTrue(redirectStub.called); |
| assert.isTrue(redirectStub.lastCall.calledWithExactly( |
| GerritNav.getEditUrlForDiff( |
| element._change, |
| element._path, |
| element._patchRange.patchNum |
| ))); |
| done(); |
| }); |
| }); |
| |
| test('edit should redirect to edit page with line number', done => { |
| const lineNumber = 42; |
| element._loggedIn = true; |
| element._path = 't.txt'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: 1, |
| }; |
| element._change = { |
| _number: 42, |
| project: 'gerrit', |
| status: ChangeStatus.NEW, |
| revisions: { |
| a: {_number: 1, commit: {parents: []}}, |
| b: {_number: 2, commit: {parents: []}}, |
| }, |
| }; |
| sinon.stub(element.$.cursor, 'getAddress') |
| .returns({number: lineNumber, isLeftSide: false}); |
| const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl'); |
| flush(() => { |
| const editBtn = element.shadowRoot |
| .querySelector('.editButton gr-button'); |
| assert.isTrue(!!editBtn); |
| MockInteractions.tap(editBtn); |
| assert.isTrue(redirectStub.called); |
| assert.isTrue(redirectStub.lastCall.calledWithExactly( |
| GerritNav.getEditUrlForDiff( |
| element._change, |
| element._path, |
| element._patchRange.patchNum, |
| lineNumber |
| ))); |
| done(); |
| }); |
| }); |
| |
| function isEditVisibile({loggedIn, changeStatus}) { |
| return new Promise(resolve => { |
| element._loggedIn = loggedIn; |
| element._path = 't.txt'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: 1, |
| }; |
| element._change = { |
| _number: 42, |
| status: changeStatus, |
| revisions: { |
| a: {_number: 1, commit: {parents: []}}, |
| b: {_number: 2, commit: {parents: []}}, |
| }, |
| }; |
| flush(() => { |
| const editBtn = element.shadowRoot |
| .querySelector('.editButton gr-button'); |
| resolve(!!editBtn); |
| }); |
| }); |
| } |
| |
| test('edit visible only when logged and status NEW', async () => { |
| for (const changeStatus in ChangeStatus) { |
| if (!ChangeStatus.hasOwnProperty(changeStatus)) { |
| continue; |
| } |
| assert.isFalse(await isEditVisibile({loggedIn: false, changeStatus}), |
| `loggedIn: false, changeStatus: ${changeStatus}`); |
| |
| if (changeStatus !== ChangeStatus.NEW) { |
| assert.isFalse(await isEditVisibile({loggedIn: true, changeStatus}), |
| `loggedIn: true, changeStatus: ${changeStatus}`); |
| } else { |
| assert.isTrue(await isEditVisibile({loggedIn: true, changeStatus}), |
| `loggedIn: true, changeStatus: ${changeStatus}`); |
| } |
| } |
| }); |
| |
| test('edit visible when logged and status NEW', async () => { |
| assert.isTrue(await isEditVisibile( |
| {loggedIn: true, changeStatus: ChangeStatus.NEW})); |
| }); |
| |
| test('edit hidden when logged and status ABANDONED', async () => { |
| assert.isFalse(await isEditVisibile( |
| {loggedIn: true, changeStatus: ChangeStatus.ABANDONED})); |
| }); |
| |
| test('edit hidden when logged and status MERGED', async () => { |
| assert.isFalse(await isEditVisibile( |
| {loggedIn: true, changeStatus: ChangeStatus.MERGED})); |
| }); |
| |
| suite('diff prefs hidden', () => { |
| test('when no prefs or logged out', () => { |
| element.disableDiffPrefs = false; |
| element._loggedIn = false; |
| flush(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = true; |
| flush(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = false; |
| element._prefs = {font_size: '12'}; |
| flush(); |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| |
| element._loggedIn = true; |
| flush(); |
| assert.isFalse(element.$.diffPrefsContainer.hidden); |
| }); |
| |
| test('when disableDiffPrefs is set', () => { |
| element._loggedIn = true; |
| element._prefs = {font_size: '12'}; |
| element.disableDiffPrefs = false; |
| flush(); |
| |
| assert.isFalse(element.$.diffPrefsContainer.hidden); |
| element.disableDiffPrefs = true; |
| flush(); |
| |
| assert.isTrue(element.$.diffPrefsContainer.hidden); |
| }); |
| }); |
| |
| test('prefsButton opens gr-diff-preferences', () => { |
| const handlePrefsTapSpy = sinon.spy(element, '_handlePrefsTap'); |
| const overlayOpenStub = sinon.stub(element.$.diffPreferencesDialog, |
| 'open'); |
| const prefsButton = |
| element.root.querySelector('.prefsButton'); |
| |
| MockInteractions.tap(prefsButton); |
| |
| assert.isTrue(handlePrefsTapSpy.called); |
| assert.isTrue(overlayOpenStub.called); |
| }); |
| |
| test('_computeCommentString', done => { |
| const path = '/test'; |
| element.$.commentAPI.loadAll().then(comments => { |
| const commentThreadCountStub = |
| sinon.stub(comments, 'computeCommentThreadCount'); |
| const unresolvedCountStub = |
| sinon.stub(comments, 'computeUnresolvedNum'); |
| commentThreadCountStub.withArgs({patchNum: 1, path}).returns(0); |
| commentThreadCountStub.withArgs({patchNum: 2, path}).returns(1); |
| commentThreadCountStub.withArgs({patchNum: 3, path}).returns(2); |
| commentThreadCountStub.withArgs({patchNum: 4, path}).returns(0); |
| unresolvedCountStub.withArgs({patchNum: 1, path}).returns(1); |
| unresolvedCountStub.withArgs({patchNum: 2, path}).returns(0); |
| unresolvedCountStub.withArgs({patchNum: 3, path}).returns(2); |
| unresolvedCountStub.withArgs({patchNum: 4, path}).returns(0); |
| |
| assert.equal(element._computeCommentString(comments, 1, path, {}), |
| '1 unresolved'); |
| assert.equal( |
| element._computeCommentString(comments, 2, path, {status: 'M'}), |
| '1 comment'); |
| assert.equal( |
| element._computeCommentString(comments, 2, path, {status: 'U'}), |
| 'no changes, 1 comment'); |
| assert.equal( |
| element._computeCommentString(comments, 3, path, {status: 'A'}), |
| '2 comments, 2 unresolved'); |
| assert.equal( |
| element._computeCommentString( |
| comments, 4, path, {status: 'M'} |
| ), ''); |
| assert.equal( |
| element._computeCommentString(comments, 4, path, {status: 'U'}), |
| 'no changes'); |
| done(); |
| }); |
| }); |
| |
| suite('url params', () => { |
| setup(() => { |
| sinon.stub(element, '_getFiles'); |
| sinon.stub( |
| GerritNav, |
| 'getUrlForDiff') |
| .callsFake((c, p, pn, bpn) => `${c._number}-${p}-${pn}-${bpn}`); |
| sinon.stub( |
| GerritNav |
| , 'getUrlForChange') |
| .callsFake((c, pn, bpn) => `${c._number}-${pn}-${bpn}`); |
| }); |
| |
| test('_formattedFiles', () => { |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: PARENT, |
| patchNum: 10, |
| }; |
| // computeCommentThreadCount is an empty function hence stubbing |
| // function that depends on it's return value |
| sinon.stub(element, '_computeCommentString').returns(''); |
| element._change = {_number: 42}; |
| element._files = getFilesFromFileList( |
| ['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._files = getFilesFromFileList( |
| ['chell.go', 'glados.txt', 'wheatley.md']); |
| element._path = 'glados.txt'; |
| flush(); |
| const linkEls = 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'; |
| flush(); |
| 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'; |
| flush(); |
| 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'; |
| flush(); |
| 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._files = getFilesFromFileList( |
| ['chell.go', 'glados.txt', 'wheatley.md']); |
| element._path = 'glados.txt'; |
| flush(); |
| const linkEls = 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'; |
| flush(); |
| 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'; |
| flush(); |
| 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 = sinon.stub(GerritNav, '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('_prefs.manual_review is respected', () => { |
| const saveReviewedStub = sinon.stub(element, '_saveReviewedState') |
| .callsFake(() => Promise.resolve()); |
| const getReviewedStub = sinon.stub(element, '_getReviewedStatus') |
| .callsFake(() => Promise.resolve()); |
| |
| sinon.stub(element.$.diffHost, 'reload'); |
| element._loggedIn = true; |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 2, |
| basePatchNum: 1, |
| path: '/COMMIT_MSG', |
| }; |
| element._patchRange = { |
| patchNum: 2, |
| basePatchNum: 1, |
| }; |
| element._prefs = {manual_review: true}; |
| flush(); |
| |
| assert.isFalse(saveReviewedStub.called); |
| assert.isTrue(getReviewedStub.called); |
| |
| element._prefs = {}; |
| flush(); |
| |
| assert.isTrue(saveReviewedStub.called); |
| assert.isTrue(getReviewedStub.calledOnce); |
| }); |
| |
| test('file review status', () => { |
| const saveReviewedStub = sinon.stub(element, '_saveReviewedState') |
| .callsFake(() => Promise.resolve()); |
| sinon.stub(element.$.diffHost, 'reload'); |
| |
| element._loggedIn = true; |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 2, |
| basePatchNum: 1, |
| path: '/COMMIT_MSG', |
| }; |
| element._patchRange = { |
| patchNum: 2, |
| basePatchNum: 1, |
| }; |
| element._prefs = {}; |
| flush(); |
| |
| const commitMsg = 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', GerritNav.View.CHANGE); |
| flush(); |
| |
| // saveReviewedState observer observes params, but should not fire when |
| // view !== GerritNav.View.DIFF. |
| assert.equal(saveReviewedStub.callCount, callCount); |
| }); |
| |
| test('file review status with edit loaded', () => { |
| const saveReviewedStub = sinon.stub(element, '_saveReviewedState'); |
| |
| element._patchRange = {patchNum: SPECIAL_PATCH_SET_NUM.EDIT}; |
| flush(); |
| |
| assert.isTrue(element._editMode); |
| element._setReviewed(); |
| assert.isFalse(saveReviewedStub.called); |
| }); |
| |
| test('hash is determined from params', done => { |
| sinon.stub(element.$.diffHost, 'reload'); |
| sinon.stub(element, '_initLineOfInterestAndCursor'); |
| |
| element._loggedIn = true; |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 2, |
| basePatchNum: 1, |
| path: '/COMMIT_MSG', |
| hash: 10, |
| }; |
| |
| flush(() => { |
| assert.isTrue(element._initLineOfInterestAndCursor.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; |
| }); |
| sinon.stub(element.$.restAPI, 'getPreferences') |
| .callsFake(() => prefsPromise); |
| |
| // Attach a new gr-diff-view so we can intercept the preferences fetch. |
| const view = document.createElement('gr-diff-view'); |
| blankFixture.instantiate().appendChild(view); |
| flush(); |
| |
| // 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'}); |
| flush(); |
| assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE'); |
| }); |
| |
| test('diff mode selector should be hidden for binary', done => { |
| element._diff = {binary: true, content: []}; |
| |
| flush(() => { |
| const diffModeSelector = element.shadowRoot |
| .querySelector('.diffModeSelector'); |
| assert.isTrue(diffModeSelector.classList.contains('hide')); |
| done(); |
| }); |
| }); |
| |
| suite('_commitRange', () => { |
| const change = { |
| _number: 42, |
| revisions: { |
| 'commit-sha-1': { |
| _number: 1, |
| commit: { |
| parents: [{commit: 'sha-1-parent'}], |
| }, |
| }, |
| 'commit-sha-2': {_number: 2, commit: {parents: []}}, |
| 'commit-sha-3': {_number: 3, commit: {parents: []}}, |
| 'commit-sha-4': {_number: 4, commit: {parents: []}}, |
| 'commit-sha-5': { |
| _number: 5, |
| commit: { |
| parents: [{commit: 'sha-5-parent'}], |
| }, |
| }, |
| }, |
| }; |
| setup(() => { |
| sinon.stub(element.$.diffHost, 'reload'); |
| sinon.stub(element, '_initCursor'); |
| element._change = change; |
| sinon.stub(element, '_getChangeDetail').returns(Promise.resolve( |
| change)); |
| }); |
| |
| test('uses the patchNum and basePatchNum ', done => { |
| element.params = { |
| view: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 4, |
| basePatchNum: 2, |
| path: '/COMMIT_MSG', |
| }; |
| element._change = change; |
| 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: GerritNav.View.DIFF, |
| changeNum: '42', |
| patchNum: 5, |
| path: '/COMMIT_MSG', |
| }; |
| element._change = change; |
| 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(false); |
| assert.isNotOk(element.$.cursor.initialLineNumber); |
| |
| // Does nothing when params specify side but no number: |
| element._initCursor(true); |
| assert.isNotOk(element.$.cursor.initialLineNumber); |
| |
| // Revision hash: specifies lineNum but not side. |
| |
| element._focusLineNum = 234; |
| element._initCursor(false); |
| assert.equal(element.$.cursor.initialLineNumber, 234); |
| assert.equal(element.$.cursor.side, 'right'); |
| |
| // Base hash: specifies lineNum and side. |
| element._focusLineNum = 345; |
| element._initCursor(true); |
| assert.equal(element.$.cursor.initialLineNumber, 345); |
| assert.equal(element.$.cursor.side, 'left'); |
| |
| // Specifies right side: |
| element._focusLineNum = 123; |
| element._initCursor(false); |
| assert.equal(element.$.cursor.initialLineNumber, 123); |
| assert.equal(element.$.cursor.side, 'right'); |
| }); |
| |
| test('_getLineOfInterest', () => { |
| assert.isUndefined(element._getLineOfInterest(false)); |
| |
| element._focusLineNum = 12; |
| let result = element._getLineOfInterest(false); |
| assert.equal(result.number, 12); |
| assert.isNotOk(result.leftSide); |
| |
| result = element._getLineOfInterest(true); |
| assert.equal(result.number, 12); |
| assert.isOk(result.leftSide); |
| }); |
| |
| test('_onLineSelected', () => { |
| const getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById'); |
| const replaceStateStub = sinon.stub(history, 'replaceState'); |
| sinon.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(replaceStateStub.called); |
| assert.isTrue(getUrlStub.called); |
| assert.isFalse(getUrlStub.lastCall.args[6]); |
| }); |
| |
| test('line selected on left side', () => { |
| const getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById'); |
| const replaceStateStub = sinon.stub(history, 'replaceState'); |
| sinon.stub(element.$.cursor, 'getAddress') |
| .returns({number: 123, isLeftSide: true}); |
| |
| element._changeNum = 321; |
| element._change = {_number: 321, project: 'foo/bar'}; |
| element._patchRange = { |
| basePatchNum: 3, |
| patchNum: 5, |
| }; |
| const e = {}; |
| const detail = {number: 123, side: 'left'}; |
| |
| element._onLineSelected(e, detail); |
| |
| assert.isTrue(replaceStateStub.called); |
| assert.isTrue(getUrlStub.called); |
| assert.isTrue(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', () => { |
| sinon.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('_initPatchRange', () => { |
| setup(async () => { |
| element.params = { |
| view: GerritView.DIFF, |
| changeNum: '42', |
| patchNum: 3, |
| }; |
| await flush(); |
| }); |
| test('empty', () => { |
| sinon.stub(element, '_getCommentsForPath'); |
| sinon.stub(element, '_getPaths').returns(new Map()); |
| element._initPatchRange(); |
| assert.equal(Object.keys(element._commentMap).length, 0); |
| }); |
| |
| test('has paths', () => { |
| sinon.stub(element, '_getFiles'); |
| sinon.stub(element, '_getPaths').returns({ |
| 'path/to/file/one.cpp': [{patch_set: 3, message: 'lorem'}], |
| 'path-to/file/two.py': [{patch_set: 5, message: 'ipsum'}], |
| }); |
| sinon.stub(element, '_getCommentsForPath').returns({meta: {}}); |
| element._changeNum = '42'; |
| element._patchRange = { |
| basePatchNum: 3, |
| patchNum: 5, |
| }; |
| element._initPatchRange(); |
| assert.deepEqual(Object.keys(element._commentMap), |
| ['path/to/file/one.cpp', 'path-to/file/two.py']); |
| }); |
| }); |
| |
| 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 = sinon.stub(element, '_navToChangeView'); |
| navToDiffStub = sinon.stub(GerritNav, 'navigateToDiff'); |
| element._files = getFilesFromFileList([ |
| '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(); |
| |
| sinon.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.isFalse(reviewed))); |
| |
| promises.push(element._getReviewedStatus(false, 3, 5, 'path') |
| .then(reviewed => assert.isTrue(reviewed))); |
| |
| return Promise.all(promises); |
| }); |
| |
| test('f open file dropdown', () => { |
| assert.isFalse(element.$.dropdown.$.dropdown.opened); |
| MockInteractions.pressAndReleaseKeyOn(element, 70, null, 'f'); |
| flush(); |
| assert.isTrue(element.$.dropdown.$.dropdown.opened); |
| }); |
| |
| suite('blame', () => { |
| test('toggle blame with button', () => { |
| const toggleBlame = sinon.stub( |
| element.$.diffHost, 'loadBlame') |
| .callsFake(() => Promise.resolve()); |
| MockInteractions.tap(element.$.toggleBlame); |
| assert.isTrue(toggleBlame.calledOnce); |
| }); |
| test('toggle blame with shortcut', () => { |
| const toggleBlame = sinon.stub( |
| element.$.diffHost, 'loadBlame').callsFake(() => Promise.resolve()); |
| MockInteractions.pressAndReleaseKeyOn(element, 66, null, 'b'); |
| assert.isTrue(toggleBlame.calledOnce); |
| }); |
| }); |
| |
| suite('editMode behavior', () => { |
| setup(() => { |
| element._loggedIn = true; |
| }); |
| |
| const isVisible = el => { |
| assert.ok(el); |
| return getComputedStyle(el).getPropertyValue('display') !== 'none'; |
| }; |
| |
| test('reviewed checkbox', () => { |
| sinon.stub(element, '_handlePatchChange'); |
| element._patchRange = {patchNum: 1}; |
| // Reviewed checkbox should be shown. |
| assert.isTrue(isVisible(element.$.reviewed)); |
| element.set('_patchRange.patchNum', SPECIAL_PATCH_SET_NUM.EDIT); |
| flush(); |
| |
| assert.isFalse(isVisible(element.$.reviewed)); |
| }); |
| }); |
| |
| test('_paramsChanged sets in projectLookup', () => { |
| sinon.stub(element, '_initLineOfInterestAndCursor'); |
| const setStub = sinon.stub(element.$.restAPI, 'setInProjectLookup'); |
| element._paramsChanged({ |
| view: GerritNav.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._files = getFilesFromFileList(['file1', 'file2', 'file3']); |
| element._reviewedFiles = new Set(['file1', 'file2']); |
| element._path = 'file1'; |
| const reviewedStub = sinon.stub(element, '_setReviewed'); |
| const navStub = sinon.stub(element, '_navToFile'); |
| MockInteractions.pressAndReleaseKeyOn(element, 77, 'shift', 'm'); |
| flush(); |
| |
| assert.isTrue(reviewedStub.lastCall.args[0]); |
| assert.deepEqual(navStub.lastCall.args, [ |
| 'file1', |
| ['file1', 'file3'], |
| 1, |
| ]); |
| }); |
| |
| test('File change should trigger navigateToDiff once', done => { |
| element._files = getFilesFromFileList(['file1', 'file2', 'file3']); |
| sinon.stub(element, '_initLineOfInterestAndCursor'); |
| sinon.stub(GerritNav, 'navigateToDiff'); |
| |
| // Load file1 |
| element.params = { |
| view: GerritNav.View.DIFF, |
| patchNum: 1, |
| changeNum: 101, |
| project: 'test-project', |
| path: 'file1', |
| }; |
| element._patchRange = { |
| patchNum: 1, |
| basePatchNum: 'PARENT', |
| }; |
| element._change = { |
| ...createChange(), |
| revisions: createRevisions(1), |
| }; |
| flush(); |
| assert.isTrue(GerritNav.navigateToDiff.notCalled); |
| |
| // Switch to file2 |
| element._handleFileChange({detail: {value: 'file2'}}); |
| assert.isTrue(GerritNav.navigateToDiff.calledOnce); |
| |
| // This is to mock the param change triggered by above navigate |
| element.params = { |
| view: GerritNav.View.DIFF, |
| patchNum: 1, |
| changeNum: 101, |
| project: 'test-project', |
| path: 'file2', |
| }; |
| element._patchRange = { |
| patchNum: 1, |
| basePatchNum: 'PARENT', |
| }; |
| |
| // No extra call |
| assert.isTrue(GerritNav.navigateToDiff.calledOnce); |
| done(); |
| }); |
| |
| test('_computeDownloadDropdownLinks', () => { |
| const downloadLinks = [ |
| { |
| url: '/changes/test~12/revisions/1/patch?zip&path=index.php', |
| name: 'Patch', |
| }, |
| { |
| url: '/changes/test~12/revisions/1' + |
| '/files/index.php/download?parent=1', |
| name: 'Left Content', |
| }, |
| { |
| url: '/changes/test~12/revisions/1' + |
| '/files/index.php/download', |
| name: 'Right Content', |
| }, |
| ]; |
| |
| const side = { |
| meta_a: true, |
| meta_b: true, |
| }; |
| |
| const base = { |
| patchNum: 1, |
| basePatchNum: 'PARENT', |
| }; |
| |
| assert.deepEqual( |
| element._computeDownloadDropdownLinks( |
| 'test', 12, base, 'index.php', side), |
| downloadLinks); |
| }); |
| |
| test('_computeDownloadDropdownLinks diff returns renamed', () => { |
| const downloadLinks = [ |
| { |
| url: '/changes/test~12/revisions/3/patch?zip&path=index.php', |
| name: 'Patch', |
| }, |
| { |
| url: '/changes/test~12/revisions/2' + |
| '/files/index2.php/download', |
| name: 'Left Content', |
| }, |
| { |
| url: '/changes/test~12/revisions/3' + |
| '/files/index.php/download', |
| name: 'Right Content', |
| }, |
| ]; |
| |
| const side = { |
| change_type: 'RENAMED', |
| meta_a: { |
| name: 'index2.php', |
| }, |
| meta_b: true, |
| }; |
| |
| const base = { |
| patchNum: 3, |
| basePatchNum: 2, |
| }; |
| |
| assert.deepEqual( |
| element._computeDownloadDropdownLinks( |
| 'test', 12, base, 'index.php', side), |
| downloadLinks); |
| }); |
| |
| test('_computeDownloadFileLink', () => { |
| const base = { |
| patchNum: 1, |
| basePatchNum: 'PARENT', |
| }; |
| |
| assert.equal( |
| element._computeDownloadFileLink( |
| 'test', 12, base, 'index.php', true), |
| '/changes/test~12/revisions/1/files/index.php/download?parent=1'); |
| |
| assert.equal( |
| element._computeDownloadFileLink( |
| 'test', 12, base, 'index.php', false), |
| '/changes/test~12/revisions/1/files/index.php/download'); |
| }); |
| |
| test('_computeDownloadPatchLink', () => { |
| assert.equal( |
| element._computeDownloadPatchLink( |
| 'test', 12, {patchNum: 1}, 'index.php'), |
| '/changes/test~12/revisions/1/patch?zip&path=index.php'); |
| }); |
| }); |
| |
| suite('gr-diff-view tests unmodified files with comments', () => { |
| let element; |
| setup(() => { |
| const changedFiles = { |
| 'file1.txt': {}, |
| 'a/b/test.c': {}, |
| }; |
| 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(changedFiles); }, |
| saveFileReviewed() { return Promise.resolve(); }, |
| getDiffComments() { return Promise.resolve({}); }, |
| getDiffRobotComments() { return Promise.resolve({}); }, |
| getDiffDrafts() { return Promise.resolve({}); }, |
| getReviewedFiles() { return Promise.resolve([]); }, |
| }); |
| element = basicFixture.instantiate(); |
| element._changeNum = '42'; |
| return element._loadComments(); |
| }); |
| |
| test('_getFiles add files with comments without changes', () => { |
| const patchChangeRecord = { |
| base: { |
| basePatchNum: 5, |
| patchNum: 10, |
| }, |
| }; |
| const changeComments = { |
| getPaths: sinon.stub().returns({ |
| 'file2.txt': {}, |
| 'file1.txt': {}, |
| }), |
| }; |
| return element._getFiles(23, patchChangeRecord, changeComments) |
| .then(() => { |
| assert.deepEqual(element._files, { |
| sortedFileList: ['a/b/test.c', 'file1.txt', 'file2.txt'], |
| changeFilesByPath: { |
| 'file1.txt': {}, |
| 'file2.txt': {status: 'U'}, |
| 'a/b/test.c': {}, |
| }, |
| }); |
| }); |
| }); |
| }); |
| }); |
| |