| /** |
| * @license |
| * Copyright 2022 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import * as sinon from 'sinon'; |
| import {assert} from '@open-wc/testing'; |
| import {DiffInfo, GrDiffLineType, Side} from '../../../api/diff'; |
| import {getAppContext} from '../../../services/app-context'; |
| import { |
| HighlightService, |
| highlightServiceToken, |
| } from '../../../services/highlight/highlight-service'; |
| import '../../../test/common-test-setup'; |
| import {testResolver} from '../../../test/common-test-setup'; |
| import {mockPromise} from '../../../test/test-utils'; |
| import {SyntaxLayerLine} from '../../../types/syntax-worker-api'; |
| import {GrDiffLine} from '../gr-diff/gr-diff-line'; |
| import {GrSyntaxLayerWorker} from './gr-syntax-layer-worker'; |
| |
| const diff: DiffInfo = { |
| meta_a: { |
| name: 'somepath/somefile.js', |
| content_type: 'text/javascript', |
| lines: 3, |
| language: 'lang-left', |
| }, |
| meta_b: { |
| name: 'somepath/somefile.js', |
| content_type: 'text/javascript', |
| lines: 4, |
| language: 'lang-right', |
| }, |
| change_type: 'MODIFIED', |
| intraline_status: 'OK', |
| content: [ |
| { |
| ab: ['import it;'], |
| }, |
| { |
| b: ['b only'], |
| }, |
| { |
| ab: [' public static final {', 'ab3'], |
| }, |
| ], |
| }; |
| |
| const leftRanges: SyntaxLayerLine[] = [ |
| {ranges: [{start: 0, length: 6, className: 'literal'}]}, |
| {ranges: []}, |
| {ranges: []}, |
| ]; |
| |
| const rightRanges: SyntaxLayerLine[] = [ |
| {ranges: []}, |
| {ranges: []}, |
| { |
| ranges: [ |
| {start: 0, length: 2, className: 'not-safe'}, |
| {start: 2, length: 6, className: 'literal'}, |
| {start: 9, length: 6, className: 'keyword'}, |
| {start: 16, length: 5, className: 'name'}, |
| ], |
| }, |
| {ranges: []}, |
| ]; |
| |
| suite('gr-syntax-layer-worker tests', () => { |
| let layer: GrSyntaxLayerWorker; |
| let listener: sinon.SinonStub; |
| let highlightService: HighlightService; |
| |
| const annotate = (side: Side, lineNumber: number, text: string) => { |
| const el = document.createElement('div'); |
| const lineNumberEl = document.createElement('td'); |
| el.setAttribute('data-side', side); |
| el.innerText = text; |
| const line = new GrDiffLine(GrDiffLineType.BOTH); |
| if (side === Side.LEFT) line.beforeNumber = lineNumber; |
| if (side === Side.RIGHT) line.afterNumber = lineNumber; |
| layer.annotate(el, lineNumberEl, line); |
| return el; |
| }; |
| |
| setup(() => { |
| highlightService = testResolver(highlightServiceToken); |
| layer = new GrSyntaxLayerWorker( |
| () => highlightService, |
| () => getAppContext().reportingService |
| ); |
| }); |
| |
| test('cancel processing', async () => { |
| const mockPromise1 = mockPromise<SyntaxLayerLine[]>(); |
| const mockPromise2 = mockPromise<SyntaxLayerLine[]>(); |
| const mockPromise3 = mockPromise<SyntaxLayerLine[]>(); |
| const mockPromise4 = mockPromise<SyntaxLayerLine[]>(); |
| const stub = sinon.stub(highlightService, 'highlight'); |
| stub.onCall(0).returns(mockPromise1); |
| stub.onCall(1).returns(mockPromise2); |
| stub.onCall(2).returns(mockPromise3); |
| stub.onCall(3).returns(mockPromise4); |
| |
| const processPromise1 = layer.process(diff); |
| // Calling the process() a second time means that the promises created |
| // during the first call are cancelled. |
| const processPromise2 = layer.process(diff); |
| // We can await the outer promise even before the inner promises resolve, |
| // because cancelling rejects the inner promises. |
| await processPromise1; |
| // It does not matter actually, whether these two inner promises are |
| // resolved or not. |
| mockPromise1.resolve(leftRanges); |
| mockPromise2.resolve(rightRanges); |
| // Both ranges must still be empty, because the promise of the first call |
| // must have been cancelled and the returned ranges ignored. |
| assert.isEmpty(layer.leftRanges); |
| assert.isEmpty(layer.rightRanges); |
| // Lets' resolve and await the promises of the second as normal. |
| mockPromise3.resolve(leftRanges); |
| mockPromise4.resolve(rightRanges); |
| await processPromise2; |
| assert.equal(layer.leftRanges, leftRanges); |
| }); |
| |
| suite('annotate and listen', () => { |
| setup(() => { |
| listener = sinon.stub(); |
| layer.addListener(listener); |
| sinon.stub(highlightService, 'highlight').callsFake((lang?: string) => { |
| if (lang === 'lang-left') return Promise.resolve(leftRanges); |
| if (lang === 'lang-right') return Promise.resolve(rightRanges); |
| return Promise.resolve([]); |
| }); |
| }); |
| |
| test('process and annotate line 2 LEFT', async () => { |
| await layer.process(diff); |
| const el = annotate(Side.LEFT, 1, 'import it;'); |
| assert.equal( |
| el.innerHTML, |
| '<hl class="gr-syntax gr-syntax-literal">import</hl> it;' |
| ); |
| assert.equal(listener.callCount, 2); |
| assert.equal(listener.getCall(0).args[0], 1); |
| assert.equal(listener.getCall(0).args[1], 1); |
| assert.equal(listener.getCall(0).args[2], Side.LEFT); |
| assert.equal(listener.getCall(1).args[0], 3); |
| assert.equal(listener.getCall(1).args[1], 3); |
| assert.equal(listener.getCall(1).args[2], Side.RIGHT); |
| }); |
| |
| test('process and annotate line 3 RIGHT', async () => { |
| await layer.process(diff); |
| const el = annotate(Side.RIGHT, 3, ' public static final {'); |
| assert.equal( |
| el.innerHTML, |
| ' <hl class="gr-syntax gr-syntax-literal">public</hl> ' + |
| '<hl class="gr-syntax gr-syntax-keyword">static</hl> ' + |
| '<hl class="gr-syntax gr-syntax-name">final</hl> {' |
| ); |
| assert.equal(listener.callCount, 2); |
| }); |
| }); |
| }); |