diff options
Diffstat (limited to 'polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html')
-rw-r--r-- | polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html | 1277 |
1 files changed, 893 insertions, 384 deletions
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html index a5fd521ffe..a44196588a 100644 --- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html +++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <!-- +@license Copyright (C) 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,7 @@ limitations under the License. <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> +<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html"> <script src="../../../scripts/util.js"></script> <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html"> @@ -29,43 +31,89 @@ limitations under the License. <script>void(0);</script> +<dom-module id="comment-api-mock"> + <template> + <gr-file-list id="fileList" + change-comments="[[_changeComments]]" + on-reload-drafts="_reloadDraftsWithCallback"></gr-file-list> + <gr-comment-api id="commentAPI"></gr-comment-api> + </template> + <script src="../../diff/gr-comment-api/gr-comment-api-mock.js"></script> +</dom-module> + <test-fixture id="basic"> <template> - <gr-file-list></gr-file-list> + <comment-api-mock></comment-api-mock> </template> </test-fixture> <script> suite('gr-file-list 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.TOGGLE_INLINE_DIFF, 'i:keyup'); + kb.bindShortcut(kb.Shortcut.TOGGLE_ALL_INLINE_DIFFS, 'shift+i:keyup'); + kb.bindShortcut(kb.Shortcut.CURSOR_NEXT_FILE, 'j', 'down'); + kb.bindShortcut(kb.Shortcut.CURSOR_PREV_FILE, 'k', 'up'); + kb.bindShortcut(kb.Shortcut.NEXT_LINE, 'j', 'down'); + kb.bindShortcut(kb.Shortcut.PREV_LINE, 'k', 'up'); + kb.bindShortcut(kb.Shortcut.NEW_COMMENT, 'c'); + kb.bindShortcut(kb.Shortcut.OPEN_LAST_FILE, '['); + kb.bindShortcut(kb.Shortcut.OPEN_FIRST_FILE, ']'); + kb.bindShortcut(kb.Shortcut.OPEN_FILE, 'o'); + kb.bindShortcut(kb.Shortcut.NEXT_CHUNK, 'n'); + kb.bindShortcut(kb.Shortcut.PREV_CHUNK, 'p'); + kb.bindShortcut(kb.Shortcut.TOGGLE_FILE_REVIEWED, 'r'); + kb.bindShortcut(kb.Shortcut.TOGGLE_LEFT_PANE, 'shift+a'); + let element; + let commentApiWrapper; let sandbox; let saveStub; - let loadCommentStub; + let loadCommentSpy; - setup(() => { + setup(done => { sandbox = sinon.sandbox.create(); stub('gr-rest-api-interface', { getLoggedIn() { return Promise.resolve(true); }, getPreferences() { return Promise.resolve({}); }, - fetchJSON() { return Promise.resolve({}); }, + getDiffPreferences() { return Promise.resolve({}); }, + getDiffComments() { return Promise.resolve({}); }, + getDiffRobotComments() { return Promise.resolve({}); }, + getDiffDrafts() { return Promise.resolve({}); }, + getAccountCapabilities() { return Promise.resolve({}); }, }); stub('gr-date-formatter', { _loadTimeFormat() { return Promise.resolve(''); }, }); - stub('gr-diff', { + stub('gr-diff-host', { reload() { return Promise.resolve(); }, }); - stub('gr-comment-api', { - getPaths() { return {}; }, - getCommentsForPath() { return {meta: {}, left: [], right: []}; }, - }); - element = fixture('basic'); + // Element must be wrapped in an element with direct access to the + // comment API. + commentApiWrapper = fixture('basic'); + element = commentApiWrapper.$.fileList; + loadCommentSpy = sandbox.spy(commentApiWrapper.$.commentAPI, 'loadAll'); + + // Stub methods on the changeComments object after changeComments has + // been initalized. + commentApiWrapper.loadComments().then(() => { + sandbox.stub(element.changeComments, 'getPaths').returns({}); + sandbox.stub(element.changeComments, 'getCommentsBySideForPath') + .returns({meta: {}, left: [], right: []}); + done(); + }); + element._loading = false; + element.diffPrefs = {}; element.numFilesShown = 200; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; saveStub = sandbox.stub(element, '_saveReviewedState', () => { return Promise.resolve(); }); - loadCommentStub = sandbox.stub(element.$.commentAPI, 'loadAll', - () => { return Promise.resolve(); }); }); teardown(() => { @@ -73,109 +121,64 @@ limitations under the License. }); test('correct number of files are shown', () => { - element._files = _.times(500, i => { - return {__path: '/file' + i, lines_inserted: 9}; - }); + element.fileListIncrement = 300; + element._filesByPath = _.range(500) + .reduce((_filesByPath, i) => { + _filesByPath['/file' + i] = {lines_inserted: 9}; + return _filesByPath; + }, {}); + flushAsynchronousOperations(); assert.equal( Polymer.dom(element.root).querySelectorAll('.file-row').length, element.numFilesShown); - }); - - test('get file list', done => { - const getChangeFilesStub = sandbox.stub(element.$.restAPI, 'getChangeFiles', - () => { - return Promise.resolve({ - '/COMMIT_MSG': {lines_inserted: 9}, - 'tags.html': {lines_deleted: 123}, - 'about.txt': {}, - }); - }); - - element._getFiles().then(files => { - const filenames = files.map(f => { return f.__path; }); - assert.deepEqual(filenames, ['/COMMIT_MSG', 'about.txt', 'tags.html']); - assert.deepEqual(files[0], { - lines_inserted: 9, - lines_deleted: 0, - __path: '/COMMIT_MSG', - }); - assert.deepEqual(files[1], { - lines_inserted: 0, - lines_deleted: 0, - __path: 'about.txt', - }); - assert.deepEqual(files[2], { - lines_inserted: 0, - lines_deleted: 123, - __path: 'tags.html', - }); + const controlRow = element.$$('.controlRow'); + assert.isFalse(controlRow.classList.contains('invisible')); + assert.equal(element.$.incrementButton.textContent.trim(), + 'Show 300 more'); + assert.equal(element.$.showAllButton.textContent.trim(), + 'Show all 500 files'); + + MockInteractions.tap(element.$.showAllButton); + flushAsynchronousOperations(); - getChangeFilesStub.restore(); - done(); - }); + assert.equal(element.numFilesShown, 500); + assert.equal(element._shownFiles.length, 500); + assert.isTrue(controlRow.classList.contains('invisible')); }); - test('get file list with change edit', done => { - element.editLoaded = true; - - sandbox.stub(element.$.restAPI, - 'getChangeEditFiles', () => { - return Promise.resolve({ - commit: {}, - files: { - '/COMMIT_MSG': { - lines_inserted: 9, - }, - 'tags.html': { - lines_deleted: 123, - }, - 'about.txt': {}, - }, - }); - }); - - element._getFiles().then(files => { - const filenames = files.map(f => { return f.__path; }); - assert.deepEqual(filenames, ['/COMMIT_MSG', 'about.txt', 'tags.html']); - assert.deepEqual(files[0], { - lines_inserted: 9, - lines_deleted: 0, - __path: '/COMMIT_MSG', - }); - assert.deepEqual(files[1], { - lines_inserted: 0, - lines_deleted: 0, - __path: 'about.txt', - }); - assert.deepEqual(files[2], { - lines_inserted: 0, - lines_deleted: 123, - __path: 'tags.html', - }); - - done(); - }); + test('rendering each row calls the _reportRenderedRow method', () => { + const renderedStub = sandbox.stub(element, '_reportRenderedRow'); + element._filesByPath = _.range(10) + .reduce((_filesByPath, i) => { + _filesByPath['/file' + i] = {lines_inserted: 9}; + return _filesByPath; + }, {}); + flushAsynchronousOperations(); + assert.equal( + Polymer.dom(element.root).querySelectorAll('.file-row').length, 10); + assert.equal(renderedStub.callCount, 10); }); test('calculate totals for patch number', () => { - element._files = [ - {__path: '/COMMIT_MSG', lines_inserted: 9}, - { - __path: 'file_added_in_rev2.txt', + element._filesByPath = { + '/COMMIT_MSG': { + lines_inserted: 9, + }, + 'file_added_in_rev2.txt': { lines_inserted: 1, lines_deleted: 1, size_delta: 10, size: 100, }, - { - __path: 'myfile.txt', + 'myfile.txt': { lines_inserted: 1, lines_deleted: 1, size_delta: 10, size: 100, }, - ]; + }; + assert.deepEqual(element._patchChange, { inserted: 2, deleted: 2, @@ -187,11 +190,20 @@ limitations under the License. assert.isFalse(element._hideChangeTotals); // Test with a commit message that isn't the first file. - element._files = [ - {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1}, - {__path: '/COMMIT_MSG', lines_inserted: 9}, - {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1}, - ]; + element._filesByPath = { + 'file_added_in_rev2.txt': { + lines_inserted: 1, + lines_deleted: 1, + }, + '/COMMIT_MSG': { + lines_inserted: 9, + }, + 'myfile.txt': { + lines_inserted: 1, + lines_deleted: 1, + }, + }; + assert.deepEqual(element._patchChange, { inserted: 2, deleted: 2, @@ -203,10 +215,17 @@ limitations under the License. assert.isFalse(element._hideChangeTotals); // Test with no commit message. - element._files = [ - {__path: 'file_added_in_rev2.txt', lines_inserted: 1, lines_deleted: 1}, - {__path: 'myfile.txt', lines_inserted: 1, lines_deleted: 1}, - ]; + element._filesByPath = { + 'file_added_in_rev2.txt': { + lines_inserted: 1, + lines_deleted: 1, + }, + 'myfile.txt': { + lines_inserted: 1, + lines_deleted: 1, + }, + }; + assert.deepEqual(element._patchChange, { inserted: 2, deleted: 2, @@ -218,10 +237,10 @@ limitations under the License. assert.isFalse(element._hideChangeTotals); // Test with files missing either lines_inserted or lines_deleted. - element._files = [ - {__path: 'file_added_in_rev2.txt', lines_inserted: 1}, - {__path: 'myfile.txt', lines_deleted: 1}, - ]; + element._filesByPath = { + 'file_added_in_rev2.txt': {lines_inserted: 1}, + 'myfile.txt': {lines_deleted: 1}, + }; assert.deepEqual(element._patchChange, { inserted: 1, deleted: 1, @@ -234,11 +253,11 @@ limitations under the License. }); test('binary only files', () => { - element._files = [ - {__path: '/COMMIT_MSG', lines_inserted: 9}, - {__path: 'file_binary', binary: true, size_delta: 10, size: 100}, - {__path: 'file_binary', binary: true, size_delta: -5, size: 120}, - ]; + element._filesByPath = { + '/COMMIT_MSG': {lines_inserted: 9}, + 'file_binary_1': {binary: true, size_delta: 10, size: 100}, + 'file_binary_2': {binary: true, size_delta: -5, size: 120}, + }; assert.deepEqual(element._patchChange, { inserted: 0, deleted: 0, @@ -251,13 +270,13 @@ limitations under the License. }); test('binary and regular files', () => { - element._files = [ - {__path: '/COMMIT_MSG', lines_inserted: 9}, - {__path: 'file_binary', binary: true, size_delta: 10, size: 100}, - {__path: 'file_binary', binary: true, size_delta: -5, size: 120}, - {__path: 'myfile.txt', lines_deleted: 5, size_delta: -10, size: 100}, - {__path: 'myfile2.txt', lines_inserted: 10}, - ]; + element._filesByPath = { + '/COMMIT_MSG': {lines_inserted: 9}, + 'file_binary_1': {binary: true, size_delta: 10, size: 100}, + 'file_binary_2': {binary: true, size_delta: -5, size: 120}, + 'myfile.txt': {lines_deleted: 5, size_delta: -10, size: 100}, + 'myfile2.txt': {lines_inserted: 10}, + }; assert.deepEqual(element._patchChange, { inserted: 10, deleted: 5, @@ -325,13 +344,229 @@ limitations under the License. } }); + test('comment filtering', () => { + element.changeComments._comments = { + '/COMMIT_MSG': [ + {patch_set: 1, message: 'Done', updated: '2017-02-08 16:40:49'}, + {patch_set: 1, message: 'oh hay', updated: '2017-02-09 16:40:49'}, + {patch_set: 2, message: 'hello', updated: '2017-02-10 16:40:49'}, + ], + 'myfile.txt': [ + {patch_set: 1, message: 'good news!', updated: '2017-02-08 16:40:49'}, + {patch_set: 2, message: 'wat!?', updated: '2017-02-09 16:40:49'}, + {patch_set: 2, message: 'hi', updated: '2017-02-10 16:40:49'}, + ], + 'unresolved.file': [ + { + patch_set: 2, + message: 'wat!?', + updated: '2017-02-09 16:40:49', + id: '1', + unresolved: true, + }, + { + patch_set: 2, + message: 'hi', + updated: '2017-02-10 16:40:49', + id: '2', + in_reply_to: '1', + unresolved: false, + }, + { + patch_set: 2, + message: 'good news!', + updated: '2017-02-08 16:40:49', + id: '3', + unresolved: true, + }, + ], + }; + element.changeComments._drafts = { + '/COMMIT_MSG': [ + { + patch_set: 1, + message: 'hi', + updated: '2017-02-15 16:40:49', + id: '5', + unresolved: true, + }, + { + patch_set: 1, + message: 'fyi', + updated: '2017-02-15 16:40:49', + id: '6', + unresolved: false, + }, + ], + 'unresolved.file': [ + { + patch_set: 1, + message: 'hi', + updated: '2017-02-11 16:40:49', + id: '4', + unresolved: false, + }, + ], + }; + + const parentTo1 = { + basePatchNum: 'PARENT', + patchNum: '1', + }; + + const parentTo2 = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + + const _1To2 = { + basePatchNum: '1', + patchNum: '2', + }; + + assert.equal( + element._computeCommentsString(element.changeComments, parentTo1, + '/COMMIT_MSG', 'comment'), '2 comments (1 unresolved)'); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + '/COMMIT_MSG', 'comment'), '3 comments (1 unresolved)'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, parentTo1 + , '/COMMIT_MSG'), '2c'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, _1To2 + , '/COMMIT_MSG'), '3c'); + assert.equal( + element._computeDraftsString(element.changeComments, parentTo1, + 'unresolved.file'), '1 draft'); + assert.equal( + element._computeDraftsString(element.changeComments, _1To2, + 'unresolved.file'), '1 draft'); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, parentTo1, + 'unresolved.file'), '1d'); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, _1To2, + 'unresolved.file'), '1d'); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo1, + 'myfile.txt', 'comment'), '1 comment'); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + 'myfile.txt', 'comment'), '3 comments'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, parentTo1, + 'myfile.txt'), '1c'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, _1To2, + 'myfile.txt'), '3c'); + assert.equal( + element._computeDraftsString(element.changeComments, parentTo1, + 'myfile.txt'), ''); + assert.equal( + element._computeDraftsString(element.changeComments, _1To2, + 'myfile.txt'), ''); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, parentTo1, + 'myfile.txt'), ''); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, _1To2, + 'myfile.txt'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo1, + 'file_added_in_rev2.txt', 'comment'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + 'file_added_in_rev2.txt', 'comment'), ''); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, parentTo1, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, _1To2, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeDraftsString(element.changeComments, parentTo1, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeDraftsString(element.changeComments, _1To2, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, parentTo1, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, _1To2, + 'file_added_in_rev2.txt'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo2, + '/COMMIT_MSG', 'comment'), '1 comment'); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + '/COMMIT_MSG', 'comment'), '3 comments (1 unresolved)'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, parentTo2, + '/COMMIT_MSG'), '1c'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, _1To2, + '/COMMIT_MSG'), '3c'); + assert.equal( + element._computeDraftsString(element.changeComments, parentTo1, + '/COMMIT_MSG'), '2 drafts'); + assert.equal( + element._computeDraftsString(element.changeComments, _1To2, + '/COMMIT_MSG'), '2 drafts'); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, parentTo1, + '/COMMIT_MSG'), '2d'); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, _1To2, + '/COMMIT_MSG'), '2d'); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo2, + 'myfile.txt', 'comment'), '2 comments'); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + 'myfile.txt', 'comment'), '3 comments'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, parentTo2, + 'myfile.txt'), '2c'); + assert.equal( + element._computeCommentsStringMobile(element.changeComments, _1To2, + 'myfile.txt'), '3c'); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, parentTo2, + 'myfile.txt'), ''); + assert.equal( + element._computeDraftsStringMobile(element.changeComments, _1To2, + 'myfile.txt'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo2, + 'file_added_in_rev2.txt', 'comment'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + 'file_added_in_rev2.txt', 'comment'), ''); + assert.equal( + element._computeCommentsString(element.changeComments, parentTo2, + 'unresolved.file', 'comment'), '3 comments (1 unresolved)'); + assert.equal( + element._computeCommentsString(element.changeComments, _1To2, + 'unresolved.file', 'comment'), '3 comments (1 unresolved)'); + }); + + test('_reviewedTitle', () => { + assert.equal( + element._reviewedTitle(true), 'Mark as not reviewed (shortcut: r)'); + + assert.equal( + element._reviewedTitle(false), 'Mark as reviewed (shortcut: r)'); + }); + suite('keyboard shortcuts', () => { setup(() => { - element._files = [ - {__path: '/COMMIT_MSG'}, - {__path: 'file_added_in_rev2.txt'}, - {__path: 'myfile.txt'}, - ]; + element._filesByPath = { + '/COMMIT_MSG': {}, + 'file_added_in_rev2.txt': {}, + 'myfile.txt': {}, + }; element.changeNum = '42'; element.patchRange = { basePatchNum: 'PARENT', @@ -403,6 +638,10 @@ limitations under the License. MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k'); assert.equal(element.$.fileCursor.index, 0); assert.equal(element.selectedIndex, 0); + + sandbox.stub(element, '_addDraftAtTarget'); + MockInteractions.pressAndReleaseKeyOn(element, 67, null, 'c'); + assert.isTrue(element._addDraftAtTarget.called); }); test('i key shows/hides selected inline diff', () => { @@ -411,24 +650,24 @@ limitations under the License. const files = Polymer.dom(element.root).querySelectorAll('.file-row'); element.$.fileCursor.stops = files; element.$.fileCursor.setCursorAtIndex(0); - MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i'); + MockInteractions.keyUpOn(element, 73, null, 'i'); flushAsynchronousOperations(); assert.include(element._expandedFilePaths, element.diffs[0].path); - MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i'); + MockInteractions.keyUpOn(element, 73, null, 'i'); flushAsynchronousOperations(); assert.notInclude(element._expandedFilePaths, element.diffs[0].path); element.$.fileCursor.setCursorAtIndex(1); - MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i'); + MockInteractions.keyUpOn(element, 73, null, 'i'); flushAsynchronousOperations(); assert.include(element._expandedFilePaths, element.diffs[1].path); - MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i'); + MockInteractions.keyUpOn(element, 73, 'shift', 'i'); flushAsynchronousOperations(); for (const index in element.diffs) { if (!element.diffs.hasOwnProperty(index)) { continue; } assert.include(element._expandedFilePaths, element.diffs[index].path); } - MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i'); + MockInteractions.keyUpOn(element, 73, 'shift', 'i'); flushAsynchronousOperations(); for (const index in element.diffs) { if (!element.diffs.hasOwnProperty(index)) { continue; } @@ -438,21 +677,24 @@ limitations under the License. }); test('r key toggles reviewed flag', () => { + const reducer = (accum, file) => file.isReviewed ? ++accum : accum; + const getNumReviewed = () => element._files.reduce(reducer, 0); flushAsynchronousOperations(); // Default state should be unreviewed. - assert.equal(element._reviewed.length, 0); + assert.equal(getNumReviewed(), 0); // Press the review key to toggle it (set the flag). MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); - assert.equal(element._reviewed.length, 1); + flushAsynchronousOperations(); + assert.equal(getNumReviewed(), 1); // Press the review key to toggle it (clear the flag). MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); - assert.equal(element._reviewed.length, 0); + assert.equal(getNumReviewed(), 0); }); - suite('_handleOKey', () => { + suite('_handleOpenFile', () => { let interact; setup(() => { @@ -470,7 +712,7 @@ limitations under the License. const e = new CustomEvent('fake-keyboard-event', opt_payload); sinon.stub(e, 'preventDefault'); - element._handleOKey(e); + element._handleOpenFile(e); assert.isTrue(e.preventDefault.called); const result = {}; if (openCursorStub.called) { @@ -494,10 +736,6 @@ limitations under the License. test('open from diff cursor', () => { element._showInlineDiffs = true; assert.deepEqual(interact(), {opened_cursor: true}); - - // "Show diffs" mode overrides userPrefs.expand_inline_diffs - element._userPrefs = {expand_inline_diffs: true}; - assert.deepEqual(interact(), {opened_cursor: true}); }); test('expand when user prefers', () => { @@ -505,123 +743,28 @@ limitations under the License. assert.deepEqual(interact(), {opened_selected: true}); element._userPrefs = {}; assert.deepEqual(interact(), {opened_selected: true}); - element._userPrefs.expand_inline_diffs = true; - assert.deepEqual(interact(), {expanded: true}); }); }); - }); - test('comment filtering', () => { - const comments = { - '/COMMIT_MSG': [ - {patch_set: 1, message: 'Done', updated: '2017-02-08 16:40:49'}, - {patch_set: 1, message: 'oh hay', updated: '2017-02-09 16:40:49'}, - {patch_set: 2, message: 'hello', updated: '2017-02-10 16:40:49'}, - ], - 'myfile.txt': [ - {patch_set: 1, message: 'good news!', updated: '2017-02-08 16:40:49'}, - {patch_set: 2, message: 'wat!?', updated: '2017-02-09 16:40:49'}, - {patch_set: 2, message: 'hi', updated: '2017-02-10 16:40:49'}, - ], - 'unresolved.file': [ - { - patch_set: 2, - message: 'wat!?', - updated: '2017-02-09 16:40:49', - id: '1', - unresolved: true, - }, - { - patch_set: 2, - message: 'hi', - updated: '2017-02-10 16:40:49', - id: '2', - in_reply_to: '1', - unresolved: false, - }, - { - patch_set: 2, - message: 'good news!', - updated: '2017-02-08 16:40:49', - id: '3', - unresolved: true, - }, - ], - }; - const drafts = { - 'unresolved.file': [ - { - patch_set: 2, - message: 'hi', - updated: '2017-02-11 16:40:49', - id: '4', - in_reply_to: '3', - unresolved: false, - }, - ], - }; - assert.equal( - element._computeCountString(comments, '1', '/COMMIT_MSG', 'comment'), - '2 comments'); - assert.equal( - element._computeCommentsStringMobile(comments, '1', '/COMMIT_MSG'), - '2c'); - assert.equal( - element._computeDraftsStringMobile(comments, '1', '/COMMIT_MSG'), - '2d'); - assert.equal( - element._computeCountString(comments, '1', 'myfile.txt', 'comment'), - '1 comment'); - assert.equal( - element._computeCommentsStringMobile(comments, '1', 'myfile.txt'), - '1c'); - assert.equal( - element._computeDraftsStringMobile(comments, '1', 'myfile.txt'), - '1d'); - assert.equal( - element._computeCountString(comments, '1', - 'file_added_in_rev2.txt', 'comment'), ''); - assert.equal( - element._computeCommentsStringMobile(comments, '1', - 'file_added_in_rev2.txt'), ''); - assert.equal( - element._computeDraftsStringMobile(comments, '1', - 'file_added_in_rev2.txt'), ''); - assert.equal( - element._computeCountString(comments, '2', '/COMMIT_MSG', 'comment'), - '1 comment'); - assert.equal( - element._computeCommentsStringMobile(comments, '2', '/COMMIT_MSG'), - '1c'); - assert.equal( - element._computeDraftsStringMobile(comments, '2', '/COMMIT_MSG'), - '1d'); - assert.equal( - element._computeCountString(comments, '2', 'myfile.txt', 'comment'), - '2 comments'); - assert.equal( - element._computeCommentsStringMobile(comments, '2', 'myfile.txt'), - '2c'); - assert.equal( - element._computeDraftsStringMobile(comments, '2', 'myfile.txt'), - '2d'); - assert.equal( - element._computeCountString(comments, '2', - 'file_added_in_rev2.txt', 'comment'), ''); - assert.equal(element._computeCountString(comments, '2', - 'unresolved.file', 'comment'), '3 comments'); - assert.equal( - element._computeUnresolvedString(comments, [], 2, 'myfile.txt'), ''); - assert.equal( - element.computeUnresolvedNum(comments, [], 2, 'myfile.txt'), 0); - assert.equal( - element._computeUnresolvedString(comments, [], 2, 'unresolved.file'), - '(1 unresolved)'); - assert.equal( - element.computeUnresolvedNum(comments, [], 2, 'unresolved.file'), 1); - assert.equal( - element._computeUnresolvedString(comments, drafts, 2, - 'unresolved.file'), ''); + test('shift+left/shift+right', () => { + const moveLeftStub = sandbox.stub(element.$.diffCursor, 'moveLeft'); + const moveRightStub = sandbox.stub(element.$.diffCursor, 'moveRight'); + + let noDiffsExpanded = true; + sandbox.stub(element, '_noDiffsExpanded', () => noDiffsExpanded); + + MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'left'); + assert.isFalse(moveLeftStub.called); + MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'right'); + assert.isFalse(moveRightStub.called); + + noDiffsExpanded = false; + + MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'left'); + assert.isTrue(moveLeftStub.called); + MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'right'); + assert.isTrue(moveRightStub.called); + }); }); test('computed properties', () => { @@ -635,12 +778,12 @@ limitations under the License. }); test('file review status', () => { - element._files = [ - {__path: '/COMMIT_MSG'}, - {__path: 'file_added_in_rev2.txt'}, - {__path: 'myfile.txt'}, - ]; element._reviewed = ['/COMMIT_MSG', 'myfile.txt']; + element._filesByPath = { + '/COMMIT_MSG': {}, + 'file_added_in_rev2.txt': {}, + 'myfile.txt': {}, + }; element._loggedIn = true; element.changeNum = '42'; element.patchRange = { @@ -680,12 +823,80 @@ limitations under the License. assert.isTrue(tapSpy.lastCall.args[0].defaultPrevented); }); + test('_computeFileStatusLabel', () => { + assert.equal(element._computeFileStatusLabel('A'), 'Added'); + assert.equal(element._computeFileStatusLabel('M'), 'Modified'); + }); + + test('_handleFileListTap', () => { + element._filesByPath = { + '/COMMIT_MSG': {}, + 'f1.txt': {}, + 'f2.txt': {}, + }; + element.changeNum = '42'; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + + const tapSpy = sandbox.spy(element, '_handleFileListTap'); + const reviewStub = sandbox.stub(element, '_reviewFile'); + const toggleExpandSpy = sandbox.spy(element, '_togglePathExpanded'); + + const row = Polymer.dom(element.root) + .querySelector('.row[data-path="f1.txt"]'); + + // Click on the expand button, resulting in _togglePathExpanded being + // called and not resulting in a call to _reviewFile. + row.querySelector('div.show-hide').click(); + assert.isTrue(tapSpy.calledOnce); + assert.isTrue(toggleExpandSpy.calledOnce); + assert.isFalse(reviewStub.called); + + // Click inside the diff. This should result in no additional calls to + // _togglePathExpanded or _reviewFile. + Polymer.dom(element.root).querySelector('gr-diff-host').click(); + assert.isTrue(tapSpy.calledTwice); + assert.isTrue(toggleExpandSpy.calledOnce); + assert.isFalse(reviewStub.called); + + // Click the reviewed checkbox, resulting in a call to _reviewFile, but + // no additional call to _togglePathExpanded. + row.querySelector('.markReviewed').click(); + assert.isTrue(tapSpy.calledThrice); + assert.isTrue(toggleExpandSpy.calledOnce); + assert.isTrue(reviewStub.calledOnce); + }); + + test('_handleFileListTap editMode', () => { + element._filesByPath = { + '/COMMIT_MSG': {}, + 'f1.txt': {}, + 'f2.txt': {}, + }; + element.changeNum = '42'; + element.patchRange = { + basePatchNum: 'PARENT', + patchNum: '2', + }; + element.editMode = true; + flushAsynchronousOperations(); + const tapSpy = sandbox.spy(element, '_handleFileListTap'); + const toggleExpandSpy = sandbox.spy(element, '_togglePathExpanded'); + + // Tap the edit controls. Should be ignored by _handleFileListTap. + MockInteractions.tap(element.$$('.editFileControls')); + assert.isTrue(tapSpy.calledOnce); + assert.isFalse(toggleExpandSpy.called); + }); + test('patch set from revisions', () => { const expected = [ - {num: 1, desc: 'test'}, - {num: 2, desc: 'test'}, - {num: 3, desc: 'test'}, {num: 4, desc: 'test'}, + {num: 3, desc: 'test'}, + {num: 2, desc: 'test'}, + {num: 1, desc: 'test'}, ]; const patchNums = element.computeAllPatchSets({ revisions: { @@ -702,9 +913,9 @@ limitations under the License. }); test('checkbox shows/hides diff inline', () => { - element._files = [ - {__path: 'myfile.txt'}, - ]; + element._filesByPath = { + 'myfile.txt': {}, + }; element.changeNum = '42'; element.patchRange = { basePatchNum: 'PARENT', @@ -727,9 +938,9 @@ limitations under the License. }); test('diff mode correctly toggles the diffs', () => { - element._files = [ - {__path: 'myfile.txt'}, - ]; + element._filesByPath = { + 'myfile.txt': {}, + }; element.changeNum = '42'; element.patchRange = { basePatchNum: 'PARENT', @@ -752,18 +963,17 @@ limitations under the License. assert.isTrue(element._updateDiffPreferences.called); }); - test('expanded attribute not set on path when not expanded', () => { - element._files = [ - {__path: '/COMMIT_MSG'}, - ]; + element._filesByPath = { + '/COMMIT_MSG': {}, + }; assert.isNotOk(element.$$('.expanded')); }); - test('expand_inline_diffs user preference', () => { - element._files = [ - {__path: '/COMMIT_MSG'}, - ]; + test('tapping row ignores links', () => { + element._filesByPath = { + '/COMMIT_MSG': {}, + }; element.changeNum = '42'; element.patchRange = { basePatchNum: 'PARENT', @@ -772,7 +982,7 @@ limitations under the License. sandbox.stub(element, '_expandedPathsChanged'); flushAsynchronousOperations(); const commitMsgFile = Polymer.dom(element.root) - .querySelectorAll('.row:not(.header) a')[0]; + .querySelectorAll('.row:not(.header) a.pathLink')[0]; // Remove href attribute so the app doesn't route to a diff view commitMsgFile.removeAttribute('href'); @@ -782,50 +992,53 @@ limitations under the License. flushAsynchronousOperations(); assert(togglePathSpy.notCalled, 'file is opened as diff view'); assert.isNotOk(element.$$('.expanded')); - - element._userPrefs = {expand_inline_diffs: true}; - flushAsynchronousOperations(); - MockInteractions.tap(commitMsgFile); - flushAsynchronousOperations(); - assert(togglePathSpy.calledOnce, 'file is expanded'); - assert.isOk(element.$$('.expanded')); + assert.notEqual(getComputedStyle(element.$$('.show-hide')).display, + 'none'); }); test('_togglePathExpanded', () => { const path = 'path/to/my/file.txt'; - element.files = [{__path: path}]; - const renderStub = sandbox.stub(element, '_renderInOrder') - .returns(Promise.resolve()); + element._filesByPath = {[path]: {}}; + const renderSpy = sandbox.spy(element, '_renderInOrder'); + const collapseStub = sandbox.stub(element, '_clearCollapsedDiffs'); + assert.equal(element.$$('iron-icon').icon, 'gr-icons:expand-more'); assert.equal(element._expandedFilePaths.length, 0); element._togglePathExpanded(path); flushAsynchronousOperations(); + assert.equal(collapseStub.lastCall.args[0].length, 0); + assert.equal(element.$$('iron-icon').icon, 'gr-icons:expand-less'); - assert.equal(renderStub.callCount, 1); + assert.equal(renderSpy.callCount, 1); assert.include(element._expandedFilePaths, path); element._togglePathExpanded(path); flushAsynchronousOperations(); - assert.equal(renderStub.callCount, 2); + assert.equal(element.$$('iron-icon').icon, 'gr-icons:expand-more'); + assert.equal(renderSpy.callCount, 1); assert.notInclude(element._expandedFilePaths, path); + assert.equal(collapseStub.lastCall.args[0].length, 1); }); - test('collapseAllDiffs', () => { - sandbox.stub(element, '_renderInOrder') - .returns(Promise.resolve()); + test('expandAllDiffs and collapseAllDiffs', () => { + const collapseStub = sandbox.stub(element, '_clearCollapsedDiffs'); const cursorUpdateStub = sandbox.stub(element.$.diffCursor, 'handleDiffUpdate'); const path = 'path/to/my/file.txt'; - element.files = [{__path: path}]; - element._expandedFilePaths = [path]; - element._showInlineDiffs = true; + element._filesByPath = {[path]: {}}; + element.expandAllDiffs(); + flushAsynchronousOperations(); + assert.isTrue(element._showInlineDiffs); + assert.isTrue(cursorUpdateStub.calledOnce); + assert.equal(collapseStub.lastCall.args[0].length, 0); element.collapseAllDiffs(); flushAsynchronousOperations(); assert.equal(element._expandedFilePaths.length, 0); assert.isFalse(element._showInlineDiffs); - assert.isTrue(cursorUpdateStub.calledOnce); + assert.isTrue(cursorUpdateStub.calledTwice); + assert.equal(collapseStub.lastCall.args[0].length, 1); }); test('_expandedPathsChanged', done => { @@ -836,6 +1049,9 @@ limitations under the License. reload() { done(); }, + cancel() {}, + getCursorStops() { return []; }, + addEventListener(eventName, callback) { callback(new Event(eventName)); }, }]; sinon.stub(element, 'diffs', { get() { return diffs; }, @@ -843,39 +1059,40 @@ limitations under the License. element.push('_expandedFilePaths', path); }); - suite('_handleFileListTap', () => { - function testForModifier(modifier) { - const e = {preventDefault() {}}; - e.detail = {sourceEvent: {}}; - e.target = { - dataset: {path: '/test'}, - classList: element.classList, - }; - - e.detail.sourceEvent[modifier] = true; - - const togglePathStub = sandbox.stub(element, '_togglePathExpanded'); - element._userPrefs = {expand_inline_diffs: true}; - - element._handleFileListTap(e); - assert.isFalse(togglePathStub.called); - - e.detail.sourceEvent[modifier] = false; - element._handleFileListTap(e); - assert.equal(togglePathStub.callCount, 1); - - element._userPrefs = {expand_inline_diffs: false}; - element._handleFileListTap(e); - assert.equal(togglePathStub.callCount, 1); - } - - test('_handleFileListTap meta', () => { - testForModifier('metaKey'); - }); + test('_clearCollapsedDiffs', () => { + const diff = { + cancel: sinon.stub(), + clearDiffContent: sinon.stub(), + }; + element._clearCollapsedDiffs([diff]); + assert.isTrue(diff.cancel.calledOnce); + assert.isTrue(diff.clearDiffContent.calledOnce); + }); - test('_handleFileListTap ctrl', () => { - testForModifier('ctrlKey'); - }); + test('filesExpanded value updates to correct enum', () => { + element._filesByPath = { + 'foo.bar': {}, + 'baz.bar': {}, + }; + flushAsynchronousOperations(); + assert.equal(element.filesExpanded, + GrFileListConstants.FilesExpandedState.NONE); + element.push('_expandedFilePaths', 'baz.bar'); + flushAsynchronousOperations(); + assert.equal(element.filesExpanded, + GrFileListConstants.FilesExpandedState.SOME); + element.push('_expandedFilePaths', 'foo.bar'); + flushAsynchronousOperations(); + assert.equal(element.filesExpanded, + GrFileListConstants.FilesExpandedState.ALL); + element.collapseAllDiffs(); + flushAsynchronousOperations(); + assert.equal(element.filesExpanded, + GrFileListConstants.FilesExpandedState.NONE); + element.expandAllDiffs(); + flushAsynchronousOperations(); + assert.equal(element.filesExpanded, + GrFileListConstants.FilesExpandedState.ALL); }); test('_renderInOrder', done => { @@ -903,13 +1120,13 @@ limitations under the License. element._renderInOrder(['p2', 'p1', 'p0'], diffs, 3) .then(() => { assert.isFalse(reviewStub.called); - assert.isTrue(loadCommentStub.called); + assert.isTrue(loadCommentSpy.called); done(); }); }); test('_renderInOrder logged in', done => { - element._isLoggedIn = true; + element._loggedIn = true; const reviewStub = sandbox.stub(element, '_reviewFile'); let callCount = 0; const diffs = [{ @@ -941,10 +1158,30 @@ limitations under the License. }); }); + test('_renderInOrder respects diffPrefs.manual_review', () => { + element._loggedIn = true; + element.diffPrefs = {manual_review: true}; + const reviewStub = sandbox.stub(element, '_reviewFile'); + const diffs = [{ + path: 'p', + reload() { return Promise.resolve(); }, + }]; + + return element._renderInOrder(['p'], diffs, 1).then(() => { + assert.isFalse(reviewStub.called); + delete element.diffPrefs.manual_review; + return element._renderInOrder(['p'], diffs, 1).then(() => { + assert.isTrue(reviewStub.called); + assert.isTrue(reviewStub.calledWithExactly('p', true)); + }); + }); + }); + test('_loadingChanged fired from reload in debouncer', done => { + sandbox.stub(element, '_getReviewedFiles').returns(Promise.resolve([])); element.changeNum = 123; element.patchRange = {patchNum: 12}; - element._files = [{__path: 'foo.bar'}]; + element._filesByPath = {'foo.bar': {}}; element.reload().then(() => { assert.isFalse(element._loading); @@ -959,6 +1196,7 @@ limitations under the License. }); test('_loadingChanged does not set class when there are no files', () => { + sandbox.stub(element, '_getReviewedFiles').returns(Promise.resolve([])); element.changeNum = 123; element.patchRange = {patchNum: 12}; element.reload(); @@ -971,7 +1209,8 @@ limitations under the License. const urlStub = sandbox.stub(element, '_computeDiffURL'); element.change = {_number: 123}; element.patchRange = {patchNum: undefined, basePatchNum: 'PARENT'}; - element._files = [{__path: 'foo/bar.cpp'}]; + element._filesByPath = {'foo/bar.cpp': {}}; + element.editMode = false; flush(() => { assert.isFalse(urlStub.called); element.set('patchRange.patchNum', 4); @@ -981,18 +1220,178 @@ limitations under the License. }); }); }); + + suite('size bars', () => { + test('_computeSizeBarLayout', () => { + assert.isUndefined(element._computeSizeBarLayout(null)); + assert.isUndefined(element._computeSizeBarLayout({})); + assert.deepEqual(element._computeSizeBarLayout({base: []}), { + maxInserted: 0, + maxDeleted: 0, + maxAdditionWidth: 0, + maxDeletionWidth: 0, + deletionOffset: 0, + }); + + const files = [ + {__path: '/COMMIT_MSG', lines_inserted: 10000}, + {__path: 'foo', lines_inserted: 4, lines_deleted: 10}, + {__path: 'bar', lines_inserted: 5, lines_deleted: 8}, + ]; + const layout = element._computeSizeBarLayout({base: files}); + assert.equal(layout.maxInserted, 5); + assert.equal(layout.maxDeleted, 10); + }); + + test('_computeBarAdditionWidth', () => { + const file = { + __path: 'foo/bar.baz', + lines_inserted: 5, + lines_deleted: 0, + }; + const stats = { + maxInserted: 10, + maxDeleted: 0, + maxAdditionWidth: 60, + maxDeletionWidth: 0, + deletionOffset: 60, + }; + + // Uses half the space when file is half the largest addition and there + // are no deletions. + assert.equal(element._computeBarAdditionWidth(file, stats), 30); + + // If there are no insetions, there is no width. + stats.maxInserted = 0; + assert.equal(element._computeBarAdditionWidth(file, stats), 0); + + // If the insertions is not present on the file, there is no width. + stats.maxInserted = 10; + file.lines_inserted = undefined; + assert.equal(element._computeBarAdditionWidth(file, stats), 0); + + // If the file is a commit message, returns zero. + file.lines_inserted = 5; + file.__path = '/COMMIT_MSG'; + assert.equal(element._computeBarAdditionWidth(file, stats), 0); + + // Width bottoms-out at the minimum width. + file.__path = 'stuff.txt'; + file.lines_inserted = 1; + stats.maxInserted = 1000000; + assert.equal(element._computeBarAdditionWidth(file, stats), 1.5); + }); + + test('_computeBarAdditionX', () => { + const file = { + __path: 'foo/bar.baz', + lines_inserted: 5, + lines_deleted: 0, + }; + const stats = { + maxInserted: 10, + maxDeleted: 0, + maxAdditionWidth: 60, + maxDeletionWidth: 0, + deletionOffset: 60, + }; + assert.equal(element._computeBarAdditionX(file, stats), 30); + }); + + test('_computeBarDeletionWidth', () => { + const file = { + __path: 'foo/bar.baz', + lines_inserted: 0, + lines_deleted: 5, + }; + const stats = { + maxInserted: 10, + maxDeleted: 10, + maxAdditionWidth: 30, + maxDeletionWidth: 30, + deletionOffset: 31, + }; + + // Uses a quarter the space when file is half the largest deletions and + // there are equal additions. + assert.equal(element._computeBarDeletionWidth(file, stats), 15); + + // If there are no deletions, there is no width. + stats.maxDeleted = 0; + assert.equal(element._computeBarDeletionWidth(file, stats), 0); + + // If the deletions is not present on the file, there is no width. + stats.maxDeleted = 10; + file.lines_deleted = undefined; + assert.equal(element._computeBarDeletionWidth(file, stats), 0); + + // If the file is a commit message, returns zero. + file.lines_deleted = 5; + file.__path = '/COMMIT_MSG'; + assert.equal(element._computeBarDeletionWidth(file, stats), 0); + + // Width bottoms-out at the minimum width. + file.__path = 'stuff.txt'; + file.lines_deleted = 1; + stats.maxDeleted = 1000000; + assert.equal(element._computeBarDeletionWidth(file, stats), 1.5); + }); + + test('_computeSizeBarsClass', () => { + assert.equal(element._computeSizeBarsClass(false, 'foo/bar.baz'), + 'sizeBars desktop hide'); + assert.equal(element._computeSizeBarsClass(true, '/COMMIT_MSG'), + 'sizeBars desktop invisible'); + assert.equal(element._computeSizeBarsClass(true, 'foo/bar.baz'), + 'sizeBars desktop '); + }); + }); }); suite('gr-file-list inline diff tests', () => { let element; let sandbox; + const commitMsgComments = [ + { + patch_set: 2, + id: 'ecf0b9fa_fe1a5f62', + line: 20, + updated: '2018-02-08 18:49:18.000000000', + message: 'another comment', + unresolved: true, + }, + { + patch_set: 2, + id: '503008e2_0ab203ee', + line: 10, + updated: '2018-02-14 22:07:43.000000000', + message: 'response', + unresolved: true, + }, + { + patch_set: 2, + id: 'cc788d2c_cb1d728c', + line: 20, + in_reply_to: 'ecf0b9fa_fe1a5f62', + updated: '2018-02-13 22:07:43.000000000', + message: 'a comments', + unresolved: true, + }, + ]; + const setupDiff = function(diff) { const mock = document.createElement('mock-diff-response'); - diff._diff = mock.diffResponse; diff.comments = { - left: [], + left: diff.path === '/COMMIT_MSG' ? commitMsgComments : [], right: [], + meta: { + changeNum: 1, + patchRange: { + basePatchNum: 'PARENT', + patchNum: 2, + }, + }, }; diff.prefs = { context: 10, @@ -1010,12 +1409,12 @@ limitations under the License. theme: 'DEFAULT', ignore_whitespace: 'IGNORE_NONE', }; - diff._renderDiffTable(); + diff._diff = mock.diffResponse; }; const renderAndGetNewDiffs = function(index) { const diffs = - Polymer.dom(element.root).querySelectorAll('gr-diff'); + Polymer.dom(element.root).querySelectorAll('gr-diff-host'); for (let i = index; i < diffs.length; i++) { setupDiff(diffs[i]); @@ -1026,43 +1425,56 @@ limitations under the License. return diffs; }; - setup(() => { + setup(done => { sandbox = sinon.sandbox.create(); stub('gr-rest-api-interface', { getLoggedIn() { return Promise.resolve(true); }, getPreferences() { return Promise.resolve({}); }, + getDiffComments() { return Promise.resolve({}); }, + getDiffRobotComments() { return Promise.resolve({}); }, + getDiffDrafts() { return Promise.resolve({}); }, }); stub('gr-date-formatter', { _loadTimeFormat() { return Promise.resolve(''); }, }); - stub('gr-diff', { + stub('gr-diff-host', { reload() { return Promise.resolve(); }, }); - stub('gr-comment-api', { - loadAll() { return Promise.resolve(); }, - getPaths() { return {}; }, - getCommentsForPath() { return {meta: {}, left: [], right: []}; }, + + // Element must be wrapped in an element with direct access to the + // comment API. + commentApiWrapper = fixture('basic'); + element = commentApiWrapper.$.fileList; + loadCommentSpy = sandbox.spy(commentApiWrapper.$.commentAPI, 'loadAll'); + element.diffPrefs = {}; + sandbox.stub(element, '_reviewFile'); + + // Stub methods on the changeComments object after changeComments has + // been initalized. + commentApiWrapper.loadComments().then(() => { + sandbox.stub(element.changeComments, 'getPaths').returns({}); + sandbox.stub(element.changeComments, 'getCommentsBySideForPath') + .returns({meta: {}, left: [], right: []}); + done(); }); - element = fixture('basic'); + element._loading = false; element.numFilesShown = 75; element.selectedIndex = 0; - element._files = [ - {__path: '/COMMIT_MSG', lines_inserted: 9}, - { - __path: 'file_added_in_rev2.txt', + element._filesByPath = { + '/COMMIT_MSG': {lines_inserted: 9}, + 'file_added_in_rev2.txt': { lines_inserted: 1, lines_deleted: 1, size_delta: 10, size: 100, }, - { - __path: 'myfile.txt', + 'myfile.txt': { lines_inserted: 1, lines_deleted: 1, size_delta: 10, size: 100, }, - ]; + }; element._reviewed = ['/COMMIT_MSG', 'myfile.txt']; element._loggedIn = true; element.changeNum = '42'; @@ -1081,7 +1493,7 @@ limitations under the License. }); test('cursor with individually opened files', () => { - MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i'); + MockInteractions.keyUpOn(element, 73, null, 'i'); flushAsynchronousOperations(); let diffs = renderAndGetNewDiffs(0); const diffStops = diffs[0].getCursorStops(); @@ -1107,7 +1519,7 @@ limitations under the License. // The file cusor is now at 1. assert.equal(element.$.fileCursor.index, 1); - MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i'); + MockInteractions.keyUpOn(element, 73, null, 'i'); flushAsynchronousOperations(); diffs = renderAndGetNewDiffs(1); @@ -1122,7 +1534,7 @@ limitations under the License. }); test('cursor with toggle all files', () => { - MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i'); + MockInteractions.keyUpOn(element, 73, 'shift', 'i'); flushAsynchronousOperations(); const diffs = renderAndGetNewDiffs(0); @@ -1156,9 +1568,10 @@ limitations under the License. let nextCommentStub; let nextChunkStub; let fileRows; + setup(() => { sandbox.stub(element, '_renderInOrder').returns(Promise.resolve()); - nKeySpy = sandbox.spy(element, '_handleNKey'); + nKeySpy = sandbox.spy(element, '_handleNextChunk'); nextCommentStub = sandbox.stub(element.$.diffCursor, 'moveToNextCommentThread'); nextChunkStub = sandbox.stub(element.$.diffCursor, @@ -1166,9 +1579,11 @@ limitations under the License. fileRows = Polymer.dom(element.root).querySelectorAll('.row:not(.header)'); }); - test('n key with all files expanded and no shift key', () => { - MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i'); + + test('n key with some files expanded and no shift key', () => { + MockInteractions.keyUpOn(fileRows[0], 73, null, 'i'); flushAsynchronousOperations(); + assert.equal(nextChunkStub.callCount, 1); // Handle N key should return before calling diff cursor functions. MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n'); @@ -1176,25 +1591,26 @@ limitations under the License. assert.isFalse(nextCommentStub.called); // This is also called in diffCursor.moveToFirstChunk. - assert.equal(nextChunkStub.callCount, 1); - assert.isFalse(!!element._showInlineDiffs); + assert.equal(nextChunkStub.callCount, 2); + assert.equal(element.filesExpanded, 'some'); }); - test('n key with all files expanded and shift key', () => { - MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i'); + test('n key with some files expanded and shift key', () => { + MockInteractions.keyUpOn(fileRows[0], 73, null, 'i'); flushAsynchronousOperations(); + assert.equal(nextChunkStub.callCount, 1); MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n'); assert.isTrue(nKeySpy.called); - assert.isFalse(nextCommentStub.called); + assert.isTrue(nextCommentStub.called); // This is also called in diffCursor.moveToFirstChunk. assert.equal(nextChunkStub.callCount, 1); - assert.isFalse(!!element._showInlineDiffs); + assert.equal(element.filesExpanded, 'some'); }); test('n key without all files expanded and shift key', () => { - MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i'); + MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i'); flushAsynchronousOperations(); MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n'); @@ -1207,7 +1623,7 @@ limitations under the License. }); test('n key without all files expanded and no shift key', () => { - MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i'); + MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i'); flushAsynchronousOperations(); MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n'); @@ -1221,14 +1637,14 @@ limitations under the License. }); test('_openSelectedFile behavior', () => { - const _files = element._files; - element.set('_files', []); + const _filesByPath = element._filesByPath; + element.set('_filesByPath', {}); const navStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff'); // Noop when there are no files. element._openSelectedFile(); assert.isFalse(navStub.called); - element.set('_files', _files); + element.set('_filesByPath', _filesByPath); flushAsynchronousOperations(); // Navigates when a file is selected. element._openSelectedFile(); @@ -1242,11 +1658,11 @@ limitations under the License. const mockEvent = {preventDefault() {}}; element._displayLine = false; - element._handleDownKey(mockEvent); + element._handleCursorNext(mockEvent); assert.isTrue(element._displayLine); element._displayLine = false; - element._handleUpKey(mockEvent); + element._handleCursorPrev(mockEvent); assert.isTrue(element._displayLine); element._displayLine = true; @@ -1254,42 +1670,135 @@ limitations under the License. assert.isFalse(element._displayLine); }); - suite('editLoaded behavior', () => { - const isVisible = el => { - assert.ok(el); - return getComputedStyle(el).getPropertyValue('display') !== 'none'; - }; - + suite('editMode behavior', () => { test('reviewed checkbox', () => { - const alertStub = sandbox.stub(); + element._reviewFile.restore(); const saveReviewStub = sandbox.stub(element, '_saveReviewedState'); - element.addEventListener('show-alert', alertStub); - element.editLoaded = false; - // Reviewed checkbox should be shown. - assert.isTrue(isVisible(element.$$('.reviewed'))); + element.editMode = false; MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); - assert.isFalse(alertStub.called); assert.isTrue(saveReviewStub.calledOnce); - element.editLoaded = true; + element.editMode = true; flushAsynchronousOperations(); - assert.isFalse(isVisible(element.$$('.reviewed'))); MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r'); - assert.isTrue(alertStub.called); assert.isTrue(saveReviewStub.calledOnce); }); test('_getReviewedFiles does not call API', () => { const apiSpy = sandbox.spy(element.$.restAPI, 'getReviewedFiles'); - element.editLoaded = true; + element.editMode = true; return element._getReviewedFiles().then(files => { assert.equal(files.length, 0); assert.isFalse(apiSpy.called); }); }); }); + + test('editing actions', () => { + // Edit controls are guarded behind a dom-if initially and not rendered. + assert.isNotOk(Polymer.dom(element.root) + .querySelector('gr-edit-file-controls')); + + element.editMode = true; + flushAsynchronousOperations(); + + // Commit message should not have edit controls. + const editControls = + Polymer.dom(element.root).querySelectorAll('.row:not(.header)') + .map(row => row.querySelector('gr-edit-file-controls')); + assert.isTrue(editControls[0].classList.contains('invisible')); + }); + + test('reloadCommentsForThreadWithRootId', () => { + const commentStub = + sandbox.stub(element.changeComments, 'getCommentsForThread'); + const commentStubRes1 = [ + { + patch_set: 2, + id: 'ecf0b9fa_fe1a5f62', + line: 20, + updated: '2018-02-08 18:49:18.000000000', + message: 'another comment', + unresolved: true, + }, + { + patch_set: 2, + id: '503008e2_0ab203ee', + line: 10, + updated: '2018-02-14 22:07:43.000000000', + message: 'response', + unresolved: true, + }, + { + patch_set: 2, + id: '503008e2_0ab203ef', + line: 20, + in_reply_to: 'ecf0b9fa_fe1a5f62', + updated: '2018-02-15 22:07:43.000000000', + message: 'a third comment in the thread', + unresolved: true, + }, + ]; + const commentStubRes2 = [ + { + patch_set: 2, + id: 'ecf0b9fa_fe1a5f62', + line: 20, + updated: '2018-02-08 18:49:18.000000000', + message: 'edited text', + unresolved: false, + }, + ]; + commentStub.withArgs('cc788d2c_cb1d728c').returns( + commentStubRes1); + commentStub.withArgs('ecf0b9fa_fe1a5f62').returns( + commentStubRes2); + // Expand the commit message diff + MockInteractions.keyUpOn(element, 73, 'shift', 'i'); + const diffs = renderAndGetNewDiffs(0); + flushAsynchronousOperations(); + + // Two comment threads sould be generated + const commentThreadEls = diffs[0].getThreadEls(); + assert(commentThreadEls[0].comments.length, 2); + assert(commentThreadEls[1].comments.length, 1); + assert.isTrue(commentThreadEls[1].comments[0].unresolved); + assert.equal(commentThreadEls[1].comments[0].message, 'another comment'); + + // Reload comments from the first comment thread, which should have a new + // reply. + element.reloadCommentsForThreadWithRootId('cc788d2c_cb1d728c', + '/COMMIT_MSG'); + assert(commentThreadEls[0].comments.length, 3); + + + // Reload comments from the first comment thread, which should have a + // an updated message and a toggled resolve state. + element.reloadCommentsForThreadWithRootId('ecf0b9fa_fe1a5f62', + '/COMMIT_MSG'); + assert(commentThreadEls[1].comments.length, 1); + assert.isFalse(commentThreadEls[1].comments[0].unresolved); + assert.equal(commentThreadEls[1].comments[0].message, 'edited text'); + + const commentStubCount = commentStub.callCount; + const getThreadsSpy = sandbox.spy(diffs[0], 'getThreadEls'); + + // Should not be getting threadss when the file is not expanded. + element.reloadCommentsForThreadWithRootId('ecf0b9fa_fe1a5f62', + 'other/file'); + assert.isFalse(getThreadsSpy.called); + assert.equal(commentStubCount, commentStub.callCount); + + // Should be query selecting diffs when the file is expanded. + // Should not be fetching change comments when the rootId is not found + // to match. + element.reloadCommentsForThreadWithRootId('acf0b9fa_fe1a5f62', + '/COMMIT_MSG'); + assert.isTrue(getThreadsSpy.called); + assert.equal(commentStubCount, commentStub.callCount); + }); }); a11ySuite('basic'); </script> |