From a0da287b9c29aae7881badf4c7753ce80fe8eac6 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Thu, 9 Nov 2023 16:56:07 +1100 Subject: [PATCH 01/29] Use the right document in AccessibilityManager.ts --- src/browser/AccessibilityManager.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 68c65ebd..4f550014 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -58,10 +58,11 @@ export class AccessibilityManager extends Disposable { @IRenderService private readonly _renderService: IRenderService ) { super(); - this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div'); + const doc = this._coreBrowserService.mainDocument; + this._accessibilityContainer = doc.createElement('div'); this._accessibilityContainer.classList.add('xterm-accessibility'); - this._rowContainer = this._coreBrowserService.mainDocument.createElement('div'); + this._rowContainer = doc.createElement('div'); this._rowContainer.setAttribute('role', 'list'); this._rowContainer.classList.add('xterm-accessibility-tree'); this._rowElements = []; @@ -78,7 +79,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); - this._liveRegion = this._coreBrowserService.mainDocument.createElement('div'); + this._liveRegion = doc.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityContainer.appendChild(this._liveRegion); @@ -93,12 +94,12 @@ export class AccessibilityManager extends Disposable { this._rowContainer.classList.add('debug'); // Use a `
` container so that the css will still apply. - this._debugRootContainer = document.createElement('div'); + this._debugRootContainer = doc.createElement('div'); this._debugRootContainer.classList.add('xterm'); - this._debugRootContainer.appendChild(document.createTextNode('------start a11y------')); + this._debugRootContainer.appendChild(doc.createTextNode('------start a11y------')); this._debugRootContainer.appendChild(this._accessibilityContainer); - this._debugRootContainer.appendChild(document.createTextNode('------end a11y------')); + this._debugRootContainer.appendChild(doc.createTextNode('------end a11y------')); this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer); } else { @@ -115,7 +116,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this.register(addDisposableDomListener(document, 'selectionchange', () => this._handleSelectionChange())); + this.register(addDisposableDomListener(doc, 'selectionchange', () => this._handleSelectionChange())); this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); this._refreshRows(); @@ -270,7 +271,7 @@ export class AccessibilityManager extends Disposable { return; } - const selection = document.getSelection(); + const selection = this._coreBrowserService.mainDocument.getSelection(); if (!selection) { return; } From 26dc69446ef85a20a6db52004260029c3fb0accd Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 10 Nov 2023 21:21:50 +1100 Subject: [PATCH 02/29] Align character position in a11y tree with the actual rendering This helps screen readers to draw selection outline at the correct position. --- css/xterm.css | 6 ++++++ src/browser/AccessibilityManager.ts | 31 +++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index e97b6439..51e9b39e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -157,10 +157,16 @@ } .xterm .xterm-accessibility-tree { + font-family: monospace; user-select: text; white-space: pre; } +.xterm .xterm-accessibility-tree > div { + transform-origin: left; + width: fit-content; +} + .xterm .live-region { position: absolute; left: -9999px; diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 4f550014..665968df 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -76,7 +76,6 @@ export class AccessibilityManager extends Disposable { this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); this._liveRegion = doc.createElement('div'); @@ -119,6 +118,7 @@ export class AccessibilityManager extends Disposable { this.register(addDisposableDomListener(doc, 'selectionchange', () => this._handleSelectionChange())); this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); + this._refreshRowsDimensions(); this._refreshRows(); this.register(toDisposable(() => { if (DEBUG) { @@ -193,6 +193,7 @@ export class AccessibilityManager extends Disposable { } element.setAttribute('aria-posinset', posInSet); element.setAttribute('aria-setsize', setSize); + this._alignRowWidth(element); } } this._announceCharacters(); @@ -390,19 +391,45 @@ export class AccessibilityManager extends Disposable { this._refreshRowDimensions(element); return element; } + private _refreshRowsDimensions(): void { if (!this._renderService.dimensions.css.cell.height) { return; } - this._accessibilityContainer.style.width = `${this._renderService.dimensions.css.canvas.width}px`; + Object.assign(this._accessibilityContainer.style, { + width: `${this._renderService.dimensions.css.canvas.width}px`, + fontSize: `${this._terminal.options.fontSize}px`, + }); if (this._rowElements.length !== this._terminal.rows) { this._handleResize(this._terminal.rows); } for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); + this._alignRowWidth(this._rowElements[i]); } } + private _refreshRowDimensions(element: HTMLElement): void { element.style.height = `${this._renderService.dimensions.css.cell.height}px`; } + + /** + * Scale the width of a row so that each of the character is (mostly) aligned + * with the actual rendering. This will allow the screen reader to draw + * selection outline at the correct position. + * + * On top of using the "monospace" font and correct font size, the scaling + * here is necessary to handle characters that are not covered by the font + * (e.g. CJK). + */ + private _alignRowWidth(element: HTMLElement): void { + element.style.transform = ''; + const width = element.getBoundingClientRect().width; + const lastColumn = this._rowColumns.get(element)?.slice(-1)?.[0]; + if (!lastColumn) { + return; + } + const targetWidth = lastColumn * this._renderService.dimensions.css.cell.width; + element.style.transform = `scaleX(${targetWidth / width})`; + } } From 9b1396fb3284c32ff54cb80d9c5086f4594e154f Mon Sep 17 00:00:00 2001 From: info24 <386293701@qq.com> Date: Fri, 26 Apr 2024 23:10:03 +0800 Subject: [PATCH 03/29] Add eva pro to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d9392de3..33689bce 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor. - [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments - [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows. +- [**eva**](https://github.com/info24/eva): Eva is a web application for SSH remote login, developed in Go. - [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D) Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only. From d52f09de3c663bec3ea2183349791a9dd8a19397 Mon Sep 17 00:00:00 2001 From: Joel Hockey Date: Sun, 12 May 2024 17:13:38 -0700 Subject: [PATCH 04/29] Add powerline git, LN, lock symbols Symbols taken from https://github.com/powerline/fontpatcher/blob/develop/fonts/powerline-symbols.sfd The original symbols use height and widith of approx 1060x2048, so I modified to be roughly 1000x1000 via: fontforge -lang=ff -c 'Open($1); SelectAll(); UnlinkReference(); Scale(100, 50); Move(0, 300); Export("svg/%n-%e.svg");' powerline-symbols.sfd Then I edited by hand to round values to approx 2 significant digits, and then used a script to convert relative SVG commands such as 'l', 'c', 's' to use only 'M', 'L' and 'C' and used expected format with comma separators rather than spaces, and divide all values by 1000 to map into a 1x1 space. --- src/browser/renderer/shared/CustomGlyphs.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index cf6292af..da9c3d36 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -355,6 +355,12 @@ const enum VectorType { * Original symbols defined in https://github.com/powerline/fontpatcher */ export const powerlineDefinitions: { [index: string]: IVectorShape } = { + // Git branch + '\u{E0A0}': { d: 'M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655', type: VectorType.FILL }, + // L N + '\u{E0A1}': { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: VectorType.FILL }, + // Lock + '\u{E0A2}': { d: 'M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82', type: VectorType.FILL }, // Right triangle solid '\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL, rightPadding: 2 }, // Right triangle line From dd106ae9b85aaf434ab98eb873586fcffd093f0a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:43:31 -0700 Subject: [PATCH 05/29] Speed up clearing all markers, mass event listener dispose Part of microsoft/vscode#213174 --- src/common/EventEmitter.ts | 17 ++++++----------- src/common/buffer/Buffer.ts | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 589748a3..06ddf7fb 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -20,23 +20,18 @@ export interface IEventEmitter { } export class EventEmitter implements IEventEmitter { - private _listeners: IListener[] = []; + private _listeners: Set> = new Set(); private _event?: IEvent; private _disposed: boolean = false; public get event(): IEvent { if (!this._event) { this._event = (listener: (arg1: T, arg2: U) => any) => { - this._listeners.push(listener); + this._listeners.add(listener); const disposable = { dispose: () => { if (!this._disposed) { - for (let i = 0; i < this._listeners.length; i++) { - if (this._listeners[i] === listener) { - this._listeners.splice(i, 1); - return; - } - } + this._listeners.delete(listener); } } }; @@ -48,8 +43,8 @@ export class EventEmitter implements IEventEmitter { public fire(arg1: T, arg2: U): void { const queue: IListener[] = []; - for (let i = 0; i < this._listeners.length; i++) { - queue.push(this._listeners[i]); + for (const l of this._listeners.values()) { + queue.push(l); } for (let i = 0; i < queue.length; i++) { queue[i].call(undefined, arg1, arg2); @@ -63,7 +58,7 @@ export class EventEmitter implements IEventEmitter { public clearListeners(): void { if (this._listeners) { - this._listeners.length = 0; + this._listeners.clear(); } } } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 250c96bc..1d2922e8 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -611,8 +611,8 @@ export class Buffer implements IBuffer { this._isClearing = true; for (let i = 0; i < this.markers.length; i++) { this.markers[i].dispose(); - this.markers.splice(i--, 1); } + this.markers.length = 0; this._isClearing = false; } From 12e5841959b8f9a4be5b599250f0e959a5bc5fd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Jun 2024 05:30:54 -0700 Subject: [PATCH 06/29] Fix lint --- src/browser/AccessibilityManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 665968df..a4a58187 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -398,7 +398,7 @@ export class AccessibilityManager extends Disposable { } Object.assign(this._accessibilityContainer.style, { width: `${this._renderService.dimensions.css.canvas.width}px`, - fontSize: `${this._terminal.options.fontSize}px`, + fontSize: `${this._terminal.options.fontSize}px` }); if (this._rowElements.length !== this._terminal.rows) { this._handleResize(this._terminal.rows); From 2b716cd436bdef9f648099d86271223dabd3943d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Jun 2024 06:24:59 -0700 Subject: [PATCH 07/29] Correct addon-clipboard ctor types --- addons/addon-clipboard/typings/addon-clipboard.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/addon-clipboard/typings/addon-clipboard.d.ts b/addons/addon-clipboard/typings/addon-clipboard.d.ts index f37748fa..4ef12e0f 100644 --- a/addons/addon-clipboard/typings/addon-clipboard.d.ts +++ b/addons/addon-clipboard/typings/addon-clipboard.d.ts @@ -13,8 +13,10 @@ declare module '@xterm/addon-clipboard' { export class ClipboardAddon implements ITerminalAddon { /** * Creates a new clipboard addon. + * @param base64 An optional base64 encoder/decoder. + * @param provider An optional clipboard provider. */ - constructor(provider?: IClipboardProvider); + constructor(base64?: IBase64, provider?: IClipboardProvider); /** * Activates the addon 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 08/29] 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 b9725acd..16248955 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 ff166727..bdfbc7af 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 0cd6f85c..f5621a18 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 09/29] 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 16248955..b6492454 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 10/29] 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 b0e565c5..79cf9f02 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 b0e565c5..79cf9f02 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 c79b1196..5e15baee 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 3d1fe4fb..c94fff87 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 11/29] 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 92fd6b29..00000000 --- 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 00000000..0adf2bfe --- /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 00000000..b0e565c5 --- /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 ee45e725..395be00d 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 f5621a18..34738122 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 12/29] 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 24641fc6..00000000 --- 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 00000000..8c186b43 --- /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 00000000..79cf9f02 --- /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 67ad42b7..cb6fed28 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 0adf2bfe..80b0a120 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 395be00d..cb6fed28 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 34738122..30d21e95 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 13/29] 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 4ef768e8..00000000 --- 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 00000000..6234a3cd --- /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 00000000..79cf9f02 --- /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 67ad42b7..cb6fed28 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 30d21e95..4063f5f5 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 14/29] 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 230b9e2e..00000000 --- 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 00000000..efb64e6f --- /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 00000000..79cf9f02 --- /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 67ad42b7..cb6fed28 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 4063f5f5..0417f276 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 15/29] Migrate serialize api tests to playwright --- ...izeAddon.api.ts => SerializeAddon.test.ts} | 293 +++++++++--------- .../addon-serialize/test/playwright.config.ts | 35 +++ addons/addon-serialize/test/tsconfig.json | 11 +- bin/test_playwright.js | 1 + 4 files changed, 188 insertions(+), 152 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 c943e7b2..30ff6002 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()`)); - - 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);`); - - await page.evaluate(`term.reset();`); - await writeRawSync(page, '67890'); - const buffer2 = await page.evaluate(`inspectBuffer(term.buffer.normal);`); - - assert.throw(() => { - assert.equal(JSON.stringify(buffer1), JSON.stringify(buffer2)); - }); + test.beforeEach(async () => { + await ctx.proxy.reset(); }); - 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 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, '123456789012345'); - const buffer4 = 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(buffer3), JSON.stringify(buffer4)); - }); + notDeepStrictEqual(JSON.stringify(buffer1), JSON.stringify(buffer2)); }); - it('empty content', async function(): Promise { - assert.equal(await page.evaluate(`serializeAddon.serialize();`), ''); + 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 ctx.page.evaluate(`term.reset();`); + await writeRawSync(ctx.page, '123456789012345'); + const buffer4 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`); + + notDeepStrictEqual(JSON.stringify(buffer3), JSON.stringify(buffer4)); }); - it('unwrap wrapped line', async function(): Promise { + test('empty content', async function(): Promise { + strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), ''); + }); + + 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 00000000..b0e565c5 --- /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 6eeb5cde..cb6fed28 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 0417f276..775bd175 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 16/29] 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 e13ad292..00000000 --- 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 00000000..2603c056 --- /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 00000000..b0e565c5 --- /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 4b3cb31c..cb6fed28 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 775bd175..291d381b 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 17/29] 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 2603c056..9bf272ef 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 83c7a61d..00000000 --- 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 00000000..4e941c34 --- /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 00000000..b0e565c5 --- /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 0406d3a4..cb6fed28 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 291d381b..7ba7a8f5 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 18/29] 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 99c11600..abad5150 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()); - after(async () => await browser.close()); - beforeEach(async () => { - await page.evaluate(` +test.describe('WebLinksAddon', () => { + + 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 00000000..b0e565c5 --- /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 2b4ec58f..cb6fed28 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 7ba7a8f5..c48ccdb6 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 19/29] 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 776e96bf..00000000 --- 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 00000000..abd1295c --- /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 00000000..b0e565c5 --- /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 a8c25b5c..cb6fed28 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 c48ccdb6..da173300 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 20/29] 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 bdfbc7af..00000000 --- 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 5e15baee..c1320d60 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 21/29] 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 c893d8a7..439d383f 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 22/29] 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 439d383f..fc937f3c 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 c1320d60..859198fc 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 23/29] 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 5e49b021..958b53a1 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 1c86815c..00000000 --- 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 cee59cce..00000000 --- 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 bc050db4..00000000 --- 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 d40761f3..0311a65e 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 24/29] 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 958b53a1..2218c6fe 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 859198fc..fa32040a 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 e566fff2..73b5eaff 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 25/29] 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 859198fc..938d2d89 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 e566fff2..e4718d56 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 26/29] 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 2218c6fe..d4d7eddc 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 0d234ee7..ba4c9ff7 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 27/29] 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 cb6fed28..5f0d9f6c 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 9523e64b..f73f1014 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..5f0d9f6c 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 cb6fed28..0c61a31e 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 d53b2c04..7ff5f6c1 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 aeb31e32..9857ff0d 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 28/29] Add missing plugin include --- .eslintrc.json.typings | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json.typings b/.eslintrc.json.typings index ba4c9ff7..9cb5491d 100644 --- a/.eslintrc.json.typings +++ b/.eslintrc.json.typings @@ -6,6 +6,7 @@ }, "parser": "@typescript-eslint/parser", "plugins": [ + "@stylistic/ts", "@typescript-eslint", "jsdoc" ], From a9fd92eba66cd9a90c733a482c2c0ed6181a4de5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:39:42 +0000 Subject: [PATCH 29/29] Bump ws from 8.13.0 to 8.17.1 Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.17.1. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.13.0...8.17.1) --- updated-dependencies: - dependency-name: ws dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index fbaf4d86..254de92a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4169,9 +4169,9 @@ ws@^7.4.6: integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.2.3: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml-name-validator@^4.0.0: version "4.0.0"