From ceef536b7919cd6761777baa8c66ef03c64e6a53 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:36:48 -0700 Subject: [PATCH 01/59] Upgrade es6 -> es2021 This uses the recommended target for node 16 in tsconfig/bases. Fixes #4706 --- .eslintrc.json | 2 +- .eslintrc.json.typings | 2 +- addons/xterm-addon-attach/src/tsconfig.json | 2 +- addons/xterm-addon-attach/test/tsconfig.json | 2 +- addons/xterm-addon-canvas/src/tsconfig.json | 4 ++-- addons/xterm-addon-fit/src/tsconfig.json | 2 +- addons/xterm-addon-fit/test/tsconfig.json | 2 +- addons/xterm-addon-search/src/tsconfig.json | 4 ++-- addons/xterm-addon-search/test/tsconfig.json | 4 ++-- addons/xterm-addon-serialize/benchmark/tsconfig.json | 7 +++++-- addons/xterm-addon-serialize/src/tsconfig.json | 2 +- addons/xterm-addon-serialize/test/tsconfig.json | 2 +- addons/xterm-addon-unicode11/src/tsconfig.json | 2 +- addons/xterm-addon-unicode11/test/tsconfig.json | 2 +- addons/xterm-addon-web-links/src/tsconfig.json | 2 +- addons/xterm-addon-web-links/test/tsconfig.json | 2 +- addons/xterm-addon-webgl/src/tsconfig.json | 4 ++-- addons/xterm-addon-webgl/test/tsconfig.json | 4 ++-- demo/tsconfig.json | 2 +- src/browser/tsconfig.json | 3 +-- src/tsconfig-base.json | 2 +- test/api/tsconfig.json | 2 +- test/benchmark/tsconfig.json | 4 ++-- 23 files changed, 33 insertions(+), 31 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9287da5c..015776d3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,7 @@ { "env": { "browser": true, - "es6": true, + "es2021": true, "node": true }, "parser": "@typescript-eslint/parser", diff --git a/.eslintrc.json.typings b/.eslintrc.json.typings index 5af028d3..0d234ee7 100644 --- a/.eslintrc.json.typings +++ b/.eslintrc.json.typings @@ -1,7 +1,7 @@ { "env": { "browser": true, - "es6": true, + "es2021": true, "node": true }, "parser": "@typescript-eslint/parser", diff --git a/addons/xterm-addon-attach/src/tsconfig.json b/addons/xterm-addon-attach/src/tsconfig.json index f3e409d1..7e68574d 100644 --- a/addons/xterm-addon-attach/src/tsconfig.json +++ b/addons/xterm-addon-attach/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-attach/test/tsconfig.json b/addons/xterm-addon-attach/test/tsconfig.json index fa67b169..67ad42b7 100644 --- a/addons/xterm-addon-attach/test/tsconfig.json +++ b/addons/xterm-addon-attach/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "es2015" ], diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/xterm-addon-canvas/src/tsconfig.json index f752dc5b..f6fb08a7 100644 --- a/addons/xterm-addon-canvas/src/tsconfig.json +++ b/addons/xterm-addon-canvas/src/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", - "es6" + "es2021" ], "rootDir": ".", "outDir": "../out", diff --git a/addons/xterm-addon-fit/src/tsconfig.json b/addons/xterm-addon-fit/src/tsconfig.json index 3bfbea67..668f910b 100644 --- a/addons/xterm-addon-fit/src/tsconfig.json +++ b/addons/xterm-addon-fit/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-fit/test/tsconfig.json b/addons/xterm-addon-fit/test/tsconfig.json index fa67b169..67ad42b7 100644 --- a/addons/xterm-addon-fit/test/tsconfig.json +++ b/addons/xterm-addon-fit/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "es2015" ], diff --git a/addons/xterm-addon-search/src/tsconfig.json b/addons/xterm-addon-search/src/tsconfig.json index 9ef27b5c..1f08b12d 100644 --- a/addons/xterm-addon-search/src/tsconfig.json +++ b/addons/xterm-addon-search/src/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", - "es6", + "es2021", ], "rootDir": ".", "outDir": "../out", diff --git a/addons/xterm-addon-search/test/tsconfig.json b/addons/xterm-addon-search/test/tsconfig.json index 052fac43..ee45e725 100644 --- a/addons/xterm-addon-search/test/tsconfig.json +++ b/addons/xterm-addon-search/test/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ - "es6", + "es2021", ], "rootDir": ".", "outDir": "../out-test", diff --git a/addons/xterm-addon-serialize/benchmark/tsconfig.json b/addons/xterm-addon-serialize/benchmark/tsconfig.json index bf5e335c..6bbb2a4b 100644 --- a/addons/xterm-addon-serialize/benchmark/tsconfig.json +++ b/addons/xterm-addon-serialize/benchmark/tsconfig.json @@ -1,11 +1,14 @@ { "compilerOptions": { - "lib": ["dom", "es6"], + "lib": [ + "dom", + "es2021" + ], "outDir": "../out-benchmark", "types": ["../../../node_modules/@types/node"], "moduleResolution": "node", "strict": false, - "target": "es2015", + "target": "es2021", "module": "commonjs", "baseUrl": ".", "paths": { diff --git a/addons/xterm-addon-serialize/src/tsconfig.json b/addons/xterm-addon-serialize/src/tsconfig.json index ba26f22e..22ffe53e 100644 --- a/addons/xterm-addon-serialize/src/tsconfig.json +++ b/addons/xterm-addon-serialize/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-serialize/test/tsconfig.json b/addons/xterm-addon-serialize/test/tsconfig.json index 971f3e92..6eeb5cde 100644 --- a/addons/xterm-addon-serialize/test/tsconfig.json +++ b/addons/xterm-addon-serialize/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "es2015" ], diff --git a/addons/xterm-addon-unicode11/src/tsconfig.json b/addons/xterm-addon-unicode11/src/tsconfig.json index f5489fcc..e2c1464e 100644 --- a/addons/xterm-addon-unicode11/src/tsconfig.json +++ b/addons/xterm-addon-unicode11/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-unicode11/test/tsconfig.json b/addons/xterm-addon-unicode11/test/tsconfig.json index 4b3cb31c..0406d3a4 100644 --- a/addons/xterm-addon-unicode11/test/tsconfig.json +++ b/addons/xterm-addon-unicode11/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-web-links/src/tsconfig.json b/addons/xterm-addon-web-links/src/tsconfig.json index f3e409d1..7e68574d 100644 --- a/addons/xterm-addon-web-links/src/tsconfig.json +++ b/addons/xterm-addon-web-links/src/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", "es2015" diff --git a/addons/xterm-addon-web-links/test/tsconfig.json b/addons/xterm-addon-web-links/test/tsconfig.json index 48975764..2b4ec58f 100644 --- a/addons/xterm-addon-web-links/test/tsconfig.json +++ b/addons/xterm-addon-web-links/test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "es2015" ], diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index 74989973..4d4194db 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ "dom", - "es6" + "es2021" ], "rootDir": ".", "outDir": "../out", diff --git a/addons/xterm-addon-webgl/test/tsconfig.json b/addons/xterm-addon-webgl/test/tsconfig.json index 1ff9217e..6479cec9 100644 --- a/addons/xterm-addon-webgl/test/tsconfig.json +++ b/addons/xterm-addon-webgl/test/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "lib": [ - "es6", + "es2021", ], "rootDir": ".", "outDir": "../out-test", diff --git a/demo/tsconfig.json b/demo/tsconfig.json index 018ddb42..f728f20e 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "rootDir": ".", "sourceMap": true, "baseUrl": ".", diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 212aeab8..673a55a6 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "lib": [ "dom", - "es2015", - "es2016.Array.Include" + "es2021" ], "outDir": "../../out", "types": [ diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index cf4454a4..5ea04ea2 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es2021", "rootDir": ".", "sourceMap": true, diff --git a/test/api/tsconfig.json b/test/api/tsconfig.json index 60eb33cd..bc050db4 100644 --- a/test/api/tsconfig.json +++ b/test/api/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "lib": [ "dom", - "es6" + "es2021" ], "rootDir": ".", "outDir": "../../out-test/api", diff --git a/test/benchmark/tsconfig.json b/test/benchmark/tsconfig.json index 2ebe2ebe..8d1736df 100644 --- a/test/benchmark/tsconfig.json +++ b/test/benchmark/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "lib": [ "dom", - "es6", + "es2021", ], "outDir": "../../out-test/benchmark", "types": [ @@ -10,7 +10,7 @@ ], "moduleResolution": "node", "strict": false, - "target": "es2015", + "target": "es2021", "module": "commonjs", "baseUrl": ".", "paths": { From c64115e0a0850939ba13f2df78166a731c0b94be Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 8 Sep 2023 17:34:03 +0800 Subject: [PATCH 02/59] Add handling of invisible --- src/browser/renderer/dom/DomRendererRowFactory.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index e6412b80..2d21a647 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -180,7 +180,11 @@ export class DomRendererRowFactory { && !isDecorated ) { // no span alterations, thus only account chars skipping all code below - text += chars; + if (cell.isInvisible()) { + text += WHITESPACE_CELL_CHAR; + } else { + text += chars; + } cellAmount++; continue; } else { From 393a8b3a864c933b28ae6458639ba5ebef20f233 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 8 Sep 2023 18:08:14 +0800 Subject: [PATCH 03/59] When DomRenderer blue,perform rendering. --- demo/client.ts | 16 ++++++++++++++++ demo/index.html | 8 ++++++++ src/browser/renderer/dom/DomRenderer.ts | 1 + 3 files changed, 25 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index bde5ca72..85a1104e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -119,6 +119,8 @@ const actionElements = { findResults: document.querySelector('#find-results') }; const paddingElement = document.getElementById('padding') as HTMLInputElement; +const selectionBackgroundElement = document.getElementById('selectionBackground') as HTMLInputElement; +const selectionInactiveBackgroundElement = document.getElementById('selectionInactiveBackground') as HTMLInputElement; const xtermjsTheme = { foreground: '#F8F8F8', @@ -146,6 +148,18 @@ function setPadding(): void { addons.fit.instance.fit(); } +function setSelectionInactiveBackground(): void { + const theme = { ...term.options.theme }; + theme.selectionInactiveBackground = selectionInactiveBackgroundElement.value; + term.options.theme = theme; +} + +function setSelectionBackground(): void { + const theme = { ...term.options.theme }; + theme.selectionBackground = selectionBackgroundElement.value; + term.options.theme = theme; +} + function getSearchOptions(): ISearchOptions { return { regex: (document.getElementById('regex') as HTMLInputElement).checked, @@ -333,6 +347,8 @@ function createTerminal(): void { resizeObserver.observe(terminalContainer); addDomListener(paddingElement, 'change', setPadding); + addDomListener(selectionInactiveBackgroundElement, 'change', setSelectionInactiveBackground); + addDomListener(selectionBackgroundElement, 'change', setSelectionBackground); addDomListener(actionElements.findNext, 'keydown', (e) => { if (e.key === 'Enter') { diff --git a/demo/index.html b/demo/index.html index 6d9be706..594f0ee7 100644 --- a/demo/index.html +++ b/demo/index.html @@ -75,6 +75,14 @@ +
+ + +
+
+ + +

Test

diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 7413776c..98213e08 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -299,6 +299,7 @@ export class DomRenderer extends Disposable implements IRenderer { public handleBlur(): void { this._rowContainer.classList.remove(FOCUS_CLASS); + this.renderRows(0, this._bufferService.rows - 1); } public handleFocus(): void { From 2f233f2389ae05758aa1afd43e1111d963978165 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 8 Sep 2023 18:52:11 +0800 Subject: [PATCH 04/59] =?UTF-8?q?=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/index.html b/demo/index.html index 594f0ee7..f56af820 100644 --- a/demo/index.html +++ b/demo/index.html @@ -76,7 +76,7 @@
- +
From f23af3661bc25e38c1df49dad0d493022c563882 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 07:28:41 -0700 Subject: [PATCH 05/59] wip --- package.json | 1 + test/playwright/Renderer.test.ts | 82 ++++++++++++++++++++++++++++++++ test/playwright/tsconfig.json | 3 +- yarn.lock | 12 +++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test/playwright/Renderer.test.ts diff --git a/package.json b/package.json index 8ffd1385..77f223aa 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts" }, "devDependencies": { + "@lunapaint/png-codec": "^0.2.0", "@playwright/test": "^1.37.1", "@types/chai": "^4.2.22", "@types/debug": "^4.1.7", diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts new file mode 100644 index 00000000..90cca908 --- /dev/null +++ b/test/playwright/Renderer.test.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { decodePng } from '@lunapaint/png-codec'; +import { test } from '@playwright/test'; +import { ITheme } from 'xterm'; +import { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils'; + +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('WebGL Renderer Integration Tests', async () => { + // itWebgl('dispose removes renderer canvases', async function(): Promise { + // await setupBrowser(); + // assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); + // await page.evaluate(`addon.dispose()`); + // assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); + // await browser.close(); + // }); + + test.describe.only('colors', () => { + test('foreground 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + }); +}); + +async function getCellColor(col: number, row: number): Promise { + const buffer = await ctx.page.locator('#terminal-container .xterm-rows').screenshot({ path: 'ss.png' }); + const decoded = (await decodePng(buffer)).image; + const cellSize = { + width: decoded.width / await ctx.proxy.cols, + height: decoded.height / await ctx.proxy.rows, + }; + console.log('buffer', buffer); + console.log('decoded', decoded); + console.log('cellSize', cellSize); + const x = 1; //Math.floor((col - 1) + 0.5 * cellSize.width); + const y = 1; //Math.floor((row - 1) + 0.5 * cellSize.height); + const i = y * decoded.width + x; + console.log({ x, y, i }); + console.log('result', Array.from(decoded.data.slice(i, i + 4))); + return Array.from(decoded.data.slice(i, i + 4)); + + await ctx.page.evaluate(` + window.gl = window.term._core._renderService._renderer.value._gl; + window.result = new Uint8Array(4); + window.d = window.term._core._renderService.dimensions; + window.gl.readPixels( + Math.floor((${col - 0.5}) * window.d.device.cell.width), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), + 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + ); + `); + // await page.locator('.header').screenshot({ path: 'screenshot.png' }); + return await ctx.page.evaluate(`Array.from(window.result)`); +} diff --git a/test/playwright/tsconfig.json b/test/playwright/tsconfig.json index 2b5e55fa..aeb31e32 100644 --- a/test/playwright/tsconfig.json +++ b/test/playwright/tsconfig.json @@ -7,7 +7,8 @@ "rootDir": ".", "outDir": "../../out-test/playwright", "types": [ - "../../node_modules/@types/node" + "../../node_modules/@types/node", + "../../node_modules/@lunapaint/png-codec" ], "sourceMap": true, "removeComments": true, diff --git a/yarn.lock b/yarn.lock index 2945d97b..50a4fb84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -326,6 +326,13 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@lunapaint/png-codec@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@lunapaint/png-codec/-/png-codec-0.2.0.tgz#b9fe0a0728889af280c31239b061eb3f8c848816" + integrity sha512-S2Fk8+I27j8ZL585PlEK9hhljZcp6j+JWB5ZHAeePdufJMYHxXD2zlatLRaiy5riXRFqkCi/gong7yP9kSsEZg== + dependencies: + pako "^2.0.4" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3002,6 +3009,11 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" From 66a6ecea1483dbf9755a407b9ea9822067ee3a62 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 07:46:45 -0700 Subject: [PATCH 06/59] Improve playwright debug logging --- test/playwright/TestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 0ab95b76..e91b8310 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -383,7 +383,7 @@ export async function pollFor(page: playwright.Page, evalOrFn: string | (() = const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); if (process.env.DEBUG) { - console.log('pollFor result: ', JSON.stringify(result)); + console.log('pollFor\n actual: ', JSON.stringify(result), ' expected: ', JSON.stringify(val)); } if (!deepEqual(result, val)) { From ea45b04105eead07db4ecdf8f8f298fc2b66a466 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 07:47:14 -0700 Subject: [PATCH 07/59] First working dom renderer api tests Part of #4767 --- test/playwright/Renderer.test.ts | 78 ++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 90cca908..0721b678 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -4,7 +4,7 @@ */ import { decodePng } from '@lunapaint/png-codec'; -import { test } from '@playwright/test'; +import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; import { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils'; @@ -15,16 +15,10 @@ test.beforeAll(async ({ browser }) => { }); test.afterAll(async () => await ctx.page.close()); -test.describe('WebGL Renderer Integration Tests', async () => { - // itWebgl('dispose removes renderer canvases', async function(): Promise { - // await setupBrowser(); - // assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); - // await page.evaluate(`addon.dispose()`); - // assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); - // await browser.close(); - // }); +test.describe.only('WebGL Renderer Integration Tests', async () => { + test.beforeEach(() => ctx.proxy.reset()); - test.describe.only('colors', () => { + test.describe('colors', () => { test('foreground 0-15', async () => { const theme: ITheme = { black: '#010203', @@ -48,35 +42,49 @@ test.describe('WebGL Renderer Integration Tests', async () => { await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); }); + + test('foreground 0-7 drawBoldTextInBrightColors', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.drawBoldTextInBrightColors = true; + `); + await ctx.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); }); -async function getCellColor(col: number, row: number): Promise { - const buffer = await ctx.page.locator('#terminal-container .xterm-rows').screenshot({ path: 'ss.png' }); +/** + * Gets the color of the pixel in the center of a cell. + * @param col The 1-based column index to get the color for. + * @param row The 1-based row index to get the color for. + */ +async function getCellColor(col: number, row: number): Promise<[red: number, green: number, blue: number, alpha: number]> { + const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; + const buffer = await ctx.page.locator('#terminal-container .xterm-rows').screenshot(screenshotOptions); const decoded = (await decodePng(buffer)).image; const cellSize = { width: decoded.width / await ctx.proxy.cols, - height: decoded.height / await ctx.proxy.rows, + height: decoded.height / await ctx.proxy.rows }; - console.log('buffer', buffer); - console.log('decoded', decoded); - console.log('cellSize', cellSize); - const x = 1; //Math.floor((col - 1) + 0.5 * cellSize.width); - const y = 1; //Math.floor((row - 1) + 0.5 * cellSize.height); - const i = y * decoded.width + x; - console.log({ x, y, i }); - console.log('result', Array.from(decoded.data.slice(i, i + 4))); - return Array.from(decoded.data.slice(i, i + 4)); - - await ctx.page.evaluate(` - window.gl = window.term._core._renderService._renderer.value._gl; - window.result = new Uint8Array(4); - window.d = window.term._core._renderService.dimensions; - window.gl.readPixels( - Math.floor((${col - 0.5}) * window.d.device.cell.width), - Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), - 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result - ); - `); - // await page.locator('.header').screenshot({ path: 'screenshot.png' }); - return await ctx.page.evaluate(`Array.from(window.result)`); + const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); + const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); + const i = (y * decoded.width + x) * 4/* 4 channels per pixel */; + return Array.from(decoded.data.slice(i, i + 4)) as [number, number, number, number]; } From a656409bb5717e831c001f4e996a07b6ce98da91 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:04:36 -0700 Subject: [PATCH 08/59] All color dom renderer tests --- test/playwright/Renderer.test.ts | 708 ++++++++++++++++++++++++++++++- test/playwright/TestUtils.ts | 3 +- 2 files changed, 701 insertions(+), 10 deletions(-) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 0721b678..45705bb1 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { decodePng } from '@lunapaint/png-codec'; +import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; -import { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils'; +import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor } from './TestUtils'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { @@ -16,7 +16,14 @@ test.beforeAll(async ({ browser }) => { test.afterAll(async () => await ctx.page.close()); test.describe.only('WebGL Renderer Integration Tests', async () => { - test.beforeEach(() => ctx.proxy.reset()); + // TODO: Speed up tests + test.setTimeout(30000); + + test.beforeEach(async () => { + await ctx.proxy.reset(); + // Clear the cached screenshot before each test + frameDetails = undefined; + }); test.describe('colors', () => { test('foreground 0-15', async () => { @@ -68,6 +75,655 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + + test('background 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0, 0, 0, 255]); + }); + + test('background 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + // It's difficult to assert the exact color due to rounding, just ensure the color differs + // to the regular color + await pollFor(ctx.page, async () => { + const c = await getCellColor(x + 1, y + 1); + return ( + (c[0] === 0 || c[0] !== r) && + (c[1] === 0 || c[1] !== g) && + (c[2] === 0 || c[2] !== b) + ); + }, true); + } + } + }); + + test('background 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); /** @@ -75,16 +731,50 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { * @param col The 1-based column index to get the color for. * @param row The 1-based row index to get the color for. */ -async function getCellColor(col: number, row: number): Promise<[red: number, green: number, blue: number, alpha: number]> { +function getCellColor(col: number, row: number): MaybeAsync<[red: number, green: number, blue: number, alpha: number]> { + if (!frameDetails) { + return getFrameDetails().then(frameDetails => getCellColorInner(frameDetails, col, row)); + } + return getCellColorInner(frameDetails, col, row); +} + +let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined; +async function getFrameDetails(): Promise<{ cols: number, rows: number, decoded: IImage32 }> { const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; const buffer = await ctx.page.locator('#terminal-container .xterm-rows').screenshot(screenshotOptions); - const decoded = (await decodePng(buffer)).image; + frameDetails = { + cols: await ctx.proxy.cols, + rows: await ctx.proxy.rows, + decoded: (await decodePng(buffer, { force32: true })).image + }; + return frameDetails; +} + +function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] { const cellSize = { - width: decoded.width / await ctx.proxy.cols, - height: decoded.height / await ctx.proxy.rows + width: frameDetails.decoded.width / frameDetails.cols, + height: frameDetails.decoded.height / frameDetails.rows }; const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); - const i = (y * decoded.width + x) * 4/* 4 channels per pixel */; - return Array.from(decoded.data.slice(i, i + 4)) as [number, number, number, number]; + const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */; + return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number]; } + +const COLORS_16_TO_255 = [ + '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', + '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', + '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', + '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', + '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', + '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', + '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', + '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', + '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', + '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', + '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', + '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', + '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', + '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', + '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' +]; diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index e91b8310..f556e41b 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -374,8 +374,9 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions } +export type MaybeAsync = Promise | T; -export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise, maxDuration?: number, stack?: string): Promise { +export async function pollFor(page: playwright.Page, evalOrFn: string | (() => MaybeAsync), val: T, preFn?: () => Promise, maxDuration?: number, stack?: string): Promise { stack ??= new Error().stack; if (preFn) { await preFn(); From 97be015234dea2f3aa775e8ba2aaa9aca0f49c00 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:08:45 -0700 Subject: [PATCH 09/59] All tests copied over, some failing --- test/playwright/Renderer.test.ts | 338 +++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 45705bb1..e1f40213 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -724,6 +724,344 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { } } }); + + test.describe.skip('minimumContrastRatio', async () => { + test('should adjust 0-15 colors on black background', async () => { + const theme: ITheme = { + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block characters ignore block elements so a different char is used here + await ctx.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [208, 179, 49, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [161, 183, 215, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [191, 174, 194, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [110, 197, 198, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [211, 215, 207, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [183, 185, 183, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [249, 156, 156, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [138, 226, 52, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [252, 233, 79, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [154, 186, 221, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [203, 173, 199, 255]); + // Unchanged + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + }); + + test('should adjust 0-15 colors on white background', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block characters ignore block elements so a different char is used here + await ctx.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [72, 59, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [32, 64, 106, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [75, 51, 80, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0, 71, 72, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [64, 64, 63, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [61, 63, 59, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [125, 19, 19, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [40, 67, 13, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [67, 63, 19, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [45, 65, 87, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [81, 57, 78, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [13, 67, 67, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [64, 64, 64, 255]); + }); + + test('should enforce half the contrast for dim cells', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block characters ignore block elements so a different char is used here + await ctx.proxy.write( + '\x1b[2m' + + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [127, 104, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [153, 178, 209, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [186, 167, 189, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [4, 122, 124, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [110, 112, 108, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [170, 171, 169, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [215, 36, 36, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [72, 117, 25, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [117, 109, 36, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [72, 103, 135, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [125, 91, 121, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [25, 117, 117, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [111, 111, 110, 255]); + }); + }); + + test.describe.skip('selectionBackground', async () => { + test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + const theme: ITheme = { + foreground: '#FF0000', + background: '#00FF00', + selectionBackground: '#0000FF' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write( ` █\x1b[7m█\x1b[0m`); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 255, 0, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); + await ctx.page.evaluate(`window.term.selectAll()`); + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); + }); + }); + + test.describe('allowTransparency', async () => { + test.beforeEach(() => ctx.page.evaluate(`term.options.allowTransparency = true`)); + + test('transparent background inverse', async () => { + const theme: ITheme = { + background: '#ff000080' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write( data); + // Inverse background should be opaque + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('selectionForeground', () => { + test('transparent background inverse', async () => { + const theme: ITheme = { + selectionForeground: '#ff0000' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write( data); + await ctx.page.evaluate(`window.term.selectAll()`); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('decoration color overrides', async () => { + test('foregroundColor', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `█`; + await ctx.proxy.write( data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write( data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse (only fg on decoration)', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + foregroundColor: '#ff0000' + }); + `); + const data = `\x1b[7m█ \x1b[0m`; + await ctx.proxy.write( data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground + }); + test('backgroundColor', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = ` `; + await ctx.proxy.write( data); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m \x1b[0m`; + await ctx.proxy.write( data); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse (only bg on decoration)', async () => { + const data = `\x1b[7m█ \x1b[0m`; + await ctx.proxy.write( data); + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + backgroundColor: '#0000ff' + }); + `); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default + await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override + }); + }); }); /** From b543842fc01eb5a4cb3e0b6b60b6f6a4a3bad427 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:23:20 -0700 Subject: [PATCH 10/59] Fix dom MCR tests --- .../xterm-addon-webgl/test/WebglRenderer.api.ts | 6 +++--- test/playwright/Renderer.test.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 69fd4ffd..c41538cd 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -767,7 +767,7 @@ describe('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await writeSync(page, `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` @@ -836,7 +836,7 @@ describe('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await writeSync(page, `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` @@ -904,7 +904,7 @@ describe('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await writeSync(page, '\\x1b[2m' + `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index e1f40213..e5ae45a3 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -6,7 +6,7 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; -import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor } from './TestUtils'; +import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor, timeout } from './TestUtils'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { @@ -16,9 +16,6 @@ test.beforeAll(async ({ browser }) => { test.afterAll(async () => await ctx.page.close()); test.describe.only('WebGL Renderer Integration Tests', async () => { - // TODO: Speed up tests - test.setTimeout(30000); - test.beforeEach(async () => { await ctx.proxy.reset(); // Clear the cached screenshot before each test @@ -725,7 +722,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { } }); - test.describe.skip('minimumContrastRatio', async () => { + test.describe.only('minimumContrastRatio', async () => { test('should adjust 0-15 colors on black background', async () => { const theme: ITheme = { black: '#2e3436', @@ -749,7 +746,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await ctx.proxy.write( `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` @@ -775,6 +772,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); @@ -818,7 +816,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await ctx.proxy.write( `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` @@ -844,6 +842,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); @@ -886,7 +885,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block characters ignore block elements so a different char is used here + // Block elements ignore minimum contrast ratio so a different char is used here await ctx.proxy.write( '\x1b[2m' + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + @@ -913,6 +912,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); From db608e33fb8cd90bb66b93eadca6985166bb6aa8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:26:49 -0700 Subject: [PATCH 11/59] Fix remaining dom renderer tests --- test/playwright/Renderer.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index e5ae45a3..692f0c32 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -15,9 +15,13 @@ test.beforeAll(async ({ browser }) => { }); test.afterAll(async () => await ctx.page.close()); -test.describe.only('WebGL Renderer Integration Tests', async () => { +test.describe.only('DOM Renderer Integration Tests', async () => { test.beforeEach(async () => { await ctx.proxy.reset(); + ctx.page.evaluate([ + `window.term.options.minimumContrastRatio = 1`, + `window.term.options.allowTransparency = false` + ].join('\n')); // Clear the cached screenshot before each test frameDetails = undefined; }); @@ -722,7 +726,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { } }); - test.describe.only('minimumContrastRatio', async () => { + test.describe('minimumContrastRatio', async () => { test('should adjust 0-15 colors on black background', async () => { const theme: ITheme = { black: '#2e3436', @@ -932,7 +936,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { }); }); - test.describe.skip('selectionBackground', async () => { + test.describe('selectionBackground', async () => { test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { const theme: ITheme = { foreground: '#FF0000', @@ -945,6 +949,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); await ctx.page.evaluate(`window.term.selectAll()`); + frameDetails = undefined; // Selection only cell needs to be first to ensure renderer has kicked in await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); From e54755b074524a69a1f7fb3aca49948272c4ba85 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:37:06 -0700 Subject: [PATCH 12/59] Skip failure on WebKit --- test/playwright/Renderer.test.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 692f0c32..f407dc5d 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -6,7 +6,7 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; -import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor, timeout } from './TestUtils'; +import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor } from './TestUtils'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { @@ -15,13 +15,13 @@ test.beforeAll(async ({ browser }) => { }); test.afterAll(async () => await ctx.page.close()); -test.describe.only('DOM Renderer Integration Tests', async () => { +test.describe('DOM Renderer Integration Tests', async () => { test.beforeEach(async () => { await ctx.proxy.reset(); - ctx.page.evaluate([ - `window.term.options.minimumContrastRatio = 1`, - `window.term.options.allowTransparency = false` - ].join('\n')); + ctx.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + `); // Clear the cached screenshot before each test frameDetails = undefined; }); @@ -866,6 +866,11 @@ test.describe.only('DOM Renderer Integration Tests', async () => { }); test('should enforce half the contrast for dim cells', async () => { + // TODO: This test fails on webkit + if (ctx.browser.browserType().name() === 'webkit') { + test.skip(); + return; + } const theme: ITheme = { background: '#ffffff', black: '#2e3436', From 070d194c6c144cf234d82401c169d3c0e87ada5d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:04:25 -0700 Subject: [PATCH 13/59] Migrate webgl to playwright/test, add unified runner script --- .../test/WebglRenderer.api.ts | 1163 ----------------- .../test/WebglRenderer.test.ts | 1133 ++++++++++++++++ .../test/playwright.config.ts | 35 + addons/xterm-addon-webgl/test/tsconfig.json | 3 +- bin/test_playwright.js | 34 + package.json | 10 +- test/playwright/TestUtils.ts | 11 +- 7 files changed, 1216 insertions(+), 1173 deletions(-) delete mode 100644 addons/xterm-addon-webgl/test/WebglRenderer.api.ts create mode 100644 addons/xterm-addon-webgl/test/WebglRenderer.test.ts create mode 100644 addons/xterm-addon-webgl/test/playwright.config.ts create mode 100644 bin/test_playwright.js diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts deleted file mode 100644 index c41538cd..00000000 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ /dev/null @@ -1,1163 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { Browser, Page } from '@playwright/test'; -import { ITheme } from 'xterm'; -import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils'; -import { ITerminalOptions } from '../../../src/common/Types'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - -describe('WebGL Renderer Integration Tests', async () => { - const browserType = getBrowserType(); - const isHeadless = process.argv.includes('--headless'); - // Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032 - const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless); - const itWebgl = areTestsEnabled ? it : it.skip; - - itWebgl('dispose removes renderer canvases', async function(): Promise { - await setupBrowser(); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); - await page.evaluate(`addon.dispose()`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); - await browser.close(); - }); - - describe('colors', () => { - if (areTestsEnabled) { - before(async () => setupBrowser()); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('foreground 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('foreground 0-7 drawBoldTextInBrightColors', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.drawBoldTextInBrightColors = true; - `); - await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('background 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('foreground 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('background 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('foreground 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `); - await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]); - await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]); - }); - - itWebgl('background 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('foreground 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('background 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - itWebgl('foreground 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[38;5;${16 + y * 16 + x}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('background 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('foreground 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('background 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('foreground 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - itWebgl('background 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[8;48;5;${16 + y * 16 + x}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('foreground 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - // It's difficult to assert the exact color due to rounding, just ensure the color differs - // to the regular color - await pollFor(page, async () => { - const c = await getCellColor(x + 1, y + 1); - return ( - (c[0] === 0 || c[0] !== r) && - (c[1] === 0 || c[1] !== g) && - (c[2] === 0 || c[2] !== b) - ); - }, true); - } - } - }); - - itWebgl('background 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\\x1b[2;48;5;${16 + y * 16 + x}m \\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - itWebgl('foreground true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[38;2;${i};0;0m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - itWebgl('background true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[48;2;${i};0;0m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - itWebgl('foreground true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[38;2;0;${i};0m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - itWebgl('background true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[48;2;0;${i};0m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - itWebgl('foreground true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[38;2;0;0;${i}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - itWebgl('background true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[48;2;0;0;${i}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - itWebgl('foreground true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[38;2;${i};${i};${i}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - itWebgl('background true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - itWebgl('foreground true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - itWebgl('background true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - itWebgl('foreground true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - itWebgl('background true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;48;2;0;${i};0m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - itWebgl('foreground true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - itWebgl('background true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;48;2;0;0;${i}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - itWebgl('foreground true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - itWebgl('background true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[7;48;2;${i};${i};${i}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - itWebgl('foreground true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - itWebgl('background true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\\x1b[8;48;2;${i};${i};${i}m█\\x1b[0m`; - } - data += '\\r\\n'; - } - await writeSync(page, data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - }); - - describe('minimumContrastRatio', async () => { - if (areTestsEnabled) { - before(async () => setupBrowser()); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('should adjust 0-15 colors on black background', async () => { - const theme: ITheme = { - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await writeSync(page, - `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + - `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]); - await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]); - await pollFor(page, () => getCellColor(3, 1), [152, 198, 110, 255]); - await pollFor(page, () => getCellColor(4, 1), [208, 179, 49, 255]); - await pollFor(page, () => getCellColor(5, 1), [161, 183, 215, 255]); - await pollFor(page, () => getCellColor(6, 1), [191, 174, 194, 255]); - await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]); - await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); - await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]); - await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]); - await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); - await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); - await pollFor(page, () => getCellColor(5, 2), [154, 186, 221, 255]); - await pollFor(page, () => getCellColor(6, 2), [203, 173, 199, 255]); - // Unchanged - await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - }); - - itWebgl('should adjust 0-15 colors on white background', async () => { - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await writeSync(page, - `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + - `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]); - await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]); - await pollFor(page, () => getCellColor(3, 1), [36, 72, 0, 255]); - await pollFor(page, () => getCellColor(4, 1), [72, 59, 0, 255]); - await pollFor(page, () => getCellColor(5, 1), [32, 64, 106, 255]); - await pollFor(page, () => getCellColor(6, 1), [75, 51, 80, 255]); - await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]); - await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]); - await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]); - await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]); - await pollFor(page, () => getCellColor(3, 2), [40, 67, 13, 255]); - await pollFor(page, () => getCellColor(4, 2), [67, 63, 19, 255]); - await pollFor(page, () => getCellColor(5, 2), [45, 65, 87, 255]); - await pollFor(page, () => getCellColor(6, 2), [81, 57, 78, 255]); - await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]); - await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); - }); - - itWebgl('should enforce half the contrast for dim cells', async () => { - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await writeSync(page, - '\\x1b[2m' + - `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + - `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); - await pollFor(page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); - await pollFor(page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); - await pollFor(page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); - await pollFor(page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); - await pollFor(page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); - await pollFor(page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); - await pollFor(page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); - await pollFor(page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); - await pollFor(page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); - await pollFor(page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); - await pollFor(page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - await pollFor(page, () => getCellColor(1, 1), [150, 153, 154, 255]); - await pollFor(page, () => getCellColor(2, 1), [229, 127, 127, 255]); - await pollFor(page, () => getCellColor(3, 1), [63, 124, 4, 255]); - await pollFor(page, () => getCellColor(4, 1), [127, 104, 0, 255]); - await pollFor(page, () => getCellColor(5, 1), [153, 178, 209, 255]); - await pollFor(page, () => getCellColor(6, 1), [186, 167, 189, 255]); - await pollFor(page, () => getCellColor(7, 1), [4, 122, 124, 255]); - await pollFor(page, () => getCellColor(8, 1), [110, 112, 108, 255]); - await pollFor(page, () => getCellColor(1, 2), [170, 171, 169, 255]); - await pollFor(page, () => getCellColor(2, 2), [215, 36, 36, 255]); - await pollFor(page, () => getCellColor(3, 2), [72, 117, 25, 255]); - await pollFor(page, () => getCellColor(4, 2), [117, 109, 36, 255]); - await pollFor(page, () => getCellColor(5, 2), [72, 103, 135, 255]); - await pollFor(page, () => getCellColor(6, 2), [125, 91, 121, 255]); - await pollFor(page, () => getCellColor(7, 2), [25, 117, 117, 255]); - await pollFor(page, () => getCellColor(8, 2), [111, 111, 110, 255]); - }); - }); - - describe('selectionBackground', async () => { - if (areTestsEnabled) { - before(async () => setupBrowser()); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('should resolve the inverse foreground color based on the original background color, not the selection', async () => { - const theme: ITheme = { - foreground: '#FF0000', - background: '#00FF00', - selectionBackground: '#0000FF' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await writeSync(page, ` █\\x1b[7m█\\x1b[0m`); - await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]); - await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); - await page.evaluate(`window.term.selectAll()`); - // Selection only cell needs to be first to ensure renderer has kicked in - await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); - await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); - }); - }); - - describe('allowTransparency', async () => { - if (areTestsEnabled) { - before(async () => setupBrowser({ allowTransparency: true })); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('transparent background inverse', async () => { - const theme: ITheme = { - background: '#ff000080' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\\x1b[7m█\\x1b[0m`; - await writeSync(page, data); - // Inverse background should be opaque - await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - describe('selectionForeground', () => { - if (areTestsEnabled) { - before(async () => setupBrowser()); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('transparent background inverse', async () => { - const theme: ITheme = { - selectionForeground: '#ff0000' - }; - await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\\x1b[7m█\\x1b[0m`; - await writeSync(page, data); - await page.evaluate(`window.term.selectAll()`); - await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - describe('decoration color overrides', async () => { - if (areTestsEnabled) { - before(async () => setupBrowser()); - after(async () => browser.close()); - beforeEach(async () => page.evaluate(`window.term.reset()`)); - } - - itWebgl('foregroundColor', async () => { - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `█`; - await writeSync(page, data); - await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - itWebgl('foregroundColor should ignore inverse', async () => { - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\\x1b[7m█\\x1b[0m`; - await writeSync(page, data); - await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - itWebgl('foregroundColor should ignore inverse (only fg on decoration)', async () => { - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - foregroundColor: '#ff0000' - }); - `); - const data = `\\x1b[7m█ \\x1b[0m`; - await writeSync(page, data); - await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override - await pollFor(page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground - }); - itWebgl('backgroundColor', async () => { - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = ` `; - await writeSync(page, data); - await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - itWebgl('backgroundColor should ignore inverse', async () => { - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\\x1b[7m \\x1b[0m`; - await writeSync(page, data); - await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - itWebgl('backgroundColor should ignore inverse (only bg on decoration)', async () => { - const data = `\\x1b[7m█ \\x1b[0m`; - await writeSync(page, data); - await page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - backgroundColor: '#0000ff' - }); - `); - await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default - await pollFor(page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override - }); - }); -}); - -async function getCellColor(col: number, row: number): Promise { - await page.evaluate(` - window.gl = window.term._core._renderService._renderer.value._gl; - window.result = new Uint8Array(4); - window.d = window.term._core._renderService.dimensions; - window.gl.readPixels( - Math.floor((${col - 0.5}) * window.d.device.cell.width), - Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), - 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result - ); - `); - return await page.evaluate(`Array.from(window.result)`); -} - -async function getCellPixels(col: number, row: number): Promise { - await page.evaluate(` - window.gl = window.term._core._renderService._renderer.value._gl; - window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); - window.d = window.term._core._renderService.dimensions; - window.gl.readPixels( - Math.floor(${col - 1} * window.d.device.cell.width), - Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.device.cell.height), - window.d.device.cell.width, window.d.device.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result - ); - `); - return await page.evaluate(`Array.from(window.result)`); -} - -async function setupBrowser(options: ITerminalOptions = {}): Promise { - browser = await launchBrowser(); - page = await (await browser.newContext()).newPage(); - await page.setViewportSize({ width, height }); - await page.goto(APP); - await openTerminal(page, options); - await page.evaluate(` - window.addon = new WebglAddon(true); - window.term.loadAddon(window.addon); - `); -} - -const COLORS_16_TO_255 = [ - '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', - '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', - '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', - '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', - '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', - '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', - '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', - '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', - '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', - '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', - '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', - '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', - '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', - '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', - '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' -]; diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts new file mode 100644 index 00000000..690913b0 --- /dev/null +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -0,0 +1,1133 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import test from '@playwright/test'; +import { ITheme } from 'xterm'; +import { ITestContext, createTestContext, openTerminal, pollFor } 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 WebglAddon(true); + window.term.loadAddon(window.addon); + `); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe.only('WebGL Renderer Integration Tests', async () => { + test.beforeEach(async () => { + await ctx.proxy.reset(); + ctx.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + `); + // TODO: Introduce frame caching + // Clear the cached screenshot before each test + // frameDetails = undefined; + }); + + test('dispose removes renderer canvases', async function(): Promise { + assert.equal(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); + await ctx.page.evaluate(`addon.dispose()`); + assert.equal(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); + // Re-create webgl addon to avoid side effects impacting other tests + await ctx.page.evaluate(` + window.addon = new WebglAddon(true); + window.term.loadAddon(window.addon); + `); + }); + + test.describe('colors', () => { + test('foreground 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-7 drawBoldTextInBrightColors', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.drawBoldTextInBrightColors = true; + `); + await ctx.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0, 0, 0, 255]); + }); + + test('background 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `); + await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + test('foreground 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + // It's difficult to assert the exact color due to rounding, just ensure the color differs + // to the regular color + await pollFor(ctx.page, async () => { + const c = await getCellColor(x + 1, y + 1); + return ( + (c[0] === 0 || c[0] !== r) && + (c[1] === 0 || c[1] !== g) && + (c[2] === 0 || c[2] !== b) + ); + }, true); + } + } + }); + + test('background 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + }); + + test.describe('minimumContrastRatio', async () => { + test('should adjust 0-15 colors on black background', async () => { + const theme: ITheme = { + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [208, 179, 49, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [161, 183, 215, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [191, 174, 194, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [110, 197, 198, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [211, 215, 207, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [183, 185, 183, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [249, 156, 156, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [138, 226, 52, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [252, 233, 79, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [154, 186, 221, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [203, 173, 199, 255]); + // Unchanged + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + }); + + test('should adjust 0-15 colors on white background', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [72, 59, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [32, 64, 106, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [75, 51, 80, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [0, 71, 72, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [64, 64, 63, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [61, 63, 59, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [125, 19, 19, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [40, 67, 13, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [67, 63, 19, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [45, 65, 87, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [81, 57, 78, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [13, 67, 67, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [64, 64, 64, 255]); + }); + + test('should enforce half the contrast for dim cells', async () => { + // TODO: This test fails on webkit + if (ctx.browser.browserType().name() === 'webkit') { + test.skip(); + return; + } + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.proxy.write( + '\x1b[2m' + + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); + await pollFor(ctx.page, () => getCellColor(4, 1), [127, 104, 0, 255]); + await pollFor(ctx.page, () => getCellColor(5, 1), [153, 178, 209, 255]); + await pollFor(ctx.page, () => getCellColor(6, 1), [186, 167, 189, 255]); + await pollFor(ctx.page, () => getCellColor(7, 1), [4, 122, 124, 255]); + await pollFor(ctx.page, () => getCellColor(8, 1), [110, 112, 108, 255]); + await pollFor(ctx.page, () => getCellColor(1, 2), [170, 171, 169, 255]); + await pollFor(ctx.page, () => getCellColor(2, 2), [215, 36, 36, 255]); + await pollFor(ctx.page, () => getCellColor(3, 2), [72, 117, 25, 255]); + await pollFor(ctx.page, () => getCellColor(4, 2), [117, 109, 36, 255]); + await pollFor(ctx.page, () => getCellColor(5, 2), [72, 103, 135, 255]); + await pollFor(ctx.page, () => getCellColor(6, 2), [125, 91, 121, 255]); + await pollFor(ctx.page, () => getCellColor(7, 2), [25, 117, 117, 255]); + await pollFor(ctx.page, () => getCellColor(8, 2), [111, 111, 110, 255]); + }); + }); + + test.describe('selectionBackground', async () => { + test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + const theme: ITheme = { + foreground: '#FF0000', + background: '#00FF00', + selectionBackground: '#0000FF' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.proxy.write(` █\x1b[7m█\x1b[0m`); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 255, 0, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); + await ctx.page.evaluate(`window.term.selectAll()`); + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); + }); + }); + + test.describe('allowTransparency', async () => { + test.beforeEach(() => ctx.page.evaluate(`term.options.allowTransparency = true`)); + + test('transparent background inverse', async () => { + const theme: ITheme = { + background: '#ff000080' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write(data); + // Inverse background should be opaque + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('selectionForeground', () => { + test('transparent background inverse', async () => { + const theme: ITheme = { + selectionForeground: '#ff0000' + }; + await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write(data); + await ctx.page.evaluate(`window.term.selectAll()`); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('decoration color overrides', async () => { + test('foregroundColor', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `█`; + await ctx.proxy.write(data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m█\x1b[0m`; + await ctx.proxy.write(data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse (only fg on decoration)', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + foregroundColor: '#ff0000' + }); + `); + const data = `\x1b[7m█ \x1b[0m`; + await ctx.proxy.write(data); + await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override + await pollFor(ctx.page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground + }); + test('backgroundColor', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = ` `; + await ctx.proxy.write(data); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse', async () => { + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m \x1b[0m`; + await ctx.proxy.write(data); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse (only bg on decoration)', async () => { + const data = `\x1b[7m█ \x1b[0m`; + await ctx.proxy.write(data); + await ctx.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + backgroundColor: '#0000ff' + }); + `); + await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default + await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override + }); + }); +}); + +async function getCellColor(col: number, row: number): Promise { + await ctx.page.evaluate(` + window.gl = window.term._core._renderService._renderer.value._gl; + window.result = new Uint8Array(4); + window.d = window.term._core._renderService.dimensions; + window.gl.readPixels( + Math.floor((${col - 0.5}) * window.d.device.cell.width), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), + 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + ); + `); + return await ctx.page.evaluate(`Array.from(window.result)`); +} + +// async function getCellPixels(col: number, row: number): Promise { +// await ctx.page.evaluate(` +// window.gl = window.term._core._renderService._renderer.value._gl; +// window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); +// window.d = window.term._core._renderService.dimensions; +// window.gl.readPixels( +// Math.floor(${col - 1} * window.d.device.cell.width), +// Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.device.cell.height), +// window.d.device.cell.width, window.d.device.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result +// ); +// `); +// return await ctx.page.evaluate(`Array.from(window.result)`); +// } + +const COLORS_16_TO_255 = [ + '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', + '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', + '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', + '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', + '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', + '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', + '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', + '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', + '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', + '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', + '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', + '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', + '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', + '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', + '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' +]; diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/xterm-addon-webgl/test/playwright.config.ts new file mode 100644 index 00000000..3d1fe4fb --- /dev/null +++ b/addons/xterm-addon-webgl/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 start', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/xterm-addon-webgl/test/tsconfig.json b/addons/xterm-addon-webgl/test/tsconfig.json index 1ff9217e..8b951c40 100644 --- a/addons/xterm-addon-webgl/test/tsconfig.json +++ b/addons/xterm-addon-webgl/test/tsconfig.json @@ -20,9 +20,8 @@ }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../out-test/playwright/TestUtils" ] }, "include": [ diff --git a/bin/test_playwright.js b/bin/test_playwright.js new file mode 100644 index 00000000..bc4e7b02 --- /dev/null +++ b/bin/test_playwright.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// @ts-check + +const cp = require('child_process'); +const path = require('path'); + +const configs = [ + { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } +]; + +function npmBinScript(script) { + return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? + `${script}.cmd` : script)); +} + +for (const config of configs) { + const command = npmBinScript('playwright'); + const args = ['test', '-c', config.path, ...process.argv.slice(2)]; + console.log(`Running suite \x1b[1;34m${config.name}...\x1b[0m`); + console.log(`\n\x1b[32m${command}\x1b[0m`, args); + const run = cp.spawnSync(command, args, { + cwd: path.resolve(__dirname, '..'), + stdio: 'inherit' + } + ); + if (run.status) { + process.exit(run.status); + } +} diff --git a/package.json b/package.json index 77f223aa..77be8f26 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,11 @@ "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": "playwright test -c ./out-test/playwright/playwright.config.js --workers 4", - "test-playwright-chromium": "playwright test -c ./out-test/playwright/playwright.config.js --workers 4 --project='Chrome Stable'", - "test-playwright-firefox": "playwright test -c ./out-test/playwright/playwright.config.js --workers 4 --project='Firefox Stable'", - "test-playwright-webkit": "playwright test -c ./out-test/playwright/playwright.config.js --workers 4 --project='WebKit'", - "test-playwright-debug": "playwright test -c ./out-test/playwright/playwright.config.js --headed --workers 1 --timeout 30000", + "test-playwright": "node ./bin/test_playwright.js --workers 4", + "test-playwright-chromium": "node ./bin/test_playwright.js --workers 4 \"--project=Chrome Stable\"", + "test-playwright-firefox": "node ./bin/test_playwright.js --workers 4 \"--project=Firefox Stable\"", + "test-playwright-webkit": "node ./bin/test_playwright.js --workers 4 \"--project=WebKit\"", + "test-playwright-debug": "node ./bin/test_playwright.js --headed --workers 1 --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", diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index f556e41b..68ef2d69 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -12,8 +12,6 @@ import * as playwright from '@playwright/test'; import { PageFunction } from 'playwright-core/types/structs'; import { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from 'xterm'; import { EventEmitter } from '../../out/common/EventEmitter'; -// TODO: We could avoid needing this -import deepEqual = require('deep-equal'); export interface ITestContext { browser: Browser; @@ -387,7 +385,14 @@ export async function pollFor(page: playwright.Page, evalOrFn: string | (() = console.log('pollFor\n actual: ', JSON.stringify(result), ' expected: ', JSON.stringify(val)); } - if (!deepEqual(result, val)) { + let equalityCheck = true; + try { + deepStrictEqual(result, val); + } catch (e) { + equalityCheck = false; + } + + if (!equalityCheck) { if (maxDuration === undefined) { maxDuration = 2000; } From 413e884efb60805201c97f5fa84fa312302d31b5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:12:06 -0700 Subject: [PATCH 14/59] Use faster pixel check method with caching --- .../test/WebglRenderer.test.ts | 68 +++++++++++-------- addons/xterm-addon-webgl/test/tsconfig.json | 1 + bin/test_playwright.js | 2 +- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 690913b0..9c0418b4 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -4,9 +4,10 @@ */ import { assert } from 'chai'; -import test from '@playwright/test'; +import test, { LocatorScreenshotOptions } from '@playwright/test'; import { ITheme } from 'xterm'; -import { ITestContext, createTestContext, openTerminal, pollFor } from '../../../out-test/playwright/TestUtils'; +import { ITestContext, MaybeAsync, createTestContext, openTerminal, pollFor } from '../../../out-test/playwright/TestUtils'; +import { IImage32, decodePng } from '@lunapaint/png-codec'; let ctx: ITestContext; test.beforeAll(async ({ browser }) => { @@ -28,7 +29,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { `); // TODO: Introduce frame caching // Clear the cached screenshot before each test - // frameDetails = undefined; + frameDetails = undefined; }); test('dispose removes renderer canvases', async function(): Promise { @@ -792,6 +793,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); @@ -861,6 +863,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); @@ -935,6 +938,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); @@ -967,6 +971,7 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); await ctx.page.evaluate(`window.term.selectAll()`); + frameDetails = undefined; // Selection only cell needs to be first to ensure renderer has kicked in await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); @@ -1086,33 +1091,40 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { }); }); -async function getCellColor(col: number, row: number): Promise { - await ctx.page.evaluate(` - window.gl = window.term._core._renderService._renderer.value._gl; - window.result = new Uint8Array(4); - window.d = window.term._core._renderService.dimensions; - window.gl.readPixels( - Math.floor((${col - 0.5}) * window.d.device.cell.width), - Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), - 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result - ); - `); - return await ctx.page.evaluate(`Array.from(window.result)`); +/** + * Gets the color of the pixel in the center of a cell. + * @param col The 1-based column index to get the color for. + * @param row The 1-based row index to get the color for. + */ +function getCellColor(col: number, row: number): MaybeAsync<[red: number, green: number, blue: number, alpha: number]> { + if (!frameDetails) { + return getFrameDetails().then(frameDetails => getCellColorInner(frameDetails, col, row)); + } + return getCellColorInner(frameDetails, col, row); } -// async function getCellPixels(col: number, row: number): Promise { -// await ctx.page.evaluate(` -// window.gl = window.term._core._renderService._renderer.value._gl; -// window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); -// window.d = window.term._core._renderService.dimensions; -// window.gl.readPixels( -// Math.floor(${col - 1} * window.d.device.cell.width), -// Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.device.cell.height), -// window.d.device.cell.width, window.d.device.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result -// ); -// `); -// return await ctx.page.evaluate(`Array.from(window.result)`); -// } +let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined; +async function getFrameDetails(): Promise<{ cols: number, rows: number, decoded: IImage32 }> { + const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; + const buffer = await ctx.page.locator('#terminal-container .xterm-screen').screenshot(screenshotOptions); + frameDetails = { + cols: await ctx.proxy.cols, + rows: await ctx.proxy.rows, + decoded: (await decodePng(buffer, { force32: true })).image + }; + return frameDetails; +} + +function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] { + const cellSize = { + width: frameDetails.decoded.width / frameDetails.cols, + height: frameDetails.decoded.height / frameDetails.rows + }; + const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); + const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); + const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */; + return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number]; +} const COLORS_16_TO_255 = [ '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', diff --git a/addons/xterm-addon-webgl/test/tsconfig.json b/addons/xterm-addon-webgl/test/tsconfig.json index 8b951c40..8a4135a6 100644 --- a/addons/xterm-addon-webgl/test/tsconfig.json +++ b/addons/xterm-addon-webgl/test/tsconfig.json @@ -21,6 +21,7 @@ "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 bc4e7b02..809f29a8 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -9,7 +9,7 @@ const cp = require('child_process'); const path = require('path'); const configs = [ - { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + // { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } ]; From cdf02d54dadc7b625c54e6561b6044a57d69a900 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:22:13 -0700 Subject: [PATCH 15/59] Share test code between dom and webgl --- .../test/WebglRenderer.test.ts | 1128 +---------------- addons/xterm-addon-webgl/test/tsconfig.json | 3 +- bin/test_playwright.js | 2 +- test/playwright/Renderer.test.ts | 1118 +--------------- test/playwright/SharedRendererTests.ts | 1122 ++++++++++++++++ 5 files changed, 1141 insertions(+), 2232 deletions(-) create mode 100644 test/playwright/SharedRendererTests.ts diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 9c0418b4..5fc5d300 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -3,16 +3,17 @@ * @license MIT */ -import { assert } from 'chai'; -import test, { LocatorScreenshotOptions } from '@playwright/test'; -import { ITheme } from 'xterm'; -import { ITestContext, MaybeAsync, createTestContext, openTerminal, pollFor } from '../../../out-test/playwright/TestUtils'; -import { IImage32, decodePng } from '@lunapaint/png-codec'; +import test from '@playwright/test'; +import { strictEqual } from 'assert'; +import { injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; +import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; let ctx: ITestContext; +const ctxWrapper: { value: ITestContext } = { value: undefined } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); await openTerminal(ctx); + ctxWrapper.value = ctx; await ctx.page.evaluate(` window.addon = new WebglAddon(true); window.term.loadAddon(window.addon); @@ -20,22 +21,11 @@ test.beforeAll(async ({ browser }) => { }); test.afterAll(async () => await ctx.page.close()); -test.describe.only('WebGL Renderer Integration Tests', async () => { - test.beforeEach(async () => { - await ctx.proxy.reset(); - ctx.page.evaluate(` - window.term.options.minimumContrastRatio = 1; - window.term.options.allowTransparency = false; - `); - // TODO: Introduce frame caching - // Clear the cached screenshot before each test - frameDetails = undefined; - }); - +test.describe('WebGL Renderer Integration Tests', async () => { test('dispose removes renderer canvases', async function(): Promise { - assert.equal(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); + strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); await ctx.page.evaluate(`addon.dispose()`); - assert.equal(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); + strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); // Re-create webgl addon to avoid side effects impacting other tests await ctx.page.evaluate(` window.addon = new WebglAddon(true); @@ -43,1103 +33,5 @@ test.describe.only('WebGL Renderer Integration Tests', async () => { `); }); - test.describe('colors', () => { - test('foreground 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-7 drawBoldTextInBrightColors', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.drawBoldTextInBrightColors = true; - `); - await ctx.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0, 0, 0, 255]); - }); - - test('background 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('background 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('background 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - test('background 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - // It's difficult to assert the exact color due to rounding, just ensure the color differs - // to the regular color - await pollFor(ctx.page, async () => { - const c = await getCellColor(x + 1, y + 1); - return ( - (c[0] === 0 || c[0] !== r) && - (c[1] === 0 || c[1] !== g) && - (c[2] === 0 || c[2] !== b) - ); - }, true); - } - } - }); - - test('background 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;${i};0;0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('background true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;${i};0;0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('foreground true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;0;${i};0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('background true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;0;${i};0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('foreground true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('background true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;0;0;${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('foreground true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('background true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('foreground true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('background true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('foreground true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;0;${i};0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('background true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('foreground true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('background true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('foreground true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('background true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('foreground true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - test('background true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - }); - - test.describe('minimumContrastRatio', async () => { - test('should adjust 0-15 colors on black background', async () => { - const theme: ITheme = { - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [208, 179, 49, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [161, 183, 215, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [191, 174, 194, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [110, 197, 198, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [211, 215, 207, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [183, 185, 183, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [249, 156, 156, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [138, 226, 52, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [252, 233, 79, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [154, 186, 221, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [203, 173, 199, 255]); - // Unchanged - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - }); - - test('should adjust 0-15 colors on white background', async () => { - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [72, 59, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [32, 64, 106, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [75, 51, 80, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0, 71, 72, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [64, 64, 63, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [61, 63, 59, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [125, 19, 19, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [40, 67, 13, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [67, 63, 19, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [45, 65, 87, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [81, 57, 78, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [13, 67, 67, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [64, 64, 64, 255]); - }); - - test('should enforce half the contrast for dim cells', async () => { - // TODO: This test fails on webkit - if (ctx.browser.browserType().name() === 'webkit') { - test.skip(); - return; - } - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - '\x1b[2m' + - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [127, 104, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [153, 178, 209, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [186, 167, 189, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [4, 122, 124, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [110, 112, 108, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [170, 171, 169, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [215, 36, 36, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [72, 117, 25, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [117, 109, 36, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [72, 103, 135, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [125, 91, 121, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [25, 117, 117, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [111, 111, 110, 255]); - }); - }); - - test.describe('selectionBackground', async () => { - test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { - const theme: ITheme = { - foreground: '#FF0000', - background: '#00FF00', - selectionBackground: '#0000FF' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(` █\x1b[7m█\x1b[0m`); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 255, 0, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); - await ctx.page.evaluate(`window.term.selectAll()`); - frameDetails = undefined; - // Selection only cell needs to be first to ensure renderer has kicked in - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); - }); - }); - - test.describe('allowTransparency', async () => { - test.beforeEach(() => ctx.page.evaluate(`term.options.allowTransparency = true`)); - - test('transparent background inverse', async () => { - const theme: ITheme = { - background: '#ff000080' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write(data); - // Inverse background should be opaque - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - test.describe('selectionForeground', () => { - test('transparent background inverse', async () => { - const theme: ITheme = { - selectionForeground: '#ff0000' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write(data); - await ctx.page.evaluate(`window.term.selectAll()`); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - test.describe('decoration color overrides', async () => { - test('foregroundColor', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `█`; - await ctx.proxy.write(data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - test('foregroundColor should ignore inverse', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write(data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - test('foregroundColor should ignore inverse (only fg on decoration)', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - foregroundColor: '#ff0000' - }); - `); - const data = `\x1b[7m█ \x1b[0m`; - await ctx.proxy.write(data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground - }); - test('backgroundColor', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = ` `; - await ctx.proxy.write(data); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - test('backgroundColor should ignore inverse', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\x1b[7m \x1b[0m`; - await ctx.proxy.write(data); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - test('backgroundColor should ignore inverse (only bg on decoration)', async () => { - const data = `\x1b[7m█ \x1b[0m`; - await ctx.proxy.write(data); - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - backgroundColor: '#0000ff' - }); - `); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default - await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override - }); - }); + injectSharedRendererTests(ctxWrapper); }); - -/** - * Gets the color of the pixel in the center of a cell. - * @param col The 1-based column index to get the color for. - * @param row The 1-based row index to get the color for. - */ -function getCellColor(col: number, row: number): MaybeAsync<[red: number, green: number, blue: number, alpha: number]> { - if (!frameDetails) { - return getFrameDetails().then(frameDetails => getCellColorInner(frameDetails, col, row)); - } - return getCellColorInner(frameDetails, col, row); -} - -let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined; -async function getFrameDetails(): Promise<{ cols: number, rows: number, decoded: IImage32 }> { - const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; - const buffer = await ctx.page.locator('#terminal-container .xterm-screen').screenshot(screenshotOptions); - frameDetails = { - cols: await ctx.proxy.cols, - rows: await ctx.proxy.rows, - decoded: (await decodePng(buffer, { force32: true })).image - }; - return frameDetails; -} - -function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] { - const cellSize = { - width: frameDetails.decoded.width / frameDetails.cols, - height: frameDetails.decoded.height / frameDetails.rows - }; - const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); - const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); - const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */; - return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number]; -} - -const COLORS_16_TO_255 = [ - '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', - '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', - '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', - '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', - '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', - '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', - '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', - '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', - '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', - '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', - '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', - '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', - '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', - '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', - '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' -]; diff --git a/addons/xterm-addon-webgl/test/tsconfig.json b/addons/xterm-addon-webgl/test/tsconfig.json index 8a4135a6..9523e64b 100644 --- a/addons/xterm-addon-webgl/test/tsconfig.json +++ b/addons/xterm-addon-webgl/test/tsconfig.json @@ -22,7 +22,8 @@ "types": [ "../../../node_modules/@types/node", "../../../node_modules/@lunapaint/png-codec", - "../../../out-test/playwright/TestUtils" + "../../../out-test/playwright/TestUtils", + "../../../out-test/playwright/SharedRendererTests" ] }, "include": [ diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 809f29a8..bc4e7b02 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -9,7 +9,7 @@ const cp = require('child_process'); const path = require('path'); const configs = [ - // { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } ]; diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index f407dc5d..8050001f 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -3,1126 +3,20 @@ * @license MIT */ -import { IImage32, decodePng } from '@lunapaint/png-codec'; -import { LocatorScreenshotOptions, test } from '@playwright/test'; -import { ITheme } from 'xterm'; -import { createTestContext, ITestContext, MaybeAsync, openTerminal, pollFor } from './TestUtils'; +import { test } from '@playwright/test'; +import { ITestContext, createTestContext, openTerminal } from './TestUtils'; +import { injectSharedRendererTests } from './SharedRendererTests'; let ctx: ITestContext; +const ctxWrapper: { value: ITestContext } = { value: undefined } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); + ctxWrapper.value = ctx; await openTerminal(ctx); }); test.afterAll(async () => await ctx.page.close()); test.describe('DOM Renderer Integration Tests', async () => { - test.beforeEach(async () => { - await ctx.proxy.reset(); - ctx.page.evaluate(` - window.term.options.minimumContrastRatio = 1; - window.term.options.allowTransparency = false; - `); - // Clear the cached screenshot before each test - frameDetails = undefined; - }); - - test.describe('colors', () => { - test('foreground 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - }); - - test('foreground 0-7 drawBoldTextInBrightColors', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.drawBoldTextInBrightColors = true; - `); - await ctx.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15 inverse', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0, 0, 0, 255]); - }); - - test('background 0-15 inivisible', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('background 0-15 bright', async () => { - const theme: ITheme = { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `); - await pollFor(ctx.page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - - test('foreground 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('background 16-255', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('background 16-255 inverse', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - test('background 16-255 invisible', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - // It's difficult to assert the exact color due to rounding, just ensure the color differs - // to the regular color - await pollFor(ctx.page, async () => { - const c = await getCellColor(x + 1, y + 1); - return ( - (c[0] === 0 || c[0] !== r) && - (c[1] === 0 || c[1] !== g) && - (c[2] === 0 || c[2] !== b) - ); - }, true); - } - } - }); - - test('background 16-255 dim', async () => { - let data = ''; - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 240 / 16; y++) { - for (let x = 0; x < 16; x++) { - const cssColor = COLORS_16_TO_255[y * 16 + x]; - const r = parseInt(cssColor.slice(1, 3), 16); - const g = parseInt(cssColor.slice(3, 5), 16); - const b = parseInt(cssColor.slice(5, 7), 16); - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); - } - } - }); - - test('foreground true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;${i};0;0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('background true color red', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;${i};0;0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('foreground true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;0;${i};0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('background true color green', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;0;${i};0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('foreground true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('background true color blue', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;0;0;${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('foreground true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('background true color grey', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('foreground true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('background true color red inverse', async function(): Promise { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); - } - } - }); - - test('foreground true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;0;${i};0m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('background true color green inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); - } - } - }); - - test('foreground true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('background true color blue inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); - } - } - }); - - test('foreground true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('background true color grey inverse', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test('foreground true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]); - } - } - }); - - test('background true color grey invisible', async () => { - let data = ''; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; - } - data += '\r\n'; - } - await ctx.proxy.write(data); - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - const i = y * 16 + x; - await pollFor(ctx.page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); - } - } - }); - - test.describe('minimumContrastRatio', async () => { - test('should adjust 0-15 colors on black background', async () => { - const theme: ITheme = { - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [176, 180, 180, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [238, 158, 158, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [152, 198, 110, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [208, 179, 49, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [161, 183, 215, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [191, 174, 194, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [110, 197, 198, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [211, 215, 207, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [183, 185, 183, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [249, 156, 156, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [138, 226, 52, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [252, 233, 79, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [154, 186, 221, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [203, 173, 199, 255]); - // Unchanged - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - }); - - test('should adjust 0-15 colors on white background', async () => { - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [46, 52, 54, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [132, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [36, 72, 0, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [72, 59, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [32, 64, 106, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [75, 51, 80, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [0, 71, 72, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [64, 64, 63, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [61, 63, 59, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [125, 19, 19, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [40, 67, 13, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [67, 63, 19, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [45, 65, 87, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [81, 57, 78, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [13, 67, 67, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [64, 64, 64, 255]); - }); - - test('should enforce half the contrast for dim cells', async () => { - // TODO: This test fails on webkit - if (ctx.browser.browserType().name() === 'webkit') { - test.skip(); - return; - } - const theme: ITheme = { - background: '#ffffff', - black: '#2e3436', - red: '#cc0000', - green: '#4e9a06', - yellow: '#c4a000', - blue: '#3465a4', - magenta: '#75507b', - cyan: '#06989a', - white: '#d3d7cf', - brightBlack: '#555753', - brightRed: '#ef2929', - brightGreen: '#8ae234', - brightYellow: '#fce94f', - brightBlue: '#729fcf', - brightMagenta: '#ad7fa8', - brightCyan: '#34e2e2', - brightWhite: '#eeeeec' - }; - await ctx.page.evaluate(` - window.term.options.theme = ${JSON.stringify(theme)}; - window.term.options.minimumContrastRatio = 1; - `); - // Block elements ignore minimum contrast ratio so a different char is used here - await ctx.proxy.write( - '\x1b[2m' + - `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + - `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` - ); - // Validate before minimumContrastRatio is applied - await pollFor(ctx.page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); - // Setting and check for minimum contrast values, note that these are not - // exact to the contrast ratio, if the increase luminance algorithm - // changes then these will probably fail - await ctx.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); - frameDetails = undefined; - await pollFor(ctx.page, () => getCellColor(1, 1), [150, 153, 154, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [229, 127, 127, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [63, 124, 4, 255]); - await pollFor(ctx.page, () => getCellColor(4, 1), [127, 104, 0, 255]); - await pollFor(ctx.page, () => getCellColor(5, 1), [153, 178, 209, 255]); - await pollFor(ctx.page, () => getCellColor(6, 1), [186, 167, 189, 255]); - await pollFor(ctx.page, () => getCellColor(7, 1), [4, 122, 124, 255]); - await pollFor(ctx.page, () => getCellColor(8, 1), [110, 112, 108, 255]); - await pollFor(ctx.page, () => getCellColor(1, 2), [170, 171, 169, 255]); - await pollFor(ctx.page, () => getCellColor(2, 2), [215, 36, 36, 255]); - await pollFor(ctx.page, () => getCellColor(3, 2), [72, 117, 25, 255]); - await pollFor(ctx.page, () => getCellColor(4, 2), [117, 109, 36, 255]); - await pollFor(ctx.page, () => getCellColor(5, 2), [72, 103, 135, 255]); - await pollFor(ctx.page, () => getCellColor(6, 2), [125, 91, 121, 255]); - await pollFor(ctx.page, () => getCellColor(7, 2), [25, 117, 117, 255]); - await pollFor(ctx.page, () => getCellColor(8, 2), [111, 111, 110, 255]); - }); - }); - - test.describe('selectionBackground', async () => { - test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { - const theme: ITheme = { - foreground: '#FF0000', - background: '#00FF00', - selectionBackground: '#0000FF' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.proxy.write( ` █\x1b[7m█\x1b[0m`); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 255, 0, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); - await ctx.page.evaluate(`window.term.selectAll()`); - frameDetails = undefined; - // Selection only cell needs to be first to ensure renderer has kicked in - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 0, 0, 255]); - await pollFor(ctx.page, () => getCellColor(3, 1), [0, 255, 0, 255]); - }); - }); - - test.describe('allowTransparency', async () => { - test.beforeEach(() => ctx.page.evaluate(`term.options.allowTransparency = true`)); - - test('transparent background inverse', async () => { - const theme: ITheme = { - background: '#ff000080' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write( data); - // Inverse background should be opaque - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - test.describe('selectionForeground', () => { - test('transparent background inverse', async () => { - const theme: ITheme = { - selectionForeground: '#ff0000' - }; - await ctx.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write( data); - await ctx.page.evaluate(`window.term.selectAll()`); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - }); - - test.describe('decoration color overrides', async () => { - test('foregroundColor', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `█`; - await ctx.proxy.write( data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - test('foregroundColor should ignore inverse', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\x1b[7m█\x1b[0m`; - await ctx.proxy.write( data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); - }); - test('foregroundColor should ignore inverse (only fg on decoration)', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - foregroundColor: '#ff0000' - }); - `); - const data = `\x1b[7m█ \x1b[0m`; - await ctx.proxy.write( data); - await pollFor(ctx.page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override - await pollFor(ctx.page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground - }); - test('backgroundColor', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = ` `; - await ctx.proxy.write( data); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - test('backgroundColor should ignore inverse', async () => { - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - foregroundColor: '#ff0000', - backgroundColor: '#0000ff' - }); - `); - const data = `\x1b[7m \x1b[0m`; - await ctx.proxy.write( data); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 255, 255]); - }); - test('backgroundColor should ignore inverse (only bg on decoration)', async () => { - const data = `\x1b[7m█ \x1b[0m`; - await ctx.proxy.write( data); - await ctx.page.evaluate(` - const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); - window.term.registerDecoration({ - marker, - width: 2, - backgroundColor: '#0000ff' - }); - `); - await pollFor(ctx.page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default - await pollFor(ctx.page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override - }); - }); + injectSharedRendererTests(ctxWrapper); }); -/** - * Gets the color of the pixel in the center of a cell. - * @param col The 1-based column index to get the color for. - * @param row The 1-based row index to get the color for. - */ -function getCellColor(col: number, row: number): MaybeAsync<[red: number, green: number, blue: number, alpha: number]> { - if (!frameDetails) { - return getFrameDetails().then(frameDetails => getCellColorInner(frameDetails, col, row)); - } - return getCellColorInner(frameDetails, col, row); -} - -let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined; -async function getFrameDetails(): Promise<{ cols: number, rows: number, decoded: IImage32 }> { - const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; - const buffer = await ctx.page.locator('#terminal-container .xterm-rows').screenshot(screenshotOptions); - frameDetails = { - cols: await ctx.proxy.cols, - rows: await ctx.proxy.rows, - decoded: (await decodePng(buffer, { force32: true })).image - }; - return frameDetails; -} - -function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] { - const cellSize = { - width: frameDetails.decoded.width / frameDetails.cols, - height: frameDetails.decoded.height / frameDetails.rows - }; - const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); - const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); - const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */; - return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number]; -} - -const COLORS_16_TO_255 = [ - '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', - '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', - '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', - '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', - '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', - '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', - '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', - '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', - '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', - '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', - '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', - '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', - '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', - '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', - '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' -]; diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts new file mode 100644 index 00000000..2120925f --- /dev/null +++ b/test/playwright/SharedRendererTests.ts @@ -0,0 +1,1122 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IImage32, decodePng } from '@lunapaint/png-codec'; +import { LocatorScreenshotOptions, test } from '@playwright/test'; +import { ITheme } from 'xterm'; +import { ITestContext, MaybeAsync, pollFor } from './TestUtils'; + +export function injectSharedRendererTests(ctx: { value: ITestContext }): void { + test.beforeEach(async () => { + await ctx.value.proxy.reset(); + ctx.value.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + `); + // Clear the cached screenshot before each test + frameDetails = undefined; + }); + + test.describe('colors', () => { + test('foreground 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + }); + + test('foreground 0-7 drawBoldTextInBrightColors', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.value.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.drawBoldTextInBrightColors = true; + `); + await ctx.value.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0, 0, 0, 255]); + }); + + test('background 0-15 inivisible', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('foreground 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('background 0-15 bright', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); + }); + + test('foreground 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('background 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background 16-255 invisible', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + // It's difficult to assert the exact color due to rounding, just ensure the color differs + // to the regular color + await pollFor(ctx.value.page, async () => { + const c = await getCellColor(ctx.value, x + 1, y + 1); + return ( + (c[0] === 0 || c[0] !== r) && + (c[1] === 0 || c[1] !== g) && + (c[2] === 0 || c[2] !== b) + ); + }, true); + } + } + }); + + test('background 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + test('foreground true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('background true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + test('foreground true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;${i};0m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('background true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + test('foreground true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('background true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + test('foreground true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('background true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test('foreground true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]); + } + } + }); + + test('background true color grey invisible', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\r\n'; + } + await ctx.value.proxy.write(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + test.describe('minimumContrastRatio', async () => { + test('should adjust 0-15 colors on black background', async () => { + const theme: ITheme = { + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.value.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.value.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [176, 180, 180, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [238, 158, 158, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [152, 198, 110, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [208, 179, 49, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [161, 183, 215, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [191, 174, 194, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [110, 197, 198, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [211, 215, 207, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [183, 185, 183, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [249, 156, 156, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [138, 226, 52, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [252, 233, 79, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [154, 186, 221, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [203, 173, 199, 255]); + // Unchanged + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]); + }); + + test('should adjust 0-15 colors on white background', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.value.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.value.proxy.write( + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [46, 52, 54, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [132, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [36, 72, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [72, 59, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [32, 64, 106, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [75, 51, 80, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 71, 72, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [64, 64, 63, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [61, 63, 59, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [125, 19, 19, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [40, 67, 13, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [67, 63, 19, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [45, 65, 87, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [81, 57, 78, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [13, 67, 67, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [64, 64, 64, 255]); + }); + + test('should enforce half the contrast for dim cells', async () => { + // TODO: This test fails on webkit + if (ctx.value.browser.browserType().name() === 'webkit') { + test.skip(); + return; + } + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await ctx.value.page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block elements ignore minimum contrast ratio so a different char is used here + await ctx.value.proxy.write( + '\x1b[2m' + + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [150, 153, 154, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [229, 127, 127, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [63, 124, 4, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [127, 104, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [153, 178, 209, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [186, 167, 189, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [4, 122, 124, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [110, 112, 108, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [170, 171, 169, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [215, 36, 36, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [72, 117, 25, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [117, 109, 36, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [72, 103, 135, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [125, 91, 121, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [25, 117, 117, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [111, 111, 110, 255]); + }); + }); + + test.describe('selectionBackground', async () => { + test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + const theme: ITheme = { + foreground: '#FF0000', + background: '#00FF00', + selectionBackground: '#0000FF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write( ` █\x1b[7m█\x1b[0m`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 255, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]); + await ctx.value.page.evaluate(`window.term.selectAll()`); + frameDetails = undefined; + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]); + }); + }); + + test.describe('allowTransparency', async () => { + test.beforeEach(() => ctx.value.page.evaluate(`term.options.allowTransparency = true`)); + + test('transparent background inverse', async () => { + const theme: ITheme = { + background: '#ff000080' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.value.proxy.write( data); + // Inverse background should be opaque + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('selectionForeground', () => { + test('transparent background inverse', async () => { + const theme: ITheme = { + selectionForeground: '#ff0000' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\x1b[7m█\x1b[0m`; + await ctx.value.proxy.write( data); + await ctx.value.page.evaluate(`window.term.selectAll()`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); + }); + }); + + test.describe('decoration color overrides', async () => { + test('foregroundColor', async () => { + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `█`; + await ctx.value.proxy.write( data); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse', async () => { + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m█\x1b[0m`; + await ctx.value.proxy.write( data); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); + }); + test('foregroundColor should ignore inverse (only fg on decoration)', async () => { + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + foregroundColor: '#ff0000' + }); + `); + const data = `\x1b[7m█ \x1b[0m`; + await ctx.value.proxy.write( data); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground + }); + test('backgroundColor', async () => { + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = ` `; + await ctx.value.proxy.write( data); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse', async () => { + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\x1b[7m \x1b[0m`; + await ctx.value.proxy.write( data); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + }); + test('backgroundColor should ignore inverse (only bg on decoration)', async () => { + const data = `\x1b[7m█ \x1b[0m`; + await ctx.value.proxy.write( data); + await ctx.value.page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + backgroundColor: '#0000ff' + }); + `); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override + }); + }); +} + +/** + * Gets the color of the pixel in the center of a cell. + * @param ctx The test context. + * @param col The 1-based column index to get the color for. + * @param row The 1-based row index to get the color for. + */ +function getCellColor(ctx: ITestContext, col: number, row: number): MaybeAsync<[red: number, green: number, blue: number, alpha: number]> { + if (!frameDetails) { + return getFrameDetails(ctx).then(frameDetails => getCellColorInner(frameDetails, col, row)); + } + return getCellColorInner(frameDetails, col, row); +} + +let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined; +async function getFrameDetails(ctx: ITestContext): Promise<{ cols: number, rows: number, decoded: IImage32 }> { + const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-test/playwright/screenshot.png' } : undefined; + const buffer = await ctx.page.locator('#terminal-container .xterm-screen').screenshot(screenshotOptions); + frameDetails = { + cols: await ctx.proxy.cols, + rows: await ctx.proxy.rows, + decoded: (await decodePng(buffer, { force32: true })).image + }; + return frameDetails; +} + +function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] { + const cellSize = { + width: frameDetails.decoded.width / frameDetails.cols, + height: frameDetails.decoded.height / frameDetails.rows + }; + const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width); + const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height); + const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */; + return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number]; +} + +const COLORS_16_TO_255 = [ + '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', + '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', + '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', + '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', + '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', + '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', + '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', + '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', + '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', + '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', + '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', + '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', + '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', + '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', + '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' +]; From 9b9062aea46f9c4195662dfeb8222b79b654e308 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:27:14 -0700 Subject: [PATCH 16/59] Run npm start using relative cwd --- addons/xterm-addon-webgl/test/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/xterm-addon-webgl/test/playwright.config.ts index 3d1fe4fb..a9446a22 100644 --- a/addons/xterm-addon-webgl/test/playwright.config.ts +++ b/addons/xterm-addon-webgl/test/playwright.config.ts @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = { ], reporter: 'list', webServer: { - command: 'npm start', + command: 'npm --cwd ../.. start', port: 3000, timeout: 120000, reuseExistingServer: !process.env.CI From 47183b29390cc80e179c4c97e9aba516cdc6e1ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:31:30 -0700 Subject: [PATCH 17/59] Fix relative path --- addons/xterm-addon-webgl/test/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/xterm-addon-webgl/test/playwright.config.ts index a9446a22..0cc9bb3a 100644 --- a/addons/xterm-addon-webgl/test/playwright.config.ts +++ b/addons/xterm-addon-webgl/test/playwright.config.ts @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = { ], reporter: 'list', webServer: { - command: 'npm --cwd ../.. start', + command: 'npm --cwd ../../.. start', port: 3000, timeout: 120000, reuseExistingServer: !process.env.CI From 48ba5e58560718fdcac59365572b945f2ee68112 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:35:38 -0700 Subject: [PATCH 18/59] Add npm start to webgl package.json --- addons/xterm-addon-webgl/package.json | 3 ++- addons/xterm-addon-webgl/test/playwright.config.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index e8398c92..7e9765fc 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -19,7 +19,8 @@ "build": "../../node_modules/.bin/tsc -p .", "prepackage": "npm run build", "package": "../../node_modules/.bin/webpack", - "prepublishOnly": "npm run package" + "prepublishOnly": "npm run package", + "start": "node ../../demo/start" }, "peerDependencies": { "xterm": "^5.0.0" diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/xterm-addon-webgl/test/playwright.config.ts index 0cc9bb3a..3d1fe4fb 100644 --- a/addons/xterm-addon-webgl/test/playwright.config.ts +++ b/addons/xterm-addon-webgl/test/playwright.config.ts @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = { ], reporter: 'list', webServer: { - command: 'npm --cwd ../../.. start', + command: 'npm start', port: 3000, timeout: 120000, reuseExistingServer: !process.env.CI From a0c34b19df144d32a863396db02fd73172655bfb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:52:21 -0700 Subject: [PATCH 19/59] Skip webgl tests on linux/firefox --- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 7 +++++++ test/playwright/Renderer.test.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 5fc5d300..4da9d346 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -7,6 +7,7 @@ import test from '@playwright/test'; import { strictEqual } from 'assert'; import { injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; +import { platform } from 'os'; let ctx: ITestContext; const ctxWrapper: { value: ITestContext } = { value: undefined } as any; @@ -22,6 +23,12 @@ test.beforeAll(async ({ browser }) => { test.afterAll(async () => await ctx.page.close()); test.describe('WebGL Renderer Integration Tests', async () => { + // HACK: webgl2 is often not supported in headless firefox on Linux + // https://github.com/microsoft/playwright/issues/11566 + if (platform() === 'linux') { + test.skip(({ browserName }) => browserName === 'firefox'); + } + test('dispose removes renderer canvases', async function(): Promise { strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); await ctx.page.evaluate(`addon.dispose()`); diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 8050001f..17c8b6e1 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -16,7 +16,7 @@ test.beforeAll(async ({ browser }) => { }); test.afterAll(async () => await ctx.page.close()); -test.describe('DOM Renderer Integration Tests', async () => { +test.describe('DOM Renderer Integration Tests', () => { injectSharedRendererTests(ctxWrapper); }); From 98e8c6ac73c4fe605639fa42b6ff85b671988d7a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:16:16 -0700 Subject: [PATCH 20/59] Add canvas renderer tests --- .eslintrc.json | 1 + .../test/CanvasRenderer.test.ts | 35 ++++++++++++++++ .../test/playwright.config.ts | 35 ++++++++++++++++ addons/xterm-addon-canvas/test/tsconfig.json | 41 +++++++++++++++++++ addons/xterm-addon-canvas/tsconfig.json | 3 +- .../test/WebglRenderer.test.ts | 4 +- bin/test_playwright.js | 3 +- demo/client.ts | 2 + test/playwright/Renderer.test.ts | 4 +- test/playwright/SharedRendererTests.ts | 13 ++++-- 10 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 addons/xterm-addon-canvas/test/CanvasRenderer.test.ts create mode 100644 addons/xterm-addon-canvas/test/playwright.config.ts create mode 100644 addons/xterm-addon-canvas/test/tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index 0626a446..892c297d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,6 +17,7 @@ "addons/xterm-addon-attach/src/tsconfig.json", "addons/xterm-addon-attach/test/tsconfig.json", "addons/xterm-addon-canvas/src/tsconfig.json", + "addons/xterm-addon-canvas/test/tsconfig.json", "addons/xterm-addon-fit/src/tsconfig.json", "addons/xterm-addon-fit/test/tsconfig.json", "addons/xterm-addon-image/src/tsconfig.json", diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts new file mode 100644 index 00000000..c87c376f --- /dev/null +++ b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import test from '@playwright/test'; +import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; +import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; +import { platform } from 'os'; + +let ctx: ITestContext; +const ctxWrapper: ISharedRendererTestContext = { + value: undefined, + skipSelectionTests: true +} as any; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx); + ctxWrapper.value = ctx; + await ctx.page.evaluate(` + window.addon = new CanvasAddon(true); + window.term.loadAddon(window.addon); + `); +}); +test.afterAll(async () => await ctx.page.close()); + +test.describe('Canvas Renderer Integration Tests', async () => { + // HACK: webgl2 is often not supported in headless firefox on Linux + // https://github.com/microsoft/playwright/issues/11566 + if (platform() === 'linux') { + test.skip(({ browserName }) => browserName === 'firefox'); + } + + injectSharedRendererTests(ctxWrapper); +}); diff --git a/addons/xterm-addon-canvas/test/playwright.config.ts b/addons/xterm-addon-canvas/test/playwright.config.ts new file mode 100644 index 00000000..3d1fe4fb --- /dev/null +++ b/addons/xterm-addon-canvas/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 start', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/xterm-addon-canvas/test/tsconfig.json b/addons/xterm-addon-canvas/test/tsconfig.json new file mode 100644 index 00000000..9523e64b --- /dev/null +++ b/addons/xterm-addon-canvas/test/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2015", + "lib": [ + "es6", + ], + "rootDir": ".", + "outDir": "../out-test", + "sourceMap": true, + "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, + "strict": true, + "types": [ + "../../../node_modules/@types/node", + "../../../node_modules/@lunapaint/png-codec", + "../../../out-test/playwright/TestUtils", + "../../../out-test/playwright/SharedRendererTests" + ] + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } + ] +} diff --git a/addons/xterm-addon-canvas/tsconfig.json b/addons/xterm-addon-canvas/tsconfig.json index b711f30a..2d820dd1 100644 --- a/addons/xterm-addon-canvas/tsconfig.json +++ b/addons/xterm-addon-canvas/tsconfig.json @@ -2,6 +2,7 @@ "files": [], "include": [], "references": [ - { "path": "./src" } + { "path": "./src" }, + { "path": "./test" } ] } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 4da9d346..c6789613 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -5,12 +5,12 @@ import test from '@playwright/test'; import { strictEqual } from 'assert'; -import { injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; +import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; import { platform } from 'os'; let ctx: ITestContext; -const ctxWrapper: { value: ITestContext } = { value: undefined } as any; +const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); await openTerminal(ctx); diff --git a/bin/test_playwright.js b/bin/test_playwright.js index bc4e7b02..379c0e26 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -9,7 +9,8 @@ const cp = require('child_process'); const path = require('path'); const configs = [ - { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + // { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'xterm-addon-canvas', path: 'addons/xterm-addon-canvas/out-test/playwright.config.js' }, { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } ]; diff --git a/demo/client.ts b/demo/client.ts index bde5ca72..7ade385e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -48,6 +48,7 @@ export interface IWindowWithTerminal extends Window { term: TerminalType; Terminal?: typeof TerminalType; // eslint-disable-line @typescript-eslint/naming-convention AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention + CanvasAddon?: typeof CanvasAddon; // eslint-disable-line @typescript-eslint/naming-convention FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention ImageAddon?: typeof ImageAddonType; // eslint-disable-line @typescript-eslint/naming-convention SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention @@ -218,6 +219,7 @@ const createNewWindowButtonHandler: () => void = () => { if (document.location.pathname === '/test') { window.Terminal = Terminal; window.AttachAddon = AttachAddon; + window.CanvasAddon = CanvasAddon; window.FitAddon = FitAddon; window.ImageAddon = ImageAddon; window.SearchAddon = SearchAddon; diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 17c8b6e1..c5c1e94d 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -5,10 +5,10 @@ import { test } from '@playwright/test'; import { ITestContext, createTestContext, openTerminal } from './TestUtils'; -import { injectSharedRendererTests } from './SharedRendererTests'; +import { ISharedRendererTestContext, injectSharedRendererTests } from './SharedRendererTests'; let ctx: ITestContext; -const ctxWrapper: { value: ITestContext } = { value: undefined } as any; +const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); ctxWrapper.value = ctx; diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 2120925f..f8d70cc9 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -8,7 +8,12 @@ import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; import { ITestContext, MaybeAsync, pollFor } from './TestUtils'; -export function injectSharedRendererTests(ctx: { value: ITestContext }): void { +export interface ISharedRendererTestContext { + value: ITestContext; + skipCanvasExceptions?: boolean; +} + +export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void { test.beforeEach(async () => { await ctx.value.proxy.reset(); ctx.value.page.evaluate(` @@ -934,7 +939,7 @@ export function injectSharedRendererTests(ctx: { value: ITestContext }): void { }); }); - test.describe('selectionBackground', async () => { + (ctx.skipCanvasExceptions ? test.describe.skip : test.describe)('selectionBackground', async () => { test('should resolve the inverse foreground color based on the original background color, not the selection', async () => { const theme: ITheme = { foreground: '#FF0000', @@ -970,7 +975,7 @@ export function injectSharedRendererTests(ctx: { value: ITestContext }): void { }); }); - test.describe('selectionForeground', () => { + (ctx.skipCanvasExceptions ? test.describe.skip : test.describe)('selectionForeground', () => { test('transparent background inverse', async () => { const theme: ITheme = { selectionForeground: '#ff0000' @@ -1050,7 +1055,7 @@ export function injectSharedRendererTests(ctx: { value: ITestContext }): void { await ctx.value.proxy.write( data); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); }); - test('backgroundColor should ignore inverse (only bg on decoration)', async () => { + (ctx.skipCanvasExceptions ? test.skip : test)('backgroundColor should ignore inverse (only bg on decoration)', async () => { const data = `\x1b[7m█ \x1b[0m`; await ctx.value.proxy.write( data); await ctx.value.page.evaluate(` From 02aadf7c3efc86e4763eff8dd0336e46910711aa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:17:00 -0700 Subject: [PATCH 21/59] Remove firefox exception from canvas --- addons/xterm-addon-canvas/test/CanvasRenderer.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts index c87c376f..a129efa5 100644 --- a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts @@ -25,11 +25,5 @@ test.beforeAll(async ({ browser }) => { test.afterAll(async () => await ctx.page.close()); test.describe('Canvas Renderer Integration Tests', async () => { - // HACK: webgl2 is often not supported in headless firefox on Linux - // https://github.com/microsoft/playwright/issues/11566 - if (platform() === 'linux') { - test.skip(({ browserName }) => browserName === 'firefox'); - } - injectSharedRendererTests(ctxWrapper); }); From 71eb897708fa150a6cdaa7399dc8a351e54780e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:17:22 -0700 Subject: [PATCH 22/59] Add npm start to canvas --- addons/xterm-addon-canvas/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/package.json b/addons/xterm-addon-canvas/package.json index 701baafe..2c8fcb61 100644 --- a/addons/xterm-addon-canvas/package.json +++ b/addons/xterm-addon-canvas/package.json @@ -19,7 +19,8 @@ "build": "../../node_modules/.bin/tsc -p .", "prepackage": "npm run build", "package": "../../node_modules/.bin/webpack", - "prepublishOnly": "npm run package" + "prepublishOnly": "npm run package", + "start": "node ../../demo/start" }, "peerDependencies": { "xterm": "^5.0.0" From 408240dd9708d5f65fdaf77d0551efda537335ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:22:38 -0700 Subject: [PATCH 23/59] Tweak canvas renderer test --- addons/xterm-addon-canvas/test/CanvasRenderer.test.ts | 5 ++--- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts index a129efa5..447d01dd 100644 --- a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts @@ -6,19 +6,18 @@ import test from '@playwright/test'; import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; -import { platform } from 'os'; let ctx: ITestContext; const ctxWrapper: ISharedRendererTestContext = { value: undefined, - skipSelectionTests: true + skipCanvasExceptions: true } as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); await openTerminal(ctx); ctxWrapper.value = ctx; await ctx.page.evaluate(` - window.addon = new CanvasAddon(true); + window.addon = new window.CanvasAddon(true); window.term.loadAddon(window.addon); `); }); diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index c6789613..dc23a0cd 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -16,7 +16,7 @@ test.beforeAll(async ({ browser }) => { await openTerminal(ctx); ctxWrapper.value = ctx; await ctx.page.evaluate(` - window.addon = new WebglAddon(true); + window.addon = new window.WebglAddon(true); window.term.loadAddon(window.addon); `); }); @@ -35,7 +35,7 @@ test.describe('WebGL Renderer Integration Tests', async () => { strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); // Re-create webgl addon to avoid side effects impacting other tests await ctx.page.evaluate(` - window.addon = new WebglAddon(true); + window.addon = new window.WebglAddon(true); window.term.loadAddon(window.addon); `); }); From beeff131a93a181607020665b499c0960bd5c86e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:28:08 -0700 Subject: [PATCH 24/59] Disable canvas tests on webkit --- addons/xterm-addon-canvas/test/CanvasRenderer.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts index 447d01dd..b46c813a 100644 --- a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts @@ -17,12 +17,15 @@ test.beforeAll(async ({ browser }) => { await openTerminal(ctx); ctxWrapper.value = ctx; await ctx.page.evaluate(` - window.addon = new window.CanvasAddon(true); + window.addon = new CanvasAddon(true); window.term.loadAddon(window.addon); `); }); test.afterAll(async () => await ctx.page.close()); -test.describe('Canvas Renderer Integration Tests', async () => { +test.describe('Canvas Renderer Integration Tests', () => { + // HACK: The tests fail for an unknown reason + test.skip(({ browserName }) => browserName === 'webkit'); + injectSharedRendererTests(ctxWrapper); }); From ebd27de4e1a3e617a9e6c7668bed77fb4267d30e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:31:27 -0700 Subject: [PATCH 25/59] Re-enable core tests --- bin/test_playwright.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 379c0e26..f70a1833 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -9,7 +9,7 @@ const cp = require('child_process'); const path = require('path'); const configs = [ - // { name: 'core', path: 'out-test/playwright/playwright.config.js' }, + { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'xterm-addon-canvas', path: 'addons/xterm-addon-canvas/out-test/playwright.config.js' }, { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } ]; From 1798cab2225b2bdd4037d83b74bf76b96c66deff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:37:53 -0700 Subject: [PATCH 26/59] Remove webgl canvas check It complicates loading the addon --- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index dc23a0cd..14a9cb81 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -29,16 +29,5 @@ test.describe('WebGL Renderer Integration Tests', async () => { test.skip(({ browserName }) => browserName === 'firefox'); } - test('dispose removes renderer canvases', async function(): Promise { - strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 2); - await ctx.page.evaluate(`addon.dispose()`); - strictEqual(await ctx.page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); - // Re-create webgl addon to avoid side effects impacting other tests - await ctx.page.evaluate(` - window.addon = new window.WebglAddon(true); - window.term.loadAddon(window.addon); - `); - }); - injectSharedRendererTests(ctxWrapper); }); From 2e10a7f097518dc2d1b97cfc61608f58549cf7e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:42:26 -0700 Subject: [PATCH 27/59] Add window prefer to global symbols --- addons/xterm-addon-canvas/test/CanvasRenderer.test.ts | 2 +- test/playwright/TestUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts index b46c813a..76f57394 100644 --- a/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/xterm-addon-canvas/test/CanvasRenderer.test.ts @@ -17,7 +17,7 @@ test.beforeAll(async ({ browser }) => { await openTerminal(ctx); ctxWrapper.value = ctx; await ctx.page.evaluate(` - window.addon = new CanvasAddon(true); + window.addon = new window.CanvasAddon(true); window.term.loadAddon(window.addon); `); }); diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 68ef2d69..6bf3eb8f 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -363,7 +363,7 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions // assertion catches this case early. strictEqual(await ctx.page.evaluate(`document.querySelector('#terminal-container').children.length`), 0, 'there must be no terminals on the page'); await ctx.page.evaluate(` - window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })}); + window.term = new window.Terminal(${JSON.stringify({ allowProposedApi: true, ...options })}); window.term.open(document.querySelector('#terminal-container')); `); await ctx.page.waitForSelector('.xterm-rows'); From 328e97336b183d51d520791f8057fdb80bd9786a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:58:33 -0700 Subject: [PATCH 28/59] Try space out suites --- .../test/WebglRenderer.test.ts | 1 - bin/test_playwright.js | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 14a9cb81..718d8e03 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -4,7 +4,6 @@ */ import test from '@playwright/test'; -import { strictEqual } from 'assert'; import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; import { platform } from 'os'; diff --git a/bin/test_playwright.js b/bin/test_playwright.js index f70a1833..08ecec57 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -7,6 +7,7 @@ const cp = require('child_process'); const path = require('path'); +const { setTimeout } = require('timers/promises'); const configs = [ { name: 'core', path: 'out-test/playwright/playwright.config.js' }, @@ -19,17 +20,22 @@ function npmBinScript(script) { `${script}.cmd` : script)); } -for (const config of configs) { - const command = npmBinScript('playwright'); - const args = ['test', '-c', config.path, ...process.argv.slice(2)]; - console.log(`Running suite \x1b[1;34m${config.name}...\x1b[0m`); - console.log(`\n\x1b[32m${command}\x1b[0m`, args); - const run = cp.spawnSync(command, args, { - cwd: path.resolve(__dirname, '..'), - stdio: 'inherit' +async function run() { + for (const config of configs) { + const command = npmBinScript('playwright'); + const args = ['test', '-c', config.path, ...process.argv.slice(2)]; + console.log(`Running suite \x1b[1;34m${config.name}...\x1b[0m`); + console.log(`\n\x1b[32m${command}\x1b[0m`, args); + const run = cp.spawnSync(command, args, { + cwd: path.resolve(__dirname, '..'), + stdio: 'inherit' + } + ); + if (run.status) { + process.exit(run.status); } - ); - if (run.status) { - process.exit(run.status); + // Space out test runs to ensure servers don't step on each other + await setTimeout(1000); } } +run(); From 298c5b2041f78f06891a0f9e48bc2675bd441140 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:09:47 -0700 Subject: [PATCH 29/59] Split up playwright suites into steps --- .github/workflows/ci.yml | 8 ++++++-- bin/test_playwright.js | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21c11fc4..95de7362 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,8 +259,12 @@ jobs: ls -R - name: Build demo run: yarn build-demo - - name: Integration tests - run: yarn test-playwright-${{ matrix.browser }} --forbid-only + - name: Integration tests (core) + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=core + - name: Integration tests (xterm-addon-canvas) + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-canvas + - name: Integration tests (xterm-addon-webgl) + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl test-api: needs: build diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 08ecec57..d6c9d3dd 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -7,14 +7,26 @@ const cp = require('child_process'); const path = require('path'); -const { setTimeout } = require('timers/promises'); -const configs = [ +let argv = process.argv.slice(2); +let suiteFilter = undefined; +while (argv.some(e => e.startsWith('--suite='))) { + const i = argv.findIndex(e => e.startsWith('--suite=')); + const match = argv[i].match(/--suite=(?.+)/) + suiteFilter = match?.groups?.suitename ?? undefined; + argv.splice(i, 1); +} + +let configs = [ { name: 'core', path: 'out-test/playwright/playwright.config.js' }, { name: 'xterm-addon-canvas', path: 'addons/xterm-addon-canvas/out-test/playwright.config.js' }, { name: 'xterm-addon-webgl', path: 'addons/xterm-addon-webgl/out-test/playwright.config.js' } ]; +if (suiteFilter) { + configs = configs.filter(e => e.name === suiteFilter); +} + function npmBinScript(script) { return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script)); @@ -23,7 +35,7 @@ function npmBinScript(script) { async function run() { for (const config of configs) { const command = npmBinScript('playwright'); - const args = ['test', '-c', config.path, ...process.argv.slice(2)]; + const args = ['test', '-c', config.path, ...argv.slice(2)]; console.log(`Running suite \x1b[1;34m${config.name}...\x1b[0m`); console.log(`\n\x1b[32m${command}\x1b[0m`, args); const run = cp.spawnSync(command, args, { @@ -34,8 +46,6 @@ async function run() { if (run.status) { process.exit(run.status); } - // Space out test runs to ensure servers don't step on each other - await setTimeout(1000); } } run(); From 4af4d8dcc6d57e531e5dc7261226d7ced475091d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:12:49 -0700 Subject: [PATCH 30/59] Put webgl first --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95de7362..0bcc308c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,12 +259,12 @@ jobs: ls -R - name: Build demo run: yarn build-demo + - name: Integration tests (xterm-addon-webgl) + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl - name: Integration tests (core) run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=core - name: Integration tests (xterm-addon-canvas) run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-canvas - - name: Integration tests (xterm-addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl test-api: needs: build From 534b03b99f2c98ea2a220d7c242166383c262a46 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:19:29 -0700 Subject: [PATCH 31/59] Revert "Put webgl first" This reverts commit 4af4d8dcc6d57e531e5dc7261226d7ced475091d. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bcc308c..95de7362 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,12 +259,12 @@ jobs: ls -R - name: Build demo run: yarn build-demo - - name: Integration tests (xterm-addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl - name: Integration tests (core) run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=core - name: Integration tests (xterm-addon-canvas) run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-canvas + - name: Integration tests (xterm-addon-webgl) + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl test-api: needs: build From 2d7450520871aed22ce8b79488cd0536a086019d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:26:10 -0700 Subject: [PATCH 32/59] Avoid importing os --- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 718d8e03..d24ed5a3 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -6,7 +6,6 @@ import test from '@playwright/test'; import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; -import { platform } from 'os'; let ctx: ITestContext; const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; @@ -24,9 +23,7 @@ test.afterAll(async () => await ctx.page.close()); test.describe('WebGL Renderer Integration Tests', async () => { // HACK: webgl2 is often not supported in headless firefox on Linux // https://github.com/microsoft/playwright/issues/11566 - if (platform() === 'linux') { - test.skip(({ browserName }) => browserName === 'firefox'); - } + test.skip(({ browserName, userAgent}) => (userAgent?.includes('Linux') ?? false) && browserName === 'firefox'); injectSharedRendererTests(ctxWrapper); }); From b99158f8f0d4540a2a1d201d14781d00a5c6e4ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:30:27 -0700 Subject: [PATCH 33/59] Add workers back to playwright ci --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95de7362..b33c2545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -260,11 +260,11 @@ jobs: - name: Build demo run: yarn build-demo - name: Integration tests (core) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=core + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=core - name: Integration tests (xterm-addon-canvas) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-canvas + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=xterm-addon-canvas - name: Integration tests (xterm-addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --suite=xterm-addon-webgl + run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=xterm-addon-webgl test-api: needs: build From 3d3f58b6ffa969374559a43eaa313e6662e0f86e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:10:37 -0700 Subject: [PATCH 34/59] Have playwright addon tests only start server, not build it --- addons/xterm-addon-canvas/package.json | 2 +- addons/xterm-addon-canvas/test/playwright.config.ts | 2 +- addons/xterm-addon-webgl/package.json | 2 +- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 2 +- addons/xterm-addon-webgl/test/playwright.config.ts | 2 +- demo/start-server-only.js | 10 ++++++++++ package.json | 1 + 7 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 demo/start-server-only.js diff --git a/addons/xterm-addon-canvas/package.json b/addons/xterm-addon-canvas/package.json index 2c8fcb61..76b97758 100644 --- a/addons/xterm-addon-canvas/package.json +++ b/addons/xterm-addon-canvas/package.json @@ -20,7 +20,7 @@ "prepackage": "npm run build", "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package", - "start": "node ../../demo/start" + "start-server-only": "node ../../demo/start-server-only" }, "peerDependencies": { "xterm": "^5.0.0" diff --git a/addons/xterm-addon-canvas/test/playwright.config.ts b/addons/xterm-addon-canvas/test/playwright.config.ts index 3d1fe4fb..b0e565c5 100644 --- a/addons/xterm-addon-canvas/test/playwright.config.ts +++ b/addons/xterm-addon-canvas/test/playwright.config.ts @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = { ], reporter: 'list', webServer: { - command: 'npm start', + command: 'npm run start-server-only', port: 3000, timeout: 120000, reuseExistingServer: !process.env.CI diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 7e9765fc..1f88e150 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -20,7 +20,7 @@ "prepackage": "npm run build", "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package", - "start": "node ../../demo/start" + "start-server-only": "node ../../demo/start-server-only" }, "peerDependencies": { "xterm": "^5.0.0" diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index d24ed5a3..99710cb9 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -23,7 +23,7 @@ test.afterAll(async () => await ctx.page.close()); test.describe('WebGL Renderer Integration Tests', async () => { // HACK: webgl2 is often not supported in headless firefox on Linux // https://github.com/microsoft/playwright/issues/11566 - test.skip(({ browserName, userAgent}) => (userAgent?.includes('Linux') ?? false) && browserName === 'firefox'); + test.skip(({ browserName, userAgent }) => (userAgent?.includes('Linux') ?? false) && browserName === 'firefox'); injectSharedRendererTests(ctxWrapper); }); diff --git a/addons/xterm-addon-webgl/test/playwright.config.ts b/addons/xterm-addon-webgl/test/playwright.config.ts index 3d1fe4fb..b0e565c5 100644 --- a/addons/xterm-addon-webgl/test/playwright.config.ts +++ b/addons/xterm-addon-webgl/test/playwright.config.ts @@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = { ], reporter: 'list', webServer: { - command: 'npm start', + command: 'npm run start-server-only', port: 3000, timeout: 120000, reuseExistingServer: !process.env.CI diff --git a/demo/start-server-only.js b/demo/start-server-only.js new file mode 100644 index 00000000..b240966f --- /dev/null +++ b/demo/start-server-only.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// @ts-check + +const startServer = require('./server.js'); + +startServer(); diff --git a/package.json b/package.json index 77be8f26..534f44ec 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "package-headless": "webpack --config ./webpack.config.headless.js", "postpackage-headless": "node ./bin/package_headless.js", "start": "node demo/start", + "start-server-only": "node demo/start-server-only", "build-demo": "webpack --config ./demo/webpack.config.js", "start-debug": "node --inspect-brk demo/start", "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/", From 14872e091ea6d576512613aaa77f57cf4452fd18 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:21:18 -0700 Subject: [PATCH 35/59] Revert "Avoid importing os" This reverts commit 2d7450520871aed22ce8b79488cd0536a086019d. --- addons/xterm-addon-webgl/test/WebglRenderer.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts index 99710cb9..718d8e03 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.test.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.test.ts @@ -6,6 +6,7 @@ import test from '@playwright/test'; import { ISharedRendererTestContext, injectSharedRendererTests } from '../../../out-test/playwright/SharedRendererTests'; import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils'; +import { platform } from 'os'; let ctx: ITestContext; const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; @@ -23,7 +24,9 @@ test.afterAll(async () => await ctx.page.close()); test.describe('WebGL Renderer Integration Tests', async () => { // HACK: webgl2 is often not supported in headless firefox on Linux // https://github.com/microsoft/playwright/issues/11566 - test.skip(({ browserName, userAgent }) => (userAgent?.includes('Linux') ?? false) && browserName === 'firefox'); + if (platform() === 'linux') { + test.skip(({ browserName }) => browserName === 'firefox'); + } injectSharedRendererTests(ctxWrapper); }); From 9f5ff468e1ae010cc1d9e6b421a6e5d15e3df09d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 19:55:10 -0700 Subject: [PATCH 36/59] Remove selection color test buttons There's too many theme colors, we'll leave it to the console --- demo/client.ts | 16 ---------------- demo/index.html | 10 +--------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 8cc266aa..7ade385e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -120,8 +120,6 @@ const actionElements = { findResults: document.querySelector('#find-results') }; const paddingElement = document.getElementById('padding') as HTMLInputElement; -const selectionBackgroundElement = document.getElementById('selectionBackground') as HTMLInputElement; -const selectionInactiveBackgroundElement = document.getElementById('selectionInactiveBackground') as HTMLInputElement; const xtermjsTheme = { foreground: '#F8F8F8', @@ -149,18 +147,6 @@ function setPadding(): void { addons.fit.instance.fit(); } -function setSelectionInactiveBackground(): void { - const theme = { ...term.options.theme }; - theme.selectionInactiveBackground = selectionInactiveBackgroundElement.value; - term.options.theme = theme; -} - -function setSelectionBackground(): void { - const theme = { ...term.options.theme }; - theme.selectionBackground = selectionBackgroundElement.value; - term.options.theme = theme; -} - function getSearchOptions(): ISearchOptions { return { regex: (document.getElementById('regex') as HTMLInputElement).checked, @@ -349,8 +335,6 @@ function createTerminal(): void { resizeObserver.observe(terminalContainer); addDomListener(paddingElement, 'change', setPadding); - addDomListener(selectionInactiveBackgroundElement, 'change', setSelectionInactiveBackground); - addDomListener(selectionBackgroundElement, 'change', setSelectionBackground); addDomListener(actionElements.findNext, 'keydown', (e) => { if (e.key === 'Enter') { diff --git a/demo/index.html b/demo/index.html index f56af820..7d7329a7 100644 --- a/demo/index.html +++ b/demo/index.html @@ -75,14 +75,6 @@
-
- - -
-
- - -

Test

@@ -112,7 +104,7 @@
Weblinks Addon
- +
Image Test
From f159a862e412b4db0bb61e1cf9b1a854c5fcc4b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 19:57:17 -0700 Subject: [PATCH 37/59] Add default xterm.js theme inactive selection bg --- demo/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/client.ts b/demo/client.ts index 7ade385e..fbe87195 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -125,6 +125,7 @@ const xtermjsTheme = { foreground: '#F8F8F8', background: '#2D2E2C', selectionBackground: '#5DA5D533', + selectionInactiveBackground: '#444444', black: '#1E1E1D', brightBlack: '#262625', red: '#CE5C5C', From fa168bd4c040a28266eefd90e70bda6454b26523 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Sep 2023 20:21:58 -0700 Subject: [PATCH 38/59] Add a test which fails without the change --- demo/client.ts | 2 +- test/playwright/SharedRendererTests.ts | 26 ++++++++++++++++++++++++++ test/playwright/TestUtils.ts | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index fbe87195..06949a8a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -125,7 +125,7 @@ const xtermjsTheme = { foreground: '#F8F8F8', background: '#2D2E2C', selectionBackground: '#5DA5D533', - selectionInactiveBackground: '#444444', + selectionInactiveBackground: '#555555AA', black: '#1E1E1D', brightBlack: '#262625', red: '#CE5C5C', diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index f8d70cc9..a943cd69 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -960,6 +960,32 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); }); + (ctx.skipCanvasExceptions ? test.describe.skip : test.describe)('selectionInactiveBackground', async () => { + test.only('should render the the inactive selection when not focused', async () => { + const theme: ITheme = { + selectionBackground: '#FF000080', + selectionInactiveBackground: '#0000FF80' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + // Check both the cursor line and another line + await ctx.value.proxy.writeln('_'); + await ctx.value.proxy.write('_'); + await ctx.value.page.evaluate(`window.term.selectAll()`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [128, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [128, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [128, 0, 0, 255]); + await ctx.value.page.evaluate(`document.activeElement.blur()`); + frameDetails = undefined; + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0, 0, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 128, 255]); + }); + }); + test.describe('allowTransparency', async () => { test.beforeEach(() => ctx.value.page.evaluate(`term.options.allowTransparency = true`)); diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 6bf3eb8f..02a08907 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -400,8 +400,8 @@ export async function pollFor(page: playwright.Page, evalOrFn: string | (() = deepStrictEqual(result, val, ([ `pollFor max duration exceeded.`, (`Last comparison: ` + - `${typeof result === 'object' ? JSON.stringify(result) : result} !== ` + - `${typeof val === 'object' ? JSON.stringify(val) : val}`), + `${typeof result === 'object' ? JSON.stringify(result) : result} (actual) !== ` + + `${typeof val === 'object' ? JSON.stringify(val) : val} (expected)`), `Stack: ${stack}` ].join('\n'))); } From 5126481de2aafa8256b47d0254fc0c80d1d7371c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 04:55:56 -0700 Subject: [PATCH 39/59] Remove only --- test/playwright/SharedRendererTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index a943cd69..5e245c14 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -961,7 +961,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); (ctx.skipCanvasExceptions ? test.describe.skip : test.describe)('selectionInactiveBackground', async () => { - test.only('should render the the inactive selection when not focused', async () => { + test('should render the the inactive selection when not focused', async () => { const theme: ITheme = { selectionBackground: '#FF000080', selectionInactiveBackground: '#0000FF80' From 4e69307074215b42e5fb4e152a67b1403b1d3929 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 05:59:24 -0700 Subject: [PATCH 40/59] Add regression test --- test/playwright/SharedRendererTests.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index f8d70cc9..4fb6db13 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -144,7 +144,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]); }); - test('foreground 0-15 inivisible', async () => { + test('foreground 0-15 invisible', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -167,7 +167,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0, 0, 0, 255]); }); - test('background 0-15 inivisible', async () => { + test('background 0-15 invisible', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -1070,6 +1070,17 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override }); }); + + test.describe('regression tests', () => { + test('#4758: multiple invisible text characters without SGR change should not be rendered', async () => { + // Regression test: #4758 when multiple invisible characters are used + await ctx.value.proxy.writeln(`\x1b[8m■■`); + // Full refresh as the before result is the same as after + await ctx.value.proxy.refresh(0, await ctx.value.proxy.rows - 1); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); + }); + }); } /** From 57ab29bb8529bf39703c7f2dac17c2dcba9a4250 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:02:32 -0700 Subject: [PATCH 41/59] Handle canvas renderer's more delayed rendering --- test/playwright/SharedRendererTests.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 4fb6db13..d811ad21 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1074,11 +1074,13 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void test.describe('regression tests', () => { test('#4758: multiple invisible text characters without SGR change should not be rendered', async () => { // Regression test: #4758 when multiple invisible characters are used - await ctx.value.proxy.writeln(`\x1b[8m■■`); + await ctx.value.proxy.writeln(`■\x1b[8m■■`); // Full refresh as the before result is the same as after await ctx.value.proxy.refresh(0, await ctx.value.proxy.rows - 1); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + // Control to ensure rendering has occurred + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); }); }); } From 056206d7a4ab2be784343fc4f06c9432a7cbb723 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:09:07 -0700 Subject: [PATCH 42/59] Clear theme at start of every renderer test --- test/playwright/SharedRendererTests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index d811ad21..8a7eabf4 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -19,6 +19,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void ctx.value.page.evaluate(` window.term.options.minimumContrastRatio = 1; window.term.options.allowTransparency = false; + window.term.options.theme = undefined; `); // Clear the cached screenshot before each test frameDetails = undefined; From a10113543b073619696dc3b7cdaf49cb7e15faa7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:21:37 -0700 Subject: [PATCH 43/59] Check add empty cells that are being asserted on --- test/playwright/SharedRendererTests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 4dcf590b..a2e79286 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -970,8 +970,8 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await ctx.value.proxy.focus(); // Check both the cursor line and another line - await ctx.value.proxy.writeln('_'); - await ctx.value.proxy.write('_'); + await ctx.value.proxy.writeln('_ '); + await ctx.value.proxy.write('_ '); await ctx.value.page.evaluate(`window.term.selectAll()`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [128, 0, 0, 255]); From 575cd8d28b3bb8d27d80197f9bc03745f265fb28 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:51:13 -0700 Subject: [PATCH 44/59] Avoid block elements in renderer tests Fixes #4772 --- test/playwright/SharedRendererTests.ts | 57 ++++++++++++-------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index a2e79286..e19ff146 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -38,7 +38,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void white: '#161718' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.value.proxy.write(`\x1b[30m█\x1b[31m█\x1b[32m█\x1b[33m█\x1b[34m█\x1b[35m█\x1b[36m█\x1b[37m█`); + await ctx.value.proxy.write(`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); @@ -65,7 +65,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.drawBoldTextInBrightColors = true; `); - await ctx.value.proxy.write(`\x1b[1;30m█\x1b[1;31m█\x1b[1;32m█\x1b[1;33m█\x1b[1;34m█\x1b[1;35m█\x1b[1;36m█\x1b[1;37m█`); + await ctx.value.proxy.write(`\x1b[1;30m■\x1b[1;31m■\x1b[1;32m■\x1b[1;33m■\x1b[1;34m■\x1b[1;35m■\x1b[1;36m■\x1b[1;37m■`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); @@ -134,7 +134,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void white: '#161718' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.value.proxy.write(`\x1b[7;40m█\x1b[7;41m█\x1b[7;42m█\x1b[7;43m█\x1b[7;44m█\x1b[7;45m█\x1b[7;46m█\x1b[7;47m█`); + await ctx.value.proxy.write(`\x1b[7;40m■\x1b[7;41m■\x1b[7;42m■\x1b[7;43m■\x1b[7;44m■\x1b[7;45m■\x1b[7;46m■\x1b[7;47m■`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); @@ -180,7 +180,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void white: '#161718' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.value.proxy.write(`\x1b[8;40m█\x1b[8;41m█\x1b[8;42m█\x1b[8;43m█\x1b[8;44m█\x1b[8;45m█\x1b[8;46m█\x1b[8;47m█`); + await ctx.value.proxy.write(`\x1b[8;40m■\x1b[8;41m■\x1b[8;42m■\x1b[8;43m■\x1b[8;44m■\x1b[8;45m■\x1b[8;46m■\x1b[8;47m■`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); @@ -203,7 +203,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void brightWhite: '#161718' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.value.proxy.write(`\x1b[90m█\x1b[91m█\x1b[92m█\x1b[93m█\x1b[94m█\x1b[95m█\x1b[96m█\x1b[97m█`); + await ctx.value.proxy.write(`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]); @@ -241,7 +241,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\x1b[38;5;${16 + y * 16 + x}m■\x1b[0m`; } data += '\r\n'; } @@ -301,7 +301,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\x1b[7;48;5;${16 + y * 16 + x}m■\x1b[0m`; } data += '\r\n'; } @@ -337,7 +337,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\x1b[8;48;5;${16 + y * 16 + x}m■\x1b[0m`; } data += '\r\n'; } @@ -357,7 +357,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\x1b[2;38;5;${16 + y * 16 + x}m■\x1b[0m`; } data += '\r\n'; } @@ -407,7 +407,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[38;2;${i};0;0m█\x1b[0m`; + data += `\x1b[38;2;${i};0;0m■\x1b[0m`; } data += '\r\n'; } @@ -443,7 +443,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[38;2;0;${i};0m█\x1b[0m`; + data += `\x1b[38;2;0;${i};0m■\x1b[0m`; } data += '\r\n'; } @@ -479,7 +479,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[38;2;0;0;${i}m█\x1b[0m`; + data += `\x1b[38;2;0;0;${i}m■\x1b[0m`; } data += '\r\n'; } @@ -515,7 +515,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + data += `\x1b[38;2;${i};${i};${i}m■\x1b[0m`; } data += '\r\n'; } @@ -569,7 +569,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[7;48;2;${i};0;0m█\x1b[0m`; + data += `\x1b[7;48;2;${i};0;0m■\x1b[0m`; } data += '\r\n'; } @@ -605,7 +605,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[7;48;2;0;${i};0m█\x1b[0m`; + data += `\x1b[7;48;2;0;${i};0m■\x1b[0m`; } data += '\r\n'; } @@ -641,7 +641,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + data += `\x1b[7;48;2;0;0;${i}m■\x1b[0m`; } data += '\r\n'; } @@ -677,7 +677,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + data += `\x1b[7;48;2;${i};${i};${i}m■\x1b[0m`; } data += '\r\n'; } @@ -712,7 +712,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const i = y * 16 + x; - data += `\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`; + data += `\x1b[8;48;2;${i};${i};${i}m■\x1b[0m`; } data += '\r\n'; } @@ -749,7 +749,6 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block elements ignore minimum contrast ratio so a different char is used here await ctx.value.proxy.write( `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` @@ -819,7 +818,6 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block elements ignore minimum contrast ratio so a different char is used here await ctx.value.proxy.write( `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` @@ -893,7 +891,6 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void window.term.options.theme = ${JSON.stringify(theme)}; window.term.options.minimumContrastRatio = 1; `); - // Block elements ignore minimum contrast ratio so a different char is used here await ctx.value.proxy.write( '\x1b[2m' + `\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` + @@ -948,7 +945,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void selectionBackground: '#0000FF' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await ctx.value.proxy.write( ` █\x1b[7m█\x1b[0m`); + await ctx.value.proxy.write( ` ■\x1b[7m■\x1b[0m`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 255, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]); @@ -995,7 +992,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void background: '#ff000080' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; + const data = `\x1b[7m■\x1b[0m`; await ctx.value.proxy.write( data); // Inverse background should be opaque await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); @@ -1008,7 +1005,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void selectionForeground: '#ff0000' }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - const data = `\x1b[7m█\x1b[0m`; + const data = `\x1b[7m■\x1b[0m`; await ctx.value.proxy.write( data); await ctx.value.page.evaluate(`window.term.selectAll()`); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); @@ -1025,7 +1022,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void backgroundColor: '#0000ff' }); `); - const data = `█`; + const data = `■`; await ctx.value.proxy.write( data); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); }); @@ -1038,7 +1035,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void backgroundColor: '#0000ff' }); `); - const data = `\x1b[7m█\x1b[0m`; + const data = `\x1b[7m■\x1b[0m`; await ctx.value.proxy.write( data); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); }); @@ -1051,9 +1048,9 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void foregroundColor: '#ff0000' }); `); - const data = `\x1b[7m█ \x1b[0m`; + const data = `\x1b[7m■ \x1b[0m`; await ctx.value.proxy.write( data); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); // inverse foreground of '■' should be decoration fg override await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground }); test('backgroundColor', async () => { @@ -1083,7 +1080,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); }); (ctx.skipCanvasExceptions ? test.skip : test)('backgroundColor should ignore inverse (only bg on decoration)', async () => { - const data = `\x1b[7m█ \x1b[0m`; + const data = `\x1b[7m■ \x1b[0m`; await ctx.value.proxy.write( data); await ctx.value.page.evaluate(` const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); @@ -1093,7 +1090,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void backgroundColor: '#0000ff' }); `); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '■' should be default await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override }); }); From 681ba4887fe40e39d2cc288ebcf6e6158f09a6eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 07:11:32 -0700 Subject: [PATCH 45/59] Fix viewport error during term dispose Fixes #4775 --- src/browser/Viewport.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index c6e8732f..a8e1a498 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -119,10 +119,10 @@ export class Viewport extends Disposable implements IViewport { private _innerRefresh(): void { if (this._charSizeService.height > 0) { - this._currentRowHeight = this._renderService.dimensions.device.cell.height / this._coreBrowserService.dpr; - this._currentDeviceCellHeight = this._renderService.dimensions.device.cell.height; + this._currentRowHeight = this._renderDimensions.device.cell.height / this._coreBrowserService.dpr; + this._currentDeviceCellHeight = this._renderDimensions.device.cell.height; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; - const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.css.canvas.height); + const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderDimensions.css.canvas.height); if (this._lastRecordedBufferHeight !== newBufferHeight) { this._lastRecordedBufferHeight = newBufferHeight; this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; From 1792eaf05fe32c855231edd9e8986433206bbe43 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 07:45:31 -0700 Subject: [PATCH 46/59] Fix MCR test on webkit, add pollForApproximate Fixes #4769 --- test/playwright/SharedRendererTests.ts | 72 ++++++++++++-------------- test/playwright/TestUtils.ts | 67 +++++++++++++++++++----- 2 files changed, 89 insertions(+), 50 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index e19ff146..889bfc40 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -6,7 +6,7 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from 'xterm'; -import { ITestContext, MaybeAsync, pollFor } from './TestUtils'; +import { ITestContext, MaybeAsync, pollFor, pollForApproximate } from './TestUtils'; export interface ISharedRendererTestContext { value: ITestContext; @@ -863,11 +863,6 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); test('should enforce half the contrast for dim cells', async () => { - // TODO: This test fails on webkit - if (ctx.value.browser.browserType().name() === 'webkit') { - test.skip(); - return; - } const theme: ITheme = { background: '#ffffff', black: '#2e3436', @@ -897,43 +892,44 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void `\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■` ); // Validate before minimumContrastRatio is applied - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); + const marginOfError = 1; + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); // Setting and check for minimum contrast values, note that these are not // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); frameDetails = undefined; - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [150, 153, 154, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [229, 127, 127, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [63, 124, 4, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [127, 104, 0, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [153, 178, 209, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [186, 167, 189, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [4, 122, 124, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [110, 112, 108, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [170, 171, 169, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [215, 36, 36, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [72, 117, 25, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [117, 109, 36, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [72, 103, 135, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [125, 91, 121, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [25, 117, 117, 255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [111, 111, 110, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 1), [150, 153, 154, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 1), [229, 127, 127, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 1), [63, 124, 4, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 1), [127, 104, 0, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 1), [153, 178, 209, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 1), [186, 167, 189, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 1), [4, 122, 124, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 1), [110, 112, 108, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 2), [170, 171, 169, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 2), [215, 36, 36, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 2), [72, 117, 25, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 2), [117, 109, 36, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 2), [72, 103, 135, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 2), [125, 91, 121, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 2), [25, 117, 117, 255]); + await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 2), [111, 111, 110, 255]); }); }); diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 02a08907..18a1f1bb 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -374,8 +374,17 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions export type MaybeAsync = Promise | T; -export async function pollFor(page: playwright.Page, evalOrFn: string | (() => MaybeAsync), val: T, preFn?: () => Promise, maxDuration?: number, stack?: string): Promise { - stack ??= new Error().stack; +interface IPollForOptions { + equalityFn?: (a: T, b: T) => boolean; + maxDuration?: number; + stack?: string; +} + +export async function pollFor(page: playwright.Page, evalOrFn: string | (() => MaybeAsync), val: T, preFn?: () => Promise, options?: IPollForOptions): Promise { + if (!options) { + options = {}; + } + options.stack ??= new Error().stack; if (preFn) { await preFn(); } @@ -385,32 +394,66 @@ export async function pollFor(page: playwright.Page, evalOrFn: string | (() = console.log('pollFor\n actual: ', JSON.stringify(result), ' expected: ', JSON.stringify(val)); } - let equalityCheck = true; - try { - deepStrictEqual(result, val); - } catch (e) { - equalityCheck = false; + let equalityCheck: boolean; + if (options.equalityFn) { + equalityCheck = options.equalityFn(result as T, val); + } else { + equalityCheck = true; + try { + deepStrictEqual(result, val); + } catch (e) { + equalityCheck = false; + } } if (!equalityCheck) { - if (maxDuration === undefined) { - maxDuration = 2000; + if (options.maxDuration === undefined) { + options.maxDuration = 2000; } - if (maxDuration <= 0) { + if (options.maxDuration <= 0) { deepStrictEqual(result, val, ([ `pollFor max duration exceeded.`, (`Last comparison: ` + `${typeof result === 'object' ? JSON.stringify(result) : result} (actual) !== ` + `${typeof val === 'object' ? JSON.stringify(val) : val} (expected)`), - `Stack: ${stack}` + `Stack: ${options.stack}` ].join('\n'))); } return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, maxDuration! - 10, stack)), 10); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, { + ...options, + maxDuration: options!.maxDuration! - 10, + stack: options!.stack + })), 10); }); } } +export async function pollForApproximate(page: playwright.Page, marginOfError: number, evalOrFn: string | (() => MaybeAsync), val: T, preFn?: () => Promise, maxDuration?: number, stack?: string): Promise { + await pollFor(page, evalOrFn, val, preFn, { + maxDuration, + stack, + equalityFn: (a, b) => { + if (a === b) { + return true; + } + if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { + let success = true; + for (let i = 0 ; i < a.length; i++) { + if (Math.abs(a[i] - b[i]) > marginOfError) { + success = false; + break; + } + } + if (success) { + return true; + } + } + return false; + } + }); +} + export async function writeSync(page: playwright.Page, data: string): Promise { await page.evaluate(` window.ready = false; From 851c2e39af2303a241b3f5c88b45bd19e95f3ada Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:37:55 -0700 Subject: [PATCH 47/59] Fix webgl not respecting MCR for inverse Part of #4759 --- src/browser/renderer/shared/TextureAtlas.ts | 2 +- test/playwright/SharedRendererTests.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index dd059574..56c77b68 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -309,7 +309,7 @@ export class TextureAtlas implements ITextureAtlas { } private _getForegroundColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, dim: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor { - const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, false, bold, dim, excludeFromContrastRatioDemands); + const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, dim, excludeFromContrastRatioDemands); if (minimumContrastColor) { return minimumContrastColor; } diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 889bfc40..d746ca4c 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1102,6 +1102,19 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); }); + test.only('#4759: minimum contrast ratio should be respected on inverse text', async () => { + const theme: ITheme = { + foreground: '#aaaaaa', + background: '#333333' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[7m■■`); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x33, 0x33, 0x33, 255]); + await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); }); } From 1aac37e14f9741061fed2dc06d7449fe2bc2a6ff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:51:50 -0700 Subject: [PATCH 48/59] Improve regression tests for 4759 --- test/playwright/SharedRendererTests.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index d746ca4c..849da743 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1102,7 +1102,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); }); - test.only('#4759: minimum contrast ratio should be respected on inverse text', async () => { + test('#4759: minimum contrast ratio should be respected on inverse text', async () => { const theme: ITheme = { foreground: '#aaaaaa', background: '#333333' @@ -1110,10 +1110,32 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await ctx.value.proxy.write(`\x1b[7m■■`); // Validate before minimumContrastRatio is applied - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x33, 0x33, 0x33, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [51, 51, 51, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [51, 51, 51, 255]); await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); frameDetails = undefined; await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); + }); + test('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { + const theme: ITheme = { + foreground: '#777777', + background: '#555555', + selectionBackground: '#666666' // Slightly more contrast needed for selection + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.write(`\x1b[7m■■`); + await ctx.value.proxy.selectAll(); + // Validate before minimumContrastRatio is applied + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [85, 85, 85, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [85, 85, 85, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]); + await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await ctx.value.proxy.selectAll(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]); }); }); } From b0868967cbe2348a30826e1ed2f06abae59d81ce Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:52:43 -0700 Subject: [PATCH 49/59] Fix inverse MCR with selection for dom renderer Fixes #4759 --- src/browser/renderer/dom/DomRendererRowFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 2d21a647..614b2301 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -419,7 +419,7 @@ export class DomRendererRowFactory { break; case Attributes.CM_DEFAULT: default: - if (!this._applyMinimumContrast(charElement, resolvedBg, colors.foreground, cell, bgOverride, undefined)) { + if (!this._applyMinimumContrast(charElement, resolvedBg, colors.foreground, cell, bgOverride, fgOverride)) { if (isInverse) { classes.push(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); } From fe4528c9bed849476e4ac57e56f432e3840f1ff3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 9 Sep 2023 09:00:57 -0700 Subject: [PATCH 50/59] Add one regression test to canvas exceptions --- test/playwright/SharedRendererTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 849da743..a19a7537 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1117,7 +1117,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); }); - test('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { + (ctx.skipCanvasExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on selected inverse text', async () => { const theme: ITheme = { foreground: '#777777', background: '#555555', From b50d21434e5d356dc400663f65eaf7ab4f05211e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:23:54 -0700 Subject: [PATCH 51/59] Add edge debug target --- .vscode/launch.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5e481d5c..0eb8eb4e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -43,7 +43,14 @@ { "type": "chrome", "request": "launch", - "name": "Demo Client", + "name": "Demo Client (Chrome)", + "url": "http://127.0.0.1:3000", + "webRoot": "${workspaceFolder}/" + }, + { + "type": "msedge", + "request": "launch", + "name": "Demo Client (Edge)", "url": "http://127.0.0.1:3000", "webRoot": "${workspaceFolder}/" }, From b55d6994518b50af2044a9848a1faed9e441e0e5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:41:31 -0700 Subject: [PATCH 52/59] Fix regression in force mouse selection Shift+left click is means to force terminal selection and not passthrough any mouse events, even if a mouse mode is enabled. The change this is reverting from #4583 caused this feature to break by always installing a global listener on Terminal.element so the SelectionService never got a chance to cancel is. Fixes #4781 --- src/browser/Terminal.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a092e1bc..131774f1 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -729,10 +729,8 @@ export class Terminal extends CoreTerminal implements ITerminal { if (!(events & CoreMouseEventType.UP)) { this._document!.removeEventListener('mouseup', requestedEvents.mouseup!); - el.removeEventListener('mouseup', requestedEvents.mouseup!); requestedEvents.mouseup = null; } else if (!requestedEvents.mouseup) { - el.addEventListener('mouseup', eventListeners.mouseup); requestedEvents.mouseup = eventListeners.mouseup; } From 1d57ac5c63a82eda1001443f02e01c9102864996 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:46:00 -0700 Subject: [PATCH 53/59] Fix server debugging This broke somewhere along the way, the changes update from the legacy attach method of --inspect-brk to stopOnEntry and also updates for the introduction of .nvmrc. --- .vscode/launch.json | 8 +++----- package.json | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0eb8eb4e..5dbd01ce 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -59,11 +59,9 @@ "request": "launch", "name": "Demo Server", "runtimeExecutable": "npm", - "runtimeArgs": [ - "run", - "start-debug" - ], - "port": 9229, + "runtimeArgs": ["start"], + "stopOnEntry": true, + "runtimeVersion": "16", "serverReadyAction": { "action": "openExternally", "pattern": "App listening to (http://.*?:[0-9]+)" diff --git a/package.json b/package.json index 534f44ec..23661d73 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "start": "node demo/start", "start-server-only": "node demo/start-server-only", "build-demo": "webpack --config ./demo/webpack.config.js", - "start-debug": "node --inspect-brk demo/start", "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/", "lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/", "test": "npm run test-unit", From cc43fe5191a27c23b1d707a6298438ca58804066 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:19:46 -0700 Subject: [PATCH 54/59] Fix possible exception when disposing terminal Fixes #4757 --- src/browser/services/RenderService.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 0f18a233..e6d259c2 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -203,11 +203,14 @@ export class RenderService extends Disposable implements IRenderService { public setRenderer(renderer: IRenderer): void { this._renderer.value = renderer; - this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + // If the value was not set, the terminal is being disposed so ignore it + if (this._renderer.value) { + this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); - // Force a refresh - this._needsSelectionRefresh = true; - this._fullRefresh(); + // Force a refresh + this._needsSelectionRefresh = true; + this._fullRefresh(); + } } public addRefreshCallback(callback: FrameRequestCallback): number { From 3b15fb6211cbf0eaab3fc5bda3131b8a0ee838b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:27:18 -0700 Subject: [PATCH 55/59] Fix block cursor rendering in DOM renderer Fixes #4773 --- src/browser/renderer/dom/DomRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 98213e08..5f276954 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -205,8 +205,8 @@ export class DomRenderer extends Disposable implements IRenderer { ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + - ` background-color: ${colors.cursor.css};` + - ` color: ${colors.cursorAccent.css};` + + ` background-color: ${colors.cursor.css} !important;` + + ` color: ${colors.cursorAccent.css} !important;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + From c609f5cca038e51f2c516b115a6928a8f0b402cd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:30:43 -0700 Subject: [PATCH 56/59] Add regression test for block cursor focus/selection --- test/playwright/SharedRendererTests.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index a19a7537..e1e56bc9 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1137,6 +1137,16 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]); }); + test('#4773: block cursor should render when the cell is selected', async () => { + const theme: ITheme = { + cursor: '#0000FF', + selectionBackground: '#FF0000' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + }); }); } From 6b1c1e07516efd229b57a9cfd63c1cf53521e5a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:56:04 -0700 Subject: [PATCH 57/59] Force 50% workers in CI to reduce flakiness --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b33c2545..fb003eb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,12 +259,12 @@ jobs: ls -R - name: Build demo run: yarn build-demo - - name: Integration tests (core) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=core + - name: Integration tests (core) # Tests use 50% workers to reduce flakiness + run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core - name: Integration tests (xterm-addon-canvas) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=xterm-addon-canvas + run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=xterm-addon-canvas - name: Integration tests (xterm-addon-webgl) - run: yarn test-playwright-${{ matrix.browser }} --forbid-only --workers 4 --suite=xterm-addon-webgl + run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=xterm-addon-webgl test-api: needs: build From b91de01a4d6816dd565a0da0a63924c258eb7952 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:56:19 -0700 Subject: [PATCH 58/59] Fix initial args not being passed through to pw --- bin/test_playwright.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test_playwright.js b/bin/test_playwright.js index d6c9d3dd..02c2a763 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -35,7 +35,7 @@ function npmBinScript(script) { async function run() { for (const config of configs) { const command = npmBinScript('playwright'); - const args = ['test', '-c', config.path, ...argv.slice(2)]; + const args = ['test', '-c', config.path, ...argv]; console.log(`Running suite \x1b[1;34m${config.name}...\x1b[0m`); console.log(`\n\x1b[32m${command}\x1b[0m`, args); const run = cp.spawnSync(command, args, { From efcca3fb3e8ffdd091771ee4e68270cd3b5a5c3b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:56:36 -0700 Subject: [PATCH 59/59] Run pw tests faster on high core machines --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 23661d73..4915cf3e 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,11 @@ "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 4", - "test-playwright-chromium": "node ./bin/test_playwright.js --workers 4 \"--project=Chrome Stable\"", - "test-playwright-firefox": "node ./bin/test_playwright.js --workers 4 \"--project=Firefox Stable\"", - "test-playwright-webkit": "node ./bin/test_playwright.js --workers 4 \"--project=WebKit\"", - "test-playwright-debug": "node ./bin/test_playwright.js --headed --workers 1 --timeout 30000", + "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-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", "test-unit-coverage": "node ./bin/test.js --coverage", "test-unit-dev": "cross-env NODE_PATH='./out' mocha",