| <!DOCTYPE html> |
| <!-- |
| Copyright (C) 2015 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> |
| <title>gr-diff-comment</title> |
| |
| <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> |
| <script src="../../../bower_components/web-component-tester/browser.js"></script> |
| <link rel="import" href="../../../test/common-test-setup.html"/> |
| <script src="../../../bower_components/page/page.js"></script> |
| <script src="../../../scripts/util.js"></script> |
| |
| <link rel="import" href="gr-diff-comment.html"> |
| |
| <script>void(0);</script> |
| |
| <test-fixture id="basic"> |
| <template> |
| <gr-diff-comment></gr-diff-comment> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="draft"> |
| <template> |
| <gr-diff-comment draft="true"></gr-diff-comment> |
| </template> |
| </test-fixture> |
| |
| <script> |
| |
| function isVisible(el) { |
| assert.ok(el); |
| return getComputedStyle(el).getPropertyValue('display') !== 'none'; |
| } |
| |
| suite('gr-diff-comment tests', () => { |
| let element; |
| let sandbox; |
| setup(() => { |
| stub('gr-rest-api-interface', { |
| getAccount() { return Promise.resolve(null); }, |
| }); |
| element = fixture('basic'); |
| element.comment = { |
| author: { |
| name: 'Mr. Peanutbutter', |
| email: 'tenn1sballchaser@aol.com', |
| }, |
| id: 'baf0414d_60047215', |
| line: 5, |
| message: 'is this a crossover episode!?', |
| updated: '2015-12-08 19:48:33.843000000', |
| }; |
| sandbox = sinon.sandbox.create(); |
| }); |
| |
| teardown(() => { |
| sandbox.restore(); |
| }); |
| |
| test('collapsible comments', () => { |
| // When a comment (not draft) is loaded, it should be collapsed |
| assert.isTrue(element.collapsed); |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isFalse(isVisible(element.$$('.actions')), |
| 'actions are not visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| |
| // The header middle content is only visible when comments are collapsed. |
| // It shows the message in a condensed way, and limits to a single line. |
| assert.isTrue(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is visible'); |
| |
| // When the header row is clicked, the comment should expand |
| MockInteractions.tap(element.$.header); |
| assert.isFalse(element.collapsed); |
| assert.isTrue(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is visible'); |
| assert.isTrue(isVisible(element.$$('.actions')), |
| 'actions are visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isFalse(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is not visible'); |
| }); |
| |
| test('clicking on date link does not trigger nav', () => { |
| const showStub = sinon.stub(page, 'show'); |
| const dateEl = element.$$('.date'); |
| assert.ok(dateEl); |
| MockInteractions.tap(dateEl); |
| const dest = window.location.pathname + '#5'; |
| assert(showStub.lastCall.calledWithExactly(dest, null, false), |
| 'Should navigate to ' + dest + ' without triggering nav'); |
| showStub.restore(); |
| }); |
| |
| test('message is not retrieved from storage when other edits', done => { |
| const storageStub = sandbox.stub(element.$.storage, 'getDraftComment'); |
| const loadSpy = sandbox.spy(element, '_loadLocalDraft'); |
| |
| element.changeNum = 1; |
| element.patchNum = 1; |
| element.comment = { |
| author: { |
| name: 'Mr. Peanutbutter', |
| email: 'tenn1sballchaser@aol.com', |
| }, |
| line: 5, |
| __otherEditing: true, |
| }; |
| flush(() => { |
| assert.isTrue(loadSpy.called); |
| assert.isFalse(storageStub.called); |
| done(); |
| }); |
| }); |
| |
| test('message is retrieved from storage when no other edits', done => { |
| const storageStub = sandbox.stub(element.$.storage, 'getDraftComment'); |
| const loadSpy = sandbox.spy(element, '_loadLocalDraft'); |
| |
| element.changeNum = 1; |
| element.patchNum = 1; |
| element.comment = { |
| author: { |
| name: 'Mr. Peanutbutter', |
| email: 'tenn1sballchaser@aol.com', |
| }, |
| line: 5, |
| }; |
| flush(() => { |
| assert.isTrue(loadSpy.called); |
| assert.isTrue(storageStub.called); |
| done(); |
| }); |
| }); |
| |
| test('_getPatchNum', () => { |
| element.side = 'PARENT'; |
| element.patchNum = 1; |
| assert.equal(element._getPatchNum(), 'PARENT'); |
| element.side = 'REVISION'; |
| assert.equal(element._getPatchNum(), 1); |
| }); |
| |
| test('comment expand and collapse', () => { |
| element.collapsed = true; |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isFalse(isVisible(element.$$('.actions')), |
| 'actions are not visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isTrue(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is visible'); |
| |
| element.collapsed = false; |
| assert.isFalse(element.collapsed); |
| assert.isTrue(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is visible'); |
| assert.isTrue(isVisible(element.$$('.actions')), |
| 'actions are visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isFalse(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is is not visible'); |
| }); |
| |
| suite('while editing', () => { |
| setup(() => { |
| element.editing = true; |
| element._messageText = 'test'; |
| sandbox.stub(element, '_handleCancel'); |
| sandbox.stub(element, '_handleSave'); |
| flushAsynchronousOperations(); |
| }); |
| |
| suite('when text is empty', () => { |
| setup(() => { |
| element._messageText = ''; |
| }); |
| |
| test('esc closes comment when text is empty', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 27); // esc |
| assert.isTrue(element._handleCancel.called); |
| }); |
| |
| test('ctrl+enter does not save', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 13, 'ctrl'); // ctrl + enter |
| assert.isFalse(element._handleSave.called); |
| }); |
| |
| test('meta+enter does not save', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 13, 'meta'); // meta + enter |
| assert.isFalse(element._handleSave.called); |
| }); |
| |
| test('ctrl+s does not save', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 83, 'ctrl'); // ctrl + s |
| assert.isFalse(element._handleSave.called); |
| }); |
| }); |
| |
| test('esc does not close comment that has content', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 27); // esc |
| assert.isFalse(element._handleCancel.called); |
| }); |
| |
| test('ctrl+enter saves', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 13, 'ctrl'); // ctrl + enter |
| assert.isTrue(element._handleSave.called); |
| }); |
| |
| test('meta+enter saves', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 13, 'meta'); // meta + enter |
| assert.isTrue(element._handleSave.called); |
| }); |
| |
| test('ctrl+s saves', () => { |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea, 83, 'ctrl'); // ctrl + s |
| assert.isTrue(element._handleSave.called); |
| }); |
| }); |
| test('delete comment button for non-admins is hidden', () => { |
| element._isAdmin = false; |
| assert.isFalse(element.$$('.action.delete') |
| .classList.contains('showDeleteButtons')); |
| }); |
| |
| test('delete comment button for admins with draft is hidden', () => { |
| element._isAdmin = false; |
| element.draft = true; |
| assert.isFalse(element.$$('.action.delete') |
| .classList.contains('showDeleteButtons')); |
| }); |
| |
| test('delete comment', done => { |
| sandbox.stub( |
| element.$.restAPI, 'deleteComment').returns(Promise.resolve({})); |
| sandbox.spy(element.$.overlay, 'open'); |
| element.changeNum = 42; |
| element.patchNum = 0xDEADBEEF; |
| element._isAdmin = true; |
| assert.isTrue(element.$$('.action.delete') |
| .classList.contains('showDeleteButtons')); |
| MockInteractions.tap(element.$$('.action.delete')); |
| flush(() => { |
| element.$.overlay.open.lastCall.returnValue.then(() => { |
| element.$.confirmDeleteComment.message = 'removal reason'; |
| element._handleConfirmDeleteComment(); |
| assert.isTrue(element.$.restAPI.deleteComment.calledWith( |
| 42, 0xDEADBEEF, 'baf0414d_60047215', 'removal reason')); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| suite('gr-diff-comment draft tests', () => { |
| let element; |
| let sandbox; |
| |
| setup(() => { |
| stub('gr-rest-api-interface', { |
| getAccount() { return Promise.resolve(null); }, |
| saveDiffDraft() { |
| return Promise.resolve({ |
| ok: true, |
| text() { |
| return Promise.resolve( |
| ')]}\'\n{' + |
| '"id": "baf0414d_40572e03",' + |
| '"path": "/path/to/file",' + |
| '"line": 5,' + |
| '"updated": "2015-12-08 21:52:36.177000000",' + |
| '"message": "saved!"' + |
| '}' |
| ); |
| }, |
| }); |
| }, |
| removeChangeReviewer() { |
| return Promise.resolve({ok: true}); |
| }, |
| }); |
| stub('gr-storage', { |
| getDraftComment() { return null; }, |
| }); |
| element = fixture('draft'); |
| element.changeNum = 42; |
| element.patchNum = 1; |
| element.editing = false; |
| element.comment = { |
| __commentSide: 'right', |
| __draft: true, |
| __draftID: 'temp_draft_id', |
| path: '/path/to/file', |
| line: 5, |
| }; |
| element.commentSide = 'right'; |
| sandbox = sinon.sandbox.create(); |
| }); |
| |
| teardown(() => { |
| sandbox.restore(); |
| }); |
| |
| test('button visibility states', () => { |
| element.showActions = false; |
| assert.isTrue(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.showActions = true; |
| assert.isFalse(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.draft = true; |
| assert.isTrue(isVisible(element.$$('.edit')), 'edit is visible'); |
| assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible'); |
| assert.isFalse(isVisible(element.$$('.save')), 'save is not visible'); |
| assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible'); |
| assert.isFalse(isVisible(element.$$('.resolve')), |
| 'resolve is not visible'); |
| assert.isFalse(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.editing = true; |
| assert.isFalse(isVisible(element.$$('.edit')), 'edit is not visible'); |
| assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible'); |
| assert.isTrue(isVisible(element.$$('.save')), 'save is visible'); |
| assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is visible'); |
| assert.isTrue(isVisible(element.$$('.resolve')), 'resolve is visible'); |
| assert.isFalse(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.draft = false; |
| element.editing = false; |
| assert.isFalse(isVisible(element.$$('.edit')), 'edit is not visible'); |
| assert.isFalse(isVisible(element.$$('.discard')), |
| 'discard is not visible'); |
| assert.isFalse(isVisible(element.$$('.save')), 'save is not visible'); |
| assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible'); |
| assert.isFalse(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.comment.id = 'foo'; |
| element.draft = true; |
| element.editing = true; |
| assert.isTrue(isVisible(element.$$('.cancel')), 'cancel is visible'); |
| assert.isFalse(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isTrue(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| element.isRobotComment = true; |
| element.draft = true; |
| assert.isTrue(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isFalse(element.$$('.robotActions').hasAttribute('hidden')); |
| |
| // It is not expected to see Robot comment drafts, but if they appear, |
| // they will behave the same as non-drafts. |
| element.draft = false; |
| assert.isTrue(element.$$('.humanActions').hasAttribute('hidden')); |
| assert.isFalse(element.$$('.robotActions').hasAttribute('hidden')); |
| }); |
| |
| test('collapsible drafts', () => { |
| assert.isTrue(element.collapsed); |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isFalse(isVisible(element.$$('.actions')), |
| 'actions are not visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isTrue(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is visible'); |
| |
| MockInteractions.tap(element.$.header); |
| assert.isFalse(element.collapsed); |
| assert.isTrue(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is visible'); |
| assert.isTrue(isVisible(element.$$('.actions')), |
| 'actions are visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isFalse(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is is not visible'); |
| |
| // When the edit button is pressed, should still see the actions |
| // and also textarea |
| MockInteractions.tap(element.$$('.edit')); |
| assert.isFalse(element.collapsed); |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isTrue(isVisible(element.$$('.actions')), |
| 'actions are visible'); |
| assert.isTrue(isVisible(element.$$('gr-textarea')), |
| 'textarea is visible'); |
| assert.isFalse(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is not visible'); |
| |
| // When toggle again, everything should be hidden except for textarea |
| // and header middle content should be visible |
| MockInteractions.tap(element.$.header); |
| assert.isTrue(element.collapsed); |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isFalse(isVisible(element.$$('.actions')), |
| 'actions are not visible'); |
| assert.isFalse(isVisible(element.$$('gr-textarea')), |
| 'textarea is not visible'); |
| assert.isTrue(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is visible'); |
| |
| // When toggle again, textarea should remain open in the state it was |
| // before |
| MockInteractions.tap(element.$.header); |
| assert.isFalse(isVisible(element.$$('gr-formatted-text')), |
| 'gr-formatted-text is not visible'); |
| assert.isTrue(isVisible(element.$$('.actions')), |
| 'actions are visible'); |
| assert.isTrue(isVisible(element.$$('gr-textarea')), |
| 'textarea is visible'); |
| assert.isFalse(isVisible(element.$$('.collapsedContent')), |
| 'header middle content is not visible'); |
| }); |
| |
| test('draft creation/cancelation', done => { |
| assert.isFalse(element.editing); |
| MockInteractions.tap(element.$$('.edit')); |
| assert.isTrue(element.editing); |
| |
| element._messageText = ''; |
| const eraseMessageDraftSpy = sandbox.spy(element, '_eraseDraftComment'); |
| |
| // Save should be disabled on an empty message. |
| let disabled = element.$$('.save').hasAttribute('disabled'); |
| assert.isTrue(disabled, 'save button should be disabled.'); |
| element._messageText = ' '; |
| disabled = element.$$('.save').hasAttribute('disabled'); |
| assert.isTrue(disabled, 'save button should be disabled.'); |
| |
| const updateStub = sinon.stub(); |
| element.addEventListener('comment-update', updateStub); |
| |
| let numDiscardEvents = 0; |
| element.addEventListener('comment-discard', e => { |
| numDiscardEvents++; |
| assert.isFalse(eraseMessageDraftSpy.called); |
| if (numDiscardEvents === 2) { |
| assert.isFalse(updateStub.called); |
| done(); |
| } |
| }); |
| MockInteractions.tap(element.$$('.cancel')); |
| element.flushDebouncer('fire-update'); |
| element._messageText = ''; |
| MockInteractions.pressAndReleaseKeyOn(element.$.editTextarea, 27); // esc |
| }); |
| |
| test('draft discard removes message from storage', done => { |
| element._messageText = ''; |
| const eraseMessageDraftSpy = sandbox.spy(element, '_eraseDraftComment'); |
| |
| element.addEventListener('comment-discard', e => { |
| assert.isTrue(eraseMessageDraftSpy.called); |
| done(); |
| }); |
| MockInteractions.tap(element.$$('.discard')); |
| }); |
| |
| test('ctrl+s saves comment', done => { |
| const stub = sinon.stub(element, 'save', () => { |
| assert.isTrue(stub.called); |
| stub.restore(); |
| done(); |
| }); |
| element._messageText = 'is that the horse from horsing around??'; |
| MockInteractions.pressAndReleaseKeyOn( |
| element.$.editTextarea.$.textarea.textarea, |
| 83, 'ctrl'); // 'ctrl + s' |
| }); |
| |
| test('draft saving/editing', done => { |
| const fireStub = sinon.stub(element, 'fire'); |
| const cancelDebounce = sandbox.stub(element, 'cancelDebouncer'); |
| |
| element.draft = true; |
| MockInteractions.tap(element.$$('.edit')); |
| element._messageText = 'good news, everyone!'; |
| element.flushDebouncer('fire-update'); |
| element.flushDebouncer('store'); |
| assert(fireStub.calledWith('comment-update'), |
| 'comment-update should be sent'); |
| assert.deepEqual(fireStub.lastCall.args, [ |
| 'comment-update', { |
| comment: { |
| __commentSide: 'right', |
| __draft: true, |
| __draftID: 'temp_draft_id', |
| __editing: true, |
| line: 5, |
| path: '/path/to/file', |
| message: 'good news, everyone!', |
| unresolved: false, |
| }, |
| patchNum: 1, |
| }, |
| ]); |
| MockInteractions.tap(element.$$('.save')); |
| |
| assert.isTrue(element.disabled, |
| 'Element should be disabled when creating draft.'); |
| |
| element._xhrPromise.then(draft => { |
| assert(fireStub.calledWith('comment-save'), |
| 'comment-save should be sent'); |
| assert(cancelDebounce.calledWith('store')); |
| |
| assert.deepEqual(fireStub.lastCall.args[1], { |
| comment: { |
| __commentSide: 'right', |
| __draft: true, |
| __draftID: 'temp_draft_id', |
| __editing: false, |
| id: 'baf0414d_40572e03', |
| line: 5, |
| message: 'saved!', |
| path: '/path/to/file', |
| updated: '2015-12-08 21:52:36.177000000', |
| }, |
| patchNum: 1, |
| }); |
| assert.isFalse(element.disabled, |
| 'Element should be enabled when done creating draft.'); |
| assert.equal(draft.message, 'saved!'); |
| assert.isFalse(element.editing); |
| }).then(() => { |
| MockInteractions.tap(element.$$('.edit')); |
| element._messageText = 'You’ll be delivering a package to Chapek 9, ' + |
| 'a world where humans are killed on sight.'; |
| MockInteractions.tap(element.$$('.save')); |
| assert.isTrue(element.disabled, |
| 'Element should be disabled when updating draft.'); |
| |
| element._xhrPromise.then(draft => { |
| assert.isFalse(element.disabled, |
| 'Element should be enabled when done updating draft.'); |
| assert.equal(draft.message, 'saved!'); |
| assert.isFalse(element.editing); |
| fireStub.restore(); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('clicking on date link does not trigger nav', () => { |
| const showStub = sinon.stub(page, 'show'); |
| const dateEl = element.$$('.date'); |
| assert.ok(dateEl); |
| MockInteractions.tap(dateEl); |
| const dest = window.location.pathname + '#5'; |
| assert(showStub.lastCall.calledWithExactly(dest, null, false), |
| 'Should navigate to ' + dest + ' without triggering nav'); |
| showStub.restore(); |
| }); |
| |
| test('proper event fires on resolve', done => { |
| element.addEventListener('comment-update', e => { |
| assert.isTrue(e.detail.comment.unresolved); |
| done(); |
| }); |
| MockInteractions.tap(element.$$('.resolve input')); |
| }); |
| |
| test('resolved comment state indicated by checkbox', () => { |
| element.comment = {unresolved: false}; |
| assert.isTrue(element.$$('.resolve input').checked); |
| element.comment = {unresolved: true}; |
| assert.isFalse(element.$$('.resolve input').checked); |
| }); |
| |
| suite('draft saving messages', () => { |
| test('_getSavingMessage', () => { |
| assert.equal(element._getSavingMessage(0), 'All changes saved'); |
| assert.equal(element._getSavingMessage(1), 'Saving 1 draft...'); |
| assert.equal(element._getSavingMessage(2), 'Saving 2 drafts...'); |
| assert.equal(element._getSavingMessage(3), 'Saving 3 drafts...'); |
| }); |
| |
| test('_show{Start,End}Request', () => { |
| const updateStub = sandbox.stub(element, '_updateRequestToast'); |
| element._numPendingDiffRequests.number = 1; |
| |
| element._showStartRequest(); |
| assert.isTrue(updateStub.calledOnce); |
| assert.equal(updateStub.lastCall.args[0], 2); |
| assert.equal(element._numPendingDiffRequests.number, 2); |
| |
| element._showEndRequest(); |
| assert.isTrue(updateStub.calledTwice); |
| assert.equal(updateStub.lastCall.args[0], 1); |
| assert.equal(element._numPendingDiffRequests.number, 1); |
| |
| element._showEndRequest(); |
| assert.isTrue(updateStub.calledThrice); |
| assert.equal(updateStub.lastCall.args[0], 0); |
| assert.equal(element._numPendingDiffRequests.number, 0); |
| }); |
| }); |
| }); |
| </script> |