From 958ae9b0cea85fd58f6bb351981058857005496e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:08:34 -0700 Subject: [PATCH 1/4] Migrate search api tests to playwright --- addons/addon-search/test/SearchAddon.api.ts | 510 ------------------ addons/addon-search/test/SearchAddon.test.ts | 493 +++++++++++++++++ addons/addon-search/test/playwright.config.ts | 35 ++ addons/addon-search/test/tsconfig.json | 21 +- bin/test_playwright.js | 1 + 5 files changed, 548 insertions(+), 512 deletions(-) delete mode 100644 addons/addon-search/test/SearchAddon.api.ts create mode 100644 addons/addon-search/test/SearchAddon.test.ts create mode 100644 addons/addon-search/test/playwright.config.ts diff --git a/addons/addon-search/test/SearchAddon.api.ts b/addons/addon-search/test/SearchAddon.api.ts deleted file mode 100644 index 92fd6b291e..0000000000 --- a/addons/addon-search/test/SearchAddon.api.ts +++ /dev/null @@ -1,510 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { readFile } from 'fs'; -import { resolve } from 'path'; -import { openTerminal, writeSync, launchBrowser, timeout } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - -describe('Search Tests', function (): void { - before(async function (): Promise { - browser = await launchBrowser(); - page = await (await browser.newContext()).newPage(); - await page.setViewportSize({ width, height }); - await page.goto(APP); - await openTerminal(page); - }); - - after(() => { - browser.close(); - }); - - beforeEach(async () => { - await page.evaluate(` - window.term.reset() - window.search?.dispose(); - window.search = new SearchAddon(); - window.term.loadAddon(window.search); - `); - }); - - it('Simple Search', async () => { - await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk'); - assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test'); - }); - - it('Scrolling Search', async () => { - let dataString = ''; - for (let i = 0; i < 100; i++) { - if (i === 52) { - dataString += '$^1_3{}test$#'; - } - dataString += makeData(50); - } - await writeSync(page, dataString); - assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#'); - }); - it('Incremental Find Previous', async () => { - await page.evaluate(`window.term.writeln('package.jsonc\\n')`); - await writeSync(page, 'package.json pack package.lock'); - await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); - let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`); - // We look further ahead in the line to ensure that pack was selected from package.lock - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); - await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); - await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`); - // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); - }); - it('Incremental Find Next', async () => { - await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`); - await writeSync(page, 'package.jsonc'); - await page.evaluate(`window.search.findNext('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); - let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`); - // We look further ahead in the line to ensure that pack was selected from package.lock - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); - await page.evaluate(`window.search.findNext('package.j', {incremental: true})`); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); - await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`); - // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); - }); - it('Simple Regex', async () => { - await writeSync(page, 'abc123defABCD'); - await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc'); - await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD'); - }); - - it('Search for single result twice should not unselect it', async () => { - await writeSync(page, 'abc def'); - assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc'); - assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc'); - }); - - it('Search for result bounding with wide unicode chars', async () => { - await writeSync(page, 'δΈ­ζ–‡xxπ„žπ„ž'); - assert.deepEqual(await page.evaluate(`window.search.findNext('δΈ­')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'δΈ­'); - assert.deepEqual(await page.evaluate(`window.search.findNext('xx')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'xx'); - assert.deepEqual(await page.evaluate(`window.search.findNext('π„ž')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'π„ž'); - assert.deepEqual(await page.evaluate(`window.search.findNext('π„ž')`), true); - assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { - start: { - x: 7, - y: 0 - }, - end: { - x: 8, - y: 0 - } - }); - }); - - describe('onDidChangeResults', async () => { - describe('findNext', () => { - it('should not fire unless the decorations option is set', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc'); - assert.strictEqual(await page.evaluate(`window.search.findNext('a')`), true); - assert.strictEqual(await page.evaluate('window.calls.length'), 0); - assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate('window.calls.length'), 1); - }); - it('should fire with correct event values', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc bc c'); - assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 0, resultIndex: -1 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 0, resultIndex: -1 }, - { resultCount: 3, resultIndex: 0 }, - { resultCount: 3, resultIndex: 1 }, - { resultCount: 3, resultIndex: 2 } - ]); - }); - it('should fire with correct event values (incremental)', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'd abc aabc d'); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 0, resultIndex: -1 } - ]); - }); - it('should fire with more than 1k matches', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150); - await writeSync(page, data); - assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: 0 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: 0 }, - { resultCount: 1000, resultIndex: 1 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findNext('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: 0 }, - { resultCount: 1000, resultIndex: 1 }, - { resultCount: 1000, resultIndex: 1 } - ]); - }); - it('should fire when writing to terminal', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc bc c\\n\\r'.repeat(2)); - assert.strictEqual(await page.evaluate(`window.search.findNext('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 2, resultIndex: 0 } - ]); - await writeSync(page, 'abc bc c\\n\\r'); - await timeout(300); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 2, resultIndex: 0 }, - { resultCount: 3, resultIndex: 0 } - ]); - }); - }); - describe('findPrevious', () => { - it('should not fire unless the decorations option is set', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc'); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('a')`), true); - assert.strictEqual(await page.evaluate('window.calls.length'), 0); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate('window.calls.length'), 1); - }); - it('should fire with correct event values', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc bc c'); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 } - ]); - await page.evaluate(`window.term.clearSelection()`); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 } - ]); - await timeout(2000); - assert.strictEqual(await page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 0, resultIndex: -1 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 0, resultIndex: -1 }, - { resultCount: 3, resultIndex: 2 }, - { resultCount: 3, resultIndex: 1 }, - { resultCount: 3, resultIndex: 0 } - ]); - }); - it('should fire with correct event values (incremental)', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'd abc aabc d'); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 }, - { resultCount: 2, resultIndex: 1 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 0 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 } - ]); - assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 3, resultIndex: 2 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 2, resultIndex: 0 }, - { resultCount: 2, resultIndex: 1 }, - { resultCount: 0, resultIndex: -1 } - ]); - }); - it('should fire with more than 1k matches', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150); - await writeSync(page, data); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: -1 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: -1 }, - { resultCount: 1000, resultIndex: -1 } - ]); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 1000, resultIndex: -1 }, - { resultCount: 1000, resultIndex: -1 }, - { resultCount: 1000, resultIndex: -1 } - ]); - }); - it('should fire when writing to terminal', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - await writeSync(page, 'abc bc c\\n\\r'.repeat(2)); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 2, resultIndex: 1 } - ]); - await writeSync(page, 'abc bc c\\n\\r'); - await timeout(300); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 2, resultIndex: 1 }, - { resultCount: 3, resultIndex: 1 } - ]); - }); - }); - }); - - describe('Regression tests', () => { - describe('#2444 wrapped line content not being found', () => { - let fixture: string; - before(async () => { - const rawFixture = await new Promise(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data))); - if (process.platform === 'win32') { - fixture = rawFixture.toString() - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r'); - } else { - fixture = rawFixture.toString() - .replace(/\n/g, '\\n\\r'); - } - fixture = fixture - .replace(/'/g, `\\'`); - }); - it('should find all occurrences using findNext', async () => { - await writeSync(page, fixture); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); - // Wrap around to first result - assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); - }); - - it('should y all occurrences using findPrevious', async () => { - await writeSync(page, fixture); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); - // Wrap around to first result - assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); - selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); - }); - }); - }); - describe('#3834 lines with null characters before search terms', () => { - // This case can be triggered by the prompt when using starship under conpty - it('should find all matches on a line containing null characters', async () => { - await page.evaluate(` - window.calls = []; - window.search.onDidChangeResults(e => window.calls.push(e)); - `); - // Move cursor forward 1 time to create a null character, as opposed to regular whitespace - await writeSync(page, '\\x1b[CHi Hi'); - assert.strictEqual(await page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); - assert.deepStrictEqual(await page.evaluate('window.calls'), [ - { resultCount: 2, resultIndex: 1 } - ]); - }); - }); -}); - -function makeData(length: number): string { - let result = ''; - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return result; -} diff --git a/addons/addon-search/test/SearchAddon.test.ts b/addons/addon-search/test/SearchAddon.test.ts new file mode 100644 index 0000000000..0adf2bfe73 --- /dev/null +++ b/addons/addon-search/test/SearchAddon.test.ts @@ -0,0 +1,493 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { deepStrictEqual, strictEqual } from 'assert'; +import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils'; +import test from '@playwright/test'; +import { readFile } from 'fs'; +import { resolve } from 'path'; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); + await ctx.page.evaluate(` + window.addon = new window.SearchAddon(); + window.term.loadAddon(window.addon); + `); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('Search Tests', () => { + + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() + window.search?.dispose(); + window.search = new SearchAddon(); + window.term.loadAddon(window.search); + `); + }); + + test('Simple Search', async () => { + await ctx.proxy.write('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('test')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'test'); + }); + + test('Scrolling Search', async () => { + let dataString = ''; + for (let i = 0; i < 100; i++) { + if (i === 52) { + dataString += '$^1_3{}test$#'; + } + dataString += makeData(50); + } + await ctx.proxy.write(dataString); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), '$^1_3{}test$#'); + }); + test('Incremental Find Previous', async () => { + await ctx.proxy.writeln(`package.jsonc\n`); + await ctx.proxy.write('package.json pack package.lock'); + await ctx.page.evaluate(`window.search.findPrevious('pack', {incremental: true})`); + let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!; + let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString(); + // We look further ahead in the line to ensure that pack was selected from package.lock + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); + await ctx.page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`); + selectionPosition = (await ctx.proxy.getSelectionPosition())!; + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); + await ctx.page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`); + // We have to reevaluate line because it should have switched starting rows at this point + selectionPosition = (await ctx.proxy.getSelectionPosition())!; + line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString(); + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); + }); + test('Incremental Find Next', async () => { + await ctx.proxy.writeln(`package.lock pack package.json package.ups\n`); + await ctx.proxy.write('package.jsonc'); + await ctx.page.evaluate(`window.search.findNext('pack', {incremental: true})`); + let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!; + let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString(); + // We look further ahead in the line to ensure that pack was selected from package.lock + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); + await ctx.page.evaluate(`window.search.findNext('package.j', {incremental: true})`); + selectionPosition = (await ctx.proxy.getSelectionPosition())!; + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); + await ctx.page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`); + // We have to reevaluate line because it should have switched starting rows at this point + selectionPosition = (await ctx.proxy.getSelectionPosition())!; + line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString(); + deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); + }); + test('Simple Regex', async () => { + await ctx.proxy.write('abc123defABCD'); + await ctx.page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`); + deepStrictEqual(await ctx.proxy.getSelection(), 'abc'); + await ctx.page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`); + deepStrictEqual(await ctx.proxy.getSelection(), 'ABCD'); + }); + + test('Search for single result twice should not unselect it', async () => { + await ctx.proxy.write('abc def'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'abc'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'abc'); + }); + + test('Search for result bounding with wide unicode chars', async () => { + await ctx.proxy.write('δΈ­ζ–‡xxπ„žπ„ž'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('δΈ­')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'δΈ­'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('xx')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'xx'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('π„ž')`), true); + deepStrictEqual(await ctx.proxy.getSelection(), 'π„ž'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('π„ž')`), true); + deepStrictEqual(await ctx.proxy.getSelectionPosition(), { + start: { + x: 7, + y: 0 + }, + end: { + x: 8, + y: 0 + } + }); + }); + + test.describe('onDidChangeResults', async () => { + test.describe('findNext', () => { + test('should not fire unless the decorations option is set', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc'); + strictEqual(await ctx.page.evaluate(`window.search.findNext('a')`), true); + strictEqual(await ctx.page.evaluate('window.calls.length'), 0); + strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate('window.calls.length'), 1); + }); + test('should fire with correct event values', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc bc c'); + strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 0, resultIndex: -1 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 0, resultIndex: -1 }, + { resultCount: 3, resultIndex: 0 }, + { resultCount: 3, resultIndex: 1 }, + { resultCount: 3, resultIndex: 2 } + ]); + }); + test('should fire with correct event values (incremental)', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('d abc aabc d'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 0, resultIndex: -1 } + ]); + }); + test('should fire with more than 1k matches', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150); + await ctx.proxy.write(data); + strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: 0 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: 0 }, + { resultCount: 1000, resultIndex: 1 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findNext('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: 0 }, + { resultCount: 1000, resultIndex: 1 }, + { resultCount: 1000, resultIndex: 1 } + ]); + }); + test('should fire when writing to terminal', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc bc c\\n\\r'.repeat(2)); + strictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 2, resultIndex: 0 } + ]); + await ctx.proxy.write('abc bc c\\n\\r'); + await timeout(300); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 2, resultIndex: 0 }, + { resultCount: 3, resultIndex: 0 } + ]); + }); + }); + test.describe('findPrevious', () => { + test('should not fire unless the decorations option is set', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc'); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a')`), true); + strictEqual(await ctx.page.evaluate('window.calls.length'), 0); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate('window.calls.length'), 1); + }); + test('should fire with correct event values', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc bc c'); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 } + ]); + await ctx.page.evaluate(`window.term.clearSelection()`); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 } + ]); + await timeout(2000); + strictEqual(await ctx.page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 0, resultIndex: -1 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 0, resultIndex: -1 }, + { resultCount: 3, resultIndex: 2 }, + { resultCount: 3, resultIndex: 1 }, + { resultCount: 3, resultIndex: 0 } + ]); + }); + test('should fire with correct event values (incremental)', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('d abc aabc d'); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 }, + { resultCount: 2, resultIndex: 1 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 0 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 } + ]); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 3, resultIndex: 2 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 2, resultIndex: 0 }, + { resultCount: 2, resultIndex: 1 }, + { resultCount: 0, resultIndex: -1 } + ]); + }); + test('should fire with more than 1k matches', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150); + await ctx.proxy.write(data); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: -1 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: -1 }, + { resultCount: 1000, resultIndex: -1 } + ]); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1000, resultIndex: -1 }, + { resultCount: 1000, resultIndex: -1 }, + { resultCount: 1000, resultIndex: -1 } + ]); + }); + test('should fire when writing to terminal', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + await ctx.proxy.write('abc bc c\\n\\r'.repeat(2)); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 2, resultIndex: 1 } + ]); + await ctx.proxy.write('abc bc c\\n\\r'); + await timeout(300); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 2, resultIndex: 1 }, + { resultCount: 3, resultIndex: 1 } + ]); + }); + }); + }); + + test.describe('Regression tests', () => { + test.describe('#2444 wrapped line content not being found', () => { + let fixture: string; + test.beforeAll(async () => { + fixture = (await new Promise(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)))).toString(); + }); + test('should find all occurrences using findNext', async () => { + await ctx.proxy.write(fixture); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + let selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); + // Wrap around to first result + deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); + }); + + test('should y all occurrences using findPrevious', async () => { + await ctx.proxy.write(fixture); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + let selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); + // Wrap around to first result + deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await ctx.proxy.getSelectionPosition(); + deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); + }); + }); + }); + test.describe('#3834 lines with null characters before search terms', () => { + // This case can be triggered by the prompt when using starship under conpty + test('should find all matches on a line containing null characters', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + // Move cursor forward 1 time to create a null character, as opposed to regular whitespace + await ctx.proxy.write('\\x1b[CHi Hi'); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 2, resultIndex: 1 } + ]); + }); + }); +}); + +function makeData(length: number): string { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; +} diff --git a/addons/addon-search/test/playwright.config.ts b/addons/addon-search/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-search/test/playwright.config.ts @@ -0,0 +1,35 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: '.', + timeout: 10000, + projects: [ + { + name: 'Chrome Stable', + use: { + browserName: 'chromium', + channel: 'chrome' + } + }, + { + name: 'Firefox Stable', + use: { + browserName: 'firefox' + } + }, + { + name: 'WebKit', + use: { + browserName: 'webkit' + } + } + ], + reporter: 'list', + webServer: { + command: 'npm run start-server-only', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/addon-search/test/tsconfig.json b/addons/addon-search/test/tsconfig.json index ee45e72585..395be00d03 100644 --- a/addons/addon-search/test/tsconfig.json +++ b/addons/addon-search/test/tsconfig.json @@ -9,15 +9,32 @@ "outDir": "../out-test", "sourceMap": true, "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../node_modules/@lunapaint/png-codec", + "../../../out-test/playwright/TestUtils" ] }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index f5621a18d7..3473812216 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -20,6 +20,7 @@ while (argv.some(e => e.startsWith('--suite='))) { let configs = [ { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, + { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; From 57bac91904a3f4a52b2e338290897699f0c786fb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:25:31 -0700 Subject: [PATCH 2/4] Migrate fit api tests to playwright --- addons/addon-fit/test/FitAddon.api.ts | 135 ------------------ addons/addon-fit/test/FitAddon.test.ts | 138 +++++++++++++++++++ addons/addon-fit/test/playwright.config.ts | 35 +++++ addons/addon-fit/test/tsconfig.json | 22 ++- addons/addon-search/test/SearchAddon.test.ts | 8 +- addons/addon-search/test/tsconfig.json | 1 - bin/test_playwright.js | 5 +- 7 files changed, 197 insertions(+), 147 deletions(-) delete mode 100644 addons/addon-fit/test/FitAddon.api.ts create mode 100644 addons/addon-fit/test/FitAddon.test.ts create mode 100644 addons/addon-fit/test/playwright.config.ts diff --git a/addons/addon-fit/test/FitAddon.api.ts b/addons/addon-fit/test/FitAddon.api.ts deleted file mode 100644 index 24641fc6c4..0000000000 --- a/addons/addon-fit/test/FitAddon.api.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 1024; -const height = 768; - -describe('FitAddon', () => { - before(async function(): Promise { - browser = await launchBrowser(); - page = await (await browser.newContext()).newPage(); - await page.setViewportSize({ width, height }); - await page.goto(APP); - }); - - beforeEach(async function(): Promise { - await page.evaluate(`document.querySelector('#terminal-container').style.display=''`); - await openTerminal(page); - }); - - after(async () => { - await browser.close(); - }); - - afterEach(async function(): Promise { - await page.evaluate(`window.term.dispose()`); - }); - - it('no terminal', async function(): Promise { - await page.evaluate(`window.fit = new FitAddon();`); - assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined); - }); - - describe('proposeDimensions', () => { - afterEach(() => unloadFit()); - - it('default', async function(): Promise { - await loadFit(); - const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.isAbove(dimensions.cols, 85); - assert.isBelow(dimensions.cols, 88); - assert.isAbove(dimensions.rows, 24); - assert.isBelow(dimensions.rows, 29); - }); - - it('width', async function(): Promise { - await loadFit(1008); - const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.isAbove(dimensions.cols, 108); - assert.isBelow(dimensions.cols, 111); - assert.isAbove(dimensions.rows, 24); - assert.isBelow(dimensions.rows, 29); - }); - - it('small', async function(): Promise { - await loadFit(1, 1); - assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), { - cols: 2, - rows: 1 - }); - }); - - it('hidden', async function(): Promise { - await page.evaluate(`window.term.dispose()`); - await page.evaluate(`document.querySelector('#terminal-container').style.display='none'`); - await page.evaluate(`window.term = new Terminal()`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await loadFit(); - const dimensions: { cols: number, rows: number } | undefined = await page.evaluate(`window.fit.proposeDimensions()`); - // The value of dims will be undefined if the char measure strategy falls back to the DOM - // method, so only assert if it's not undefined. - if (dimensions) { - assert.isAbove(dimensions.cols, 85); - assert.isBelow(dimensions.cols, 88); - assert.isAbove(dimensions.rows, 24); - assert.isBelow(dimensions.rows, 29); - } - }); - }); - - describe('fit', () => { - afterEach(() => unloadFit()); - - it('default', async function(): Promise { - await loadFit(); - await page.evaluate(`window.fit.fit()`); - const cols: number = await page.evaluate(`window.term.cols`); - const rows: number = await page.evaluate(`window.term.rows`); - assert.isAbove(cols, 85); - assert.isBelow(cols, 88); - assert.isAbove(rows, 24); - assert.isBelow(rows, 29); - }); - - it('width', async function(): Promise { - await loadFit(1008); - await page.evaluate(`window.fit.fit()`); - const cols: number = await page.evaluate(`window.term.cols`); - const rows: number = await page.evaluate(`window.term.rows`); - assert.isAbove(cols, 108); - assert.isBelow(cols, 111); - assert.isAbove(rows, 24); - assert.isBelow(rows, 29); - }); - - it('small', async function(): Promise { - await loadFit(1, 1); - await page.evaluate(`window.fit.fit()`); - assert.equal(await page.evaluate(`window.term.cols`), 2); - assert.equal(await page.evaluate(`window.term.rows`), 1); - }); - }); -}); - -async function loadFit(width: number = 800, height: number = 450): Promise { - await page.evaluate(` - window.fit = new FitAddon(); - window.term.loadAddon(window.fit); - document.querySelector('#terminal-container').style.width='${width}px'; - document.querySelector('#terminal-container').style.height='${height}px'; - `); -} - -async function unloadFit(): Promise { - await page.evaluate(`window.fit.dispose();`); -} diff --git a/addons/addon-fit/test/FitAddon.test.ts b/addons/addon-fit/test/FitAddon.test.ts new file mode 100644 index 0000000000..8c186b4345 --- /dev/null +++ b/addons/addon-fit/test/FitAddon.test.ts @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import test from '@playwright/test'; +import { deepEqual, ok, strictEqual } from 'assert'; +import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils'; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('FitAddon', () => { + test.beforeEach(async function(): Promise { + await ctx.page.evaluate(` + window.term.reset() + window.fit?.dispose(); + window.fit = new FitAddon(); + window.term.loadAddon(window.fit); + `); + }); + + // test.beforeEach(async function(): Promise { + // await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`); + // await openTerminal(page); + // }); + + // after(async () => { + // await browser.close(); + // }); + + // afterEach(async function(): Promise { + // await ctx.proxy.dispose(); + // }); + + test('no terminal', async function(): Promise { + await ctx.page.evaluate(`window.fit = new FitAddon();`); + strictEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), undefined); + }); + + test.describe('proposeDimensions', () => { + // test.afterEach(() => unloadFit()); + + test('default', async function(): Promise { + await loadFit(); + const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`); + ok(dimensions.cols > 85); + ok(dimensions.cols < 88); + ok(dimensions.rows > 24); + ok(dimensions.rows < 29); + }); + + test('width', async function(): Promise { + await loadFit(1008); + const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`); + ok(dimensions.cols > 108); + ok(dimensions.cols < 111); + ok(dimensions.rows > 24); + ok(dimensions.rows < 29); + }); + + test('small', async function(): Promise { + await loadFit(1, 1); + deepEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), { + cols: 2, + rows: 1 + }); + }); + + test('hidden', async function(): Promise { + await ctx.proxy.dispose(); + await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='none'`); + await ctx.page.evaluate(`window.term = new Terminal()`); + await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await loadFit(); + const dimensions: { cols: number, rows: number } | undefined = await ctx.page.evaluate(`window.fit.proposeDimensions()`); + // The value of dims will be undefined if the char measure strategy falls back to the DOM + // method, so only assert if it's not undefined. + if (dimensions) { + ok(dimensions.cols > 85); + ok(dimensions.cols < 88); + ok(dimensions.rows > 24); + ok(dimensions.rows < 29); + } + await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='block'`); + }); + }); + + test.describe('fit', () => { + test.afterEach(() => unloadFit()); + + test('default', async function(): Promise { + await loadFit(); + await ctx.page.evaluate(`window.fit.fit()`); + const cols: number = await ctx.proxy.cols; + const rows: number = await ctx.proxy.rows; + ok(cols > 85); + ok(cols < 88); + ok(rows > 24); + ok(rows < 29); + }); + + test('width', async function(): Promise { + await loadFit(1008); + await ctx.page.evaluate(`window.fit.fit()`); + const cols: number = await ctx.proxy.cols; + const rows: number = await ctx.proxy.rows; + ok(cols > 108); + ok(cols < 111); + ok(rows > 24); + ok(rows < 29); + }); + + test('small', async function(): Promise { + await loadFit(1, 1); + await ctx.page.evaluate(`window.fit.fit()`); + strictEqual(await ctx.proxy.cols, 2); + strictEqual(await ctx.proxy.rows, 1); + }); + }); +}); + +async function loadFit(width: number = 800, height: number = 450): Promise { + await ctx.page.evaluate(` + window.fit = new FitAddon(); + window.term.loadAddon(window.fit); + document.querySelector('#terminal-container').style.width='${width}px'; + document.querySelector('#terminal-container').style.height='${height}px'; + `); +} + +async function unloadFit(): Promise { + await ctx.page.evaluate(`window.fit.dispose();`); +} diff --git a/addons/addon-fit/test/playwright.config.ts b/addons/addon-fit/test/playwright.config.ts new file mode 100644 index 0000000000..79cf9f0290 --- /dev/null +++ b/addons/addon-fit/test/playwright.config.ts @@ -0,0 +1,35 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: '.', + timeout: 10000, + projects: [ + { + name: 'ChromeStable', + use: { + browserName: 'chromium', + channel: 'chrome' + } + }, + { + name: 'FirefoxStable', + use: { + browserName: 'firefox' + } + }, + { + name: 'WebKit', + use: { + browserName: 'webkit' + } + } + ], + reporter: 'list', + webServer: { + command: 'npm run start-server-only', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/addon-fit/test/tsconfig.json b/addons/addon-fit/test/tsconfig.json index 67ad42b720..cb6fed28dd 100644 --- a/addons/addon-fit/test/tsconfig.json +++ b/addons/addon-fit/test/tsconfig.json @@ -3,21 +3,37 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../out-test/playwright/TestUtils" ] }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/addons/addon-search/test/SearchAddon.test.ts b/addons/addon-search/test/SearchAddon.test.ts index 0adf2bfe73..80b0a120da 100644 --- a/addons/addon-search/test/SearchAddon.test.ts +++ b/addons/addon-search/test/SearchAddon.test.ts @@ -3,20 +3,16 @@ * @license MIT */ -import { deepStrictEqual, strictEqual } from 'assert'; -import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils'; import test from '@playwright/test'; +import { deepStrictEqual, strictEqual } from 'assert'; import { readFile } from 'fs'; import { resolve } from 'path'; +import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); await openTerminal(ctx); - await ctx.page.evaluate(` - window.addon = new window.SearchAddon(); - window.term.loadAddon(window.addon); - `); }); test.afterAll(async () => await ctx.page.close()); diff --git a/addons/addon-search/test/tsconfig.json b/addons/addon-search/test/tsconfig.json index 395be00d03..cb6fed28dd 100644 --- a/addons/addon-search/test/tsconfig.json +++ b/addons/addon-search/test/tsconfig.json @@ -21,7 +21,6 @@ "strict": true, "types": [ "../../../node_modules/@types/node", - "../../../node_modules/@lunapaint/png-codec", "../../../out-test/playwright/TestUtils" ] }, diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 3473812216..30d21e9599 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -18,10 +18,11 @@ while (argv.some(e => e.startsWith('--suite='))) { } let configs = [ - { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, + { name: 'addon-fit', path: 'addons/addon-fit/out-test/playwright.config.js' }, { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, - { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } + { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; if (suiteFilter) { From 4295dfaf20b448aaa9cfef7083ae10d92a18a616 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:49:36 -0700 Subject: [PATCH 3/4] Migrate clipboard api tests to playwright (only chromium) --- .../test/ClipboardAddon.api.ts | 88 ------------------- .../test/ClipboardAddon.test.ts | 78 ++++++++++++++++ .../addon-clipboard/test/playwright.config.ts | 35 ++++++++ addons/addon-clipboard/test/tsconfig.json | 22 ++++- bin/test_playwright.js | 11 +-- 5 files changed, 138 insertions(+), 96 deletions(-) delete mode 100644 addons/addon-clipboard/test/ClipboardAddon.api.ts create mode 100644 addons/addon-clipboard/test/ClipboardAddon.test.ts create mode 100644 addons/addon-clipboard/test/playwright.config.ts diff --git a/addons/addon-clipboard/test/ClipboardAddon.api.ts b/addons/addon-clipboard/test/ClipboardAddon.api.ts deleted file mode 100644 index 4ef768e8e7..0000000000 --- a/addons/addon-clipboard/test/ClipboardAddon.api.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2023 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { openTerminal, launchBrowser, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; -import { Browser, BrowserContext, Page } from '@playwright/test'; -import { beforeEach } from 'mocha'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let context: BrowserContext; -let page: Page; -const width = 800; -const height = 600; - -describe('ClipboardAddon', () => { - before(async function (): Promise { - browser = await launchBrowser({ - // Enable clipboard access in firefox, mainly for readText - firefoxUserPrefs: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dom.events.testing.asyncClipboard': true, - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dom.events.asyncClipboard.readText': true - } - }); - context = await browser.newContext(); - if (getBrowserType().name() !== 'webkit') { - // Enable clipboard access in chromium without user gesture - context.grantPermissions(['clipboard-read', 'clipboard-write']); - } - page = await context.newPage(); - await page.setViewportSize({ width, height }); - await page.goto(APP); - await openTerminal(page); - await page.evaluate(` - window.clipboardAddon = new ClipboardAddon(); - window.term.loadAddon(window.clipboardAddon); - `); - }); - - after(() => { - browser.close(); - }); - - beforeEach(async () => { - await page.evaluate(`window.term.reset()`); - }); - - const testDataEncoded = 'aGVsbG8gd29ybGQ='; - const testDataDecoded = 'hello world'; - - describe('write data', async function (): Promise { - it('simple string', async () => { - await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`); - assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); - }); - it('invalid base64 string', async () => { - await writeSync(page, `\x1b]52;c;${testDataEncoded}invalid\x07`); - assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); - }); - it('empty string', async () => { - await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`); - await writeSync(page, `\x1b]52;c;\x07`); - assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); - }); - }); - - describe('read data', async function (): Promise { - it('simple string', async () => { - await page.evaluate(` - window.data = []; - window.term.onData(e => data.push(e)); - `); - await page.evaluate(() => window.navigator.clipboard.writeText('hello world')); - await writeSync(page, `\x1b]52;c;?\x07`); - assert.deepEqual(await page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]); - }); - it('clear clipboard', async () => { - await writeSync(page, `\x1b]52;c;!\x07`); - await writeSync(page, `\x1b]52;c;?\x07`); - assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); - }); - }); -}); diff --git a/addons/addon-clipboard/test/ClipboardAddon.test.ts b/addons/addon-clipboard/test/ClipboardAddon.test.ts new file mode 100644 index 0000000000..6234a3cddd --- /dev/null +++ b/addons/addon-clipboard/test/ClipboardAddon.test.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import test from '@playwright/test'; +import { deepEqual, ok, strictEqual } from 'assert'; +import { ITestContext, createTestContext, launchBrowser, openTerminal, timeout } from '../../../out-test/playwright/TestUtils'; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }, testInfo) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); +}); +test.afterAll(async () => { + await ctx.page.close(); +}); + +test.describe('ClipboardAddon', () => { + + test.beforeEach(async ({}, testInfo) => { + // DEBT: This test doesn't work since the migration to @playwright/test + if (ctx.browser.browserType().name() !== 'chromium') { + testInfo.skip(); + return; + } + if (ctx.browser.browserType().name() === 'chromium') { + // Enable clipboard access in chromium without user gesture + await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); + } + await ctx.page.evaluate(` + window.term.reset() + window.clipboard?.dispose(); + window.clipboard = new ClipboardAddon(); + window.term.loadAddon(window.clipboard); + `); + }); + + test.beforeEach(async () => { + await ctx.proxy.reset(); + }); + + const testDataEncoded = 'aGVsbG8gd29ybGQ='; + const testDataDecoded = 'hello world'; + + test.describe('write data', async function (): Promise { + test('simple string', async () => { + await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); + test('invalid base64 string', async () => { + await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}invalid\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + test('empty string', async () => { + await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`); + await ctx.proxy.write(`\x1b]52;c;\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + }); + + test.describe('read data', async function (): Promise { + test('simple string', async () => { + await ctx.page.evaluate(` + window.data = []; + window.term.onData(e => data.push(e)); + `); + await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world')); + await ctx.proxy.write(`\x1b]52;c;?\x07`); + deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]); + }); + test('clear clipboard', async () => { + await ctx.proxy.write(`\x1b]52;c;!\x07`); + await ctx.proxy.write(`\x1b]52;c;?\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + }); +}); diff --git a/addons/addon-clipboard/test/playwright.config.ts b/addons/addon-clipboard/test/playwright.config.ts new file mode 100644 index 0000000000..79cf9f0290 --- /dev/null +++ b/addons/addon-clipboard/test/playwright.config.ts @@ -0,0 +1,35 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: '.', + timeout: 10000, + projects: [ + { + name: 'ChromeStable', + use: { + browserName: 'chromium', + channel: 'chrome' + } + }, + { + name: 'FirefoxStable', + use: { + browserName: 'firefox' + } + }, + { + name: 'WebKit', + use: { + browserName: 'webkit' + } + } + ], + reporter: 'list', + webServer: { + command: 'npm run start-server-only', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/addon-clipboard/test/tsconfig.json b/addons/addon-clipboard/test/tsconfig.json index 67ad42b720..cb6fed28dd 100644 --- a/addons/addon-clipboard/test/tsconfig.json +++ b/addons/addon-clipboard/test/tsconfig.json @@ -3,21 +3,37 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../out-test/playwright/TestUtils" ] }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 30d21e9599..4063f5f512 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -18,11 +18,12 @@ while (argv.some(e => e.startsWith('--suite='))) { } let configs = [ - { name: 'core', path: 'out-test/playwright/playwright.config.js' }, - { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, - { name: 'addon-fit', path: 'addons/addon-fit/out-test/playwright.config.js' }, - { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, - { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } + { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, + { name: 'addon-clipboard', path: 'addons/addon-clipboard/out-test/playwright.config.js' }, + { name: 'addon-fit', path: 'addons/addon-fit/out-test/playwright.config.js' }, + { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, + { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; if (suiteFilter) { From 713f765731e781cbadbd5b7fdcc3a9dcb30e2ca2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:52:47 -0700 Subject: [PATCH 4/4] Migrate attach api tests to playwright --- addons/addon-attach/test/AttachAddon.api.ts | 50 ------------------- addons/addon-attach/test/AttachAddon.test.ts | 42 ++++++++++++++++ addons/addon-attach/test/playwright.config.ts | 35 +++++++++++++ addons/addon-attach/test/tsconfig.json | 22 ++++++-- bin/test_playwright.js | 1 + 5 files changed, 97 insertions(+), 53 deletions(-) delete mode 100644 addons/addon-attach/test/AttachAddon.api.ts create mode 100644 addons/addon-attach/test/AttachAddon.test.ts create mode 100644 addons/addon-attach/test/playwright.config.ts diff --git a/addons/addon-attach/test/AttachAddon.api.ts b/addons/addon-attach/test/AttachAddon.api.ts deleted file mode 100644 index 230b9e2e25..0000000000 --- a/addons/addon-attach/test/AttachAddon.api.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import WebSocket = require('ws'); -import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - -describe('AttachAddon', () => { - before(async function(): Promise { - browser = await launchBrowser(); - page = await (await browser.newContext()).newPage(); - await page.setViewportSize({ width, height }); - }); - - after(async () => { - await browser.close(); - }); - - beforeEach(async () => await page.goto(APP)); - - it('string', async function(): Promise { - await openTerminal(page); - const port = 8080; - const server = new WebSocket.Server({ port }); - server.on('connection', socket => socket.send('foo')); - await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); - await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); - server.close(); - }); - - it('utf8', async function(): Promise { - await openTerminal(page); - const port = 8080; - const server = new WebSocket.Server({ port }); - const data = new Uint8Array([102, 111, 111]); - server.on('connection', socket => socket.send(data)); - await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); - await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); - server.close(); - }); -}); diff --git a/addons/addon-attach/test/AttachAddon.test.ts b/addons/addon-attach/test/AttachAddon.test.ts new file mode 100644 index 0000000000..efb64e6f12 --- /dev/null +++ b/addons/addon-attach/test/AttachAddon.test.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import WebSocket = require('ws'); + +import test from '@playwright/test'; +import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../out-test/playwright/TestUtils'; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('Search Tests', () => { + + test.beforeEach(async () => { + await ctx.proxy.reset(); + }); + + test('string', async function(): Promise { + const port = 8080; + const server = new WebSocket.Server({ port }); + server.on('connection', socket => socket.send('foo')); + await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); + server.close(); + }); + + test('utf8', async function(): Promise { + const port = 8080; + const server = new WebSocket.Server({ port }); + const data = new Uint8Array([102, 111, 111]); + server.on('connection', socket => socket.send(data)); + await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); + server.close(); + }); +}); diff --git a/addons/addon-attach/test/playwright.config.ts b/addons/addon-attach/test/playwright.config.ts new file mode 100644 index 0000000000..79cf9f0290 --- /dev/null +++ b/addons/addon-attach/test/playwright.config.ts @@ -0,0 +1,35 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: '.', + timeout: 10000, + projects: [ + { + name: 'ChromeStable', + use: { + browserName: 'chromium', + channel: 'chrome' + } + }, + { + name: 'FirefoxStable', + use: { + browserName: 'firefox' + } + }, + { + name: 'WebKit', + use: { + browserName: 'webkit' + } + } + ], + reporter: 'list', + webServer: { + command: 'npm run start-server-only', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/addon-attach/test/tsconfig.json b/addons/addon-attach/test/tsconfig.json index 67ad42b720..cb6fed28dd 100644 --- a/addons/addon-attach/test/tsconfig.json +++ b/addons/addon-attach/test/tsconfig.json @@ -3,21 +3,37 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../out-test/playwright/TestUtils" ] }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 4063f5f512..0417f2764f 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -19,6 +19,7 @@ while (argv.some(e => e.startsWith('--suite='))) { let configs = [ { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'addon-attach', path: 'addons/addon-attach/out-test/playwright.config.js' }, { name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' }, { name: 'addon-clipboard', path: 'addons/addon-clipboard/out-test/playwright.config.js' }, { name: 'addon-fit', path: 'addons/addon-fit/out-test/playwright.config.js' },