| <!DOCTYPE html> | |
| <!-- | |
| @license | |
| Copyright (C) 2015 The Android Open Source Project | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| --> | |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> | |
| <title>gr-diff</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="../../../scripts/util.js"></script> | |
| <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html"> | |
| <link rel="import" href="gr-diff.html"> | |
| <script>void(0);</script> | |
| <test-fixture id="basic"> | |
| <template> | |
| <gr-diff></gr-diff> | |
| </template> | |
| </test-fixture> | |
| <script> | |
| suite('gr-diff tests', () => { | |
| let element; | |
| let sandbox; | |
| setup(() => { | |
| sandbox = sinon.sandbox.create(); | |
| }); | |
| teardown(() => { | |
| sandbox.restore(); | |
| }); | |
| test('reload cancels before network resolves', () => { | |
| element = fixture('basic'); | |
| const cancelStub = sandbox.stub(element, 'cancel'); | |
| // Stub the network calls into requests that never resolve. | |
| sandbox.stub(element, '_getDiff', () => new Promise(() => {})); | |
| element.reload(); | |
| assert.isTrue(cancelStub.called); | |
| }); | |
| test('cancel', () => { | |
| const cancelStub = sandbox.stub(element.$.diffBuilder, 'cancel'); | |
| element.cancel(); | |
| assert.isTrue(cancelStub.calledOnce); | |
| }); | |
| test('_diffLength', () => { | |
| element = fixture('basic'); | |
| const mock = document.createElement('mock-diff-response'); | |
| assert.equal(element._diffLength(mock.diffResponse), 52); | |
| }); | |
| test('line limit with line_wrapping', () => { | |
| element = fixture('basic'); | |
| element.prefs = {line_wrapping: true, line_length: 80, tab_size: 2}; | |
| flushAsynchronousOperations(); | |
| assert.equal(element.customStyle['--line-limit'], '80ch'); | |
| }); | |
| test('line limit without line_wrapping', () => { | |
| element = fixture('basic'); | |
| element.prefs = {line_wrapping: false, line_length: 80, tab_size: 2}; | |
| flushAsynchronousOperations(); | |
| assert.isNotOk(element.customStyle['--line-limit']); | |
| }); | |
| suite('_get{PatchNum|IsParentComment}ByLineAndContent', () => { | |
| let lineEl; | |
| let contentEl; | |
| setup(() => { | |
| element = fixture('basic'); | |
| lineEl = document.createElement('td'); | |
| contentEl = document.createElement('span'); | |
| }); | |
| suite('_getPatchNumByLineAndContent', () => { | |
| test('right side', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| lineEl.classList.add('right'); | |
| assert.equal(element._getPatchNumByLineAndContent(lineEl, contentEl), | |
| 4); | |
| }); | |
| test('left side parent by linenum', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| lineEl.classList.add('left'); | |
| assert.equal(element._getPatchNumByLineAndContent(lineEl, contentEl), | |
| 4); | |
| }); | |
| test('left side parent by content', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| contentEl.classList.add('remove'); | |
| assert.equal(element._getPatchNumByLineAndContent(lineEl, contentEl), | |
| 4); | |
| }); | |
| test('left side merge parent', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: -2}; | |
| contentEl.classList.add('remove'); | |
| assert.equal(element._getPatchNumByLineAndContent(lineEl, contentEl), | |
| 4); | |
| }); | |
| test('left side non parent', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 3}; | |
| contentEl.classList.add('remove'); | |
| assert.equal(element._getPatchNumByLineAndContent(lineEl, contentEl), | |
| 3); | |
| }); | |
| }); | |
| suite('_getIsParentCommentByLineAndContent', () => { | |
| test('right side', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| lineEl.classList.add('right'); | |
| assert.isFalse( | |
| element._getIsParentCommentByLineAndContent(lineEl, contentEl)); | |
| }); | |
| test('left side parent by linenum', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| lineEl.classList.add('left'); | |
| assert.isTrue( | |
| element._getIsParentCommentByLineAndContent(lineEl, contentEl)); | |
| }); | |
| test('left side parent by content', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 'PARENT'}; | |
| contentEl.classList.add('remove'); | |
| assert.isTrue( | |
| element._getIsParentCommentByLineAndContent(lineEl, contentEl)); | |
| }); | |
| test('left side merge parent', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: -2}; | |
| contentEl.classList.add('remove'); | |
| assert.isTrue( | |
| element._getIsParentCommentByLineAndContent(lineEl, contentEl)); | |
| }); | |
| test('left side non parent', () => { | |
| element.patchRange = {patchNum: 4, basePatchNum: 3}; | |
| contentEl.classList.add('remove'); | |
| assert.isFalse( | |
| element._getIsParentCommentByLineAndContent(lineEl, contentEl)); | |
| }); | |
| }); | |
| }); | |
| suite('not logged in', () => { | |
| setup(() => { | |
| stub('gr-rest-api-interface', { | |
| getLoggedIn() { return Promise.resolve(false); }, | |
| }); | |
| element = fixture('basic'); | |
| }); | |
| test('toggleLeftDiff', () => { | |
| element.toggleLeftDiff(); | |
| assert.isTrue(element.classList.contains('no-left')); | |
| element.toggleLeftDiff(); | |
| assert.isFalse(element.classList.contains('no-left')); | |
| }); | |
| test('addDraftAtLine', done => { | |
| sandbox.stub(element, '_selectLine'); | |
| const loggedInErrorSpy = sandbox.spy(); | |
| element.addEventListener('show-auth-required', loggedInErrorSpy); | |
| element.addDraftAtLine(); | |
| flush(() => { | |
| assert.isTrue(loggedInErrorSpy.called); | |
| done(); | |
| }); | |
| }); | |
| test('view does not start with displayLine classList', () => { | |
| assert.isFalse( | |
| element.$$('.diffContainer').classList.contains('displayLine')); | |
| }); | |
| test('displayLine class added called when displayLine is true', () => { | |
| const spy = sandbox.spy(element, '_computeContainerClass'); | |
| element.displayLine = true; | |
| assert.isTrue(spy.called); | |
| assert.isTrue( | |
| element.$$('.diffContainer').classList.contains('displayLine')); | |
| }); | |
| test('loads files weblinks', () => { | |
| const weblinksStub = sandbox.stub(Gerrit.Nav, '_generateWeblinks') | |
| .returns({name: 'stubb', url: '#s'}); | |
| sandbox.stub(element.$.restAPI, 'getDiff').returns(Promise.resolve({})); | |
| element.projectName = 'test-project'; | |
| element.path = 'test-path'; | |
| element.commitRange = {baseCommit: 'test-base', commit: 'test-commit'}; | |
| element.patchRange = {}; | |
| return element._getDiff().then(() => { | |
| assert.isTrue(weblinksStub.calledTwice); | |
| assert.isTrue(weblinksStub.firstCall.calledWith({ | |
| commit: 'test-base', | |
| file: 'test-path', | |
| options: { | |
| weblinks: undefined, | |
| }, | |
| repo: 'test-project', | |
| type: Gerrit.Nav.WeblinkType.FILE})); | |
| assert.isTrue(weblinksStub.secondCall.calledWith({ | |
| commit: 'test-commit', | |
| file: 'test-path', | |
| options: { | |
| weblinks: undefined, | |
| }, | |
| repo: 'test-project', | |
| type: Gerrit.Nav.WeblinkType.FILE})); | |
| assert.deepEqual(element.filesWeblinks, { | |
| meta_a: [{name: 'stubb', url: '#s'}], | |
| meta_b: [{name: 'stubb', url: '#s'}], | |
| }); | |
| }); | |
| }); | |
| test('remove comment', () => { | |
| element.comments = { | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bc2', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| {id: 'd2', __draft: true, __commentSide: 'right'}, | |
| ], | |
| }; | |
| element._removeComment({}); | |
| // Using JSON.stringify because Safari 9.1 (11601.5.17.1) doesn’t seem | |
| // to believe that one object deepEquals another even when they do :-/. | |
| assert.equal(JSON.stringify(element.comments), JSON.stringify({ | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bc2', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| {id: 'd2', __draft: true, __commentSide: 'right'}, | |
| ], | |
| })); | |
| element._removeComment({id: 'bc2', side: 'PARENT', | |
| __commentSide: 'left'}); | |
| assert.deepEqual(element.comments, { | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| {id: 'd2', __draft: true, __commentSide: 'right'}, | |
| ], | |
| }); | |
| element._removeComment({id: 'd2', __commentSide: 'right'}); | |
| assert.deepEqual(element.comments, { | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| ], | |
| }); | |
| }); | |
| test('_getRangeString', () => { | |
| const side = 'PARENT'; | |
| const range = { | |
| startLine: 1, | |
| startChar: 1, | |
| endLine: 1, | |
| endChar: 2, | |
| }; | |
| assert.equal(element._getRangeString(side, range), | |
| 'range-1-1-1-2-PARENT'); | |
| assert.equal(element._getRangeString(side, null), | |
| 'line-PARENT'); | |
| }), | |
| test('thread groups', () => { | |
| const contentEl = document.createElement('div'); | |
| const commentSide = 'left'; | |
| const patchNum = 1; | |
| const side = 'PARENT'; | |
| let range = { | |
| startLine: 1, | |
| startChar: 1, | |
| endLine: 1, | |
| endChar: 2, | |
| }; | |
| element.changeNum = 123; | |
| element.patchRange = {basePatchNum: 1, patchNum: 2}; | |
| element.path = 'file.txt'; | |
| const mock = document.createElement('mock-diff-response'); | |
| element.$.diffBuilder._builder = element.$.diffBuilder._getDiffBuilder( | |
| mock.diffResponse, {}, {tab_size: 2, line_length: 80}); | |
| // No thread groups. | |
| assert.isNotOk(element._getThreadGroupForLine(contentEl)); | |
| // A thread group gets created. | |
| assert.isOk(element._getOrCreateThread(contentEl, | |
| patchNum, commentSide, side)); | |
| // Try to fetch a thread with a different range. | |
| range = { | |
| startLine: 1, | |
| startChar: 1, | |
| endLine: 1, | |
| endChar: 3, | |
| }; | |
| assert.isOk(element._getOrCreateThread( | |
| contentEl, patchNum, commentSide, side, range)); | |
| // The new thread group can be fetched. | |
| assert.isOk(element._getThreadGroupForLine(contentEl)); | |
| assert.equal(contentEl.querySelectorAll( | |
| 'gr-diff-comment-thread-group').length, 1); | |
| const threadGroup = contentEl.querySelector( | |
| 'gr-diff-comment-thread-group'); | |
| const threadLength = Polymer.dom(threadGroup.root). | |
| querySelectorAll('gr-diff-comment-thread').length; | |
| assert.equal(threadLength, 2); | |
| }); | |
| suite('image diffs', () => { | |
| let mockFile1; | |
| let mockFile2; | |
| const stubs = []; | |
| setup(() => { | |
| mockFile1 = { | |
| body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' + | |
| 'wsAAAAAAAAAAAAAAAAA/w==', | |
| type: 'image/bmp', | |
| }; | |
| mockFile2 = { | |
| body: 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' + | |
| 'wsAAAAAAAAAAAAA/////w==', | |
| type: 'image/bmp', | |
| }; | |
| const mockCommit = { | |
| commit: '9a1a1d10baece5efbba10bc4ccf808a67a50ac0a', | |
| parents: [{ | |
| commit: '7338aa9adfe57909f1fdaf88975cdea467d3382f', | |
| subject: 'Added a carrot', | |
| }], | |
| author: { | |
| name: 'Wyatt Allen', | |
| email: 'wyatta@google.com', | |
| date: '2016-05-23 21:44:51.000000000', | |
| tz: -420, | |
| }, | |
| committer: { | |
| name: 'Wyatt Allen', | |
| email: 'wyatta@google.com', | |
| date: '2016-05-25 00:25:41.000000000', | |
| tz: -420, | |
| }, | |
| subject: 'Updated the carrot', | |
| message: 'Updated the carrot\n\nChange-Id: Iabcd123\n', | |
| }; | |
| const mockComments = {baseComments: [], comments: []}; | |
| stubs.push(sandbox.stub(element.$.restAPI, 'getCommitInfo', | |
| () => Promise.resolve(mockCommit))); | |
| stubs.push(sandbox.stub(element.$.restAPI, | |
| 'getB64FileContents', | |
| (changeId, patchNum, path, opt_parentIndex) => { | |
| return Promise.resolve(opt_parentIndex === 1 ? mockFile1 : | |
| mockFile2); | |
| })); | |
| stubs.push(sandbox.stub(element.$.restAPI, '_getDiffComments', | |
| () => Promise.resolve(mockComments))); | |
| stubs.push(sandbox.stub(element.$.restAPI, 'getDiffDrafts', | |
| () => Promise.resolve(mockComments))); | |
| element.patchRange = {basePatchNum: 'PARENT', patchNum: 1}; | |
| element.comments = {left: [], right: []}; | |
| }); | |
| test('renders image diffs with same file name', done => { | |
| const mockDiff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66}, | |
| meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| intraline_status: 'OK', | |
| change_type: 'MODIFIED', | |
| diff_header: [ | |
| 'diff --git a/carrot.jpg b/carrot.jpg', | |
| 'index 2adc47d..f9c2f2c 100644', | |
| '--- a/carrot.jpg', | |
| '+++ b/carrot.jpg', | |
| 'Binary files differ', | |
| ], | |
| content: [{skip: 66}], | |
| binary: true, | |
| }; | |
| stubs.push(sandbox.stub(element, '_getDiff', | |
| () => Promise.resolve(mockDiff))); | |
| const rendered = () => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assert.instanceOf( | |
| element.$.diffBuilder._builder, GrDiffBuilderImage); | |
| // Left image rendered with the parent commit's version of the file. | |
| const leftImage = element.$.diffTable.querySelector('td.left img'); | |
| const leftLabel = | |
| element.$.diffTable.querySelector('td.left label'); | |
| const leftLabelContent = leftLabel.querySelector('.label'); | |
| const leftLabelName = leftLabel.querySelector('.name'); | |
| const rightImage = | |
| element.$.diffTable.querySelector('td.right img'); | |
| const rightLabel = element.$.diffTable.querySelector( | |
| 'td.right label'); | |
| const rightLabelContent = rightLabel.querySelector('.label'); | |
| const rightLabelName = rightLabel.querySelector('.name'); | |
| assert.isNotOk(rightLabelName); | |
| assert.isNotOk(leftLabelName); | |
| let leftLoaded = false; | |
| let rightLoaded = false; | |
| leftImage.addEventListener('load', () => { | |
| assert.isOk(leftImage); | |
| assert.equal(leftImage.getAttribute('src'), | |
| 'data:image/bmp;base64, ' + mockFile1.body); | |
| assert.equal(leftLabelContent.textContent, '1×1 image/bmp'); | |
| leftLoaded = true; | |
| if (rightLoaded) { | |
| element.removeEventListener('render', rendered); | |
| done(); | |
| } | |
| }); | |
| rightImage.addEventListener('load', () => { | |
| assert.isOk(rightImage); | |
| assert.equal(rightImage.getAttribute('src'), | |
| 'data:image/bmp;base64, ' + mockFile2.body); | |
| assert.equal(rightLabelContent.textContent, '1×1 image/bmp'); | |
| rightLoaded = true; | |
| if (leftLoaded) { | |
| element.removeEventListener('render', rendered); | |
| done(); | |
| } | |
| }); | |
| }; | |
| element.addEventListener('render', rendered); | |
| element.$.restAPI.getDiffPreferences().then(prefs => { | |
| element.prefs = prefs; | |
| element.reload(); | |
| }); | |
| }); | |
| test('renders image diffs with a different file name', done => { | |
| const mockDiff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66}, | |
| meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| intraline_status: 'OK', | |
| change_type: 'MODIFIED', | |
| diff_header: [ | |
| 'diff --git a/carrot.jpg b/carrot2.jpg', | |
| 'index 2adc47d..f9c2f2c 100644', | |
| '--- a/carrot.jpg', | |
| '+++ b/carrot2.jpg', | |
| 'Binary files differ', | |
| ], | |
| content: [{skip: 66}], | |
| binary: true, | |
| }; | |
| stubs.push(sandbox.stub(element, '_getDiff', | |
| () => Promise.resolve(mockDiff))); | |
| const rendered = () => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assert.instanceOf( | |
| element.$.diffBuilder._builder, GrDiffBuilderImage); | |
| // Left image rendered with the parent commit's version of the file. | |
| const leftImage = element.$.diffTable.querySelector('td.left img'); | |
| const leftLabel = | |
| element.$.diffTable.querySelector('td.left label'); | |
| const leftLabelContent = leftLabel.querySelector('.label'); | |
| const leftLabelName = leftLabel.querySelector('.name'); | |
| const rightImage = | |
| element.$.diffTable.querySelector('td.right img'); | |
| const rightLabel = element.$.diffTable.querySelector( | |
| 'td.right label'); | |
| const rightLabelContent = rightLabel.querySelector('.label'); | |
| const rightLabelName = rightLabel.querySelector('.name'); | |
| assert.isOk(rightLabelName); | |
| assert.isOk(leftLabelName); | |
| assert.equal(leftLabelName.textContent, mockDiff.meta_a.name); | |
| assert.equal(rightLabelName.textContent, mockDiff.meta_b.name); | |
| let leftLoaded = false; | |
| let rightLoaded = false; | |
| leftImage.addEventListener('load', () => { | |
| assert.isOk(leftImage); | |
| assert.equal(leftImage.getAttribute('src'), | |
| 'data:image/bmp;base64, ' + mockFile1.body); | |
| assert.equal(leftLabelContent.textContent, '1×1 image/bmp'); | |
| leftLoaded = true; | |
| if (rightLoaded) { | |
| element.removeEventListener('render', rendered); | |
| done(); | |
| } | |
| }); | |
| rightImage.addEventListener('load', () => { | |
| assert.isOk(rightImage); | |
| assert.equal(rightImage.getAttribute('src'), | |
| 'data:image/bmp;base64, ' + mockFile2.body); | |
| assert.equal(rightLabelContent.textContent, '1×1 image/bmp'); | |
| rightLoaded = true; | |
| if (leftLoaded) { | |
| element.removeEventListener('render', rendered); | |
| done(); | |
| } | |
| }); | |
| }; | |
| element.addEventListener('render', rendered); | |
| element.$.restAPI.getDiffPreferences().then(prefs => { | |
| element.prefs = prefs; | |
| element.reload(); | |
| }); | |
| }); | |
| test('renders added image', done => { | |
| const mockDiff = { | |
| meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| intraline_status: 'OK', | |
| change_type: 'ADDED', | |
| diff_header: [ | |
| 'diff --git a/carrot.jpg b/carrot.jpg', | |
| 'index 0000000..f9c2f2c 100644', | |
| '--- /dev/null', | |
| '+++ b/carrot.jpg', | |
| 'Binary files differ', | |
| ], | |
| content: [{skip: 66}], | |
| binary: true, | |
| }; | |
| stubs.push(sandbox.stub(element, '_getDiff', | |
| () => Promise.resolve(mockDiff))); | |
| element.addEventListener('render', () => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assert.instanceOf( | |
| element.$.diffBuilder._builder, GrDiffBuilderImage); | |
| const leftImage = element.$.diffTable.querySelector('td.left img'); | |
| const rightImage = element.$.diffTable.querySelector('td.right img'); | |
| assert.isNotOk(leftImage); | |
| assert.isOk(rightImage); | |
| done(); | |
| }); | |
| element.$.restAPI.getDiffPreferences().then(prefs => { | |
| element.prefs = prefs; | |
| element.reload(); | |
| }); | |
| }); | |
| test('renders removed image', done => { | |
| const mockDiff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| intraline_status: 'OK', | |
| change_type: 'DELETED', | |
| diff_header: [ | |
| 'diff --git a/carrot.jpg b/carrot.jpg', | |
| 'index f9c2f2c..0000000 100644', | |
| '--- a/carrot.jpg', | |
| '+++ /dev/null', | |
| 'Binary files differ', | |
| ], | |
| content: [{skip: 66}], | |
| binary: true, | |
| }; | |
| stubs.push(sandbox.stub(element, '_getDiff', | |
| () => Promise.resolve(mockDiff))); | |
| element.addEventListener('render', () => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assert.instanceOf( | |
| element.$.diffBuilder._builder, GrDiffBuilderImage); | |
| const leftImage = element.$.diffTable.querySelector('td.left img'); | |
| const rightImage = element.$.diffTable.querySelector('td.right img'); | |
| assert.isOk(leftImage); | |
| assert.isNotOk(rightImage); | |
| done(); | |
| }); | |
| element.$.restAPI.getDiffPreferences().then(prefs => { | |
| element.prefs = prefs; | |
| element.reload(); | |
| }); | |
| }); | |
| test('does not render disallowed image type', done => { | |
| const mockDiff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil', | |
| lines: 560}, | |
| intraline_status: 'OK', | |
| change_type: 'DELETED', | |
| diff_header: [ | |
| 'diff --git a/carrot.jpg b/carrot.jpg', | |
| 'index f9c2f2c..0000000 100644', | |
| '--- a/carrot.jpg', | |
| '+++ /dev/null', | |
| 'Binary files differ', | |
| ], | |
| content: [{skip: 66}], | |
| binary: true, | |
| }; | |
| mockFile1.type = 'image/jpeg-evil'; | |
| stubs.push(sandbox.stub(element, '_getDiff', | |
| () => Promise.resolve(mockDiff))); | |
| element.addEventListener('render', () => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assert.instanceOf( | |
| element.$.diffBuilder._builder, GrDiffBuilderImage); | |
| const leftImage = element.$.diffTable.querySelector('td.left img'); | |
| assert.isNotOk(leftImage); | |
| done(); | |
| }); | |
| element.$.restAPI.getDiffPreferences().then(prefs => { | |
| element.prefs = prefs; | |
| element.reload(); | |
| }); | |
| }); | |
| }); | |
| test('_handleTap lineNum', done => { | |
| const addDraftStub = sandbox.stub(element, 'addDraftAtLine'); | |
| const el = document.createElement('div'); | |
| el.className = 'lineNum'; | |
| el.addEventListener('click', e => { | |
| element._handleTap(e); | |
| assert.isTrue(addDraftStub.called); | |
| assert.equal(addDraftStub.lastCall.args[0], el); | |
| done(); | |
| }); | |
| el.click(); | |
| }); | |
| test('_handleTap context', done => { | |
| const showContextStub = | |
| sandbox.stub(element.$.diffBuilder, 'showContext'); | |
| const el = document.createElement('div'); | |
| el.className = 'showContext'; | |
| el.addEventListener('click', e => { | |
| element._handleTap(e); | |
| assert.isTrue(showContextStub.called); | |
| done(); | |
| }); | |
| el.click(); | |
| }); | |
| test('_handleTap content', done => { | |
| const content = document.createElement('div'); | |
| const lineEl = document.createElement('div'); | |
| const selectStub = sandbox.stub(element, '_selectLine'); | |
| sandbox.stub(element.$.diffBuilder, 'getLineElByChild', () => lineEl); | |
| content.className = 'content'; | |
| content.addEventListener('click', e => { | |
| element._handleTap(e); | |
| assert.isTrue(selectStub.called); | |
| assert.equal(selectStub.lastCall.args[0], lineEl); | |
| done(); | |
| }); | |
| content.click(); | |
| }); | |
| test('_getDiff handles null diff responses', done => { | |
| stub('gr-rest-api-interface', { | |
| getDiff() { return Promise.resolve(null); }, | |
| }); | |
| element.changeNum = 123; | |
| element.patchRange = {basePatchNum: 1, patchNum: 2}; | |
| element.path = 'file.txt'; | |
| element._getDiff().then(done); | |
| }); | |
| suite('getCursorStops', () => { | |
| const setupDiff = function() { | |
| const mock = document.createElement('mock-diff-response'); | |
| element._diff = mock.diffResponse; | |
| element.comments = { | |
| left: [], | |
| right: [], | |
| }; | |
| element.prefs = { | |
| context: 10, | |
| tab_size: 8, | |
| font_size: 12, | |
| line_length: 100, | |
| cursor_blink_rate: 0, | |
| line_wrapping: false, | |
| intraline_difference: true, | |
| show_line_endings: true, | |
| show_tabs: true, | |
| show_whitespace_errors: true, | |
| syntax_highlighting: true, | |
| auto_hide_diff_table_header: true, | |
| theme: 'DEFAULT', | |
| ignore_whitespace: 'IGNORE_NONE', | |
| }; | |
| element._renderDiffTable(); | |
| flushAsynchronousOperations(); | |
| }; | |
| test('getCursorStops returns [] when hidden and noAutoRender', () => { | |
| element.noAutoRender = true; | |
| setupDiff(); | |
| element.hidden = true; | |
| assert.equal(element.getCursorStops().length, 0); | |
| }); | |
| test('getCursorStops', () => { | |
| setupDiff(); | |
| assert.equal(element.getCursorStops().length, 50); | |
| }); | |
| }); | |
| test('adds .hiddenscroll', () => { | |
| Gerrit.hiddenscroll = true; | |
| element.displayLine = true; | |
| assert.include(element.$$('.diffContainer').className, 'hiddenscroll'); | |
| }); | |
| }); | |
| suite('logged in', () => { | |
| let fakeLineEl; | |
| setup(() => { | |
| stub('gr-rest-api-interface', { | |
| getLoggedIn() { return Promise.resolve(true); }, | |
| getPreferences() { | |
| return Promise.resolve({time_format: 'HHMM_12'}); | |
| }, | |
| getAccountCapabilities() { return Promise.resolve(); }, | |
| }); | |
| element = fixture('basic'); | |
| element.patchRange = {}; | |
| fakeLineEl = { | |
| getAttribute: sandbox.stub().returns(42), | |
| classList: { | |
| contains: sandbox.stub().returns(true), | |
| }, | |
| }; | |
| }); | |
| test('addDraftAtLine', done => { | |
| sandbox.stub(element, '_selectLine'); | |
| sandbox.stub(element, '_createComment'); | |
| const loggedInErrorSpy = sandbox.spy(); | |
| element.addEventListener('show-auth-required', loggedInErrorSpy); | |
| element.addDraftAtLine(fakeLineEl); | |
| flush(() => { | |
| assert.isFalse(loggedInErrorSpy.called); | |
| assert.isTrue(element._createComment | |
| .calledWithExactly(fakeLineEl, 42)); | |
| done(); | |
| }); | |
| }); | |
| test('addDraftAtLine on an edit', done => { | |
| element.patchRange.basePatchNum = element.EDIT_NAME; | |
| sandbox.stub(element, '_selectLine'); | |
| sandbox.stub(element, '_createComment'); | |
| const loggedInErrorSpy = sandbox.spy(); | |
| const alertSpy = sandbox.spy(); | |
| element.addEventListener('show-auth-required', loggedInErrorSpy); | |
| element.addEventListener('show-alert', alertSpy); | |
| element.addDraftAtLine(fakeLineEl); | |
| flush(() => { | |
| assert.isFalse(loggedInErrorSpy.called); | |
| assert.isTrue(alertSpy.called); | |
| assert.isFalse(element._createComment.called); | |
| done(); | |
| }); | |
| }); | |
| test('addDraftAtLine on an edit base', done => { | |
| element.patchRange.patchNum = element.EDIT_NAME; | |
| element.patchRange.basePatchNum = element.PARENT_NAME; | |
| sandbox.stub(element, '_selectLine'); | |
| sandbox.stub(element, '_createComment'); | |
| const loggedInErrorSpy = sandbox.spy(); | |
| const alertSpy = sandbox.spy(); | |
| element.addEventListener('show-auth-required', loggedInErrorSpy); | |
| element.addEventListener('show-alert', alertSpy); | |
| element.addDraftAtLine(fakeLineEl); | |
| flush(() => { | |
| assert.isFalse(loggedInErrorSpy.called); | |
| assert.isTrue(alertSpy.called); | |
| assert.isFalse(element._createComment.called); | |
| done(); | |
| }); | |
| }); | |
| suite('change in preferences', () => { | |
| setup(() => { | |
| element._diff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66}, | |
| meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| diff_header: [], | |
| intraline_status: 'OK', | |
| change_type: 'MODIFIED', | |
| content: [{skip: 66}], | |
| }; | |
| element.comments = { | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bc2', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| {id: 'd2', __draft: true, __commentSide: 'right'}, | |
| ], | |
| }; | |
| }); | |
| test('change in preferences re-renders diff', () => { | |
| sandbox.stub(element, '_renderDiffTable'); | |
| element.prefs = {}; | |
| element.prefs = {time_format: 'HHMM_12'}; | |
| assert.isTrue(element._renderDiffTable.called); | |
| }); | |
| test('change in preferences does not re-renders diff with ' + | |
| 'noRenderOnPrefsChange', () => { | |
| sandbox.stub(element, '_renderDiffTable'); | |
| element.noRenderOnPrefsChange = true; | |
| element.prefs = {}; | |
| element.prefs = {time_format: 'HHMM_12'}; | |
| assert.isFalse(element._renderDiffTable.called); | |
| }); | |
| }); | |
| suite('handle comment-update', () => { | |
| setup(() => { | |
| element.comments = { | |
| meta: { | |
| changeNum: '42', | |
| patchRange: { | |
| basePatchNum: 'PARENT', | |
| patchNum: 3, | |
| }, | |
| path: '/path/to/foo', | |
| projectConfig: {foo: 'bar'}, | |
| }, | |
| left: [ | |
| {id: 'bc1', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bc2', side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd1', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| {id: 'bd2', __draft: true, side: 'PARENT', __commentSide: 'left'}, | |
| ], | |
| right: [ | |
| {id: 'c1', __commentSide: 'right'}, | |
| {id: 'c2', __commentSide: 'right'}, | |
| {id: 'd1', __draft: true, __commentSide: 'right'}, | |
| {id: 'd2', __draft: true, __commentSide: 'right'}, | |
| ], | |
| }; | |
| }); | |
| test('creating a draft', () => { | |
| const comment = {__draft: true, __draftID: 'tempID', side: 'PARENT', | |
| __commentSide: 'left'}; | |
| element.fire('comment-update', {comment}); | |
| assert.include(element.comments.left, comment); | |
| }); | |
| test('discarding a draft', () => { | |
| const draftID = 'tempID'; | |
| const id = 'savedID'; | |
| const comment = { | |
| __draft: true, | |
| __draftID: draftID, | |
| side: 'PARENT', | |
| __commentSide: 'left', | |
| }; | |
| const diffCommentsModifiedStub = sandbox.stub(); | |
| element.addEventListener('diff-comments-modified', | |
| diffCommentsModifiedStub); | |
| element.comments.left.push(comment); | |
| comment.id = id; | |
| element.fire('comment-discard', {comment}); | |
| const drafts = element.comments.left.filter(item => { | |
| return item.__draftID === draftID; | |
| }); | |
| assert.equal(drafts.length, 0); | |
| assert.isTrue(diffCommentsModifiedStub.called); | |
| }); | |
| test('saving a draft', () => { | |
| const draftID = 'tempID'; | |
| const id = 'savedID'; | |
| const comment = { | |
| __draft: true, | |
| __draftID: draftID, | |
| side: 'PARENT', | |
| __commentSide: 'left', | |
| }; | |
| const diffCommentsModifiedStub = sandbox.stub(); | |
| element.addEventListener('diff-comments-modified', | |
| diffCommentsModifiedStub); | |
| element.comments.left.push(comment); | |
| comment.id = id; | |
| element.fire('comment-save', {comment}); | |
| const drafts = element.comments.left.filter(item => { | |
| return item.__draftID === draftID; | |
| }); | |
| assert.equal(drafts.length, 1); | |
| assert.equal(drafts[0].id, id); | |
| assert.isTrue(diffCommentsModifiedStub.called); | |
| }); | |
| }); | |
| }); | |
| suite('diff header', () => { | |
| setup(() => { | |
| element._diff = { | |
| meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66}, | |
| meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', | |
| lines: 560}, | |
| diff_header: [], | |
| intraline_status: 'OK', | |
| change_type: 'MODIFIED', | |
| content: [{skip: 66}], | |
| }; | |
| }); | |
| test('hidden', () => { | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| element.push('_diff.diff_header', 'diff --git a/test.jpg b/test.jpg'); | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| element.push('_diff.diff_header', 'index 2adc47d..f9c2f2c 100644'); | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| element.push('_diff.diff_header', '--- a/test.jpg'); | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| element.push('_diff.diff_header', '+++ b/test.jpg'); | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| element.push('_diff.diff_header', 'test'); | |
| assert.equal(element._diffHeaderItems.length, 1); | |
| flushAsynchronousOperations(); | |
| assert.equal(element.$.diffHeader.textContent.trim(), 'test'); | |
| element.set('_diff.binary', true); | |
| assert.equal(element._diffHeaderItems.length, 0); | |
| }); | |
| }); | |
| suite('safety and bypass', () => { | |
| let renderStub; | |
| setup(() => { | |
| element = fixture('basic'); | |
| renderStub = sandbox.stub(element.$.diffBuilder, 'render', | |
| () => Promise.resolve()); | |
| const mock = document.createElement('mock-diff-response'); | |
| element._diff = mock.diffResponse; | |
| element.comments = {left: [], right: []}; | |
| element.noRenderOnPrefsChange = true; | |
| }); | |
| test('lage render w/ context = 10', () => { | |
| element.prefs = {context: 10}; | |
| sandbox.stub(element, '_diffLength', () => 10000); | |
| return element._renderDiffTable().then(() => { | |
| assert.isTrue(renderStub.called); | |
| assert.isFalse(element._showWarning); | |
| }); | |
| }); | |
| test('lage render w/ whole file and bypass', () => { | |
| element.prefs = {context: -1}; | |
| element._safetyBypass = 10; | |
| sandbox.stub(element, '_diffLength', () => 10000); | |
| return element._renderDiffTable().then(() => { | |
| assert.isTrue(renderStub.called); | |
| assert.isFalse(element._showWarning); | |
| }); | |
| }); | |
| test('lage render w/ whole file and no bypass', () => { | |
| element.prefs = {context: -1}; | |
| sandbox.stub(element, '_diffLength', () => 10000); | |
| return element._renderDiffTable().then(() => { | |
| assert.isFalse(renderStub.called); | |
| assert.isTrue(element._showWarning); | |
| }); | |
| }); | |
| }); | |
| suite('blame', () => { | |
| setup(() => { | |
| element = fixture('basic'); | |
| }); | |
| test('clearBlame', () => { | |
| element._blame = []; | |
| const setBlameSpy = sandbox.spy(element.$.diffBuilder, 'setBlame'); | |
| element.classList.add('showBlame'); | |
| element.clearBlame(); | |
| assert.isNull(element._blame); | |
| assert.isTrue(setBlameSpy.calledWithExactly(null)); | |
| assert.isFalse(element.classList.contains('showBlame')); | |
| }); | |
| test('loadBlame', () => { | |
| const mockBlame = [{id: 'commit id', ranges: [{start: 1, end: 2}]}]; | |
| const showAlertStub = sinon.stub(); | |
| element.addEventListener('show-alert', showAlertStub); | |
| const getBlameStub = sandbox.stub(element.$.restAPI, 'getBlame') | |
| .returns(Promise.resolve(mockBlame)); | |
| element.changeNum = 42; | |
| element.patchRange = {patchNum: 5, basePatchNum: 4}; | |
| element.path = 'foo/bar.baz'; | |
| return element.loadBlame().then(() => { | |
| assert.isTrue(getBlameStub.calledWithExactly( | |
| 42, 5, 'foo/bar.baz', true)); | |
| assert.isFalse(showAlertStub.called); | |
| assert.equal(element._blame, mockBlame); | |
| assert.isTrue(element.classList.contains('showBlame')); | |
| }); | |
| }); | |
| test('loadBlame empty', () => { | |
| const mockBlame = []; | |
| const showAlertStub = sinon.stub(); | |
| element.addEventListener('show-alert', showAlertStub); | |
| sandbox.stub(element.$.restAPI, 'getBlame') | |
| .returns(Promise.resolve(mockBlame)); | |
| element.changeNum = 42; | |
| element.patchRange = {patchNum: 5, basePatchNum: 4}; | |
| element.path = 'foo/bar.baz'; | |
| return element.loadBlame() | |
| .then(() => { | |
| assert.isTrue(false, 'Promise should not resolve'); | |
| }) | |
| .catch(() => { | |
| assert.isTrue(showAlertStub.calledOnce); | |
| assert.isNull(element._blame); | |
| assert.isFalse(element.classList.contains('showBlame')); | |
| }); | |
| }); | |
| }); | |
| suite('_reportDiff', () => { | |
| let reportStub; | |
| setup(() => { | |
| element = fixture('basic'); | |
| element.patchRange = {basePatchNum: 1}; | |
| reportStub = sandbox.stub(element.$.reporting, 'reportInteraction'); | |
| }); | |
| test('null and content-less', () => { | |
| element._reportDiff(null); | |
| assert.isFalse(reportStub.called); | |
| element._reportDiff({}); | |
| assert.isFalse(reportStub.called); | |
| }); | |
| test('diff w/ no delta', () => { | |
| const diff = { | |
| content: [ | |
| {ab: ['foo', 'bar']}, | |
| {ab: ['baz', 'foo']}, | |
| ], | |
| }; | |
| element._reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero'); | |
| assert.isUndefined(reportStub.lastCall.args[1]); | |
| }); | |
| test('diff w/ no rebase delta', () => { | |
| const diff = { | |
| content: [ | |
| {ab: ['foo', 'bar']}, | |
| {a: ['baz', 'foo']}, | |
| {ab: ['foo', 'bar']}, | |
| {a: ['baz', 'foo'], b: ['bar', 'baz']}, | |
| {ab: ['foo', 'bar']}, | |
| {b: ['baz', 'foo']}, | |
| {ab: ['foo', 'bar']}, | |
| ], | |
| }; | |
| element._reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.equal(reportStub.lastCall.args[0], 'rebase-percent-zero'); | |
| assert.isUndefined(reportStub.lastCall.args[1]); | |
| }); | |
| test('diff w/ some rebase delta', () => { | |
| const diff = { | |
| content: [ | |
| {ab: ['foo', 'bar']}, | |
| {a: ['baz', 'foo'], due_to_rebase: true}, | |
| {ab: ['foo', 'bar']}, | |
| {a: ['baz', 'foo'], b: ['bar', 'baz']}, | |
| {ab: ['foo', 'bar']}, | |
| {b: ['baz', 'foo'], due_to_rebase: true}, | |
| {ab: ['foo', 'bar']}, | |
| {a: ['baz', 'foo']}, | |
| ], | |
| }; | |
| element._reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero'); | |
| assert.strictEqual(reportStub.lastCall.args[1], 50); | |
| }); | |
| test('diff w/ all rebase delta', () => { | |
| const diff = {content: [{ | |
| a: ['foo', 'bar'], | |
| b: ['baz', 'foo'], | |
| due_to_rebase: true, | |
| }]}; | |
| element._reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.equal(reportStub.lastCall.args[0], 'rebase-percent-nonzero'); | |
| assert.strictEqual(reportStub.lastCall.args[1], 100); | |
| }); | |
| test('diff against parent event', () => { | |
| element.patchRange.basePatchNum = 'PARENT'; | |
| const diff = {content: [{ | |
| a: ['foo', 'bar'], | |
| b: ['baz', 'foo'], | |
| }]}; | |
| element._reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.equal(reportStub.lastCall.args[0], 'diff-against-parent'); | |
| assert.isUndefined(reportStub.lastCall.args[1]); | |
| }); | |
| }); | |
| }); | |
| a11ySuite('basic'); | |
| </script> |