| /** | |
| * @license | |
| * Copyright 2018 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import '../../../test/common-test-setup'; | |
| import './gr-diff-host'; | |
| import { | |
| CommentSide, | |
| createDefaultDiffPrefs, | |
| Side, | |
| } from '../../../constants/constants'; | |
| import { | |
| createAccountDetailWithId, | |
| createBlame, | |
| createChange, | |
| createComment, | |
| createCommentThread, | |
| createDiff, | |
| createPatchRange, | |
| createRunResult, | |
| } from '../../../test/test-data-generators'; | |
| import { | |
| addListenerForTest, | |
| mockPromise, | |
| query, | |
| queryAll, | |
| queryAndAssert, | |
| stubReporting, | |
| stubRestApi, | |
| } from '../../../test/test-utils'; | |
| import { | |
| BasePatchSetNum, | |
| BlameInfo, | |
| CommentRange, | |
| CommentThread, | |
| DraftInfo, | |
| EDIT, | |
| ImageInfo, | |
| NumericChangeId, | |
| PARENT, | |
| PatchSetNum, | |
| RevisionPatchSetNum, | |
| } from '../../../types/common'; | |
| import {CoverageType} from '../../../types/types'; | |
| import {GrDiffHost} from './gr-diff-host'; | |
| import {DiffInfo, DiffViewMode, IgnoreWhitespaceType} from '../../../api/diff'; | |
| import {ErrorCallback} from '../../../api/rest'; | |
| import {SinonStub, SinonStubbedMember} from 'sinon'; | |
| import {RunResult} from '../../../models/checks/checks-model'; | |
| import {assertIsDefined} from '../../../utils/common-util'; | |
| import {fixture, html, assert} from '@open-wc/testing'; | |
| import {testResolver} from '../../../test/common-test-setup'; | |
| import {userModelToken, UserModel} from '../../../models/user/user-model'; | |
| import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader'; | |
| import {ReportingService} from '../../../services/gr-reporting/gr-reporting'; | |
| import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api'; | |
| import { | |
| CommentsModel, | |
| commentsModelToken, | |
| } from '../../../models/comments/comments-model'; | |
| import {isNewDiff} from '../../../embed/diff/gr-diff/gr-diff-utils'; | |
| suite('gr-diff-host tests', () => { | |
| let element: GrDiffHost; | |
| let account = createAccountDetailWithId(1); | |
| let getDiffRestApiStub: SinonStubbedMember<RestApiService['getDiff']>; | |
| let userModel: UserModel; | |
| setup(async () => { | |
| stubRestApi('getAccount').callsFake(() => Promise.resolve(account)); | |
| element = await fixture(html`<gr-diff-host | |
| .changeNum=${123 as NumericChangeId} | |
| .path=${'some/path'} | |
| .file=${{path: 'some/path'}} | |
| .change=${createChange()} | |
| .patchRange=${createPatchRange()} | |
| ></gr-diff-host>`); | |
| getDiffRestApiStub = stubRestApi('getDiff'); | |
| // Fall back in case a test forgets to set one up | |
| getDiffRestApiStub.returns(Promise.resolve(createDiff())); | |
| await element.updateComplete; | |
| userModel = testResolver(userModelToken); | |
| }); | |
| suite('render reporting', () => { | |
| test('ends total and syntax timer after syntax layer', async () => { | |
| const displayedStub = stubReporting('diffViewContentDisplayed'); | |
| element.patchRange = createPatchRange(); | |
| element.change = createChange(); | |
| element.prefs = createDefaultDiffPrefs(); | |
| await element.updateComplete; | |
| // Force a reload because it's not possible to wait on the reload called | |
| // from update(). | |
| await element.reload(); | |
| const timeEndStub = sinon.stub(element.reporting, 'timeEnd'); | |
| let notifySyntaxProcessed: () => void = () => {}; | |
| sinon.stub(element.syntaxLayer, 'process').returns( | |
| new Promise(resolve => { | |
| notifySyntaxProcessed = resolve; | |
| }) | |
| ); | |
| const promise = element.reload(true); | |
| // Multiple cascading microtasks are scheduled. | |
| notifySyntaxProcessed(); | |
| await element.updateComplete; | |
| await promise; | |
| const calls = timeEndStub.getCalls(); | |
| assert.equal(calls.length, 4); | |
| assert.equal(calls[0].args[0], 'Diff Load Render'); | |
| assert.equal(calls[1].args[0], 'Diff Content Render'); | |
| assert.equal(calls[2].args[0], 'Diff Syntax Render'); | |
| assert.equal(calls[3].args[0], 'Diff Total Render'); | |
| assert.isTrue(displayedStub.called); | |
| }); | |
| test('completes reload promise after syntax layer processing', async () => { | |
| let notifySyntaxProcessed: () => void = () => {}; | |
| sinon.stub(element.syntaxLayer, 'process').returns( | |
| new Promise(resolve => { | |
| notifySyntaxProcessed = resolve; | |
| }) | |
| ); | |
| getDiffRestApiStub.returns(Promise.resolve(createDiff())); | |
| element.patchRange = createPatchRange(); | |
| element.change = createChange(); | |
| let reloadComplete = false; | |
| element.prefs = createDefaultDiffPrefs(); | |
| const promise = mockPromise(); | |
| element.reload().then(() => { | |
| reloadComplete = true; | |
| promise.resolve(); | |
| }); | |
| // Multiple cascading microtasks are scheduled. | |
| assert.isFalse(reloadComplete); | |
| notifySyntaxProcessed(); | |
| await promise; | |
| assert.isTrue(reloadComplete); | |
| }); | |
| }); | |
| test('render', () => { | |
| assert.shadowDom.equal( | |
| element, | |
| /* HTML */ ` | |
| <gr-diff | |
| id="diff" | |
| style="--line-limit-marker:-1px; --content-width:100ch; --diff-max-width:none; --font-size:12px;" | |
| > | |
| </gr-diff> | |
| `, | |
| {ignoreAttributes: ['style']} | |
| ); | |
| }); | |
| test('reload() cancels before network resolves', async () => { | |
| if (isNewDiff()) return; | |
| assertIsDefined(element.diffElement); | |
| const cancelStub = sinon.stub(element.diffElement, 'cancel'); | |
| // Stub the network calls into requests that never resolve. | |
| sinon.stub(element, 'getDiff').callsFake(() => new Promise(() => {})); | |
| element.patchRange = createPatchRange(); | |
| element.change = createChange(); | |
| element.prefs = undefined; | |
| // Needs to be set to something first for it to cancel. | |
| element.diff = createDiff(); | |
| await element.updateComplete; | |
| element.reload(); | |
| assert.isTrue(cancelStub.called); | |
| }); | |
| test('prefetch getDiff', async () => { | |
| getDiffRestApiStub.returns(Promise.resolve(createDiff())); | |
| element.changeNum = 123 as NumericChangeId; | |
| element.patchRange = createPatchRange(); | |
| element.path = 'file.txt'; | |
| element.prefetchDiff(); | |
| await element.getDiff(); | |
| assert.isTrue(getDiffRestApiStub.calledOnce); | |
| }); | |
| test('getDiff handles undefined diff responses', async () => { | |
| getDiffRestApiStub.returns(Promise.resolve(undefined)); | |
| element.changeNum = 123 as NumericChangeId; | |
| element.patchRange = createPatchRange(); | |
| element.path = 'file.txt'; | |
| await element.getDiff(); | |
| }); | |
| test('reload resolves on error', () => { | |
| const onErrStub = sinon.stub(element, 'handleGetDiffError'); | |
| const error = new Response(null, {status: 500}); | |
| getDiffRestApiStub.callsFake( | |
| ( | |
| _1: NumericChangeId, | |
| _2: PatchSetNum, | |
| _3: PatchSetNum, | |
| _4: string, | |
| _5?: IgnoreWhitespaceType, | |
| onErr?: ErrorCallback | |
| ) => { | |
| if (onErr) onErr(error); | |
| return Promise.resolve(undefined); | |
| } | |
| ); | |
| element.patchRange = createPatchRange(); | |
| return element.reload().then(() => { | |
| assert.isTrue(onErrStub.calledOnce); | |
| }); | |
| }); | |
| suite('handleGetDiffError', () => { | |
| let serverErrorStub: sinon.SinonStub; | |
| let pageErrorStub: sinon.SinonStub; | |
| setup(() => { | |
| serverErrorStub = sinon.stub(); | |
| addListenerForTest(document, 'server-error', serverErrorStub); | |
| pageErrorStub = sinon.stub(); | |
| addListenerForTest(document, 'page-error', pageErrorStub); | |
| }); | |
| test('page error on HTTP-409', () => { | |
| element.handleGetDiffError({status: 409} as Response); | |
| assert.isTrue(serverErrorStub.calledOnce); | |
| assert.isFalse(pageErrorStub.called); | |
| assert.isNotOk(element.errorMessage); | |
| }); | |
| test('server error on non-HTTP-409', () => { | |
| element.handleGetDiffError({ | |
| status: 500, | |
| text: () => Promise.resolve(''), | |
| } as Response); | |
| assert.isFalse(serverErrorStub.called); | |
| assert.isTrue(pageErrorStub.calledOnce); | |
| assert.isNotOk(element.errorMessage); | |
| }); | |
| test('error message if showLoadFailure', () => { | |
| element.showLoadFailure = true; | |
| element.handleGetDiffError({ | |
| status: 500, | |
| statusText: 'Failure!', | |
| } as Response); | |
| assert.isFalse(serverErrorStub.called); | |
| assert.isFalse(pageErrorStub.called); | |
| assert.equal( | |
| element.errorMessage, | |
| 'Encountered error when loading the diff: 500 Failure!' | |
| ); | |
| }); | |
| }); | |
| suite('image diffs', () => { | |
| let mockFile1: ImageInfo; | |
| let mockFile2: ImageInfo; | |
| setup(() => { | |
| mockFile1 = { | |
| body: | |
| 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' + | |
| 'wsAAAAAAAAAAAAAAAAA/w==', | |
| type: 'image/bmp', | |
| _expectedType: 'image/bmp', | |
| _name: 'carrot.bmp', | |
| }; | |
| mockFile2 = { | |
| body: | |
| 'Qk06AAAAAAAAADYAAAAoAAAAAQAAAP////8BACAAAAAAAAAAAAATCwAAE' + | |
| 'wsAAAAAAAAAAAAA/////w==', | |
| type: 'image/bmp', | |
| _expectedType: 'image/bmp', | |
| _name: 'potato.bmp', | |
| }; | |
| element.patchRange = createPatchRange(); | |
| element.change = createChange(); | |
| }); | |
| test('renders image diffs with same file name', async () => { | |
| const mockDiff: DiffInfo = { | |
| 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, | |
| }; | |
| getDiffRestApiStub.returns(Promise.resolve(mockDiff)); | |
| stubRestApi('getImagesForDiff').returns( | |
| Promise.resolve({ | |
| baseImage: { | |
| ...mockFile1, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot.jpg', | |
| }, | |
| revisionImage: { | |
| ...mockFile2, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot.jpg', | |
| }, | |
| }) | |
| ); | |
| element.prefs = createDefaultDiffPrefs(); | |
| element.reload(); | |
| await element.waitForReloadToRender(); | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| // Left image rendered with the parent commit's version of the file. | |
| assertIsDefined(element.diffElement); | |
| const leftImage = queryAndAssert(element.diffElement, 'td.left img'); | |
| const leftLabel = queryAndAssert(element.diffElement, 'td.left label'); | |
| const leftLabelContent = leftLabel.querySelector('.label'); | |
| const leftLabelName = leftLabel.querySelector('.name'); | |
| const rightImage = queryAndAssert(element.diffElement, 'td.right img'); | |
| const rightLabel = queryAndAssert(element.diffElement, 'td.right label'); | |
| const rightLabelContent = rightLabel.querySelector('.label'); | |
| const rightLabelName = rightLabel.querySelector('.name'); | |
| assert.isOk(leftImage); | |
| assert.equal( | |
| leftImage.getAttribute('src'), | |
| 'data:image/bmp;base64,' + mockFile1.body | |
| ); | |
| assert.isTrue(leftLabelContent?.textContent?.includes('image/bmp')); | |
| assert.isNotOk(leftLabelName); | |
| assert.isOk(rightImage); | |
| assert.equal( | |
| rightImage.getAttribute('src'), | |
| 'data:image/bmp;base64,' + mockFile2.body | |
| ); | |
| assert.isTrue(rightLabelContent?.textContent?.includes('image/bmp')); | |
| assert.isNotOk(rightLabelName); | |
| }); | |
| test('renders image diffs with a different file name', async () => { | |
| const mockDiff: DiffInfo = { | |
| 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, | |
| }; | |
| getDiffRestApiStub.returns(Promise.resolve(mockDiff)); | |
| stubRestApi('getImagesForDiff').returns( | |
| Promise.resolve({ | |
| baseImage: { | |
| ...mockFile1, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot.jpg', | |
| }, | |
| revisionImage: { | |
| ...mockFile2, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot2.jpg', | |
| }, | |
| }) | |
| ); | |
| element.prefs = createDefaultDiffPrefs(); | |
| element.reload(); | |
| await element.waitForReloadToRender(); | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assertIsDefined(element.diffElement); | |
| // Left image rendered with the parent commit's version of the file. | |
| const leftImage = queryAndAssert(element.diffElement, 'td.left img'); | |
| const leftLabel = queryAndAssert(element.diffElement, 'td.left label'); | |
| const leftLabelContent = leftLabel.querySelector('.label'); | |
| const leftLabelName = leftLabel.querySelector('.name'); | |
| const rightImage = queryAndAssert(element.diffElement, 'td.right img'); | |
| const rightLabel = queryAndAssert(element.diffElement, '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); | |
| assert.isOk(leftImage); | |
| assert.equal( | |
| leftImage.getAttribute('src'), | |
| 'data:image/bmp;base64,' + mockFile1.body | |
| ); | |
| assert.isTrue(leftLabelContent?.textContent?.includes('image/bmp')); | |
| assert.isOk(rightImage); | |
| assert.equal( | |
| rightImage.getAttribute('src'), | |
| 'data:image/bmp;base64,' + mockFile2.body | |
| ); | |
| assert.isTrue(rightLabelContent?.textContent?.includes('image/bmp')); | |
| }); | |
| test('renders added image', async () => { | |
| const mockDiff: DiffInfo = { | |
| 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, | |
| }; | |
| getDiffRestApiStub.returns(Promise.resolve(mockDiff)); | |
| stubRestApi('getImagesForDiff').returns( | |
| Promise.resolve({ | |
| baseImage: null, | |
| revisionImage: { | |
| ...mockFile2, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot2.jpg', | |
| }, | |
| }) | |
| ); | |
| element.prefs = createDefaultDiffPrefs(); | |
| element.reload(); | |
| await element.waitForReloadToRender().then(() => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assertIsDefined(element.diffElement); | |
| const leftImage = query(element.diffElement, 'td.left img'); | |
| const rightImage = queryAndAssert(element.diffElement, 'td.right img'); | |
| assert.isNotOk(leftImage); | |
| assert.isOk(rightImage); | |
| }); | |
| }); | |
| test('renders removed image', async () => { | |
| const mockDiff: DiffInfo = { | |
| 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, | |
| }; | |
| getDiffRestApiStub.returns(Promise.resolve(mockDiff)); | |
| stubRestApi('getImagesForDiff').returns( | |
| Promise.resolve({ | |
| baseImage: { | |
| ...mockFile1, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot.jpg', | |
| }, | |
| revisionImage: null, | |
| }) | |
| ); | |
| element.prefs = createDefaultDiffPrefs(); | |
| element.reload(); | |
| await element.waitForReloadToRender().then(() => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assertIsDefined(element.diffElement); | |
| const leftImage = queryAndAssert(element.diffElement, 'td.left img'); | |
| const rightImage = query(element.diffElement, 'td.right img'); | |
| assert.isOk(leftImage); | |
| assert.isNotOk(rightImage); | |
| }); | |
| }); | |
| test('does not render disallowed image type', async () => { | |
| const mockDiff: DiffInfo = { | |
| 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'; | |
| getDiffRestApiStub.returns(Promise.resolve(mockDiff)); | |
| stubRestApi('getImagesForDiff').returns( | |
| Promise.resolve({ | |
| baseImage: { | |
| ...mockFile1, | |
| _expectedType: 'image/jpeg', | |
| _name: 'carrot.jpg', | |
| }, | |
| revisionImage: null, | |
| }) | |
| ); | |
| element.prefs = createDefaultDiffPrefs(); | |
| element.updateComplete.then(() => { | |
| // Recognizes that it should be an image diff. | |
| assert.isTrue(element.isImageDiff); | |
| assertIsDefined(element.diffElement); | |
| const leftImage = query(element.diffElement, 'td.left img'); | |
| assert.isNotOk(leftImage); | |
| }); | |
| }); | |
| }); | |
| test('cannot create comments when not logged in', () => { | |
| userModel.setAccount(undefined); | |
| element.patchRange = createPatchRange(); | |
| const showAuthRequireSpy = sinon.spy(); | |
| element.addEventListener('show-auth-required', showAuthRequireSpy); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| lineNum: 3, | |
| side: Side.LEFT, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assertIsDefined(element.diffElement); | |
| const threads = queryAll(element.diffElement, 'gr-comment-thread'); | |
| assert.equal(threads.length, 0); | |
| assert.isTrue(showAuthRequireSpy.called); | |
| }); | |
| test('delegates cancel()', () => { | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon.stub(element.diffElement, 'cancel'); | |
| element.patchRange = createPatchRange(); | |
| element.cancel(); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('delegates getCursorStops()', () => { | |
| const returnValue = [document.createElement('b')]; | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon | |
| .stub(element.diffElement, 'getCursorStops') | |
| .returns(returnValue); | |
| assert.equal(element.getCursorStops(), returnValue); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('delegates isRangeSelected()', () => { | |
| const returnValue = true; | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon | |
| .stub(element.diffElement, 'isRangeSelected') | |
| .returns(returnValue); | |
| assert.equal(element.isRangeSelected(), returnValue); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('delegates toggleLeftDiff()', () => { | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon.stub(element.diffElement, 'toggleLeftDiff'); | |
| element.toggleLeftDiff(); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('clearBlame', async () => { | |
| element.blame = []; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| const isBlameLoadedStub = sinon.stub(); | |
| element.addEventListener('is-blame-loaded-changed', isBlameLoadedStub); | |
| element.clearBlame(); | |
| await element.updateComplete; | |
| assert.isNull(element.blame); | |
| assert.isTrue(isBlameLoadedStub.calledOnce); | |
| assert.isFalse(isBlameLoadedStub.args[0][0].detail.value); | |
| }); | |
| test('loadBlame', async () => { | |
| const mockBlame: BlameInfo[] = [createBlame()]; | |
| const showAlertStub = sinon.stub(); | |
| element.addEventListener('show-alert', showAlertStub); | |
| const getBlameStub = stubRestApi('getBlame').returns( | |
| Promise.resolve(mockBlame) | |
| ); | |
| const changeNum = 42 as NumericChangeId; | |
| element.changeNum = changeNum; | |
| element.patchRange = createPatchRange(); | |
| element.path = 'foo/bar.baz'; | |
| await element.updateComplete; | |
| const isBlameLoadedStub = sinon.stub(); | |
| element.addEventListener('is-blame-loaded-changed', isBlameLoadedStub); | |
| return element.loadBlame().then(() => { | |
| assert.isTrue( | |
| getBlameStub.calledWithExactly( | |
| changeNum, | |
| 1 as RevisionPatchSetNum, | |
| 'foo/bar.baz', | |
| true | |
| ) | |
| ); | |
| assert.isFalse(showAlertStub.called); | |
| assert.equal(element.blame, mockBlame); | |
| assert.isTrue(isBlameLoadedStub.calledOnce); | |
| assert.isTrue(isBlameLoadedStub.args[0][0].detail.value); | |
| }); | |
| }); | |
| test('loadBlame empty', async () => { | |
| const mockBlame: BlameInfo[] = []; | |
| const showAlertStub = sinon.stub(); | |
| const isBlameLoadedStub = sinon.stub(); | |
| element.addEventListener('show-alert', showAlertStub); | |
| element.addEventListener('is-blame-loaded-changed', isBlameLoadedStub); | |
| stubRestApi('getBlame').returns(Promise.resolve(mockBlame)); | |
| const changeNum = 42 as NumericChangeId; | |
| element.changeNum = changeNum; | |
| element.patchRange = createPatchRange(); | |
| element.path = 'foo/bar.baz'; | |
| await element.updateComplete; | |
| return element | |
| .loadBlame() | |
| .then(() => { | |
| assert.isTrue(false, 'Promise should not resolve'); | |
| }) | |
| .catch(() => { | |
| assert.isTrue(showAlertStub.calledOnce); | |
| assert.isNull(element.blame); | |
| // We don't expect a call because | |
| assert.isTrue(isBlameLoadedStub.notCalled); | |
| }); | |
| }); | |
| test('delegates clearDiffContent()', () => { | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon.stub(element.diffElement, 'clearDiffContent'); | |
| element.clearDiffContent(); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('delegates toggleAllContext()', () => { | |
| assertIsDefined(element.diffElement); | |
| const stub = sinon.stub(element.diffElement, 'toggleAllContext'); | |
| element.toggleAllContext(); | |
| assert.isTrue(stub.calledOnce); | |
| assert.equal(stub.lastCall.args.length, 0); | |
| }); | |
| test('passes in noAutoRender', async () => { | |
| const value = true; | |
| element.noAutoRender = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.noAutoRender, value); | |
| }); | |
| test('passes in path', async () => { | |
| const value = 'some/file/path'; | |
| element.path = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.path, value); | |
| }); | |
| test('passes in prefs', async () => { | |
| const value = createDefaultDiffPrefs(); | |
| element.prefs = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.prefs, value); | |
| }); | |
| test('passes in hidden', async () => { | |
| const value = true; | |
| element.hidden = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.hidden, value); | |
| assert.isNotNull(element.getAttribute('hidden')); | |
| }); | |
| test('passes in noRenderOnPrefsChange', async () => { | |
| const value = true; | |
| element.noRenderOnPrefsChange = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.noRenderOnPrefsChange, value); | |
| }); | |
| test('passes in lineWrapping', async () => { | |
| const value = true; | |
| element.lineWrapping = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.lineWrapping, value); | |
| }); | |
| test('passes in viewMode', async () => { | |
| const value = DiffViewMode.SIDE_BY_SIDE; | |
| element.viewMode = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.viewMode, value); | |
| }); | |
| test('passes in lineOfInterest', async () => { | |
| const value = {lineNum: 123, side: Side.LEFT}; | |
| element.lineOfInterest = value; | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assert.equal(element.diffElement.lineOfInterest, value); | |
| }); | |
| suite('reportDiff', () => { | |
| let reportStub: SinonStubbedMember<ReportingService['reportInteraction']>; | |
| setup(async () => { | |
| element.patchRange = createPatchRange(1, 2); | |
| await element.updateComplete; | |
| reportStub = sinon.stub(element.reporting, 'reportInteraction'); | |
| reportStub.reset(); | |
| }); | |
| test('undefined', () => { | |
| element.reportDiff(undefined); | |
| assert.isFalse(reportStub.called); | |
| }); | |
| test('diff w/ no delta', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| 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: DiffInfo = { | |
| ...createDiff(), | |
| 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: DiffInfo = { | |
| ...createDiff(), | |
| 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.isTrue( | |
| reportStub.calledWith('rebase-percent-nonzero', { | |
| percentRebaseDelta: 50, | |
| }) | |
| ); | |
| }); | |
| test('diff w/ all rebase delta', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [ | |
| { | |
| a: ['foo', 'bar'], | |
| b: ['baz', 'foo'], | |
| due_to_rebase: true, | |
| }, | |
| ], | |
| }; | |
| element.reportDiff(diff); | |
| assert.isTrue(reportStub.calledOnce); | |
| assert.isTrue( | |
| reportStub.calledWith('rebase-percent-nonzero', { | |
| percentRebaseDelta: 100, | |
| }) | |
| ); | |
| }); | |
| test('diff against parent event', () => { | |
| element.patchRange = createPatchRange(); | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| 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]); | |
| }); | |
| }); | |
| suite('render thread elements', () => { | |
| test('right start_line:1', async () => { | |
| const thread: CommentThread = { | |
| ...createCommentThread([createComment()]), | |
| }; | |
| element.threads = [thread]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-comment-thread | |
| class="comment-thread" | |
| diff-side="right" | |
| line-num="1" | |
| slot="right-1" | |
| > | |
| </gr-comment-thread> | |
| ` | |
| ); | |
| }); | |
| test('left start_line:2', async () => { | |
| const thread: CommentThread = { | |
| ...createCommentThread([ | |
| createComment({side: CommentSide.PARENT, line: 2}), | |
| ]), | |
| }; | |
| element.threads = [thread]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-comment-thread | |
| class="comment-thread" | |
| diff-side="left" | |
| line-num="2" | |
| slot="left-2" | |
| > | |
| </gr-comment-thread> | |
| ` | |
| ); | |
| }); | |
| }); | |
| suite('render check elements', () => { | |
| test('start_line:12', async () => { | |
| const result: RunResult = { | |
| ...createRunResult(), | |
| codePointers: [{path: 'a', range: {start_line: 12} as CommentRange}], | |
| }; | |
| element.checks = [result]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-diff-check-result | |
| class="comment-thread" | |
| diff-side="right" | |
| line-num="12" | |
| slot="right-12" | |
| > | |
| </gr-diff-check-result> | |
| ` | |
| ); | |
| }); | |
| test('start_line:13 end_line:14 without char positions', async () => { | |
| const result: RunResult = { | |
| ...createRunResult(), | |
| codePointers: [ | |
| {path: 'a', range: {start_line: 13, end_line: 14} as CommentRange}, | |
| ], | |
| }; | |
| element.checks = [result]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-diff-check-result | |
| class="comment-thread" | |
| diff-side="right" | |
| line-num="14" | |
| slot="right-14" | |
| > | |
| </gr-diff-check-result> | |
| ` | |
| ); | |
| }); | |
| test('start_line:13 end_line:14 with char positions', async () => { | |
| const result: RunResult = { | |
| ...createRunResult(), | |
| codePointers: [ | |
| { | |
| path: 'a', | |
| range: { | |
| start_line: 13, | |
| end_line: 14, | |
| start_character: 5, | |
| end_character: 7, | |
| }, | |
| }, | |
| ], | |
| }; | |
| element.checks = [result]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-diff-check-result | |
| class="comment-thread" | |
| diff-side="right" | |
| line-num="14" | |
| slot="right-14" | |
| range='{"start_line":13,"end_line":14,"start_character":5,"end_character":7}' | |
| > | |
| </gr-diff-check-result> | |
| ` | |
| ); | |
| }); | |
| test('empty range', async () => { | |
| const result: RunResult = { | |
| ...createRunResult(), | |
| codePointers: [{path: 'a', range: {} as CommentRange}], | |
| }; | |
| element.checks = [result]; | |
| await element.updateComplete; | |
| assert.lightDom.equal( | |
| element.diffElement, | |
| /* HTML */ ` | |
| <gr-diff-check-result | |
| class="comment-thread" | |
| diff-side="right" | |
| line-num="FILE" | |
| slot="right-FILE" | |
| > | |
| </gr-diff-check-result> | |
| ` | |
| ); | |
| }); | |
| }); | |
| suite('create-comment', () => { | |
| let addDraftSpy: sinon.SinonSpy; | |
| setup(async () => { | |
| const commentsModel: CommentsModel = testResolver(commentsModelToken); | |
| addDraftSpy = sinon.spy(commentsModel, 'addNewDraft'); | |
| account = createAccountDetailWithId(1); | |
| element.disconnectedCallback(); | |
| element.connectedCallback(); | |
| await element.updateComplete; | |
| }); | |
| test('creates comments if they do not exist yet', async () => { | |
| element.patchRange = createPatchRange(); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| lineNum: 3, | |
| side: Side.LEFT, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.PARENT); | |
| assert.equal(draft1.range, undefined); | |
| assert.equal(draft1.patch_set, 1 as RevisionPatchSetNum); | |
| // Try to fetch a thread with a different range. | |
| const range = { | |
| start_line: 1, | |
| start_character: 1, | |
| end_line: 1, | |
| end_character: 3, | |
| }; | |
| element.patchRange = createPatchRange(); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| lineNum: 1, | |
| side: Side.LEFT, | |
| path: '/p', | |
| range, | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 2); | |
| const draft2: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft2.side, CommentSide.PARENT); | |
| assert.equal(draft2.range, range); | |
| assert.equal(draft2.patch_set, 1 as RevisionPatchSetNum); | |
| }); | |
| test('should not be on parent if on the right', async () => { | |
| element.patchRange = createPatchRange(2, 3); | |
| // Need to recompute threads. | |
| await element.updateComplete; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.RIGHT, | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.REVISION); | |
| assert.equal(draft1.patch_set, 3 as RevisionPatchSetNum); | |
| }); | |
| test('should be on parent if right and base is PARENT', () => { | |
| element.patchRange = createPatchRange(); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.LEFT, | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.PARENT); | |
| assert.equal(draft1.patch_set, 1 as RevisionPatchSetNum); | |
| }); | |
| test('should be on parent if right and base negative', () => { | |
| element.patchRange = createPatchRange(-2, 3); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.LEFT, | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.PARENT); | |
| assert.equal(draft1.patch_set, 3 as RevisionPatchSetNum); | |
| assert.equal(draft1.parent, 2); | |
| }); | |
| test('should not be on parent otherwise', () => { | |
| element.patchRange = createPatchRange(2, 3); | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.LEFT, | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.REVISION); | |
| assert.equal(draft1.patch_set, 2 as RevisionPatchSetNum); | |
| }); | |
| test( | |
| 'thread should use old file path if first created ' + | |
| 'on patch set (left) before renaming', | |
| async () => { | |
| element.patchRange = createPatchRange(2, 3); | |
| element.file = {basePath: 'file_renamed.txt', path: element.path ?? ''}; | |
| await element.updateComplete; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.LEFT, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.REVISION); | |
| assert.equal(draft1.patch_set, 2 as RevisionPatchSetNum); | |
| assert.equal(draft1.path, element.file.basePath); | |
| } | |
| ); | |
| test( | |
| 'thread should use new file path if first created ' + | |
| 'on patch set (right) after renaming', | |
| async () => { | |
| element.patchRange = createPatchRange(2, 3); | |
| element.file = {basePath: 'file_renamed.txt', path: element.path ?? ''}; | |
| await element.updateComplete; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.RIGHT, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.REVISION); | |
| assert.equal(draft1.patch_set, 3 as RevisionPatchSetNum); | |
| assert.equal(draft1.path, element.file.path); | |
| } | |
| ); | |
| test( | |
| 'thread should use new file path if first created ' + | |
| 'on patch set (left) but is base', | |
| async () => { | |
| element.patchRange = createPatchRange(); | |
| element.file = {basePath: 'file_renamed.txt', path: element.path ?? ''}; | |
| await element.updateComplete; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: Side.LEFT, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.equal(addDraftSpy.callCount, 1); | |
| const draft1: DraftInfo = addDraftSpy.lastCall.firstArg; | |
| assert.equal(draft1.side, CommentSide.PARENT); | |
| assert.equal(draft1.patch_set, 1 as RevisionPatchSetNum); | |
| assert.equal(draft1.path, element.file.path); | |
| } | |
| ); | |
| test('cannot create thread on an edit', () => { | |
| const alertSpy = sinon.spy(); | |
| element.addEventListener('show-alert', alertSpy); | |
| const diffSide = Side.RIGHT; | |
| element.patchRange = { | |
| basePatchNum: 3 as BasePatchSetNum, | |
| patchNum: EDIT, | |
| }; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: diffSide, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.isFalse(addDraftSpy.called); | |
| assert.isTrue(alertSpy.called); | |
| }); | |
| test('cannot create thread on an edit base', () => { | |
| const alertSpy = sinon.spy(); | |
| element.addEventListener('show-alert', alertSpy); | |
| const diffSide = Side.LEFT; | |
| element.patchRange = { | |
| basePatchNum: PARENT, | |
| patchNum: EDIT, | |
| }; | |
| element.dispatchEvent( | |
| new CustomEvent('create-comment', { | |
| detail: { | |
| side: diffSide, | |
| path: '/p', | |
| }, | |
| }) | |
| ); | |
| assert.isFalse(addDraftSpy.called); | |
| assert.isTrue(alertSpy.called); | |
| }); | |
| }); | |
| suite('syntax layer with syntax_highlighting on', async () => { | |
| setup(async () => { | |
| const prefs = { | |
| ...createDefaultDiffPrefs(), | |
| line_length: 10, | |
| show_tabs: true, | |
| tab_size: 4, | |
| context: -1, | |
| syntax_highlighting: true, | |
| }; | |
| element.patchRange = createPatchRange(); | |
| element.prefs = prefs; | |
| element.changeNum = 123 as NumericChangeId; | |
| element.change = createChange(); | |
| element.path = 'some/path'; | |
| }); | |
| test('gr-diff-host provides syntax highlighting layer', async () => { | |
| getDiffRestApiStub.returns(Promise.resolve(createDiff())); | |
| await element.updateComplete; | |
| assertIsDefined(element.diffElement); | |
| assertIsDefined(element.diffElement.layers); | |
| assert.equal(element.diffElement.layers[1], element.syntaxLayer); | |
| }); | |
| test('rendering normal-sized diff does not disable syntax', async () => { | |
| element.diff = createDiff(); | |
| getDiffRestApiStub.returns(Promise.resolve(element.diff)); | |
| await element.updateComplete; | |
| assert.isTrue(element.syntaxLayer.enabled); | |
| }); | |
| test('rendering large diff disables syntax', async () => { | |
| // Before it renders, set the first diff line to 500 '*' characters. | |
| getDiffRestApiStub.returns( | |
| Promise.resolve({ | |
| ...createDiff(), | |
| content: [ | |
| { | |
| a: ['*'.repeat(501)], | |
| }, | |
| ], | |
| }) | |
| ); | |
| element.reload(); | |
| await element.waitForReloadToRender(); | |
| assert.isFalse(element.syntaxLayer.enabled); | |
| }); | |
| test('starts syntax layer processing on render event', async () => { | |
| const stub = sinon | |
| .stub(element.syntaxLayer, 'process') | |
| .returns(Promise.resolve()); | |
| getDiffRestApiStub.returns(Promise.resolve(createDiff())); | |
| await element.reload(); | |
| element.dispatchEvent( | |
| new CustomEvent('render', {bubbles: true, composed: true}) | |
| ); | |
| assert.isTrue(stub.called); | |
| }); | |
| }); | |
| suite('syntax layer with syntax_highlighting off', () => { | |
| setup(async () => { | |
| const prefs = { | |
| ...createDefaultDiffPrefs(), | |
| line_length: 10, | |
| show_tabs: true, | |
| tab_size: 4, | |
| context: -1, | |
| syntax_highlighting: false, | |
| }; | |
| element.patchRange = createPatchRange(); | |
| element.change = createChange(); | |
| element.prefs = prefs; | |
| }); | |
| test('gr-diff-host provides syntax highlighting layer', async () => { | |
| await element.waitForReloadToRender(); | |
| assertIsDefined(element.diffElement); | |
| assertIsDefined(element.diffElement.layers); | |
| assert.equal(element.diffElement.layers[1], element.syntaxLayer); | |
| }); | |
| test('syntax layer should be disabled', async () => { | |
| await element.waitForReloadToRender(); | |
| assert.isFalse(element.syntaxLayer.enabled); | |
| }); | |
| test('still disabled for large diff', async () => { | |
| getDiffRestApiStub.callsFake(() => | |
| Promise.resolve({ | |
| ...createDiff(), | |
| content: [ | |
| { | |
| a: ['*'.repeat(501)], | |
| }, | |
| ], | |
| }) | |
| ); | |
| await element.waitForReloadToRender(); | |
| assert.isFalse(element.syntaxLayer.enabled); | |
| }); | |
| }); | |
| suite('coverage layer', () => { | |
| let coverageProviderStub: SinonStub; | |
| const exampleRanges = [ | |
| { | |
| type: CoverageType.COVERED, | |
| side: Side.RIGHT, | |
| code_range: { | |
| start_line: 1, | |
| end_line: 2, | |
| }, | |
| }, | |
| { | |
| type: CoverageType.NOT_COVERED, | |
| side: Side.RIGHT, | |
| code_range: { | |
| start_line: 3, | |
| end_line: 4, | |
| }, | |
| }, | |
| ]; | |
| setup(async () => { | |
| element.prefs = { | |
| ...createDefaultDiffPrefs(), | |
| line_length: 10, | |
| show_tabs: true, | |
| tab_size: 4, | |
| context: -1, | |
| }; | |
| await element.updateComplete; | |
| coverageProviderStub = sinon | |
| .stub() | |
| .returns(Promise.resolve(exampleRanges)); | |
| getDiffRestApiStub.returns( | |
| Promise.resolve({ | |
| ...createDiff(), | |
| content: [{a: ['foo']}], | |
| }) | |
| ); | |
| testResolver(pluginLoaderToken).pluginsModel.coverageRegister({ | |
| pluginName: 'test-coverage-plugin', | |
| provider: coverageProviderStub, | |
| }); | |
| await element.reload(); | |
| }); | |
| test('provider is called with appropriate params', async () => { | |
| element.patchRange = createPatchRange(1, 3); | |
| await element.updateComplete; | |
| await element.reload(); | |
| await element.waitForReloadToRender(); | |
| assert.isTrue( | |
| coverageProviderStub.calledWithExactly( | |
| 123, | |
| 'some/path', | |
| 1, | |
| 3, | |
| element.change | |
| ) | |
| ); | |
| }); | |
| test('provider is called with appropriate params - special patchset values', async () => { | |
| element.patchRange = createPatchRange(); | |
| await element.waitForReloadToRender(); | |
| assert.isTrue( | |
| coverageProviderStub.calledWithExactly( | |
| 123, | |
| 'some/path', | |
| undefined, | |
| 1, | |
| element.change | |
| ) | |
| ); | |
| }); | |
| }); | |
| suite('trailing newlines', () => { | |
| setup(() => {}); | |
| suite('lastChunkForSide', () => { | |
| test('deltas', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [ | |
| {a: ['foo', 'bar'], b: ['baz']}, | |
| {ab: ['foo', 'bar', 'baz']}, | |
| {b: ['foo']}, | |
| ], | |
| }; | |
| assert.equal(element.lastChunkForSide(diff, false), diff.content[2]); | |
| assert.equal(element.lastChunkForSide(diff, true), diff.content[1]); | |
| diff.content.push({a: ['foo'], b: ['bar']}); | |
| assert.equal(element.lastChunkForSide(diff, false), diff.content[3]); | |
| assert.equal(element.lastChunkForSide(diff, true), diff.content[3]); | |
| }); | |
| test('addition with a undefined', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [{b: ['foo', 'bar', 'baz']}], | |
| }; | |
| assert.equal(element.lastChunkForSide(diff, false), diff.content[0]); | |
| assert.isNull(element.lastChunkForSide(diff, true)); | |
| }); | |
| test('addition with a empty', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [{a: [], b: ['foo', 'bar', 'baz']}], | |
| }; | |
| assert.equal(element.lastChunkForSide(diff, false), diff.content[0]); | |
| assert.isNull(element.lastChunkForSide(diff, true)); | |
| }); | |
| test('deletion with b undefined', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [{a: ['foo', 'bar', 'baz']}], | |
| }; | |
| assert.isNull(element.lastChunkForSide(diff, false)); | |
| assert.equal(element.lastChunkForSide(diff, true), diff.content[0]); | |
| }); | |
| test('deletion with b empty', () => { | |
| const diff: DiffInfo = { | |
| ...createDiff(), | |
| content: [{a: ['foo', 'bar', 'baz'], b: []}], | |
| }; | |
| assert.isNull(element.lastChunkForSide(diff, false)); | |
| assert.equal(element.lastChunkForSide(diff, true), diff.content[0]); | |
| }); | |
| test('empty', () => { | |
| const diff: DiffInfo = {...createDiff(), content: []}; | |
| assert.isNull(element.lastChunkForSide(diff, false)); | |
| assert.isNull(element.lastChunkForSide(diff, true)); | |
| }); | |
| }); | |
| suite('hasTrailingNewlines', () => { | |
| test('shared no trailing', () => { | |
| const diff = undefined; | |
| sinon.stub(element, 'lastChunkForSide').returns({ab: ['foo', 'bar']}); | |
| assert.isFalse(element.hasTrailingNewlines(diff, false)); | |
| assert.isFalse(element.hasTrailingNewlines(diff, true)); | |
| }); | |
| test('delta trailing in right', () => { | |
| const diff = undefined; | |
| sinon | |
| .stub(element, 'lastChunkForSide') | |
| .returns({a: ['foo', 'bar'], b: ['baz', '']}); | |
| assert.isTrue(element.hasTrailingNewlines(diff, false)); | |
| assert.isFalse(element.hasTrailingNewlines(diff, true)); | |
| }); | |
| test('addition', () => { | |
| const diff: DiffInfo | undefined = undefined; | |
| sinon | |
| .stub(element, 'lastChunkForSide') | |
| .callsFake((_: DiffInfo | undefined, leftSide: boolean) => { | |
| if (leftSide) { | |
| return null; | |
| } | |
| return {b: ['foo', '']}; | |
| }); | |
| assert.isTrue(element.hasTrailingNewlines(diff, false)); | |
| assert.isNull(element.hasTrailingNewlines(diff, true)); | |
| }); | |
| test('deletion', () => { | |
| const diff: DiffInfo | undefined = undefined; | |
| sinon | |
| .stub(element, 'lastChunkForSide') | |
| .callsFake((_: DiffInfo | undefined, leftSide: boolean) => { | |
| if (!leftSide) { | |
| return null; | |
| } | |
| return {a: ['foo']}; | |
| }); | |
| assert.isNull(element.hasTrailingNewlines(diff, false)); | |
| assert.isFalse(element.hasTrailingNewlines(diff, true)); | |
| }); | |
| }); | |
| }); | |
| }); |