From 4b6c8f1fe4218a7c508f55bd4b5d9176efbebf28 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:04:04 -0700 Subject: [PATCH 01/21] Get tests working on node that requires shell arg These silently fail on Windows after a recent change in node.js --- bin/test.js | 8 ++++++-- bin/test_api.js | 6 +++++- bin/test_playwright.js | 7 +++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bin/test.js b/bin/test.js index b9725acdad..1624895524 100644 --- a/bin/test.js +++ b/bin/test.js @@ -31,7 +31,6 @@ if (process.argv.length > 2) { } const checkCoverage = flagArgs.indexOf('--coverage') >= 0; - if (checkCoverage) { flagArgs.splice(flagArgs.indexOf('--coverage'), 1); const executable = npmBinScript('nyc'); @@ -44,6 +43,7 @@ if (checkCoverage) { { cwd: path.resolve(__dirname, '..'), env, + shell: true, stdio: 'inherit' } ); @@ -56,6 +56,7 @@ const run = cp.spawnSync( { cwd: path.resolve(__dirname, '..'), env, + shell: true, stdio: 'inherit' } ); @@ -64,4 +65,7 @@ function npmBinScript(script) { return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script)); } -process.exit(run.status); +if (run.error) { + console.error(run.error); +} +process.exit(run.status ?? -1); diff --git a/bin/test_api.js b/bin/test_api.js index ff166727df..bdfbc7aff9 100644 --- a/bin/test_api.js +++ b/bin/test_api.js @@ -50,6 +50,7 @@ server.stdout.on('data', (data) => { [...testFiles, ...flagArgs], { cwd: path.resolve(__dirname, '..'), env, + shell: true, stdio: 'inherit' } ); @@ -61,7 +62,10 @@ server.stdout.on('data', (data) => { server.kill(); - process.exit(run.status); + if (run.error) { + console.error(run.error); + } + process.exit(run.status ?? -1); } }); diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 0cd6f85cf6..f5621a18d7 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -40,12 +40,15 @@ async function run() { console.log(`\n\x1b[32m${command}\x1b[0m`, args); const run = cp.spawnSync(command, args, { cwd: path.resolve(__dirname, '..'), + shell: true, stdio: 'inherit' } ); - if (run.status) { - process.exit(run.status); + + if (run.error) { + console.error(run.error); } + process.exit(run.status ?? -1); } } run(); From 645b698f37864b8ddbd31d902aa3c3948f331056 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:39:54 -0700 Subject: [PATCH 02/21] Reduce coverage threshold --- bin/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test.js b/bin/test.js index 1624895524..b6492454b1 100644 --- a/bin/test.js +++ b/bin/test.js @@ -6,7 +6,7 @@ const cp = require('child_process'); const path = require('path'); -const COVERAGE_LINES_THRESHOLD = 60; +const COVERAGE_LINES_THRESHOLD = 40; // Add `out` to the NODE_PATH so absolute paths can be resolved. const env = { ...process.env }; From daa2440a407139a8c32bbdcc431d1e7f44109399 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:42:39 -0700 Subject: [PATCH 03/21] Fix playwright project names --- addons/addon-canvas/test/playwright.config.ts | 4 ++-- addons/addon-webgl/test/playwright.config.ts | 4 ++-- package.json | 4 ++-- test/playwright/playwright.config.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/addons/addon-canvas/test/playwright.config.ts b/addons/addon-canvas/test/playwright.config.ts index b0e565c546..79cf9f0290 100644 --- a/addons/addon-canvas/test/playwright.config.ts +++ b/addons/addon-canvas/test/playwright.config.ts @@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = { timeout: 10000, projects: [ { - name: 'Chrome Stable', + name: 'ChromeStable', use: { browserName: 'chromium', channel: 'chrome' } }, { - name: 'Firefox Stable', + name: 'FirefoxStable', use: { browserName: 'firefox' } diff --git a/addons/addon-webgl/test/playwright.config.ts b/addons/addon-webgl/test/playwright.config.ts index b0e565c546..79cf9f0290 100644 --- a/addons/addon-webgl/test/playwright.config.ts +++ b/addons/addon-webgl/test/playwright.config.ts @@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = { timeout: 10000, projects: [ { - name: 'Chrome Stable', + name: 'ChromeStable', use: { browserName: 'chromium', channel: 'chrome' } }, { - name: 'Firefox Stable', + name: 'FirefoxStable', use: { browserName: 'firefox' } diff --git a/package.json b/package.json index c79b1196ed..5e15baee9a 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000", "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000", "test-playwright": "node ./bin/test_playwright.js --workers=75%", - "test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=Chrome Stable\"", - "test-playwright-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=Firefox Stable\"", + "test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=ChromeStable\"", + "test-playwright-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=FirefoxStable\"", "test-playwright-webkit": "node ./bin/test_playwright.js --workers=75% \"--project=WebKit\"", "test-playwright-debug": "node ./bin/test_playwright.js --workers=1 --headed --timeout=30000", "test-unit": "node ./bin/test.js", diff --git a/test/playwright/playwright.config.ts b/test/playwright/playwright.config.ts index 3d1fe4fb02..c94fff8707 100644 --- a/test/playwright/playwright.config.ts +++ b/test/playwright/playwright.config.ts @@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = { timeout: 10000, projects: [ { - name: 'Chrome Stable', + name: 'ChromeStable', use: { browserName: 'chromium', channel: 'chrome' } }, { - name: 'Firefox Stable', + name: 'FirefoxStable', use: { browserName: 'firefox' } 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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' }, From c9ad9cbf0e07f5ebd066722c112bd28db3b0cbc0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:30:27 -0700 Subject: [PATCH 08/21] Migrate serialize api tests to playwright --- ...izeAddon.api.ts => SerializeAddon.test.ts} | 287 +++++++++--------- .../addon-serialize/test/playwright.config.ts | 35 +++ addons/addon-serialize/test/tsconfig.json | 11 +- bin/test_playwright.js | 1 + 4 files changed, 185 insertions(+), 149 deletions(-) rename addons/addon-serialize/test/{SerializeAddon.api.ts => SerializeAddon.test.ts} (61%) create mode 100644 addons/addon-serialize/test/playwright.config.ts diff --git a/addons/addon-serialize/test/SerializeAddon.api.ts b/addons/addon-serialize/test/SerializeAddon.test.ts similarity index 61% rename from addons/addon-serialize/test/SerializeAddon.api.ts rename to addons/addon-serialize/test/SerializeAddon.test.ts index c943e7b299..30ff60026e 100644 --- a/addons/addon-serialize/test/SerializeAddon.api.ts +++ b/addons/addon-serialize/test/SerializeAddon.test.ts @@ -3,49 +3,47 @@ * @license MIT */ -import { assert } from 'chai'; -import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; +import test from '@playwright/test'; +import { deepStrictEqual, notDeepStrictEqual, strictEqual } from 'assert'; +import { readFile } from 'fs'; +import { resolve } from 'path'; +import { ITestContext, createTestContext, openTerminal, timeout, writeSync } from '../../../out-test/playwright/TestUtils'; -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - -const writeRawSync = (page: any, str: string): Promise => writeSync(page, `' +` + JSON.stringify(str) + `+ '`); +const writeRawSync = (page: any, str: string): Promise => writeSync(ctx.page, `' +` + JSON.stringify(str) + `+ '`); const testNormalScreenEqual = async (page: any, str: string): Promise => { - await writeRawSync(page, str); - const originalBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + await writeRawSync(ctx.page, str); + const originalBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - const result = await page.evaluate(`serializeAddon.serialize();`) as string; - await page.evaluate(`term.reset();`); - await writeRawSync(page, result); - const newBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string; + await ctx.page.evaluate(`term.reset();`); + await writeRawSync(ctx.page, result); + const newBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - // chai decides -0 and 0 are different number... - // and firefox have a bug that output -0 for unknown reason - assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer)); + deepStrictEqual(JSON.stringify(originalBuffer), JSON.stringify(newBuffer)); }; async function testSerializeEquals(writeContent: string, expectedSerialized: string): Promise { - await writeRawSync(page, writeContent); - const result = await page.evaluate(`serializeAddon.serialize();`) as string; - assert.strictEqual(result, expectedSerialized); + await writeRawSync(ctx.page, writeContent); + const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string; + strictEqual(result, expectedSerialized); } -describe('SerializeAddon', () => { - 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, { rows: 10, cols: 10 }); - await page.evaluate(` - window.serializeAddon = new SerializeAddon(); - window.term.loadAddon(window.serializeAddon); +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx, { rows: 10, cols: 10 }); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('SerializeAddon', () => { + + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() + window.serialize?.dispose(); + window.serialize = new SerializeAddon(); + window.term.loadAddon(window.serialize); window.inspectBuffer = (buffer) => { const lines = []; for (let i = 0; i < buffer.length; i++) { @@ -62,56 +60,53 @@ describe('SerializeAddon', () => { `); }); - after(async () => await browser.close()); - beforeEach(async () => await page.evaluate(`window.term.reset()`)); + test.beforeEach(async () => { + await ctx.proxy.reset(); + }); - it('produce different output when we call test util with different text', async function(): Promise { - await writeRawSync(page, '12345'); - const buffer1 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + test('produce different output when we call test util with different text', async function(): Promise { + await writeRawSync(ctx.page, '12345'); + const buffer1 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - await page.evaluate(`term.reset();`); - await writeRawSync(page, '67890'); - const buffer2 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + await ctx.page.evaluate(`term.reset();`); + await writeRawSync(ctx.page, '67890'); + const buffer2 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - assert.throw(() => { - assert.equal(JSON.stringify(buffer1), JSON.stringify(buffer2)); - }); + notDeepStrictEqual(JSON.stringify(buffer1), JSON.stringify(buffer2)); }); - it('produce different output when we call test util with different line wrap', async function(): Promise { - await writeRawSync(page, '1234567890\r\n12345'); - const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + test('produce different output when we call test util with different line wrap', async function(): Promise { + await writeRawSync(ctx.page, '1234567890\r\n12345'); + const buffer3 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - await page.evaluate(`term.reset();`); - await writeRawSync(page, '123456789012345'); - const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); + await ctx.page.evaluate(`term.reset();`); + await writeRawSync(ctx.page, '123456789012345'); + const buffer4 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); - assert.throw(() => { - assert.equal(JSON.stringify(buffer3), JSON.stringify(buffer4)); - }); + notDeepStrictEqual(JSON.stringify(buffer3), JSON.stringify(buffer4)); }); - it('empty content', async function(): Promise { - assert.equal(await page.evaluate(`serializeAddon.serialize();`), ''); + test('empty content', async function(): Promise { + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), ''); }); - it('unwrap wrapped line', async function(): Promise { + test('unwrap wrapped line', async function(): Promise { const lines = ['123456789123456789']; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('does not unwrap non-wrapped line', async function(): Promise { + test('does not unwrap non-wrapped line', async function(): Promise { const lines = [ '123456789', '123456789' ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('preserve last empty lines', async function(): Promise { + test('preserve last empty lines', async function(): Promise { const cols = 10; const lines = [ '', @@ -126,50 +121,50 @@ describe('SerializeAddon', () => { '', '' ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('digits content', async function(): Promise { + test('digits content', async function(): Promise { const rows = 10; const cols = 10; const digitsLine = digitsString(cols); const lines = newArray(digitsLine, rows); - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize with half of scrollback', async function(): Promise { + test('serialize with half of scrollback', async function(): Promise { const rows = 20; const scrollback = rows - 10; const halfScrollback = scrollback / 2; const cols = 10; const lines = newArray((index: number) => digitsString(cols, index), rows); - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n')); }); - it('serialize 0 rows of scrollback', async function(): Promise { + test('serialize 0 rows of scrollback', async function(): Promise { const rows = 20; const cols = 10; const lines = newArray((index: number) => digitsString(cols, index), rows); - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n')); }); - it('serialize exclude modes', async () => { - await writeSync(page, 'before\\x1b[?1hafter'); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'beforeafter\x1b[?1h'); - assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeModes: true });`), 'beforeafter'); + test('serialize exclude modes', async () => { + await ctx.proxy.write('before\x1b[?1hafter'); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'beforeafter\x1b[?1h'); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeModes: true });`), 'beforeafter'); }); - it('serialize exclude alt buffer', async () => { - await writeSync(page, 'normal\\x1b[?1049h\\x1b[Halt'); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'normal\x1b[?1049h\x1b[Halt'); - assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeAltBuffer: true });`), 'normal'); + test('serialize exclude alt buffer', async () => { + await ctx.proxy.write('normal\x1b[?1049h\x1b[Halt'); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'normal\x1b[?1049h\x1b[Halt'); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeAltBuffer: true });`), 'normal'); }); - it('serialize all rows of content with color16', async function(): Promise { + test('serialize all rows of content with color16', async function(): Promise { const cols = 10; const color16 = [ 30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color @@ -182,11 +177,11 @@ describe('SerializeAddon', () => { (index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`), rows ); - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with fg/bg flags', async function(): Promise { + test('serialize all rows of content with fg/bg flags', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -204,22 +199,22 @@ describe('SerializeAddon', () => { sgr(NO_INVISIBLE) + line, sgr(NO_STRIKETHROUGH) + line ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with color256', async function(): Promise { + test('serialize all rows of content with color256', async function(): Promise { const rows = 32; const cols = 10; const lines = newArray( (index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`), rows ); - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with overline', async () => { + test('serialize all rows of content with overline', async () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -227,11 +222,11 @@ describe('SerializeAddon', () => { sgr(UNDERLINED) + line, // Overlined, Underlined sgr(NORMAL) + line // Normal ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with color16 and style separately', async function(): Promise { + test('serialize all rows of content with color16 and style separately', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -246,11 +241,11 @@ describe('SerializeAddon', () => { sgr(BG_RESET) + line, // Underlined, Inverse sgr(NORMAL) + line // Back to normal ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with color16 and style together', async function(): Promise { + test('serialize all rows of content with color16 and style together', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -268,11 +263,11 @@ describe('SerializeAddon', () => { sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic sgr(BG_RESET) + line // Italic ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with color256 and style separately', async function(): Promise { + test('serialize all rows of content with color256 and style separately', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -287,11 +282,11 @@ describe('SerializeAddon', () => { sgr(BG_RESET) + line, // Underlined, Inverse sgr(NORMAL) + line // Back to normal ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with color256 and style together', async function(): Promise { + test('serialize all rows of content with color256 and style together', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -309,11 +304,11 @@ describe('SerializeAddon', () => { sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic sgr(BG_RESET) + line // Italic ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with colorRGB and style separately', async function(): Promise { + test('serialize all rows of content with colorRGB and style separately', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -328,11 +323,11 @@ describe('SerializeAddon', () => { sgr(BG_RESET) + line, // Underlined, Inverse sgr(NORMAL) + line // Back to normal ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize all rows of content with colorRGB and style together', async function(): Promise { + test('serialize all rows of content with colorRGB and style together', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ @@ -350,11 +345,11 @@ describe('SerializeAddon', () => { sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic sgr(BG_RESET) + line // Italic ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize tabs correctly', async () => { + test('serialize tabs correctly', async () => { const lines = [ 'a\tb', 'aa\tc', @@ -365,11 +360,11 @@ describe('SerializeAddon', () => { 'aa\x1b[6Cc', 'aaa\x1b[5Cd' ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\r\n')); }); - it('serialize CJK correctly', async () => { + test('serialize CJK correctly', async () => { const lines = [ 'δΈ­ζ–‡δΈ­ζ–‡', '12δΈ­ζ–‡', @@ -381,22 +376,22 @@ describe('SerializeAddon', () => { // see also #3097 '1δΈ­ζ–‡δΈ­ζ–‡δΈ­' ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n')); }); - it('serialize CJK Mixed with tab correctly', async () => { + test('serialize CJK Mixed with tab correctly', async () => { const lines = [ 'δΈ­ζ–‡\t12' // CJK mixed with tab ]; const expected = [ 'δΈ­ζ–‡\x1b[4C12' ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n')); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\r\n')); }); - it('serialize with alt screen correctly', async () => { + test('serialize with alt screen correctly', async () => { const SMCUP = '\u001b[?1049h'; const CUP = '\u001b[H'; @@ -407,12 +402,12 @@ describe('SerializeAddon', () => { `1${SMCUP}${CUP}2` ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate'); - assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n'))); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'alternate'); + strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\r\n'))); }); - it('serialize without alt screen correctly', async () => { + test('serialize without alt screen correctly', async () => { const SMCUP = '\u001b[?1049h'; const RMCUP = '\u001b[?1049l'; @@ -423,12 +418,12 @@ describe('SerializeAddon', () => { `1` ]; - await writeSync(page, lines.join('\\r\\n')); - assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal'); - assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n'))); + await ctx.proxy.write(lines.join('\r\n')); + strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'normal'); + strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\r\n'))); }); - it('serialize with background', async () => { + test('serialize with background', async () => { const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; const lines = [ @@ -436,10 +431,10 @@ describe('SerializeAddon', () => { `2${CLEAR_RIGHT(9)}` ]; - await testNormalScreenEqual(page, lines.join('\r\n')); + await testNormalScreenEqual(ctx.page, lines.join('\r\n')); }); - it('cause the BCE on scroll', async () => { + test('cause the BCE on scroll', async () => { const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; const padLines = newArray( @@ -452,10 +447,10 @@ describe('SerializeAddon', () => { `\u001b[44m${CLEAR_RIGHT(5)}1111111111111111` ]; - await testNormalScreenEqual(page, lines.join('\r\n')); + await testNormalScreenEqual(ctx.page, lines.join('\r\n')); }); - it('handle invalid wrap before scroll', async () => { + test('handle invalid wrap before scroll', async () => { const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; const MOVE_UP = (l: number): string => `\u001b[${l}A`; const MOVE_DOWN = (l: number): string => `\u001b[${l}B`; @@ -475,10 +470,10 @@ describe('SerializeAddon', () => { '1' ]; - await testNormalScreenEqual(page, segments.join('')); + await testNormalScreenEqual(ctx.page, segments.join('')); }); - it('handle invalid wrap after scroll', async () => { + test('handle invalid wrap after scroll', async () => { const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`; const MOVE_UP = (l: number): string => `\u001b[${l}A`; const MOVE_DOWN = (l: number): string => `\u001b[${l}B`; @@ -505,27 +500,27 @@ describe('SerializeAddon', () => { '1' ]; - await testNormalScreenEqual(page, lines.join('')); + await testNormalScreenEqual(ctx.page, lines.join('')); }); - describe('handle modes', () => { - it('applicationCursorKeysMode', async () => { + test.describe('handle modes', () => { + test('applicationCursorKeysMode', async () => { await testSerializeEquals('test\u001b[?1h', 'test\u001b[?1h'); await testSerializeEquals('\u001b[?1l', 'test'); }); - it('applicationKeypadMode', async () => { + test('applicationKeypadMode', async () => { await testSerializeEquals('test\u001b[?66h', 'test\u001b[?66h'); await testSerializeEquals('\u001b[?66l', 'test'); }); - it('bracketedPasteMode', async () => { + test('bracketedPasteMode', async () => { await testSerializeEquals('test\u001b[?2004h', 'test\u001b[?2004h'); await testSerializeEquals('\u001b[?2004l', 'test'); }); - it('insertMode', async () => { + test('insertMode', async () => { await testSerializeEquals('test\u001b[4h', 'test\u001b[4h'); await testSerializeEquals('\u001b[4l', 'test'); }); - it('mouseTrackingMode', async () => { + test('mouseTrackingMode', async () => { await testSerializeEquals('test\u001b[?9h', 'test\u001b[?9h'); await testSerializeEquals('\u001b[?9l', 'test'); await testSerializeEquals('\u001b[?1000h', 'test\u001b[?1000h'); @@ -535,20 +530,20 @@ describe('SerializeAddon', () => { await testSerializeEquals('\u001b[?1003h', 'test\u001b[?1003h'); await testSerializeEquals('\u001b[?1003l', 'test'); }); - it('originMode', async () => { + test('originMode', async () => { // origin mode moves cursor to (0,0) await testSerializeEquals('test\u001b[?6h', 'test\u001b[4D\u001b[?6h'); await testSerializeEquals('\u001b[?6l', 'test\u001b[4D'); }); - it('reverseWraparoundMode', async () => { + test('reverseWraparoundMode', async () => { await testSerializeEquals('test\u001b[?45h', 'test\u001b[?45h'); await testSerializeEquals('\u001b[?45l', 'test'); }); - it('sendFocusMode', async () => { + test('sendFocusMode', async () => { await testSerializeEquals('test\u001b[?1004h', 'test\u001b[?1004h'); await testSerializeEquals('\u001b[?1004l', 'test'); }); - it('wraparoundMode', async () => { + test('wraparoundMode', async () => { await testSerializeEquals('test\u001b[?7l', 'test\u001b[?7l'); await testSerializeEquals('\u001b[?7h', 'test'); }); diff --git a/addons/addon-serialize/test/playwright.config.ts b/addons/addon-serialize/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-serialize/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-serialize/test/tsconfig.json b/addons/addon-serialize/test/tsconfig.json index 6eeb5cde04..cb6fed28dd 100644 --- a/addons/addon-serialize/test/tsconfig.json +++ b/addons/addon-serialize/test/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", @@ -13,13 +13,15 @@ "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": [ @@ -29,6 +31,9 @@ "references": [ { "path": "../../../src/common" + }, + { + "path": "../../../src/browser" } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 0417f2764f..775bd1752b 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -24,6 +24,7 @@ let configs = [ { 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-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; From ab2cb9a80922ab48a71cf35d9cceb694e7cd1fb7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:33:17 -0700 Subject: [PATCH 09/21] Migrate unicode-graphemes api tests to playwright --- .../test/UnicodeGraphemesAddon.api.ts | 73 ------------------- .../test/UnicodeGraphemesAddon.test.ts | 71 ++++++++++++++++++ .../test/playwright.config.ts | 35 +++++++++ .../test/tsconfig.json | 16 ++-- bin/test_playwright.js | 17 +++-- 5 files changed, 125 insertions(+), 87 deletions(-) delete mode 100644 addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts create mode 100644 addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts create mode 100644 addons/addon-unicode-graphemes/test/playwright.config.ts diff --git a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts deleted file mode 100644 index e13ad2921c..0000000000 --- a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.api.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { openTerminal, 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('UnicodeGraphemesAddon', () => { - 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 function(): Promise { - await page.goto(APP); - await openTerminal(page); - }); - async function evalWidth(str: string): Promise { - return page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`); - } - const ourVersion = '15-graphemes'; - it('wcwidth V15 emoji test', async () => { - await page.evaluate(` - window.unicode = new UnicodeGraphemesAddon(); - window.term.loadAddon(window.unicode); - `); - // should have loaded '15-graphemes' - assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']); - // switch should not throw - await page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`); - assert.equal(await page.evaluate(`window.term.unicode.activeVersion`), ourVersion); - assert.equal(await evalWidth('🀣🀣🀣🀣🀣🀣🀣🀣🀣🀣'), 20, - '10 emoji - width 10 in V6; 20 in V11 or later'); - assert.equal(await evalWidth('\u{1F476}\u{1F3FF}\u{1F476}'), 4, - 'baby with emoji modifier fitzpatrick type-6; baby'); - assert.equal(await evalWidth('\u{1F469}\u200d\u{1f469}\u200d\u{1f466}'), 2, - 'woman+zwj+woman+zwj+boy'); - assert.equal(await evalWidth('=\u{1F3CB}\u{FE0F}=\u{F3CB}\u{1F3FE}\u200D\u2640='), 7, - 'person lifting weights (plain, emoji); woman lighting weights, medium dark'); - assert.equal(await evalWidth('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393}'), 6, - 'woman; woman student; man student dark'); - assert.equal(await evalWidth('\u{1f1f3}\u{1f1f4}/'), 3, - 'regional indicator symbol letters N and O, cluster'); - assert.equal(await evalWidth('\u{1f1f3}/\u{1f1f4}'), 3, - 'regional indicator symbol letters N and O, separated'); - assert.equal(await evalWidth('\u0061\u0301'), 1, - 'letter a with acute accent'); - assert.equal(await evalWidth('{\u1100\u1161\u11a8\u1100\u1161}'), 6, - 'Korean Jamo'); - assert.equal(await evalWidth('\uAC00=\uD685='), 6, - 'Hangul syllables (pre-composed)'); - assert.equal(await evalWidth('(\u26b0\ufe0e)'), 3, - 'coffin with text presentation'); - assert.equal(await evalWidth('(\u26b0\ufe0f)'), 4, - 'coffin with emoji presentation'); - assert.equal(await evalWidth(''), 16, - 'Γ‰galitΓ© (using separate acute) emoij_presentation'); - }); -}); diff --git a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts new file mode 100644 index 0000000000..2603c056c0 --- /dev/null +++ b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +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); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('UnicodeGraphemesAddon', () => { + + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() + window.search?.dispose(); + window.search = new SearchAddon(); + window.term.loadAddon(window.search); + `); + }); + + async function evalWidth(str: string): Promise { + return ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`); + } + const ourVersion = '15-graphemes'; + test('wcwidth V15 emoji test', async () => { + await ctx.page.evaluate(` + window.unicode = new UnicodeGraphemesAddon(); + window.term.loadAddon(window.unicode); + `); + // should have loaded '15-graphemes' + deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']); + // switch should not throw + await ctx.page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`); + strictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), ourVersion); + strictEqual(await evalWidth('🀣🀣🀣🀣🀣🀣🀣🀣🀣🀣'), 20, + '10 emoji - width 10 in V6; 20 in V11 or later'); + strictEqual(await evalWidth('\u{1F476}\u{1F3FF}\u{1F476}'), 4, + 'baby with emoji modifier fitzpatrick type-6; baby'); + strictEqual(await evalWidth('\u{1F469}\u200d\u{1f469}\u200d\u{1f466}'), 2, + 'woman+zwj+woman+zwj+boy'); + strictEqual(await evalWidth('=\u{1F3CB}\u{FE0F}=\u{F3CB}\u{1F3FE}\u200D\u2640='), 7, + 'person lifting weights (plain, emoji); woman lighting weights, medium dark'); + strictEqual(await evalWidth('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393}'), 6, + 'woman; woman student; man student dark'); + strictEqual(await evalWidth('\u{1f1f3}\u{1f1f4}/'), 3, + 'regional indicator symbol letters N and O, cluster'); + strictEqual(await evalWidth('\u{1f1f3}/\u{1f1f4}'), 3, + 'regional indicator symbol letters N and O, separated'); + strictEqual(await evalWidth('\u0061\u0301'), 1, + 'letter a with acute accent'); + strictEqual(await evalWidth('{\u1100\u1161\u11a8\u1100\u1161}'), 6, + 'Korean Jamo'); + strictEqual(await evalWidth('\uAC00=\uD685='), 6, + 'Hangul syllables (pre-composed)'); + strictEqual(await evalWidth('(\u26b0\ufe0e)'), 3, + 'coffin with text presentation'); + strictEqual(await evalWidth('(\u26b0\ufe0f)'), 4, + 'coffin with emoji presentation'); + strictEqual(await evalWidth(''), 16, + 'Γ‰galitΓ© (using separate acute) emoij_presentation'); + }); +}); diff --git a/addons/addon-unicode-graphemes/test/playwright.config.ts b/addons/addon-unicode-graphemes/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-unicode-graphemes/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-unicode-graphemes/test/tsconfig.json b/addons/addon-unicode-graphemes/test/tsconfig.json index 4b3cb31cfd..cb6fed28dd 100644 --- a/addons/addon-unicode-graphemes/test/tsconfig.json +++ b/addons/addon-unicode-graphemes/test/tsconfig.json @@ -1,26 +1,27 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ - "dom", - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, - "strict": 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": [ @@ -30,6 +31,9 @@ "references": [ { "path": "../../../src/common" + }, + { + "path": "../../../src/browser" } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 775bd1752b..291d381b2f 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -18,14 +18,15 @@ 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' }, - { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, - { name: 'addon-serialize', path: 'addons/addon-serialize/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-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' }, + { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, + { name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, + { name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' }, + { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; if (suiteFilter) { From 89f3353a832d8753aadde5ee81b86655651cd50f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:37:06 -0700 Subject: [PATCH 10/21] Migrate unicode11 api tests to playwright --- .../test/UnicodeGraphemesAddon.test.ts | 14 ++---- .../test/Unicode11Addon.api.ts | 46 ------------------- .../test/Unicode11Addon.test.ts | 37 +++++++++++++++ .../addon-unicode11/test/playwright.config.ts | 35 ++++++++++++++ addons/addon-unicode11/test/tsconfig.json | 14 ++++-- bin/test_playwright.js | 1 + 6 files changed, 86 insertions(+), 61 deletions(-) delete mode 100644 addons/addon-unicode11/test/Unicode11Addon.api.ts create mode 100644 addons/addon-unicode11/test/Unicode11Addon.test.ts create mode 100644 addons/addon-unicode11/test/playwright.config.ts diff --git a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts index 2603c056c0..9bf272ef40 100644 --- a/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts +++ b/addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts @@ -5,9 +5,7 @@ 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'; +import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { @@ -21,9 +19,9 @@ test.describe('UnicodeGraphemesAddon', () => { test.beforeEach(async () => { await ctx.page.evaluate(` window.term.reset() - window.search?.dispose(); - window.search = new SearchAddon(); - window.term.loadAddon(window.search); + window.unicode?.dispose(); + window.unicode = new UnicodeGraphemesAddon(); + window.term.loadAddon(window.unicode); `); }); @@ -32,10 +30,6 @@ test.describe('UnicodeGraphemesAddon', () => { } const ourVersion = '15-graphemes'; test('wcwidth V15 emoji test', async () => { - await ctx.page.evaluate(` - window.unicode = new UnicodeGraphemesAddon(); - window.term.loadAddon(window.unicode); - `); // should have loaded '15-graphemes' deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']); // switch should not throw diff --git a/addons/addon-unicode11/test/Unicode11Addon.api.ts b/addons/addon-unicode11/test/Unicode11Addon.api.ts deleted file mode 100644 index 83c7a61d47..0000000000 --- a/addons/addon-unicode11/test/Unicode11Addon.api.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { openTerminal, 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('Unicode11Addon', () => { - 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 function(): Promise { - await page.goto(APP); - await openTerminal(page); - }); - - it('wcwidth V11 emoji test', async () => { - await page.evaluate(` - window.unicode11 = new Unicode11Addon(); - window.term.loadAddon(window.unicode11); - `); - // should have loaded '11' - assert.deepEqual((await page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true); - // switch should not throw - await page.evaluate(`window.term.unicode.activeVersion = '11';`); - assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11'); - // v6: 10, V11: 20 - assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🀣🀣🀣🀣🀣🀣🀣🀣🀣🀣')`), 20); - }); -}); diff --git a/addons/addon-unicode11/test/Unicode11Addon.test.ts b/addons/addon-unicode11/test/Unicode11Addon.test.ts new file mode 100644 index 0000000000..4e941c34d9 --- /dev/null +++ b/addons/addon-unicode11/test/Unicode11Addon.test.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import test from '@playwright/test'; +import { deepStrictEqual } from 'assert'; +import { ITestContext, createTestContext, openTerminal } 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('Unicode11Addon', () => { + + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() + window.unicode11?.dispose(); + window.unicode11 = new Unicode11Addon(); + window.term.loadAddon(window.unicode11); + `); + }); + + test('wcwidth V11 emoji test', async () => { + // should have loaded '11' + deepStrictEqual((await ctx.page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true); + // switch should not throw + await ctx.page.evaluate(`window.term.unicode.activeVersion = '11';`); + deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), '11'); + // v6: 10, V11: 20 + deepStrictEqual(await ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🀣🀣🀣🀣🀣🀣🀣🀣🀣🀣')`), 20); + }); +}); diff --git a/addons/addon-unicode11/test/playwright.config.ts b/addons/addon-unicode11/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-unicode11/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-unicode11/test/tsconfig.json b/addons/addon-unicode11/test/tsconfig.json index 0406d3a414..cb6fed28dd 100644 --- a/addons/addon-unicode11/test/tsconfig.json +++ b/addons/addon-unicode11/test/tsconfig.json @@ -3,24 +3,25 @@ "module": "commonjs", "target": "es2021", "lib": [ - "dom", - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, - "strict": 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": [ @@ -30,6 +31,9 @@ "references": [ { "path": "../../../src/common" + }, + { + "path": "../../../src/browser" } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 291d381b2f..7ba7a8f574 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -26,6 +26,7 @@ let configs = [ { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, { name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, { name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' }, + { name: 'addon-unicode11', path: 'addons/addon-unicode11/out-test/playwright.config.js' }, { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; From 779ec7fad8c7f87a8149bc72384ce98ed51e17fa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:41:19 -0700 Subject: [PATCH 11/21] Migrate web-links api tests to playwright --- ...inksAddon.api.ts => WebLinksAddon.test.ts} | 130 +++++++++--------- .../addon-web-links/test/playwright.config.ts | 35 +++++ addons/addon-web-links/test/tsconfig.json | 23 +++- bin/test_playwright.js | 1 + 4 files changed, 118 insertions(+), 71 deletions(-) rename addons/addon-web-links/test/{WebLinksAddon.api.ts => WebLinksAddon.test.ts} (65%) create mode 100644 addons/addon-web-links/test/playwright.config.ts diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.test.ts similarity index 65% rename from addons/addon-web-links/test/WebLinksAddon.api.ts rename to addons/addon-web-links/test/WebLinksAddon.test.ts index 99c11600db..abad515097 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.test.ts @@ -2,18 +2,11 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ - -import { assert } from 'chai'; -import { openTerminal, pollFor, writeSync, 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; - +import test from '@playwright/test'; +import { deepStrictEqual, strictEqual } from 'assert'; +import { readFile } from 'fs'; +import { resolve } from 'path'; +import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../out-test/playwright/TestUtils'; interface ILinkStateData { uri?: string; @@ -30,21 +23,20 @@ interface ILinkStateData { } -describe('WebLinksAddon', () => { - 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, { cols: 40 }); - }); +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx, { cols: 40 }); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('WebLinksAddon', () => { - after(async () => await browser.close()); - beforeEach(async () => { - await page.evaluate(` + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() window._linkaddon?.dispose(); - window.term.reset(); - window._linkaddon = new window.WebLinksAddon(); + window._linkaddon = new WebLinksAddon(); window.term.loadAddon(window._linkaddon); `); }); @@ -71,36 +63,36 @@ describe('WebLinksAddon', () => { '.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw' ]; for (const tld of countryTlds) { - it(tld, async () => await testHostName(`foo${tld}`)); + test(tld, async () => await testHostName(`foo${tld}`)); } - it(`.com`, async () => await testHostName(`foo.com`)); + test(`.com`, async () => await testHostName(`foo.com`)); for (const tld of countryTlds) { - it(`.com${tld}`, async () => await testHostName(`foo.com${tld}`)); + test(`.com${tld}`, async () => await testHostName(`foo.com${tld}`)); } - describe('correct buffer offsets & uri', () => { - beforeEach(async () => { - await page.evaluate(` + test.describe('correct buffer offsets & uri', () => { + test.beforeEach(async () => { + await ctx.page.evaluate(` window._linkStateData = {uri:''}; window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; }; `); }); - it('all half width', async () => { - await writeSync(page, 'aaa http://example.com aaa http://example.com aaa'); + test('all half width', async () => { + await ctx.proxy.write('aaa http://example.com aaa http://example.com aaa'); await resetAndHover(5, 0); await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } }); await resetAndHover(1, 1); await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } }); }); - it('url after full width', async () => { - await writeSync(page, 'οΏ₯οΏ₯οΏ₯ http://example.com οΏ₯οΏ₯οΏ₯ http://example.com aaa'); + test('url after full width', async () => { + await ctx.proxy.write('οΏ₯οΏ₯οΏ₯ http://example.com οΏ₯οΏ₯οΏ₯ http://example.com aaa'); await resetAndHover(8, 0); await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } }); await resetAndHover(1, 1); await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } }); }); - it('full width within url and before', async () => { - await writeSync(page, 'οΏ₯οΏ₯οΏ₯ https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ aaa https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ οΏ₯οΏ₯οΏ₯'); + test('full width within url and before', async () => { + await ctx.proxy.write('οΏ₯οΏ₯οΏ₯ https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ aaa https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ οΏ₯οΏ₯οΏ₯'); await resetAndHover(8, 0); await evalLinkStateData('https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } }); await resetAndHover(1, 1); @@ -108,15 +100,15 @@ describe('WebLinksAddon', () => { await resetAndHover(17, 1); await evalLinkStateData('https://ko.wikipedia.org/wiki/μœ„ν‚€λ°±κ³Ό:λŒ€λ¬Έ', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } }); }); - it('name + password url after full width and combining', async () => { - await writeSync(page, 'οΏ₯οΏ₯οΏ₯cafe\u0301 http://test:password@example.com/some_path'); + test('name + password url after full width and combining', async () => { + await ctx.proxy.write('οΏ₯οΏ₯οΏ₯cafe\u0301 http://test:password@example.com/some_path'); await resetAndHover(12, 0); await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } }); await resetAndHover(5, 1); await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } }); }); - it('url encoded params work properly', async () => { - await writeSync(page, 'οΏ₯οΏ₯οΏ₯cafe\u0301 http://test:password@example.com/some_path?param=1%202%3'); + test('url encoded params work properly', async () => { + await ctx.proxy.write('οΏ₯οΏ₯οΏ₯cafe\u0301 http://test:password@example.com/some_path?param=1%202%3'); await resetAndHover(12, 0); await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } }); await resetAndHover(5, 1); @@ -125,13 +117,14 @@ describe('WebLinksAddon', () => { }); // issue #4964 - it('uppercase in protocol and host, default ports', async () => { - const data = ` HTTP://EXAMPLE.COM \\r\\n` + - ` HTTPS://Example.com \\r\\n` + - ` HTTP://Example.com:80 \\r\\n` + - ` HTTP://Example.com:80/staysUpper \\r\\n` + - ` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`; - await writeSync(page, data); + test('uppercase in protocol and host, default ports', async () => { + await ctx.proxy.write( + ` HTTP://EXAMPLE.COM \r\n` + + ` HTTPS://Example.com \r\n` + + ` HTTP://Example.com:80 \r\n` + + ` HTTP://Example.com:80/staysUpper \r\n` + + ` HTTP://Ab:xY@abc.com:80/staysUpper \r\n` + ); await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); @@ -141,14 +134,15 @@ describe('WebLinksAddon', () => { }); async function testHostName(hostname: string): Promise { - const data = ` http://${hostname} \\r\\n` + - ` http://${hostname}/a~b#c~d?e~f \\r\\n` + - ` http://${hostname}/colon:test \\r\\n` + - ` http://${hostname}/colon:test: \\r\\n` + - `"http://${hostname}/"\\r\\n` + - `\\'http://${hostname}/\\'\\r\\n` + - `http://${hostname}/subpath/+/id`; - await writeSync(page, data); + await ctx.proxy.write( + ` http://${hostname} \r\n` + + ` http://${hostname}/a~b#c~d?e~f \r\n` + + ` http://${hostname}/colon:test \r\n` + + ` http://${hostname}/colon:test: \r\n` + + `"http://${hostname}/"\r\n` + + `\'http://${hostname}/\'\r\n` + + `http://${hostname}/subpath/+/id` + ); await pollForLinkAtCell(3, 0, `http://${hostname}`); await pollForLinkAtCell(3, 1, `http://${hostname}/a~b#c~d?e~f`); await pollForLinkAtCell(3, 2, `http://${hostname}/colon:test`); @@ -159,28 +153,28 @@ async function testHostName(hostname: string): Promise { } async function pollForLinkAtCell(col: number, row: number, value: string): Promise { - await page.mouse.move(...(await cellPos(col, row))); - await pollFor(page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true); - const text = await page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`); - assert.deepEqual(text, value); + await ctx.page.mouse.move(...(await cellPos(col, row))); + await pollFor(ctx.page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true); + const text = await ctx.page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`); + deepStrictEqual(text, value); } async function resetAndHover(col: number, row: number): Promise { - await page.mouse.move(0, 0); - await page.evaluate(`window._linkStateData = {uri:''};`); + await ctx.page.mouse.move(0, 0); + await ctx.page.evaluate(`window._linkStateData = {uri:''};`); await new Promise(r => setTimeout(r, 200)); - await page.mouse.move(...(await cellPos(col, row))); - await pollFor(page, `!!window._linkStateData.uri.length`, true); + await ctx.page.mouse.move(...(await cellPos(col, row))); + await pollFor(ctx.page, `!!window._linkStateData.uri.length`, true); } async function evalLinkStateData(uri: string, range: any): Promise { - const data: ILinkStateData = await page.evaluate(`window._linkStateData`); - assert.equal(data.uri, uri); - assert.deepEqual(data.range, range); + const data: ILinkStateData = await ctx.page.evaluate(`window._linkStateData`); + strictEqual(data.uri, uri); + deepStrictEqual(data.range, range); } async function cellPos(col: number, row: number): Promise<[number, number]> { - const coords: any = await page.evaluate(` + const coords: any = await ctx.page.evaluate(` (function() { const rect = window.term.element.getBoundingClientRect(); const dim = term._core._renderService.dimensions; diff --git a/addons/addon-web-links/test/playwright.config.ts b/addons/addon-web-links/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-web-links/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-web-links/test/tsconfig.json b/addons/addon-web-links/test/tsconfig.json index 2b4ec58f7a..cb6fed28dd 100644 --- a/addons/addon-web-links/test/tsconfig.json +++ b/addons/addon-web-links/test/tsconfig.json @@ -3,20 +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" + "../../../node_modules/@types/node", + "../../../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 7ba7a8f574..c48ccdb68c 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -27,6 +27,7 @@ let configs = [ { name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, { name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' }, { name: 'addon-unicode11', path: 'addons/addon-unicode11/out-test/playwright.config.js' }, + { name: 'addon-web-links', path: 'addons/addon-web-links/out-test/playwright.config.js' }, { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ]; From 580bb082b52963311c9cdee3ebcb733a779f6578 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:56:09 -0700 Subject: [PATCH 12/21] Migrate image api tests to playwright --- addons/addon-image/test/ImageAddon.api.ts | 321 ------------------- addons/addon-image/test/ImageAddon.test.ts | 313 ++++++++++++++++++ addons/addon-image/test/playwright.config.ts | 35 ++ addons/addon-image/test/tsconfig.json | 26 +- bin/test_playwright.js | 1 + 5 files changed, 367 insertions(+), 329 deletions(-) delete mode 100644 addons/addon-image/test/ImageAddon.api.ts create mode 100644 addons/addon-image/test/ImageAddon.test.ts create mode 100644 addons/addon-image/test/playwright.config.ts diff --git a/addons/addon-image/test/ImageAddon.api.ts b/addons/addon-image/test/ImageAddon.api.ts deleted file mode 100644 index 776e96bfd0..0000000000 --- a/addons/addon-image/test/ImageAddon.api.ts +++ /dev/null @@ -1,321 +0,0 @@ -/** - * Copyright (c) 2020 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { openTerminal, launchBrowser, pollFor } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; -import { IImageAddonOptions } from '../src/Types'; -import { FINALIZER, introducer, sixelEncode } from 'sixel'; -import { readFileSync } from 'fs'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - -// eslint-disable-next-line -declare const ImageAddon: { - new(options?: Partial): any; -}; - -interface ITestData { - width: number; - height: number; - bytes: Uint8Array; - palette: number[]; - sixel: string; -} - -interface IDimensions { - cellWidth: number; - cellHeight: number; - width: number; - height: number; -} - -// image: 640 x 80, 512 color -const TESTDATA: ITestData = (() => { - const data8 = readFileSync('./addons/addon-image/fixture/palette.blob'); - const data32 = new Uint32Array(data8.buffer); - const palette = new Set(); - for (let i = 0; i < data32.length; ++i) palette.add(data32[i]); - const sixel = sixelEncode(data8, 640, 80, [...palette]); - return { - width: 640, - height: 80, - bytes: data8, - palette: [...palette], - sixel - }; -})(); -const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER; -// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER; -// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER; - -// NOTE: the data is loaded as string for easier transport through playwright -const TESTDATA_IIP: [string, [number, number]][] = [ - [readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]], - [readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]], - [readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]], - [readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]], - [readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]] -]; - -describe('ImageAddon', () => { - before(async () => { - 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); - await openTerminal(page); - await page.evaluate(opts => { - (window as any).imageAddon = new ImageAddon(opts.opts); - (window as any).term.loadAddon((window as any).imageAddon); - }, { opts: { sixelPaletteLimit: 512 } }); - }); - - it('test for private accessors', async () => { - // terminal privates - const accessors = [ - '_core', - '_core._renderService', - '_core._inputHandler', - '_core._inputHandler._parser', - '_core._inputHandler._curAttrData', - '_core._inputHandler._dirtyRowTracker', - '_core._themeService.colors', - '_core._coreBrowserService' - ]; - for (const prop of accessors) { - assert.equal( - await page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'), - true, `problem at ${prop}` - ); - } - // bufferline privates - assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true); - assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true); - // inputhandler privates - assert.equal(await page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), 'AttributeData'); - assert.equal(await page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser'); - }); - - describe('ctor options', () => { - it('empty settings should load defaults', async () => { - const DEFAULT_OPTIONS: IImageAddonOptions = { - enableSizeReports: true, - pixelLimit: 16777216, - sixelSupport: true, - sixelScrolling: true, - sixelPaletteLimit: 512, // set to 512 to get example image working - sixelSizeLimit: 25000000, - storageLimit: 128, - showPlaceholder: true, - iipSupport: true, - iipSizeLimit: 20000000 - }; - assert.deepEqual(await page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS); - }); - it('custom settings should overload defaults', async () => { - const customSettings: IImageAddonOptions = { - enableSizeReports: false, - pixelLimit: 5, - sixelSupport: false, - sixelScrolling: false, - sixelPaletteLimit: 1024, - sixelSizeLimit: 1000, - storageLimit: 10, - showPlaceholder: false, - iipSupport: false, - iipSizeLimit: 1000 - }; - await page.evaluate(opts => { - (window as any).imageAddonCustom = new ImageAddon(opts.opts); - (window as any).term.loadAddon((window as any).imageAddonCustom); - }, { opts: customSettings }); - assert.deepEqual(await page.evaluate(`window.imageAddonCustom._opts`), customSettings); - }); - }); - - describe('scrolling & cursor modes', () => { - it('testdata default (scrolling with VT240 cursor pos)', async () => { - const dim = await getDimensions(); - await writeToTerminal(SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]); - // moved to right by 10 cells - await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]); - }); - it('write testdata noScrolling', async () => { - await writeToTerminal('\x1b[?80h' + SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [0, 0]); - // second draw does not change anything - await writeToTerminal(SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [0, 0]); - }); - it('testdata cursor always at VT240 pos', async () => { - const dim = await getDimensions(); - // offset 0 - await writeToTerminal(SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]); - // moved to right by 10 cells - await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]); - // moved by 30 cells (+10 prev) - await writeToTerminal('#'.repeat(30) + SIXEL_SEQ_0); - assert.deepEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]); - }); - }); - - describe('image lifecycle & eviction', () => { - it('delete image once scrolled off', async () => { - await writeToTerminal(SIXEL_SEQ_0); - pollFor(page, 'window.imageAddon._storage._images.size', 1); - // scroll to scrollback + rows - 1 - await page.evaluate( - scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)), - (await getScrollbackPlusRows() - 1) - ); - // wait here, as we have to make sure, that eviction did not yet occur - await new Promise(r => setTimeout(r, 100)); - pollFor(page, 'window.imageAddon._storage._images.size', 1); - // scroll one further should delete the image - await page.evaluate(() => new Promise(res => (window as any).term.write('\n', res))); - pollFor(page, 'window.imageAddon._storage._images.size', 0); - }); - it('get storageUsage', async () => { - assert.equal(await page.evaluate('imageAddon.storageUsage'), 0); - await writeToTerminal(SIXEL_SEQ_0); - assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05); - }); - it('get/set storageLimit', async () => { - assert.equal(await page.evaluate('imageAddon.storageLimit'), 128); - assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1); - assert.equal(await page.evaluate('imageAddon.storageLimit'), 1); - }); - it('remove images by storage limit pressure', async () => { - assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1); - // never go beyond storage limit - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await new Promise(r => setTimeout(r, 50)); - const usage = await page.evaluate('imageAddon.storageUsage'); - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0); - await new Promise(r => setTimeout(r, 50)); - assert.equal(await page.evaluate('imageAddon.storageUsage'), usage); - assert.equal(usage as number < 1, true); - }); - it('set storageLimit removes images synchronously', async () => { - await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); - const usage: number = await page.evaluate('imageAddon.storageUsage'); - const newUsage: number = await page.evaluate('imageAddon.storageLimit = 0.5; imageAddon.storageUsage'); - assert.equal(newUsage < usage, true); - assert.equal(newUsage < 0.5, true); - }); - it('clear alternate images on buffer change', async () => { - assert.equal(await page.evaluate('imageAddon.storageUsage'), 0); - await writeToTerminal('\x1b[?1049h' + SIXEL_SEQ_0); - assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05); - await writeToTerminal('\x1b[?1049l'); - assert.equal(await page.evaluate('imageAddon.storageUsage'), 0); - }); - it('evict tiles by in-place overwrites (only full overwrite tested)', async () => { - await new Promise(r => setTimeout(r, 50)); - await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H'); - let usage = await page.evaluate('imageAddon.storageUsage'); - while (usage === 0) { - await new Promise(r => setTimeout(r, 50)); - usage = await page.evaluate('imageAddon.storageUsage'); - } - await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H'); - await new Promise(r => setTimeout(r, 200)); // wait some time and re-check - assert.equal(await page.evaluate('imageAddon.storageUsage'), usage); - }); - it('manual eviction on alternate buffer must not miss images', async () => { - await writeToTerminal('\x1b[?1049h'); - await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); - await new Promise(r => setTimeout(r, 50)); - const usage: number = await page.evaluate('imageAddon.storageUsage'); - await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); - await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); - await new Promise(r => setTimeout(r, 50)); - const newUsage: number = await page.evaluate('imageAddon.storageUsage'); - assert.equal(newUsage, usage); - }); - }); - - describe('IIP support - testimages', () => { - it('palette.png', async () => { - await writeToTerminal(TESTDATA_IIP[0][0]); - assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[0][1]); - }); - it('spinfox.png', async () => { - await writeToTerminal(TESTDATA_IIP[1][0]); - assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[1][1]); - }); - it('w3c gif', async () => { - await writeToTerminal(TESTDATA_IIP[2][0]); - assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[2][1]); - }); - it('w3c jpeg', async () => { - await writeToTerminal(TESTDATA_IIP[3][0]); - assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[3][1]); - }); - it('w3c png', async () => { - await writeToTerminal(TESTDATA_IIP[4][0]); - assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[4][1]); - }); - }); -}); - -/** - * terminal access helpers. - */ -async function getDimensions(): Promise { - const dimensions: any = await page.evaluate(`term._core._renderService.dimensions`); - return { - cellWidth: Math.round(dimensions.css.cell.width), - cellHeight: Math.round(dimensions.css.cell.height), - width: Math.round(dimensions.css.canvas.width), - height: Math.round(dimensions.css.canvas.height) - }; -} - -async function getCursor(): Promise<[number, number]> { - return page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]'); -} - -async function getImageStorageLength(): Promise { - return page.evaluate('window.imageAddon._storage._images.size'); -} - -async function getScrollbackPlusRows(): Promise { - return page.evaluate('window.term.options.scrollback + window.term.rows'); -} - -async function writeToTerminal(d: string): Promise { - return page.evaluate(data => new Promise(res => (window as any).term.write(data, res)), d); -} - -async function getOrigSize(id: number): Promise<[number, number]> { - return page.evaluate(`[ - window.imageAddon._storage._images.get(${id}).orig.width, - window.imageAddon._storage._images.get(${id}).orig.height - ]`); -} diff --git a/addons/addon-image/test/ImageAddon.test.ts b/addons/addon-image/test/ImageAddon.test.ts new file mode 100644 index 0000000000..abd1295c22 --- /dev/null +++ b/addons/addon-image/test/ImageAddon.test.ts @@ -0,0 +1,313 @@ +/** + * Copyright (c) 2020 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import test from '@playwright/test'; +import { readFileSync } from 'fs'; +import { FINALIZER, introducer, sixelEncode } from 'sixel'; +import { ITestContext, createTestContext, openTerminal, pollFor } from '../../../out-test/playwright/TestUtils'; +import { IImageAddonOptions } from '../src/Types'; +import { deepStrictEqual, ok, strictEqual } from 'assert'; + +// eslint-disable-next-line +declare const ImageAddon: { + new(options?: Partial): any; +}; + +interface ITestData { + width: number; + height: number; + bytes: Uint8Array; + palette: number[]; + sixel: string; +} + +interface IDimensions { + cellWidth: number; + cellHeight: number; + width: number; + height: number; +} + +// image: 640 x 80, 512 color +const TESTDATA: ITestData = (() => { + const data8 = readFileSync('./addons/addon-image/fixture/palette.blob'); + const data32 = new Uint32Array(data8.buffer); + const palette = new Set(); + for (let i = 0; i < data32.length; ++i) palette.add(data32[i]); + const sixel = sixelEncode(data8, 640, 80, [...palette]); + return { + width: 640, + height: 80, + bytes: data8, + palette: [...palette], + sixel + }; +})(); +const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER; +// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER; +// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER; + +// NOTE: the data is loaded as string for easier transport through playwright +const TESTDATA_IIP: [string, [number, number]][] = [ + [readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]], + [readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]], + [readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]] +]; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('ImageAddon', () => { + + test.beforeEach(async ({}, testInfo) => { + // DEBT: This test never worked on webkit + if (ctx.browser.browserType().name() === 'webkit') { + testInfo.skip(); + return; + } + await ctx.page.evaluate(` + window.term.reset() + window.imageAddon?.dispose(); + window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 }); + window.term.loadAddon(window.imageAddon); + `); + }); + + test('test for private accessors', async () => { + // terminal privates + const accessors = [ + '_core', + '_core._renderService', + '_core._inputHandler', + '_core._inputHandler._parser', + '_core._inputHandler._curAttrData', + '_core._inputHandler._dirtyRowTracker', + '_core._themeService.colors', + '_core._coreBrowserService' + ]; + for (const prop of accessors) { + strictEqual( + await ctx.page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'), + true, `problem at ${prop}` + ); + } + // bufferline privates + strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true); + strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true); + // inputhandler privates + strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), 'AttributeData'); + strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser'); + }); + + test.describe('ctor options', () => { + test('empty settings should load defaults', async () => { + const DEFAULT_OPTIONS: IImageAddonOptions = { + enableSizeReports: true, + pixelLimit: 16777216, + sixelSupport: true, + sixelScrolling: true, + sixelPaletteLimit: 512, // set to 512 to get example image working + sixelSizeLimit: 25000000, + storageLimit: 128, + showPlaceholder: true, + iipSupport: true, + iipSizeLimit: 20000000 + }; + deepStrictEqual(await ctx.page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS); + }); + test('custom settings should overload defaults', async () => { + const customSettings: IImageAddonOptions = { + enableSizeReports: false, + pixelLimit: 5, + sixelSupport: false, + sixelScrolling: false, + sixelPaletteLimit: 1024, + sixelSizeLimit: 1000, + storageLimit: 10, + showPlaceholder: false, + iipSupport: false, + iipSizeLimit: 1000 + }; + await ctx.page.evaluate(opts => { + (window as any).imageAddonCustom = new ImageAddon(opts.opts); + (window as any).term.loadAddon((window as any).imageAddonCustom); + }, { opts: customSettings }); + deepStrictEqual(await ctx.page.evaluate(`window.imageAddonCustom._opts`), customSettings); + }); + }); + + test.describe('scrolling & cursor modes', () => { + test('testdata default (scrolling with VT240 cursor pos)', async () => { + const dim = await getDimensions(); + await ctx.proxy.write(SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]); + // moved to right by 10 cells + await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]); + }); + test('write testdata noScrolling', async () => { + await ctx.proxy.write('\x1b[?80h' + SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [0, 0]); + // second draw does not change anything + await ctx.proxy.write(SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [0, 0]); + }); + test('testdata cursor always at VT240 pos', async () => { + const dim = await getDimensions(); + // offset 0 + await ctx.proxy.write(SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]); + // moved to right by 10 cells + await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]); + // moved by 30 cells (+10 prev) + await ctx.proxy.write('#'.repeat(30) + SIXEL_SEQ_0); + deepStrictEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]); + }); + }); + + test.describe('image lifecycle & eviction', () => { + test('delete image once scrolled off', async () => { + await ctx.proxy.write(SIXEL_SEQ_0); + pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1); + // scroll to scrollback + rows - 1 + await ctx.page.evaluate( + scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)), + (await getScrollbackPlusRows() - 1) + ); + // wait here, as we have to make sure, that eviction did not yet occur + await new Promise(r => setTimeout(r, 100)); + pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1); + // scroll one further should delete the image + await ctx.page.evaluate(() => new Promise(res => (window as any).term.write('\n', res))); + pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0); + }); + test('get storageUsage', async () => { + strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0); + await ctx.proxy.write(SIXEL_SEQ_0); + ok(Math.abs((await ctx.page.evaluate('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05); + }); + test('get/set storageLimit', async () => { + strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 128); + strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1); + strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 1); + }); + test('remove images by storage limit pressure', async () => { + strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1); + // never go beyond storage limit + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await new Promise(r => setTimeout(r, 50)); + const usage = await ctx.page.evaluate('window.imageAddon.storageUsage'); + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0); + await new Promise(r => setTimeout(r, 50)); + strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage); + strictEqual(usage as number < 1, true); + }); + test('set storageLimit removes images synchronously', async () => { + await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); + const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage'); + const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageLimit = 0.5; window.imageAddon.storageUsage'); + strictEqual(newUsage < usage, true); + strictEqual(newUsage < 0.5, true); + }); + test('clear alternate images on buffer change', async () => { + strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0); + await ctx.proxy.write('\x1b[?1049h' + SIXEL_SEQ_0); + ok(Math.abs((await ctx.page.evaluate('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05); + await ctx.proxy.write('\x1b[?1049l'); + strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0); + }); + test('evict tiles by in-place overwrites (only full overwrite tested)', async () => { + await new Promise(r => setTimeout(r, 50)); + await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H'); + let usage = await ctx.page.evaluate('window.imageAddon.storageUsage'); + while (usage === 0) { + await new Promise(r => setTimeout(r, 50)); + usage = await ctx.page.evaluate('window.imageAddon.storageUsage'); + } + await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H'); + await new Promise(r => setTimeout(r, 200)); // wait some time and re-check + strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage); + }); + test('manual eviction on alternate buffer must not miss images', async () => { + await ctx.proxy.write('\x1b[?1049h'); + await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); + await new Promise(r => setTimeout(r, 50)); + const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage'); + await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); + await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0); + await new Promise(r => setTimeout(r, 50)); + const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageUsage'); + strictEqual(newUsage, usage); + }); + }); + + test.describe('IIP support - testimages', () => { + test('palette.png', async () => { + await ctx.proxy.write(TESTDATA_IIP[0][0]); + deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[0][1]); + }); + test('spinfox.png', async () => { + await ctx.proxy.write(TESTDATA_IIP[1][0]); + deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]); + }); + test('w3c gif', async () => { + await ctx.proxy.write(TESTDATA_IIP[2][0]); + deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[2][1]); + }); + test('w3c jpeg', async () => { + await ctx.proxy.write(TESTDATA_IIP[3][0]); + deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[3][1]); + }); + test('w3c png', async () => { + await ctx.proxy.write(TESTDATA_IIP[4][0]); + deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[4][1]); + }); + }); +}); + +/** + * terminal access helpers. + */ +async function getDimensions(): Promise { + const dimensions: any = await ctx.page.evaluate(`term._core._renderService.dimensions`); + return { + cellWidth: Math.round(dimensions.css.cell.width), + cellHeight: Math.round(dimensions.css.cell.height), + width: Math.round(dimensions.css.canvas.width), + height: Math.round(dimensions.css.canvas.height) + }; +} + +async function getCursor(): Promise<[number, number]> { + return ctx.page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]'); +} + +async function getImageStorageLength(): Promise { + return ctx.page.evaluate('window.imageAddon._storage._images.size'); +} + +async function getScrollbackPlusRows(): Promise { + return ctx.page.evaluate('window.term.options.scrollback + window.term.rows'); +} + +async function getOrigSize(id: number): Promise<[number, number]> { + return ctx.page.evaluate(`[ + window.imageAddon._storage._images.get(${id}).orig.width, + window.imageAddon._storage._images.get(${id}).orig.height + ]`); +} diff --git a/addons/addon-image/test/playwright.config.ts b/addons/addon-image/test/playwright.config.ts new file mode 100644 index 0000000000..b0e565c546 --- /dev/null +++ b/addons/addon-image/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-image/test/tsconfig.json b/addons/addon-image/test/tsconfig.json index a8c25b5cae..cb6fed28dd 100644 --- a/addons/addon-image/test/tsconfig.json +++ b/addons/addon-image/test/tsconfig.json @@ -1,21 +1,27 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2017", + "target": "es2021", + "lib": [ + "es2021", + ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, - "strict": true, "baseUrl": ".", "paths": { - "browser/*": [ "../../../src/browser/*" ], - "common/*": [ "../../../src/common/*" ] + "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": [ @@ -23,7 +29,11 @@ "../../../typings/xterm.d.ts" ], "references": [ - { "path": "../../../src/browser" }, - { "path": "../../../src/common" } + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index c48ccdb68c..da1733002a 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -23,6 +23,7 @@ let configs = [ { 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-image', path: 'addons/addon-image/out-test/playwright.config.js' }, { name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' }, { name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, { name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' }, From efd4c5b340e3ea09703c5ef7b4bd319ae6d825c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:57:05 -0700 Subject: [PATCH 13/21] Remove API test infrastructure --- bin/test_api.js | 74 ------------------------------------------------- package.json | 4 --- 2 files changed, 78 deletions(-) delete mode 100644 bin/test_api.js diff --git a/bin/test_api.js b/bin/test_api.js deleted file mode 100644 index bdfbc7aff9..0000000000 --- a/bin/test_api.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const cp = require('child_process'); -const path = require('path'); - - -// Add `out` to the NODE_PATH so absolute paths can be resolved. -const env = { ...process.env }; -env.NODE_PATH = path.resolve(__dirname, '../out'); - -let testFiles = [ - './addons/**/out-test/*api.js', - './out-test/**/*api.js', -]; - - -let flagArgs = []; - -if (process.argv.length > 2) { - const args = process.argv.slice(2); - flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat(val.slice(), [])); - console.info(flagArgs); - // ability to inject particular test files via - // yarn test [testFileA testFileB ...] - files = args.filter(e => !e.startsWith('--')); - if (files.length) { - testFiles = files; - } -} - - - -env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : ''; -env.PORT = 3001; - -const server = cp.spawn('node', ['demo/start'], { - cwd: path.resolve(__dirname, '..'), - env, - stdio: 'pipe' -}) - -server.stdout.on('data', (data) => { - // await for the server to fully start - if (data.includes("successfully")) { - const run = cp.spawnSync( - npmBinScript('mocha'), - [...testFiles, ...flagArgs], { - cwd: path.resolve(__dirname, '..'), - env, - shell: true, - stdio: 'inherit' - } - ); - - function npmBinScript(script) { - return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? - `${script}.cmd` : script)); - } - - server.kill(); - - if (run.error) { - console.error(run.error); - } - process.exit(run.status ?? -1); - } -}); - -server.stderr.on('data', (data) => { - console.error(data.toString()); -}); diff --git a/package.json b/package.json index 5e15baee9a..c1320d60aa 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,6 @@ "lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/", "test": "npm run test-unit", "posttest": "npm run lint", - "test-api": "npm run test-api-chromium", - "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000", - "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000", - "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000", "test-playwright": "node ./bin/test_playwright.js --workers=75%", "test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=ChromeStable\"", "test-playwright-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=FirefoxStable\"", From 0f37152a9b821c2013afea0127b9f967b3da1159 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 03:01:25 -0700 Subject: [PATCH 14/21] Remove API test CI jobs --- .github/workflows/ci.yml | 78 ---------------------------------------- 1 file changed, 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c893d8a7dc..439d383f61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,47 +150,6 @@ jobs: - name: Unit tests run: yarn test-unit --forbid-only - test-api-parallel: - timeout-minutes: 20 - strategy: - matrix: - node-version: [18] # just one as integration tests are about testing in browser - runs-on: [ubuntu] # macos is flaky - browser: [chromium, firefox] - runs-on: ${{ matrix.runs-on }}-latest - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }}.x - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }}.x - cache: 'yarn' - - name: Install dependencies - run: | - yarn --frozen-lockfile - yarn install-addons - - name: Install playwright - run: npx playwright install - - name: Wait for build job - uses: NathanFirmo/wait-for-other-job@v1.1.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - job: build - - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - name: Unzip artifacts - shell: bash - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}" - else - unzip -o compressed-build.zip - fi - ls -R - - name: Integration tests (${{ matrix.browser }}) - run: yarn test-api-${{ matrix.browser }} --headless --forbid-only - test-playwright-parallel: timeout-minutes: 20 strategy: @@ -238,43 +197,6 @@ jobs: - name: Integration tests (addon-webgl) run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl - test-api: - needs: build - timeout-minutes: 20 - strategy: - matrix: - node-version: [18] # just one as integration tests are about testing in browser - runs-on: [windows] # macos is flaky - browser: [chromium, firefox] - runs-on: ${{ matrix.runs-on }}-latest - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }}.x - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }}.x - cache: 'yarn' - - name: Install dependencies - run: | - yarn --frozen-lockfile - yarn install-addons - - name: Install playwright - run: npx playwright install - - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - name: Unzip artifacts - shell: bash - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}" - else - unzip -o compressed-build.zip - fi - ls -R - - name: Integration tests (${{ matrix.browser }}) - run: yarn test-api-${{ matrix.browser }} --headless --forbid-only - release-dry-run: needs: build runs-on: ubuntu-latest From 54a20c511ba2e13c094519c98a5015ca08b8c332 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 03:10:51 -0700 Subject: [PATCH 15/21] Simplify GH action names --- .github/workflows/ci.yml | 10 +++++----- package.json | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 439d383f61..fc937f3cb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,7 @@ jobs: ./node_modules/.bin/nyc report --reporter=cobertura exit $EXIT_CODE - test-unit-parallel: + test-unit: timeout-minutes: 20 strategy: matrix: @@ -150,7 +150,7 @@ jobs: - name: Unit tests run: yarn test-unit --forbid-only - test-playwright-parallel: + test-integration: timeout-minutes: 20 strategy: matrix: @@ -191,11 +191,11 @@ jobs: - name: Build demo run: yarn build-demo - name: Integration tests (core) # Tests use 50% workers to reduce flakiness - run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core + run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core - name: Integration tests (addon-canvas) - run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas + run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas - name: Integration tests (addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl + run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl release-dry-run: needs: build diff --git a/package.json b/package.json index c1320d60aa..859198fc37 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,11 @@ "lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/", "test": "npm run test-unit", "posttest": "npm run lint", - "test-playwright": "node ./bin/test_playwright.js --workers=75%", - "test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=ChromeStable\"", - "test-playwright-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=FirefoxStable\"", - "test-playwright-webkit": "node ./bin/test_playwright.js --workers=75% \"--project=WebKit\"", - "test-playwright-debug": "node ./bin/test_playwright.js --workers=1 --headed --timeout=30000", + "test-integration": "node ./bin/test_playwright.js --workers=75%", + "test-integration-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=ChromeStable\"", + "test-integration-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=FirefoxStable\"", + "test-integration-webkit": "node ./bin/test_playwright.js --workers=75% \"--project=WebKit\"", + "test-integration-debug": "node ./bin/test_playwright.js --workers=1 --headed --timeout=30000", "test-unit": "node ./bin/test.js", "test-unit-coverage": "node ./bin/test.js --coverage", "test-unit-dev": "cross-env NODE_PATH='./out' mocha", From f44d68ef4aeca9986b4b4f8e577354d5d373385c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 03:10:58 -0700 Subject: [PATCH 16/21] Remain test/api project --- .eslintrc.json | 1 - test/api/README.md | 1 - test/api/TestUtils.ts | 90 ------------------------------------------ test/api/tsconfig.json | 34 ---------------- tsconfig.all.json | 1 - 5 files changed, 127 deletions(-) delete mode 100644 test/api/README.md delete mode 100644 test/api/TestUtils.ts delete mode 100644 test/api/tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index 5e49b021bb..958b53a106 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,6 @@ "src/browser/tsconfig.json", "src/common/tsconfig.json", "src/headless/tsconfig.json", - "test/api/tsconfig.json", "test/benchmark/tsconfig.json", "test/playwright/tsconfig.json", "addons/addon-attach/src/tsconfig.json", diff --git a/test/api/README.md b/test/api/README.md deleted file mode 100644 index 1c86815c5f..0000000000 --- a/test/api/README.md +++ /dev/null @@ -1 +0,0 @@ -This project contains helpers for the legacy playwright tests. Currently addons use this as a dependency, when all addons have moved over to use `@playright/test` this can be removed. diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts deleted file mode 100644 index cee59cceb1..0000000000 --- a/test/api/TestUtils.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import * as playwright from '@playwright/test'; -import deepEqual = require('deep-equal'); -import { ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm'; -import { deepStrictEqual } from 'assert'; - -export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise, maxDuration?: number): Promise { - if (preFn) { - await preFn(); - } - const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - - if (process.env.DEBUG) { - console.log('pollFor result: ', result); - } - - if (!deepEqual(result, val)) { - if (maxDuration === undefined) { - maxDuration = 2000; - } - if (maxDuration <= 0) { - deepStrictEqual(result, val, 'pollFor max duration exceeded'); - } - return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, maxDuration! - 10)), 10); - }); - } -} - -export async function writeSync(page: playwright.Page, data: string): Promise { - await page.evaluate(` - window.ready = false; - window.term.write('${data}', () => window.ready = true); - `); - await pollFor(page, 'window.ready', true); -} - -export async function timeout(ms: number): Promise { - return new Promise(r => setTimeout(r, ms)); -} - -export async function openTerminal(page: playwright.Page, options: ITerminalOptions & ITerminalInitOnlyOptions = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): Promise { - await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - - // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have - // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453 - if (testOptions.loadUnicodeGraphemesAddon) { - await page.evaluate(` - window.unicode = new UnicodeGraphemesAddon(); - window.term.loadAddon(window.unicode); - window.term.unicode.activeVersion = '15-graphemes'; - `); - } - await page.waitForSelector('.xterm-rows'); -} - -export function getBrowserType(): playwright.BrowserType | playwright.BrowserType | playwright.BrowserType { - // Default to chromium - let browserType: playwright.BrowserType | playwright.BrowserType | playwright.BrowserType = playwright['chromium']; - - const index = process.argv.indexOf('--browser'); - if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') { - const string = process.argv[index + 1]; - if (string === 'firefox' || string === 'webkit') { - browserType = playwright[string]; - } - } - - return browserType; -} - -export function launchBrowser(opts?: playwright.LaunchOptions): Promise { - const browserType = getBrowserType(); - const options: playwright.LaunchOptions = { - ...opts, - headless: process.argv.includes('--headless') - }; - - const index = process.argv.indexOf('--executablePath'); - if (index > 0 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') { - options.executablePath = process.argv[index + 1]; - } - - return browserType.launch(options); -} diff --git a/test/api/tsconfig.json b/test/api/tsconfig.json deleted file mode 100644 index bc050db4ff..0000000000 --- a/test/api/tsconfig.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "es2021" - ], - "rootDir": ".", - "outDir": "../../out-test/api", - "types": [ - "../../node_modules/@types/mocha", - "../../node_modules/@types/node" - ], - "sourceMap": true, - "removeComments": true, - "pretty": true, - "strict": true, - "declaration": true, - "baseUrl": ".", - "paths": { - "browser/*": [ - "../../src/browser/*" - ] - } - }, - "include": [ - "./**/*", - "../../typings/xterm.d.ts" - ], - "references": [ - { - "path": "../../src/browser" - } - ] -} diff --git a/tsconfig.all.json b/tsconfig.all.json index d40761f337..0311a65eda 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -4,7 +4,6 @@ "references": [ { "path": "./src/browser" }, { "path": "./src/headless" }, - { "path": "./test/api" }, { "path": "./test/benchmark" }, { "path": "./test/playwright" }, { "path": "./addons/addon-attach" }, From e2c5d91a2de0c590ac47027f85afab69cd0799f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:15:04 -0700 Subject: [PATCH 17/21] Fix .eslintrc.json warnings, move deprecated to stylistic/ts --- .eslintrc.json | 19 +++---- package.json | 1 + yarn.lock | 144 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 10 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 958b53a106..2218c6fec7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -51,11 +51,21 @@ "**/*.js" ], "plugins": [ + "@stylistic/ts", "@typescript-eslint", "jsdoc" ], "rules": { "no-extra-semi": "error", + "@stylistic/ts/indent": [ + "warn", + 2 + ], + "@stylistic/ts/quotes": [ + "warn", + "single", + { "allowTemplateLiterals": true } + ], "@typescript-eslint/array-type": [ "warn", { @@ -80,10 +90,6 @@ } } ], - "@typescript-eslint/indent": [ - "warn", - 2 - ], "@typescript-eslint/member-delimiter-style": [ "warn", { @@ -143,11 +149,6 @@ "@typescript-eslint/no-useless-constructor": "warn", "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", - "@typescript-eslint/quotes": [ - "warn", - "single", - { "allowTemplateLiterals": true } - ], "@typescript-eslint/semi": [ "warn", "always" diff --git a/package.json b/package.json index 859198fc37..fa32040a9f 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "devDependencies": { "@lunapaint/png-codec": "^0.2.0", "@playwright/test": "^1.37.1", + "@stylistic/eslint-plugin": "^2.3.0", "@types/chai": "^4.2.22", "@types/debug": "^4.1.7", "@types/deep-equal": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index e566fff21f..73b5eafff3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -429,6 +429,54 @@ optionalDependencies: fsevents "2.3.2" +"@stylistic/eslint-plugin-js@2.3.0", "@stylistic/eslint-plugin-js@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.3.0.tgz#a3faee05863c50c0bb6f879db72b7ee895bfa74e" + integrity sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w== + dependencies: + "@types/eslint" "^8.56.10" + acorn "^8.11.3" + eslint-visitor-keys "^4.0.0" + espree "^10.0.1" + +"@stylistic/eslint-plugin-jsx@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-2.3.0.tgz#f1a01b6dcdf3d6159727eef6ae298107facdb098" + integrity sha512-tsQ0IEKB195H6X9A4iUSgLLLKBc8gUBWkBIU8tp1/3g2l8stu+PtMQVV/VmK1+3bem5FJCyvfcZIQ/WF1fsizA== + dependencies: + "@stylistic/eslint-plugin-js" "^2.3.0" + "@types/eslint" "^8.56.10" + estraverse "^5.3.0" + picomatch "^4.0.2" + +"@stylistic/eslint-plugin-plus@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-2.3.0.tgz#0ccadea6cb52c7ecb9af61b6f27077ba885ba145" + integrity sha512-xboPWGUU5yaPlR+WR57GwXEuY4PSlPqA0C3IdNA/+1o2MuBi95XgDJcZiJ9N+aXsqBXAPIpFFb+WQ7QEHo4f7g== + dependencies: + "@types/eslint" "^8.56.10" + "@typescript-eslint/utils" "^7.12.0" + +"@stylistic/eslint-plugin-ts@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.3.0.tgz#2c9e047304df2094124a638b273ac02410cc98f1" + integrity sha512-wqOR38/uz/0XPnHX68ftp8sNMSAqnYGjovOTN7w00xnjS6Lxr3Sk7q6AaxWWqbMvOj7V2fQiMC5HWAbTruJsCg== + dependencies: + "@stylistic/eslint-plugin-js" "2.3.0" + "@types/eslint" "^8.56.10" + "@typescript-eslint/utils" "^7.12.0" + +"@stylistic/eslint-plugin@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.3.0.tgz#e9b411524d94a120dc757c2bc79919088f7385f6" + integrity sha512-rtiz6u5gRyyEZp36FcF1/gHJbsbT3qAgXZ1qkad6Nr/xJ9wrSJkiSFFQhpYVTIZ7FJNRJurEcumZDCwN9dEI4g== + dependencies: + "@stylistic/eslint-plugin-js" "2.3.0" + "@stylistic/eslint-plugin-jsx" "2.3.0" + "@stylistic/eslint-plugin-plus" "2.3.0" + "@stylistic/eslint-plugin-ts" "2.3.0" + "@types/eslint" "^8.56.10" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -492,6 +540,14 @@ "@types/estree" "*" "@types/json-schema" "*" +"@types/eslint@^8.56.10": + version "8.56.10" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d" + integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + "@types/estree@*", "@types/estree@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" @@ -712,6 +768,14 @@ "@typescript-eslint/types" "6.2.0" "@typescript-eslint/visitor-keys" "6.2.0" +"@typescript-eslint/scope-manager@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2" + integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw== + dependencies: + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/type-utils@6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.2.0.tgz#02b27a3eeb41aa5460d6275d12cce5dd72e1c9fc" @@ -727,6 +791,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.0.tgz#b341a4e6d5f609267306b07afc6f62bcf92b1495" integrity sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA== +"@typescript-eslint/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" + integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== + "@typescript-eslint/typescript-estree@6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz#4969944b831b481996aa4fbd73c7164ca683b8ef" @@ -740,6 +809,20 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" + integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== + dependencies: + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/utils@6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.2.0.tgz#606a20e5c13883c2d2bd0538ddc4b96b8d410979" @@ -753,6 +836,16 @@ "@typescript-eslint/typescript-estree" "6.2.0" semver "^7.5.4" +"@typescript-eslint/utils@^7.12.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960" + integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/visitor-keys@6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz#71943f42fdaa2ec86dc3222091f41761a49ae71a" @@ -761,6 +854,14 @@ "@typescript-eslint/types" "6.2.0" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66" + integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw== + dependencies: + "@typescript-eslint/types" "7.15.0" + eslint-visitor-keys "^3.4.3" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -955,6 +1056,11 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.11.3, acorn@^8.12.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" @@ -1727,6 +1833,11 @@ eslint-visitor-keys@^3.4.3: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + eslint@^8.56.0: version "8.56.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" @@ -1771,6 +1882,15 @@ eslint@^8.56.0: strip-ansi "^6.0.1" text-table "^0.2.0" +espree@^10.0.1: + version "10.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56" + integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA== + dependencies: + acorn "^8.12.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" @@ -1804,7 +1924,7 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -2861,6 +2981,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + mocha@^10.1.0: version "10.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" @@ -3171,6 +3298,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -3390,6 +3522,11 @@ semver@^7.3.4, semver@^7.5.3, semver@^7.5.4: dependencies: lru-cache "^6.0.0" +semver@^7.6.0: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -3696,6 +3833,11 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + ts-loader@^9.3.1: version "9.4.4" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.4.4.tgz#6ceaf4d58dcc6979f84125335904920884b7cee4" From 3c84cfe610c5299b2fc4697b3d204c28c6b0fb02 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:17:11 -0700 Subject: [PATCH 18/21] typescript@5.5 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 859198fc37..938d2d8950 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", "ts-loader": "^9.3.1", - "typescript": "^5.1.6", + "typescript": "5.5", "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", diff --git a/yarn.lock b/yarn.lock index e566fff21f..e4718d56d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3748,16 +3748,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@5.5: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== + typescript@^4.2.3: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== - universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" From cca051041fc99d4d72a37d23fb7fdd1ce70a7c47 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:21:41 -0700 Subject: [PATCH 19/21] Also migrate semi rule, alphabetize --- .eslintrc.json | 12 +++++++----- .eslintrc.json.typings | 30 ++++++++++++++++-------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2218c6fec7..d4d7eddc07 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -56,16 +56,20 @@ "jsdoc" ], "rules": { - "no-extra-semi": "error", "@stylistic/ts/indent": [ "warn", 2 ], + "@stylistic/ts/semi": [ + "warn", + "always" + ], "@stylistic/ts/quotes": [ "warn", "single", { "allowTemplateLiterals": true } ], + "@typescript-eslint/array-type": [ "warn", { @@ -149,10 +153,7 @@ "@typescript-eslint/no-useless-constructor": "warn", "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", - "@typescript-eslint/semi": [ - "warn", - "always" - ], + "comma-dangle": [ "warn", { @@ -193,6 +194,7 @@ } ], "no-eval": "warn", + "no-extra-semi": "error", "no-irregular-whitespace": "warn", "no-restricted-imports": [ "warn", diff --git a/.eslintrc.json.typings b/.eslintrc.json.typings index 0d234ee731..ba4c9ff799 100644 --- a/.eslintrc.json.typings +++ b/.eslintrc.json.typings @@ -10,7 +10,20 @@ "jsdoc" ], "rules": { - "no-extra-semi": "error", + "@stylistic/ts/indent": [ + "warn", + 2 + ], + "@stylistic/ts/semi": [ + "warn", + "always" + ], + "@stylistic/ts/quotes": [ + "warn", + "single", + { "allowTemplateLiterals": true } + ], + "@typescript-eslint/array-type": [ "warn", { @@ -24,10 +37,6 @@ "allowExpressions": true } ], - "@typescript-eslint/indent": [ - "warn", - 2 - ], "@typescript-eslint/member-delimiter-style": [ "warn", { @@ -48,15 +57,7 @@ ], "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", - "@typescript-eslint/quotes": [ - "warn", - "single", - { "allowTemplateLiterals": true } - ], - "@typescript-eslint/semi": [ - "warn", - "always" - ], + "comma-dangle": [ "warn", { @@ -86,6 +87,7 @@ "ignorePattern": "^ *(?\\* Ps=)" } ], + "no-extra-semi": "error", "no-irregular-whitespace": "warn", "no-trailing-spaces": "warn", "object-curly-spacing": [ From b7988bf00a2c52bc90402e35acf69c5978228d0a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:31:34 -0700 Subject: [PATCH 20/21] Upgrade playwright tsconfigs to use ESNext This ends up outputing the required object destructuring that's needed by playwright, see microsoft/playwright#8798 --- addons/addon-attach/test/tsconfig.json | 2 +- addons/addon-canvas/test/tsconfig.json | 2 +- addons/addon-clipboard/test/tsconfig.json | 2 +- addons/addon-fit/test/tsconfig.json | 2 +- addons/addon-image/test/tsconfig.json | 2 +- addons/addon-search/test/tsconfig.json | 2 +- addons/addon-serialize/test/tsconfig.json | 2 +- addons/addon-unicode-graphemes/test/tsconfig.json | 2 +- addons/addon-unicode11/test/tsconfig.json | 2 +- addons/addon-web-links/test/tsconfig.json | 1 + addons/addon-webgl/test/tsconfig.json | 2 +- test/playwright/tsconfig.json | 2 +- 12 files changed, 12 insertions(+), 11 deletions(-) diff --git a/addons/addon-attach/test/tsconfig.json b/addons/addon-attach/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-attach/test/tsconfig.json +++ b/addons/addon-attach/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-canvas/test/tsconfig.json b/addons/addon-canvas/test/tsconfig.json index 9523e64b50..f73f10141d 100644 --- a/addons/addon-canvas/test/tsconfig.json +++ b/addons/addon-canvas/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "ESNext", "lib": [ "es6", ], diff --git a/addons/addon-clipboard/test/tsconfig.json b/addons/addon-clipboard/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-clipboard/test/tsconfig.json +++ b/addons/addon-clipboard/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-fit/test/tsconfig.json b/addons/addon-fit/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-fit/test/tsconfig.json +++ b/addons/addon-fit/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-image/test/tsconfig.json b/addons/addon-image/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-image/test/tsconfig.json +++ b/addons/addon-image/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-search/test/tsconfig.json b/addons/addon-search/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-search/test/tsconfig.json +++ b/addons/addon-search/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-serialize/test/tsconfig.json b/addons/addon-serialize/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-serialize/test/tsconfig.json +++ b/addons/addon-serialize/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-unicode-graphemes/test/tsconfig.json b/addons/addon-unicode-graphemes/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-unicode-graphemes/test/tsconfig.json +++ b/addons/addon-unicode-graphemes/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-unicode11/test/tsconfig.json b/addons/addon-unicode11/test/tsconfig.json index cb6fed28dd..5f0d9f6cbe 100644 --- a/addons/addon-unicode11/test/tsconfig.json +++ b/addons/addon-unicode11/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/addons/addon-web-links/test/tsconfig.json b/addons/addon-web-links/test/tsconfig.json index cb6fed28dd..0c61a31ebf 100644 --- a/addons/addon-web-links/test/tsconfig.json +++ b/addons/addon-web-links/test/tsconfig.json @@ -5,6 +5,7 @@ "lib": [ "es2021", ], + // "downlevelIteration": true, "rootDir": ".", "outDir": "../out-test", "sourceMap": true, diff --git a/addons/addon-webgl/test/tsconfig.json b/addons/addon-webgl/test/tsconfig.json index d53b2c041b..7ff5f6c1b7 100644 --- a/addons/addon-webgl/test/tsconfig.json +++ b/addons/addon-webgl/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2021", + "target": "ESNext", "lib": [ "es2021", ], diff --git a/test/playwright/tsconfig.json b/test/playwright/tsconfig.json index aeb31e3297..9857ff0de3 100644 --- a/test/playwright/tsconfig.json +++ b/test/playwright/tsconfig.json @@ -24,7 +24,7 @@ "../../src/common/*" ] }, - "target": "es2016", + "target": "ESNext", "module": "commonjs", }, "include": [ From 26c072f45c99f334c4005bf38c8ad6d68e54826f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:32:22 -0700 Subject: [PATCH 21/21] Add missing plugin include --- .eslintrc.json.typings | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json.typings b/.eslintrc.json.typings index ba4c9ff799..9cb5491d8d 100644 --- a/.eslintrc.json.typings +++ b/.eslintrc.json.typings @@ -6,6 +6,7 @@ }, "parser": "@typescript-eslint/parser", "plugins": [ + "@stylistic/ts", "@typescript-eslint", "jsdoc" ],