Merge branch 'master' into add-balena-real-world-use
This commit is contained in:
+17
-15
@@ -11,7 +11,6 @@
|
||||
"src/browser/tsconfig.json",
|
||||
"src/common/tsconfig.json",
|
||||
"src/headless/tsconfig.json",
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"test/playwright/tsconfig.json",
|
||||
"addons/addon-attach/src/tsconfig.json",
|
||||
@@ -52,11 +51,25 @@
|
||||
"**/*.js"
|
||||
],
|
||||
"plugins": [
|
||||
"@stylistic/ts",
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@stylistic/ts/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@stylistic/ts/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"@stylistic/ts/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
@@ -81,10 +94,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
@@ -144,15 +153,7 @@
|
||||
"@typescript-eslint/no-useless-constructor": "warn",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
{
|
||||
@@ -193,6 +194,7 @@
|
||||
}
|
||||
],
|
||||
"no-eval": "warn",
|
||||
"no-extra-semi": "error",
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-restricted-imports": [
|
||||
"warn",
|
||||
|
||||
+17
-14
@@ -6,11 +6,25 @@
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@stylistic/ts",
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@stylistic/ts/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@stylistic/ts/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"@stylistic/ts/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
@@ -24,10 +38,6 @@
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
@@ -48,15 +58,7 @@
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
{
|
||||
@@ -86,6 +88,7 @@
|
||||
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
|
||||
}
|
||||
],
|
||||
"no-extra-semi": "error",
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"object-curly-spacing": [
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
exit $EXIT_CODE
|
||||
|
||||
test-unit-parallel:
|
||||
test-unit:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -150,48 +150,7 @@ jobs:
|
||||
- name: Unit tests
|
||||
run: yarn test-unit --forbid-only
|
||||
|
||||
test-api-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [ubuntu] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
|
||||
test-playwright-parallel:
|
||||
test-integration:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -232,48 +191,11 @@ jobs:
|
||||
- name: Build demo
|
||||
run: yarn build-demo
|
||||
- name: Integration tests (core) # Tests use 50% workers to reduce flakiness
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
|
||||
- name: Integration tests (addon-canvas)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas
|
||||
- name: Integration tests (addon-webgl)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
|
||||
|
||||
test-api:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [windows] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
|
||||
|
||||
release-dry-run:
|
||||
needs: build
|
||||
|
||||
@@ -224,6 +224,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
|
||||
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
|
||||
- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows.
|
||||
- [**eva**](https://github.com/info24/eva): Eva is a web application for SSH remote login, developed in Go.
|
||||
- [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client.
|
||||
- [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud).
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('AttachAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('string', async function(): Promise<any> {
|
||||
await openTerminal(page);
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
server.on('connection', socket => socket.send('foo'));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('utf8', async function(): Promise<any> {
|
||||
await openTerminal(page);
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
const data = new Uint8Array([102, 111, 111]);
|
||||
server.on('connection', socket => socket.send(data));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import WebSocket = require('ws');
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Search Tests', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.proxy.reset();
|
||||
});
|
||||
|
||||
test('string', async function(): Promise<any> {
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
server.on('connection', socket => socket.send('foo'));
|
||||
await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('utf8', async function(): Promise<any> {
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
const data = new Uint8Array([102, 111, 111]);
|
||||
server.on('connection', socket => socket.send(data));
|
||||
await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,23 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es6",
|
||||
],
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, BrowserContext, Page } from '@playwright/test';
|
||||
import { beforeEach } from 'mocha';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let context: BrowserContext;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('ClipboardAddon', () => {
|
||||
before(async function (): Promise<any> {
|
||||
browser = await launchBrowser({
|
||||
// Enable clipboard access in firefox, mainly for readText
|
||||
firefoxUserPrefs: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'dom.events.testing.asyncClipboard': true,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'dom.events.asyncClipboard.readText': true
|
||||
}
|
||||
});
|
||||
context = await browser.newContext();
|
||||
if (getBrowserType().name() !== 'webkit') {
|
||||
// Enable clipboard access in chromium without user gesture
|
||||
context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
}
|
||||
page = await context.newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`
|
||||
window.clipboardAddon = new ClipboardAddon();
|
||||
window.term.loadAddon(window.clipboardAddon);
|
||||
`);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`window.term.reset()`);
|
||||
});
|
||||
|
||||
const testDataEncoded = 'aGVsbG8gd29ybGQ=';
|
||||
const testDataDecoded = 'hello world';
|
||||
|
||||
describe('write data', async function (): Promise<any> {
|
||||
it('simple string', async () => {
|
||||
await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`);
|
||||
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
|
||||
});
|
||||
it('invalid base64 string', async () => {
|
||||
await writeSync(page, `\x1b]52;c;${testDataEncoded}invalid\x07`);
|
||||
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
it('empty string', async () => {
|
||||
await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`);
|
||||
await writeSync(page, `\x1b]52;c;\x07`);
|
||||
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('read data', async function (): Promise<any> {
|
||||
it('simple string', async () => {
|
||||
await page.evaluate(`
|
||||
window.data = [];
|
||||
window.term.onData(e => data.push(e));
|
||||
`);
|
||||
await page.evaluate(() => window.navigator.clipboard.writeText('hello world'));
|
||||
await writeSync(page, `\x1b]52;c;?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]);
|
||||
});
|
||||
it('clear clipboard', async () => {
|
||||
await writeSync(page, `\x1b]52;c;!\x07`);
|
||||
await writeSync(page, `\x1b]52;c;?\x07`);
|
||||
assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepEqual, ok, strictEqual } from 'assert';
|
||||
import { ITestContext, createTestContext, launchBrowser, openTerminal, timeout } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await ctx.page.close();
|
||||
});
|
||||
|
||||
test.describe('ClipboardAddon', () => {
|
||||
|
||||
test.beforeEach(async ({}, testInfo) => {
|
||||
// DEBT: This test doesn't work since the migration to @playwright/test
|
||||
if (ctx.browser.browserType().name() !== 'chromium') {
|
||||
testInfo.skip();
|
||||
return;
|
||||
}
|
||||
if (ctx.browser.browserType().name() === 'chromium') {
|
||||
// Enable clipboard access in chromium without user gesture
|
||||
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
}
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.clipboard?.dispose();
|
||||
window.clipboard = new ClipboardAddon();
|
||||
window.term.loadAddon(window.clipboard);
|
||||
`);
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.proxy.reset();
|
||||
});
|
||||
|
||||
const testDataEncoded = 'aGVsbG8gd29ybGQ=';
|
||||
const testDataDecoded = 'hello world';
|
||||
|
||||
test.describe('write data', async function (): Promise<any> {
|
||||
test('simple string', async () => {
|
||||
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
|
||||
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
|
||||
});
|
||||
test('invalid base64 string', async () => {
|
||||
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}invalid\x07`);
|
||||
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
test('empty string', async () => {
|
||||
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
|
||||
await ctx.proxy.write(`\x1b]52;c;\x07`);
|
||||
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('read data', async function (): Promise<any> {
|
||||
test('simple string', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.data = [];
|
||||
window.term.onData(e => data.push(e));
|
||||
`);
|
||||
await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world'));
|
||||
await ctx.proxy.write(`\x1b]52;c;?\x07`);
|
||||
deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]);
|
||||
});
|
||||
test('clear clipboard', async () => {
|
||||
await ctx.proxy.write(`\x1b]52;c;!\x07`);
|
||||
await ctx.proxy.write(`\x1b]52;c;?\x07`);
|
||||
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,23 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+3
-1
@@ -13,8 +13,10 @@ declare module '@xterm/addon-clipboard' {
|
||||
export class ClipboardAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Creates a new clipboard addon.
|
||||
* @param base64 An optional base64 encoder/decoder.
|
||||
* @param provider An optional clipboard provider.
|
||||
*/
|
||||
constructor(provider?: IClipboardProvider);
|
||||
constructor(base64?: IBase64, provider?: IClipboardProvider);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 1024;
|
||||
const height = 768;
|
||||
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
afterEach(async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
});
|
||||
|
||||
it('no terminal', async function(): Promise<any> {
|
||||
await page.evaluate(`window.fit = new FitAddon();`);
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
});
|
||||
|
||||
describe('proposeDimensions', () => {
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.isAbove(dimensions.cols, 108);
|
||||
assert.isBelow(dimensions.cols, 111);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 2,
|
||||
rows: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('hidden', async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
|
||||
await page.evaluate(`window.term = new Terminal()`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
const dimensions: { cols: number, rows: number } | undefined = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
// The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
// method, so only assert if it's not undefined.
|
||||
if (dimensions) {
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('fit', () => {
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.isAbove(cols, 85);
|
||||
assert.isBelow(cols, 88);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.isAbove(cols, 108);
|
||||
assert.isBelow(cols, 111);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 2);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.fit = new FitAddon();
|
||||
window.term.loadAddon(window.fit);
|
||||
document.querySelector('#terminal-container').style.width='${width}px';
|
||||
document.querySelector('#terminal-container').style.height='${height}px';
|
||||
`);
|
||||
}
|
||||
|
||||
async function unloadFit(): Promise<void> {
|
||||
await page.evaluate(`window.fit.dispose();`);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepEqual, ok, strictEqual } from 'assert';
|
||||
import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('FitAddon', () => {
|
||||
test.beforeEach(async function(): Promise<any> {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.fit?.dispose();
|
||||
window.fit = new FitAddon();
|
||||
window.term.loadAddon(window.fit);
|
||||
`);
|
||||
});
|
||||
|
||||
// test.beforeEach(async function(): Promise<any> {
|
||||
// await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
// await openTerminal(page);
|
||||
// });
|
||||
|
||||
// after(async () => {
|
||||
// await browser.close();
|
||||
// });
|
||||
|
||||
// afterEach(async function(): Promise<any> {
|
||||
// await ctx.proxy.dispose();
|
||||
// });
|
||||
|
||||
test('no terminal', async function(): Promise<any> {
|
||||
await ctx.page.evaluate(`window.fit = new FitAddon();`);
|
||||
strictEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
});
|
||||
|
||||
test.describe('proposeDimensions', () => {
|
||||
// test.afterEach(() => unloadFit());
|
||||
|
||||
test('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
|
||||
ok(dimensions.cols > 85);
|
||||
ok(dimensions.cols < 88);
|
||||
ok(dimensions.rows > 24);
|
||||
ok(dimensions.rows < 29);
|
||||
});
|
||||
|
||||
test('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
|
||||
ok(dimensions.cols > 108);
|
||||
ok(dimensions.cols < 111);
|
||||
ok(dimensions.rows > 24);
|
||||
ok(dimensions.rows < 29);
|
||||
});
|
||||
|
||||
test('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
deepEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 2,
|
||||
rows: 1
|
||||
});
|
||||
});
|
||||
|
||||
test('hidden', async function(): Promise<any> {
|
||||
await ctx.proxy.dispose();
|
||||
await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
|
||||
await ctx.page.evaluate(`window.term = new Terminal()`);
|
||||
await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
const dimensions: { cols: number, rows: number } | undefined = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
|
||||
// The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
// method, so only assert if it's not undefined.
|
||||
if (dimensions) {
|
||||
ok(dimensions.cols > 85);
|
||||
ok(dimensions.cols < 88);
|
||||
ok(dimensions.rows > 24);
|
||||
ok(dimensions.rows < 29);
|
||||
}
|
||||
await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='block'`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('fit', () => {
|
||||
test.afterEach(() => unloadFit());
|
||||
|
||||
test('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
await ctx.page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await ctx.proxy.cols;
|
||||
const rows: number = await ctx.proxy.rows;
|
||||
ok(cols > 85);
|
||||
ok(cols < 88);
|
||||
ok(rows > 24);
|
||||
ok(rows < 29);
|
||||
});
|
||||
|
||||
test('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await ctx.page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await ctx.proxy.cols;
|
||||
const rows: number = await ctx.proxy.rows;
|
||||
ok(cols > 108);
|
||||
ok(cols < 111);
|
||||
ok(rows > 24);
|
||||
ok(rows < 29);
|
||||
});
|
||||
|
||||
test('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
await ctx.page.evaluate(`window.fit.fit()`);
|
||||
strictEqual(await ctx.proxy.cols, 2);
|
||||
strictEqual(await ctx.proxy.rows, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
|
||||
await ctx.page.evaluate(`
|
||||
window.fit = new FitAddon();
|
||||
window.term.loadAddon(window.fit);
|
||||
document.querySelector('#terminal-container').style.width='${width}px';
|
||||
document.querySelector('#terminal-container').style.height='${height}px';
|
||||
`);
|
||||
}
|
||||
|
||||
async function unloadFit(): Promise<void> {
|
||||
await ctx.page.evaluate(`window.fit.dispose();`);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,23 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser, pollFor } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
import { IImageAddonOptions } from '../src/Types';
|
||||
import { FINALIZER, introducer, sixelEncode } from 'sixel';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
// eslint-disable-next-line
|
||||
declare const ImageAddon: {
|
||||
new(options?: Partial<IImageAddonOptions>): any;
|
||||
};
|
||||
|
||||
interface ITestData {
|
||||
width: number;
|
||||
height: number;
|
||||
bytes: Uint8Array;
|
||||
palette: number[];
|
||||
sixel: string;
|
||||
}
|
||||
|
||||
interface IDimensions {
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// image: 640 x 80, 512 color
|
||||
const TESTDATA: ITestData = (() => {
|
||||
const data8 = readFileSync('./addons/addon-image/fixture/palette.blob');
|
||||
const data32 = new Uint32Array(data8.buffer);
|
||||
const palette = new Set<number>();
|
||||
for (let i = 0; i < data32.length; ++i) palette.add(data32[i]);
|
||||
const sixel = sixelEncode(data8, 640, 80, [...palette]);
|
||||
return {
|
||||
width: 640,
|
||||
height: 80,
|
||||
bytes: data8,
|
||||
palette: [...palette],
|
||||
sixel
|
||||
};
|
||||
})();
|
||||
const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
|
||||
|
||||
// NOTE: the data is loaded as string for easier transport through playwright
|
||||
const TESTDATA_IIP: [string, [number, number]][] = [
|
||||
[readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]]
|
||||
];
|
||||
|
||||
describe('ImageAddon', () => {
|
||||
before(async () => {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
await page.evaluate(opts => {
|
||||
(window as any).imageAddon = new ImageAddon(opts.opts);
|
||||
(window as any).term.loadAddon((window as any).imageAddon);
|
||||
}, { opts: { sixelPaletteLimit: 512 } });
|
||||
});
|
||||
|
||||
it('test for private accessors', async () => {
|
||||
// terminal privates
|
||||
const accessors = [
|
||||
'_core',
|
||||
'_core._renderService',
|
||||
'_core._inputHandler',
|
||||
'_core._inputHandler._parser',
|
||||
'_core._inputHandler._curAttrData',
|
||||
'_core._inputHandler._dirtyRowTracker',
|
||||
'_core._themeService.colors',
|
||||
'_core._coreBrowserService'
|
||||
];
|
||||
for (const prop of accessors) {
|
||||
assert.equal(
|
||||
await page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'),
|
||||
true, `problem at ${prop}`
|
||||
);
|
||||
}
|
||||
// bufferline privates
|
||||
assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
|
||||
assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
|
||||
// inputhandler privates
|
||||
assert.equal(await page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), 'AttributeData');
|
||||
assert.equal(await page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
|
||||
});
|
||||
|
||||
describe('ctor options', () => {
|
||||
it('empty settings should load defaults', async () => {
|
||||
const DEFAULT_OPTIONS: IImageAddonOptions = {
|
||||
enableSizeReports: true,
|
||||
pixelLimit: 16777216,
|
||||
sixelSupport: true,
|
||||
sixelScrolling: true,
|
||||
sixelPaletteLimit: 512, // set to 512 to get example image working
|
||||
sixelSizeLimit: 25000000,
|
||||
storageLimit: 128,
|
||||
showPlaceholder: true,
|
||||
iipSupport: true,
|
||||
iipSizeLimit: 20000000
|
||||
};
|
||||
assert.deepEqual(await page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);
|
||||
});
|
||||
it('custom settings should overload defaults', async () => {
|
||||
const customSettings: IImageAddonOptions = {
|
||||
enableSizeReports: false,
|
||||
pixelLimit: 5,
|
||||
sixelSupport: false,
|
||||
sixelScrolling: false,
|
||||
sixelPaletteLimit: 1024,
|
||||
sixelSizeLimit: 1000,
|
||||
storageLimit: 10,
|
||||
showPlaceholder: false,
|
||||
iipSupport: false,
|
||||
iipSizeLimit: 1000
|
||||
};
|
||||
await page.evaluate(opts => {
|
||||
(window as any).imageAddonCustom = new ImageAddon(opts.opts);
|
||||
(window as any).term.loadAddon((window as any).imageAddonCustom);
|
||||
}, { opts: customSettings });
|
||||
assert.deepEqual(await page.evaluate(`window.imageAddonCustom._opts`), customSettings);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scrolling & cursor modes', () => {
|
||||
it('testdata default (scrolling with VT240 cursor pos)', async () => {
|
||||
const dim = await getDimensions();
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
|
||||
// moved to right by 10 cells
|
||||
await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
|
||||
});
|
||||
it('write testdata noScrolling', async () => {
|
||||
await writeToTerminal('\x1b[?80h' + SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [0, 0]);
|
||||
// second draw does not change anything
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [0, 0]);
|
||||
});
|
||||
it('testdata cursor always at VT240 pos', async () => {
|
||||
const dim = await getDimensions();
|
||||
// offset 0
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
|
||||
// moved to right by 10 cells
|
||||
await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
|
||||
// moved by 30 cells (+10 prev)
|
||||
await writeToTerminal('#'.repeat(30) + SIXEL_SEQ_0);
|
||||
assert.deepEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('image lifecycle & eviction', () => {
|
||||
it('delete image once scrolled off', async () => {
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
pollFor(page, 'window.imageAddon._storage._images.size', 1);
|
||||
// scroll to scrollback + rows - 1
|
||||
await page.evaluate(
|
||||
scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)),
|
||||
(await getScrollbackPlusRows() - 1)
|
||||
);
|
||||
// wait here, as we have to make sure, that eviction did not yet occur
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
pollFor(page, 'window.imageAddon._storage._images.size', 1);
|
||||
// scroll one further should delete the image
|
||||
await page.evaluate(() => new Promise(res => (window as any).term.write('\n', res)));
|
||||
pollFor(page, 'window.imageAddon._storage._images.size', 0);
|
||||
});
|
||||
it('get storageUsage', async () => {
|
||||
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05);
|
||||
});
|
||||
it('get/set storageLimit', async () => {
|
||||
assert.equal(await page.evaluate('imageAddon.storageLimit'), 128);
|
||||
assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1);
|
||||
assert.equal(await page.evaluate('imageAddon.storageLimit'), 1);
|
||||
});
|
||||
it('remove images by storage limit pressure', async () => {
|
||||
assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1);
|
||||
// never go beyond storage limit
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const usage = await page.evaluate('imageAddon.storageUsage');
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
assert.equal(await page.evaluate('imageAddon.storageUsage'), usage);
|
||||
assert.equal(usage as number < 1, true);
|
||||
});
|
||||
it('set storageLimit removes images synchronously', async () => {
|
||||
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
const usage: number = await page.evaluate('imageAddon.storageUsage');
|
||||
const newUsage: number = await page.evaluate('imageAddon.storageLimit = 0.5; imageAddon.storageUsage');
|
||||
assert.equal(newUsage < usage, true);
|
||||
assert.equal(newUsage < 0.5, true);
|
||||
});
|
||||
it('clear alternate images on buffer change', async () => {
|
||||
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
|
||||
await writeToTerminal('\x1b[?1049h' + SIXEL_SEQ_0);
|
||||
assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05);
|
||||
await writeToTerminal('\x1b[?1049l');
|
||||
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
|
||||
});
|
||||
it('evict tiles by in-place overwrites (only full overwrite tested)', async () => {
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
|
||||
let usage = await page.evaluate('imageAddon.storageUsage');
|
||||
while (usage === 0) {
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
usage = await page.evaluate('imageAddon.storageUsage');
|
||||
}
|
||||
await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
|
||||
await new Promise(r => setTimeout(r, 200)); // wait some time and re-check
|
||||
assert.equal(await page.evaluate('imageAddon.storageUsage'), usage);
|
||||
});
|
||||
it('manual eviction on alternate buffer must not miss images', async () => {
|
||||
await writeToTerminal('\x1b[?1049h');
|
||||
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const usage: number = await page.evaluate('imageAddon.storageUsage');
|
||||
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const newUsage: number = await page.evaluate('imageAddon.storageUsage');
|
||||
assert.equal(newUsage, usage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IIP support - testimages', () => {
|
||||
it('palette.png', async () => {
|
||||
await writeToTerminal(TESTDATA_IIP[0][0]);
|
||||
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[0][1]);
|
||||
});
|
||||
it('spinfox.png', async () => {
|
||||
await writeToTerminal(TESTDATA_IIP[1][0]);
|
||||
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
|
||||
});
|
||||
it('w3c gif', async () => {
|
||||
await writeToTerminal(TESTDATA_IIP[2][0]);
|
||||
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[2][1]);
|
||||
});
|
||||
it('w3c jpeg', async () => {
|
||||
await writeToTerminal(TESTDATA_IIP[3][0]);
|
||||
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[3][1]);
|
||||
});
|
||||
it('w3c png', async () => {
|
||||
await writeToTerminal(TESTDATA_IIP[4][0]);
|
||||
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[4][1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* terminal access helpers.
|
||||
*/
|
||||
async function getDimensions(): Promise<IDimensions> {
|
||||
const dimensions: any = await page.evaluate(`term._core._renderService.dimensions`);
|
||||
return {
|
||||
cellWidth: Math.round(dimensions.css.cell.width),
|
||||
cellHeight: Math.round(dimensions.css.cell.height),
|
||||
width: Math.round(dimensions.css.canvas.width),
|
||||
height: Math.round(dimensions.css.canvas.height)
|
||||
};
|
||||
}
|
||||
|
||||
async function getCursor(): Promise<[number, number]> {
|
||||
return page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');
|
||||
}
|
||||
|
||||
async function getImageStorageLength(): Promise<number> {
|
||||
return page.evaluate('window.imageAddon._storage._images.size');
|
||||
}
|
||||
|
||||
async function getScrollbackPlusRows(): Promise<number> {
|
||||
return page.evaluate('window.term.options.scrollback + window.term.rows');
|
||||
}
|
||||
|
||||
async function writeToTerminal(d: string): Promise<any> {
|
||||
return page.evaluate(data => new Promise(res => (window as any).term.write(data, res)), d);
|
||||
}
|
||||
|
||||
async function getOrigSize(id: number): Promise<[number, number]> {
|
||||
return page.evaluate<any>(`[
|
||||
window.imageAddon._storage._images.get(${id}).orig.width,
|
||||
window.imageAddon._storage._images.get(${id}).orig.height
|
||||
]`);
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { readFileSync } from 'fs';
|
||||
import { FINALIZER, introducer, sixelEncode } from 'sixel';
|
||||
import { ITestContext, createTestContext, openTerminal, pollFor } from '../../../out-test/playwright/TestUtils';
|
||||
import { IImageAddonOptions } from '../src/Types';
|
||||
import { deepStrictEqual, ok, strictEqual } from 'assert';
|
||||
|
||||
// eslint-disable-next-line
|
||||
declare const ImageAddon: {
|
||||
new(options?: Partial<IImageAddonOptions>): any;
|
||||
};
|
||||
|
||||
interface ITestData {
|
||||
width: number;
|
||||
height: number;
|
||||
bytes: Uint8Array;
|
||||
palette: number[];
|
||||
sixel: string;
|
||||
}
|
||||
|
||||
interface IDimensions {
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// image: 640 x 80, 512 color
|
||||
const TESTDATA: ITestData = (() => {
|
||||
const data8 = readFileSync('./addons/addon-image/fixture/palette.blob');
|
||||
const data32 = new Uint32Array(data8.buffer);
|
||||
const palette = new Set<number>();
|
||||
for (let i = 0; i < data32.length; ++i) palette.add(data32[i]);
|
||||
const sixel = sixelEncode(data8, 640, 80, [...palette]);
|
||||
return {
|
||||
width: 640,
|
||||
height: 80,
|
||||
bytes: data8,
|
||||
palette: [...palette],
|
||||
sixel
|
||||
};
|
||||
})();
|
||||
const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
|
||||
|
||||
// NOTE: the data is loaded as string for easier transport through playwright
|
||||
const TESTDATA_IIP: [string, [number, number]][] = [
|
||||
[readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]],
|
||||
[readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]]
|
||||
];
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('ImageAddon', () => {
|
||||
|
||||
test.beforeEach(async ({}, testInfo) => {
|
||||
// DEBT: This test never worked on webkit
|
||||
if (ctx.browser.browserType().name() === 'webkit') {
|
||||
testInfo.skip();
|
||||
return;
|
||||
}
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.imageAddon?.dispose();
|
||||
window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 });
|
||||
window.term.loadAddon(window.imageAddon);
|
||||
`);
|
||||
});
|
||||
|
||||
test('test for private accessors', async () => {
|
||||
// terminal privates
|
||||
const accessors = [
|
||||
'_core',
|
||||
'_core._renderService',
|
||||
'_core._inputHandler',
|
||||
'_core._inputHandler._parser',
|
||||
'_core._inputHandler._curAttrData',
|
||||
'_core._inputHandler._dirtyRowTracker',
|
||||
'_core._themeService.colors',
|
||||
'_core._coreBrowserService'
|
||||
];
|
||||
for (const prop of accessors) {
|
||||
strictEqual(
|
||||
await ctx.page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'),
|
||||
true, `problem at ${prop}`
|
||||
);
|
||||
}
|
||||
// bufferline privates
|
||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
|
||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
|
||||
// inputhandler privates
|
||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), 'AttributeData');
|
||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
|
||||
});
|
||||
|
||||
test.describe('ctor options', () => {
|
||||
test('empty settings should load defaults', async () => {
|
||||
const DEFAULT_OPTIONS: IImageAddonOptions = {
|
||||
enableSizeReports: true,
|
||||
pixelLimit: 16777216,
|
||||
sixelSupport: true,
|
||||
sixelScrolling: true,
|
||||
sixelPaletteLimit: 512, // set to 512 to get example image working
|
||||
sixelSizeLimit: 25000000,
|
||||
storageLimit: 128,
|
||||
showPlaceholder: true,
|
||||
iipSupport: true,
|
||||
iipSizeLimit: 20000000
|
||||
};
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);
|
||||
});
|
||||
test('custom settings should overload defaults', async () => {
|
||||
const customSettings: IImageAddonOptions = {
|
||||
enableSizeReports: false,
|
||||
pixelLimit: 5,
|
||||
sixelSupport: false,
|
||||
sixelScrolling: false,
|
||||
sixelPaletteLimit: 1024,
|
||||
sixelSizeLimit: 1000,
|
||||
storageLimit: 10,
|
||||
showPlaceholder: false,
|
||||
iipSupport: false,
|
||||
iipSizeLimit: 1000
|
||||
};
|
||||
await ctx.page.evaluate(opts => {
|
||||
(window as any).imageAddonCustom = new ImageAddon(opts.opts);
|
||||
(window as any).term.loadAddon((window as any).imageAddonCustom);
|
||||
}, { opts: customSettings });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.imageAddonCustom._opts`), customSettings);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('scrolling & cursor modes', () => {
|
||||
test('testdata default (scrolling with VT240 cursor pos)', async () => {
|
||||
const dim = await getDimensions();
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
|
||||
// moved to right by 10 cells
|
||||
await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
|
||||
});
|
||||
test('write testdata noScrolling', async () => {
|
||||
await ctx.proxy.write('\x1b[?80h' + SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [0, 0]);
|
||||
// second draw does not change anything
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [0, 0]);
|
||||
});
|
||||
test('testdata cursor always at VT240 pos', async () => {
|
||||
const dim = await getDimensions();
|
||||
// offset 0
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
|
||||
// moved to right by 10 cells
|
||||
await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
|
||||
// moved by 30 cells (+10 prev)
|
||||
await ctx.proxy.write('#'.repeat(30) + SIXEL_SEQ_0);
|
||||
deepStrictEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('image lifecycle & eviction', () => {
|
||||
test('delete image once scrolled off', async () => {
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);
|
||||
// scroll to scrollback + rows - 1
|
||||
await ctx.page.evaluate(
|
||||
scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)),
|
||||
(await getScrollbackPlusRows() - 1)
|
||||
);
|
||||
// wait here, as we have to make sure, that eviction did not yet occur
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);
|
||||
// scroll one further should delete the image
|
||||
await ctx.page.evaluate(() => new Promise(res => (window as any).term.write('\n', res)));
|
||||
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);
|
||||
});
|
||||
test('get storageUsage', async () => {
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);
|
||||
});
|
||||
test('get/set storageLimit', async () => {
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 128);
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 1);
|
||||
});
|
||||
test('remove images by storage limit pressure', async () => {
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);
|
||||
// never go beyond storage limit
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);
|
||||
strictEqual(usage as number < 1, true);
|
||||
});
|
||||
test('set storageLimit removes images synchronously', async () => {
|
||||
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageLimit = 0.5; window.imageAddon.storageUsage');
|
||||
strictEqual(newUsage < usage, true);
|
||||
strictEqual(newUsage < 0.5, true);
|
||||
});
|
||||
test('clear alternate images on buffer change', async () => {
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
|
||||
await ctx.proxy.write('\x1b[?1049h' + SIXEL_SEQ_0);
|
||||
ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);
|
||||
await ctx.proxy.write('\x1b[?1049l');
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
|
||||
});
|
||||
test('evict tiles by in-place overwrites (only full overwrite tested)', async () => {
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
|
||||
let usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
while (usage === 0) {
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
}
|
||||
await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
|
||||
await new Promise(r => setTimeout(r, 200)); // wait some time and re-check
|
||||
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);
|
||||
});
|
||||
test('manual eviction on alternate buffer must not miss images', async () => {
|
||||
await ctx.proxy.write('\x1b[?1049h');
|
||||
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
|
||||
strictEqual(newUsage, usage);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('IIP support - testimages', () => {
|
||||
test('palette.png', async () => {
|
||||
await ctx.proxy.write(TESTDATA_IIP[0][0]);
|
||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[0][1]);
|
||||
});
|
||||
test('spinfox.png', async () => {
|
||||
await ctx.proxy.write(TESTDATA_IIP[1][0]);
|
||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
|
||||
});
|
||||
test('w3c gif', async () => {
|
||||
await ctx.proxy.write(TESTDATA_IIP[2][0]);
|
||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[2][1]);
|
||||
});
|
||||
test('w3c jpeg', async () => {
|
||||
await ctx.proxy.write(TESTDATA_IIP[3][0]);
|
||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[3][1]);
|
||||
});
|
||||
test('w3c png', async () => {
|
||||
await ctx.proxy.write(TESTDATA_IIP[4][0]);
|
||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[4][1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* terminal access helpers.
|
||||
*/
|
||||
async function getDimensions(): Promise<IDimensions> {
|
||||
const dimensions: any = await ctx.page.evaluate(`term._core._renderService.dimensions`);
|
||||
return {
|
||||
cellWidth: Math.round(dimensions.css.cell.width),
|
||||
cellHeight: Math.round(dimensions.css.cell.height),
|
||||
width: Math.round(dimensions.css.canvas.width),
|
||||
height: Math.round(dimensions.css.canvas.height)
|
||||
};
|
||||
}
|
||||
|
||||
async function getCursor(): Promise<[number, number]> {
|
||||
return ctx.page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');
|
||||
}
|
||||
|
||||
async function getImageStorageLength(): Promise<number> {
|
||||
return ctx.page.evaluate('window.imageAddon._storage._images.size');
|
||||
}
|
||||
|
||||
async function getScrollbackPlusRows(): Promise<number> {
|
||||
return ctx.page.evaluate('window.term.options.scrollback + window.term.rows');
|
||||
}
|
||||
|
||||
async function getOrigSize(id: number): Promise<[number, number]> {
|
||||
return ctx.page.evaluate<any>(`[
|
||||
window.imageAddon._storage._images.get(${id}).orig.width,
|
||||
window.imageAddon._storage._images.get(${id}).orig.height
|
||||
]`);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,21 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"browser/*": [ "../../../src/browser/*" ],
|
||||
"common/*": [ "../../../src/common/*" ]
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
@@ -23,7 +29,11 @@
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../../src/browser" },
|
||||
{ "path": "../../../src/common" }
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,510 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { openTerminal, writeSync, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Search Tests', function (): void {
|
||||
before(async function (): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset()
|
||||
window.search?.dispose();
|
||||
window.search = new SearchAddon();
|
||||
window.term.loadAddon(window.search);
|
||||
`);
|
||||
});
|
||||
|
||||
it('Simple Search', async () => {
|
||||
await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
|
||||
});
|
||||
|
||||
it('Scrolling Search', async () => {
|
||||
let dataString = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
if (i === 52) {
|
||||
dataString += '$^1_3{}test$#';
|
||||
}
|
||||
dataString += makeData(50);
|
||||
}
|
||||
await writeSync(page, dataString);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
|
||||
});
|
||||
it('Incremental Find Previous', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
|
||||
await writeSync(page, 'package.json pack package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`);
|
||||
let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');
|
||||
});
|
||||
it('Incremental Find Next', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
|
||||
await writeSync(page, 'package.jsonc');
|
||||
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`);
|
||||
let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');
|
||||
});
|
||||
it('Simple Regex', async () => {
|
||||
await writeSync(page, 'abc123defABCD');
|
||||
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
|
||||
});
|
||||
|
||||
it('Search for single result twice should not unselect it', async () => {
|
||||
await writeSync(page, 'abc def');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
});
|
||||
|
||||
it('Search for result bounding with wide unicode chars', async () => {
|
||||
await writeSync(page, '中文xx𝄞𝄞');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('中')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '中');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('xx')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'xx');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('𝄞')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '𝄞');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('𝄞')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), {
|
||||
start: {
|
||||
x: 7,
|
||||
y: 0
|
||||
},
|
||||
end: {
|
||||
x: 8,
|
||||
y: 0
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDidChangeResults', async () => {
|
||||
describe('findNext', () => {
|
||||
it('should not fire unless the decorations option is set', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a')`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
it('should fire with correct event values', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
});
|
||||
it('should fire with correct event values (incremental)', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'd abc aabc d');
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
it('should fire with more than 1k matches', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150);
|
||||
await writeSync(page, data);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 },
|
||||
{ resultCount: 1000, resultIndex: 1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 },
|
||||
{ resultCount: 1000, resultIndex: 1 },
|
||||
{ resultCount: 1000, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
it('should fire when writing to terminal', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c\\n\\r'.repeat(2));
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
await writeSync(page, 'abc bc c\\n\\r');
|
||||
await timeout(300);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('findPrevious', () => {
|
||||
it('should not fire unless the decorations option is set', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a')`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
it('should fire with correct event values', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
await page.evaluate(`window.term.clearSelection()`);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
await timeout(2000);
|
||||
assert.strictEqual(await page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
});
|
||||
it('should fire with correct event values (incremental)', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'd abc aabc d');
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
it('should fire with more than 1k matches', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150);
|
||||
await writeSync(page, data);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
it('should fire when writing to terminal', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c\\n\\r'.repeat(2));
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
await writeSync(page, 'abc bc c\\n\\r');
|
||||
await timeout(300);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regression tests', () => {
|
||||
describe('#2444 wrapped line content not being found', () => {
|
||||
let fixture: string;
|
||||
before(async () => {
|
||||
const rawFixture = await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)));
|
||||
if (process.platform === 'win32') {
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r');
|
||||
} else {
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n\\r');
|
||||
}
|
||||
fixture = fixture
|
||||
.replace(/'/g, `\\'`);
|
||||
});
|
||||
it('should find all occurrences using findNext', async () => {
|
||||
await writeSync(page, fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
// Wrap around to first result
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
});
|
||||
|
||||
it('should y all occurrences using findPrevious', async () => {
|
||||
await writeSync(page, fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
// Wrap around to first result
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#3834 lines with null characters before search terms', () => {
|
||||
// This case can be triggered by the prompt when using starship under conpty
|
||||
it('should find all matches on a line containing null characters', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
|
||||
await writeSync(page, '\\x1b[CHi Hi');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeData(length: number): string {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { ITestContext, createTestContext, openTerminal, timeout } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Search Tests', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.search?.dispose();
|
||||
window.search = new SearchAddon();
|
||||
window.term.loadAddon(window.search);
|
||||
`);
|
||||
});
|
||||
|
||||
test('Simple Search', async () => {
|
||||
await ctx.proxy.write('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('test')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'test');
|
||||
});
|
||||
|
||||
test('Scrolling Search', async () => {
|
||||
let dataString = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
if (i === 52) {
|
||||
dataString += '$^1_3{}test$#';
|
||||
}
|
||||
dataString += makeData(50);
|
||||
}
|
||||
await ctx.proxy.write(dataString);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), '$^1_3{}test$#');
|
||||
});
|
||||
test('Incremental Find Previous', async () => {
|
||||
await ctx.proxy.writeln(`package.jsonc\n`);
|
||||
await ctx.proxy.write('package.json pack package.lock');
|
||||
await ctx.page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
|
||||
let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!;
|
||||
let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');
|
||||
await ctx.page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
|
||||
selectionPosition = (await ctx.proxy.getSelectionPosition())!;
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');
|
||||
await ctx.page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
selectionPosition = (await ctx.proxy.getSelectionPosition())!;
|
||||
line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');
|
||||
});
|
||||
test('Incremental Find Next', async () => {
|
||||
await ctx.proxy.writeln(`package.lock pack package.json package.ups\n`);
|
||||
await ctx.proxy.write('package.jsonc');
|
||||
await ctx.page.evaluate(`window.search.findNext('pack', {incremental: true})`);
|
||||
let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!;
|
||||
let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');
|
||||
await ctx.page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
|
||||
selectionPosition = (await ctx.proxy.getSelectionPosition())!;
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');
|
||||
await ctx.page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
selectionPosition = (await ctx.proxy.getSelectionPosition())!;
|
||||
line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();
|
||||
deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');
|
||||
});
|
||||
test('Simple Regex', async () => {
|
||||
await ctx.proxy.write('abc123defABCD');
|
||||
await ctx.page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'abc');
|
||||
await ctx.page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'ABCD');
|
||||
});
|
||||
|
||||
test('Search for single result twice should not unselect it', async () => {
|
||||
await ctx.proxy.write('abc def');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'abc');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'abc');
|
||||
});
|
||||
|
||||
test('Search for result bounding with wide unicode chars', async () => {
|
||||
await ctx.proxy.write('中文xx𝄞𝄞');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('中')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), '中');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('xx')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), 'xx');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('𝄞')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelection(), '𝄞');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('𝄞')`), true);
|
||||
deepStrictEqual(await ctx.proxy.getSelectionPosition(), {
|
||||
start: {
|
||||
x: 7,
|
||||
y: 0
|
||||
},
|
||||
end: {
|
||||
x: 8,
|
||||
y: 0
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('onDidChangeResults', async () => {
|
||||
test.describe('findNext', () => {
|
||||
test('should not fire unless the decorations option is set', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc');
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('a')`), true);
|
||||
strictEqual(await ctx.page.evaluate('window.calls.length'), 0);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
test('should fire with correct event values', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc bc c');
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
});
|
||||
test('should fire with correct event values (incremental)', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('d abc aabc d');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
test('should fire with more than 1k matches', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150);
|
||||
await ctx.proxy.write(data);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 },
|
||||
{ resultCount: 1000, resultIndex: 1 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: 0 },
|
||||
{ resultCount: 1000, resultIndex: 1 },
|
||||
{ resultCount: 1000, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
test('should fire when writing to terminal', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc bc c\\n\\r'.repeat(2));
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
await ctx.proxy.write('abc bc c\\n\\r');
|
||||
await timeout(300);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
test.describe('findPrevious', () => {
|
||||
test('should not fire unless the decorations option is set', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc');
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a')`), true);
|
||||
strictEqual(await ctx.page.evaluate('window.calls.length'), 0);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
test('should fire with correct event values', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc bc c');
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
await ctx.page.evaluate(`window.term.clearSelection()`);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
await timeout(2000);
|
||||
strictEqual(await ctx.page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
});
|
||||
test('should fire with correct event values (incremental)', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('d abc aabc d');
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
test('should fire with more than 1k matches', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
const data = ('a bc'.repeat(10) + '\\n\\r').repeat(150);
|
||||
await ctx.proxy.write(data);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 },
|
||||
{ resultCount: 1000, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
test('should fire when writing to terminal', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await ctx.proxy.write('abc bc c\\n\\r'.repeat(2));
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
await ctx.proxy.write('abc bc c\\n\\r');
|
||||
await timeout(300);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
test.describe('#2444 wrapped line content not being found', () => {
|
||||
let fixture: string;
|
||||
test.beforeAll(async () => {
|
||||
fixture = (await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)))).toString();
|
||||
});
|
||||
test('should find all occurrences using findNext', async () => {
|
||||
await ctx.proxy.write(fixture);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
let selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
// Wrap around to first result
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
});
|
||||
|
||||
test('should y all occurrences using findPrevious', async () => {
|
||||
await ctx.proxy.write(fixture);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
let selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });
|
||||
// Wrap around to first result
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
selectionPosition = await ctx.proxy.getSelectionPosition();
|
||||
deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });
|
||||
});
|
||||
});
|
||||
});
|
||||
test.describe('#3834 lines with null characters before search terms', () => {
|
||||
// This case can be triggered by the prompt when using starship under conpty
|
||||
test('should find all matches on a line containing null characters', async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
|
||||
await ctx.proxy.write('\\x1b[CHi Hi');
|
||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeData(length: number): string {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2021",
|
||||
],
|
||||
@@ -9,15 +9,31 @@
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+144
-149
@@ -3,49 +3,47 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual, notDeepStrictEqual, strictEqual } from 'assert';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { ITestContext, createTestContext, openTerminal, timeout, writeSync } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(page, `' +` + JSON.stringify(str) + `+ '`);
|
||||
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(ctx.page, `' +` + JSON.stringify(str) + `+ '`);
|
||||
|
||||
const testNormalScreenEqual = async (page: any, str: string): Promise<void> => {
|
||||
await writeRawSync(page, str);
|
||||
const originalBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
await writeRawSync(ctx.page, str);
|
||||
const originalBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
const result = await page.evaluate(`serializeAddon.serialize();`) as string;
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, result);
|
||||
const newBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string;
|
||||
await ctx.page.evaluate(`term.reset();`);
|
||||
await writeRawSync(ctx.page, result);
|
||||
const newBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
// chai decides -0 and 0 are different number...
|
||||
// and firefox have a bug that output -0 for unknown reason
|
||||
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
||||
deepStrictEqual(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
||||
};
|
||||
|
||||
async function testSerializeEquals(writeContent: string, expectedSerialized: string): Promise<void> {
|
||||
await writeRawSync(page, writeContent);
|
||||
const result = await page.evaluate(`serializeAddon.serialize();`) as string;
|
||||
assert.strictEqual(result, expectedSerialized);
|
||||
await writeRawSync(ctx.page, writeContent);
|
||||
const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string;
|
||||
strictEqual(result, expectedSerialized);
|
||||
}
|
||||
|
||||
describe('SerializeAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page, { rows: 10, cols: 10 });
|
||||
await page.evaluate(`
|
||||
window.serializeAddon = new SerializeAddon();
|
||||
window.term.loadAddon(window.serializeAddon);
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx, { rows: 10, cols: 10 });
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('SerializeAddon', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.serialize?.dispose();
|
||||
window.serialize = new SerializeAddon();
|
||||
window.term.loadAddon(window.serialize);
|
||||
window.inspectBuffer = (buffer) => {
|
||||
const lines = [];
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
@@ -62,56 +60,53 @@ describe('SerializeAddon', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('produce different output when we call test util with different text', async function(): Promise<any> {
|
||||
await writeRawSync(page, '12345');
|
||||
const buffer1 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, '67890');
|
||||
const buffer2 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
assert.throw(() => {
|
||||
assert.equal(JSON.stringify(buffer1), JSON.stringify(buffer2));
|
||||
});
|
||||
test.beforeEach(async () => {
|
||||
await ctx.proxy.reset();
|
||||
});
|
||||
|
||||
it('produce different output when we call test util with different line wrap', async function(): Promise<any> {
|
||||
await writeRawSync(page, '1234567890\r\n12345');
|
||||
const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
test('produce different output when we call test util with different text', async function(): Promise<any> {
|
||||
await writeRawSync(ctx.page, '12345');
|
||||
const buffer1 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, '123456789012345');
|
||||
const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
await ctx.page.evaluate(`term.reset();`);
|
||||
await writeRawSync(ctx.page, '67890');
|
||||
const buffer2 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
assert.throw(() => {
|
||||
assert.equal(JSON.stringify(buffer3), JSON.stringify(buffer4));
|
||||
});
|
||||
notDeepStrictEqual(JSON.stringify(buffer1), JSON.stringify(buffer2));
|
||||
});
|
||||
|
||||
it('empty content', async function(): Promise<any> {
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
|
||||
test('produce different output when we call test util with different line wrap', async function(): Promise<any> {
|
||||
await writeRawSync(ctx.page, '1234567890\r\n12345');
|
||||
const buffer3 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await ctx.page.evaluate(`term.reset();`);
|
||||
await writeRawSync(ctx.page, '123456789012345');
|
||||
const buffer4 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
notDeepStrictEqual(JSON.stringify(buffer3), JSON.stringify(buffer4));
|
||||
});
|
||||
|
||||
it('unwrap wrapped line', async function(): Promise<any> {
|
||||
test('empty content', async function(): Promise<any> {
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), '');
|
||||
});
|
||||
|
||||
test('unwrap wrapped line', async function(): Promise<any> {
|
||||
const lines = ['123456789123456789'];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('does not unwrap non-wrapped line', async function(): Promise<any> {
|
||||
test('does not unwrap non-wrapped line', async function(): Promise<any> {
|
||||
const lines = [
|
||||
'123456789',
|
||||
'123456789'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
|
||||
it('preserve last empty lines', async function(): Promise<any> {
|
||||
test('preserve last empty lines', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const lines = [
|
||||
'',
|
||||
@@ -126,50 +121,50 @@ describe('SerializeAddon', () => {
|
||||
'',
|
||||
''
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('digits content', async function(): Promise<any> {
|
||||
test('digits content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
const digitsLine = digitsString(cols);
|
||||
const lines = newArray<string>(digitsLine, rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize with half of scrollback', async function(): Promise<any> {
|
||||
test('serialize with half of scrollback', async function(): Promise<any> {
|
||||
const rows = 20;
|
||||
const scrollback = rows - 10;
|
||||
const halfScrollback = scrollback / 2;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize 0 rows of scrollback', async function(): Promise<any> {
|
||||
test('serialize 0 rows of scrollback', async function(): Promise<any> {
|
||||
const rows = 20;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize exclude modes', async () => {
|
||||
await writeSync(page, 'before\\x1b[?1hafter');
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'beforeafter\x1b[?1h');
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeModes: true });`), 'beforeafter');
|
||||
test('serialize exclude modes', async () => {
|
||||
await ctx.proxy.write('before\x1b[?1hafter');
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'beforeafter\x1b[?1h');
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeModes: true });`), 'beforeafter');
|
||||
});
|
||||
|
||||
it('serialize exclude alt buffer', async () => {
|
||||
await writeSync(page, 'normal\\x1b[?1049h\\x1b[Halt');
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'normal\x1b[?1049h\x1b[Halt');
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeAltBuffer: true });`), 'normal');
|
||||
test('serialize exclude alt buffer', async () => {
|
||||
await ctx.proxy.write('normal\x1b[?1049h\x1b[Halt');
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'normal\x1b[?1049h\x1b[Halt');
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeAltBuffer: true });`), 'normal');
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color16', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const color16 = [
|
||||
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
|
||||
@@ -182,11 +177,11 @@ describe('SerializeAddon', () => {
|
||||
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
|
||||
test('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -204,22 +199,22 @@ describe('SerializeAddon', () => {
|
||||
sgr(NO_INVISIBLE) + line,
|
||||
sgr(NO_STRIKETHROUGH) + line
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color256', async function(): Promise<any> {
|
||||
const rows = 32;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>(
|
||||
(index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with overline', async () => {
|
||||
test('serialize all rows of content with overline', async () => {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -227,11 +222,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(UNDERLINED) + line, // Overlined, Underlined
|
||||
sgr(NORMAL) + line // Normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -246,11 +241,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(BG_RESET) + line, // Underlined, Inverse
|
||||
sgr(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style together', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color16 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -268,11 +263,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic
|
||||
sgr(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -287,11 +282,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(BG_RESET) + line, // Underlined, Inverse
|
||||
sgr(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style together', async function(): Promise<any> {
|
||||
test('serialize all rows of content with color256 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -309,11 +304,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
|
||||
sgr(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
|
||||
test('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -328,11 +323,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(BG_RESET) + line, // Underlined, Inverse
|
||||
sgr(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
|
||||
test('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
@@ -350,11 +345,11 @@ describe('SerializeAddon', () => {
|
||||
sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
|
||||
sgr(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize tabs correctly', async () => {
|
||||
test('serialize tabs correctly', async () => {
|
||||
const lines = [
|
||||
'a\tb',
|
||||
'aa\tc',
|
||||
@@ -365,11 +360,11 @@ describe('SerializeAddon', () => {
|
||||
'aa\x1b[6Cc',
|
||||
'aaa\x1b[5Cd'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK correctly', async () => {
|
||||
test('serialize CJK correctly', async () => {
|
||||
const lines = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
@@ -381,22 +376,22 @@ describe('SerializeAddon', () => {
|
||||
// see also #3097
|
||||
'1中文中文中'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK Mixed with tab correctly', async () => {
|
||||
test('serialize CJK Mixed with tab correctly', async () => {
|
||||
const lines = [
|
||||
'中文\t12' // CJK mixed with tab
|
||||
];
|
||||
const expected = [
|
||||
'中文\x1b[4C12'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize with alt screen correctly', async () => {
|
||||
test('serialize with alt screen correctly', async () => {
|
||||
const SMCUP = '\u001b[?1049h';
|
||||
const CUP = '\u001b[H';
|
||||
|
||||
@@ -407,12 +402,12 @@ describe('SerializeAddon', () => {
|
||||
`1${SMCUP}${CUP}2`
|
||||
];
|
||||
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate');
|
||||
assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'alternate');
|
||||
strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
});
|
||||
|
||||
it('serialize without alt screen correctly', async () => {
|
||||
test('serialize without alt screen correctly', async () => {
|
||||
const SMCUP = '\u001b[?1049h';
|
||||
const RMCUP = '\u001b[?1049l';
|
||||
|
||||
@@ -423,12 +418,12 @@ describe('SerializeAddon', () => {
|
||||
`1`
|
||||
];
|
||||
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
|
||||
assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'normal');
|
||||
strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
});
|
||||
|
||||
it('serialize with background', async () => {
|
||||
test('serialize with background', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
|
||||
const lines = [
|
||||
@@ -436,10 +431,10 @@ describe('SerializeAddon', () => {
|
||||
`2${CLEAR_RIGHT(9)}`
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join('\r\n'));
|
||||
await testNormalScreenEqual(ctx.page, lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('cause the BCE on scroll', async () => {
|
||||
test('cause the BCE on scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
|
||||
const padLines = newArray<string>(
|
||||
@@ -452,10 +447,10 @@ describe('SerializeAddon', () => {
|
||||
`\u001b[44m${CLEAR_RIGHT(5)}1111111111111111`
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join('\r\n'));
|
||||
await testNormalScreenEqual(ctx.page, lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('handle invalid wrap before scroll', async () => {
|
||||
test('handle invalid wrap before scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
const MOVE_UP = (l: number): string => `\u001b[${l}A`;
|
||||
const MOVE_DOWN = (l: number): string => `\u001b[${l}B`;
|
||||
@@ -475,10 +470,10 @@ describe('SerializeAddon', () => {
|
||||
'1'
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, segments.join(''));
|
||||
await testNormalScreenEqual(ctx.page, segments.join(''));
|
||||
});
|
||||
|
||||
it('handle invalid wrap after scroll', async () => {
|
||||
test('handle invalid wrap after scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
const MOVE_UP = (l: number): string => `\u001b[${l}A`;
|
||||
const MOVE_DOWN = (l: number): string => `\u001b[${l}B`;
|
||||
@@ -505,27 +500,27 @@ describe('SerializeAddon', () => {
|
||||
'1'
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join(''));
|
||||
await testNormalScreenEqual(ctx.page, lines.join(''));
|
||||
});
|
||||
|
||||
describe('handle modes', () => {
|
||||
it('applicationCursorKeysMode', async () => {
|
||||
test.describe('handle modes', () => {
|
||||
test('applicationCursorKeysMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?1h', 'test\u001b[?1h');
|
||||
await testSerializeEquals('\u001b[?1l', 'test');
|
||||
});
|
||||
it('applicationKeypadMode', async () => {
|
||||
test('applicationKeypadMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?66h', 'test\u001b[?66h');
|
||||
await testSerializeEquals('\u001b[?66l', 'test');
|
||||
});
|
||||
it('bracketedPasteMode', async () => {
|
||||
test('bracketedPasteMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?2004h', 'test\u001b[?2004h');
|
||||
await testSerializeEquals('\u001b[?2004l', 'test');
|
||||
});
|
||||
it('insertMode', async () => {
|
||||
test('insertMode', async () => {
|
||||
await testSerializeEquals('test\u001b[4h', 'test\u001b[4h');
|
||||
await testSerializeEquals('\u001b[4l', 'test');
|
||||
});
|
||||
it('mouseTrackingMode', async () => {
|
||||
test('mouseTrackingMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?9h', 'test\u001b[?9h');
|
||||
await testSerializeEquals('\u001b[?9l', 'test');
|
||||
await testSerializeEquals('\u001b[?1000h', 'test\u001b[?1000h');
|
||||
@@ -535,20 +530,20 @@ describe('SerializeAddon', () => {
|
||||
await testSerializeEquals('\u001b[?1003h', 'test\u001b[?1003h');
|
||||
await testSerializeEquals('\u001b[?1003l', 'test');
|
||||
});
|
||||
it('originMode', async () => {
|
||||
test('originMode', async () => {
|
||||
// origin mode moves cursor to (0,0)
|
||||
await testSerializeEquals('test\u001b[?6h', 'test\u001b[4D\u001b[?6h');
|
||||
await testSerializeEquals('\u001b[?6l', 'test\u001b[4D');
|
||||
});
|
||||
it('reverseWraparoundMode', async () => {
|
||||
test('reverseWraparoundMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?45h', 'test\u001b[?45h');
|
||||
await testSerializeEquals('\u001b[?45l', 'test');
|
||||
});
|
||||
it('sendFocusMode', async () => {
|
||||
test('sendFocusMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?1004h', 'test\u001b[?1004h');
|
||||
await testSerializeEquals('\u001b[?1004l', 'test');
|
||||
});
|
||||
it('wraparoundMode', async () => {
|
||||
test('wraparoundMode', async () => {
|
||||
await testSerializeEquals('test\u001b[?7l', 'test\u001b[?7l');
|
||||
await testSerializeEquals('\u001b[?7h', 'test');
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
@@ -13,13 +13,15 @@
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
@@ -29,6 +31,9 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('UnicodeGraphemesAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
async function evalWidth(str: string): Promise<number> {
|
||||
return page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`);
|
||||
}
|
||||
const ourVersion = '15-graphemes';
|
||||
it('wcwidth V15 emoji test', async () => {
|
||||
await page.evaluate(`
|
||||
window.unicode = new UnicodeGraphemesAddon();
|
||||
window.term.loadAddon(window.unicode);
|
||||
`);
|
||||
// should have loaded '15-graphemes'
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']);
|
||||
// switch should not throw
|
||||
await page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`);
|
||||
assert.equal(await page.evaluate(`window.term.unicode.activeVersion`), ourVersion);
|
||||
assert.equal(await evalWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'), 20,
|
||||
'10 emoji - width 10 in V6; 20 in V11 or later');
|
||||
assert.equal(await evalWidth('\u{1F476}\u{1F3FF}\u{1F476}'), 4,
|
||||
'baby with emoji modifier fitzpatrick type-6; baby');
|
||||
assert.equal(await evalWidth('\u{1F469}\u200d\u{1f469}\u200d\u{1f466}'), 2,
|
||||
'woman+zwj+woman+zwj+boy');
|
||||
assert.equal(await evalWidth('=\u{1F3CB}\u{FE0F}=\u{F3CB}\u{1F3FE}\u200D\u2640='), 7,
|
||||
'person lifting weights (plain, emoji); woman lighting weights, medium dark');
|
||||
assert.equal(await evalWidth('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393}'), 6,
|
||||
'woman; woman student; man student dark');
|
||||
assert.equal(await evalWidth('\u{1f1f3}\u{1f1f4}/'), 3,
|
||||
'regional indicator symbol letters N and O, cluster');
|
||||
assert.equal(await evalWidth('\u{1f1f3}/\u{1f1f4}'), 3,
|
||||
'regional indicator symbol letters N and O, separated');
|
||||
assert.equal(await evalWidth('\u0061\u0301'), 1,
|
||||
'letter a with acute accent');
|
||||
assert.equal(await evalWidth('{\u1100\u1161\u11a8\u1100\u1161}'), 6,
|
||||
'Korean Jamo');
|
||||
assert.equal(await evalWidth('\uAC00=\uD685='), 6,
|
||||
'Hangul syllables (pre-composed)');
|
||||
assert.equal(await evalWidth('(\u26b0\ufe0e)'), 3,
|
||||
'coffin with text presentation');
|
||||
assert.equal(await evalWidth('(\u26b0\ufe0f)'), 4,
|
||||
'coffin with emoji presentation');
|
||||
assert.equal(await evalWidth('<E\u0301\ufe0fg\ufe0fa\ufe0fl\ufe0fi\ufe0f\ufe0ft\ufe0fe\u0301\ufe0f>'), 16,
|
||||
'Égalité (using separate acute) emoij_presentation');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('UnicodeGraphemesAddon', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.unicode?.dispose();
|
||||
window.unicode = new UnicodeGraphemesAddon();
|
||||
window.term.loadAddon(window.unicode);
|
||||
`);
|
||||
});
|
||||
|
||||
async function evalWidth(str: string): Promise<number> {
|
||||
return ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`);
|
||||
}
|
||||
const ourVersion = '15-graphemes';
|
||||
test('wcwidth V15 emoji test', async () => {
|
||||
// should have loaded '15-graphemes'
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']);
|
||||
// switch should not throw
|
||||
await ctx.page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`);
|
||||
strictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), ourVersion);
|
||||
strictEqual(await evalWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'), 20,
|
||||
'10 emoji - width 10 in V6; 20 in V11 or later');
|
||||
strictEqual(await evalWidth('\u{1F476}\u{1F3FF}\u{1F476}'), 4,
|
||||
'baby with emoji modifier fitzpatrick type-6; baby');
|
||||
strictEqual(await evalWidth('\u{1F469}\u200d\u{1f469}\u200d\u{1f466}'), 2,
|
||||
'woman+zwj+woman+zwj+boy');
|
||||
strictEqual(await evalWidth('=\u{1F3CB}\u{FE0F}=\u{F3CB}\u{1F3FE}\u200D\u2640='), 7,
|
||||
'person lifting weights (plain, emoji); woman lighting weights, medium dark');
|
||||
strictEqual(await evalWidth('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393}'), 6,
|
||||
'woman; woman student; man student dark');
|
||||
strictEqual(await evalWidth('\u{1f1f3}\u{1f1f4}/'), 3,
|
||||
'regional indicator symbol letters N and O, cluster');
|
||||
strictEqual(await evalWidth('\u{1f1f3}/\u{1f1f4}'), 3,
|
||||
'regional indicator symbol letters N and O, separated');
|
||||
strictEqual(await evalWidth('\u0061\u0301'), 1,
|
||||
'letter a with acute accent');
|
||||
strictEqual(await evalWidth('{\u1100\u1161\u11a8\u1100\u1161}'), 6,
|
||||
'Korean Jamo');
|
||||
strictEqual(await evalWidth('\uAC00=\uD685='), 6,
|
||||
'Hangul syllables (pre-composed)');
|
||||
strictEqual(await evalWidth('(\u26b0\ufe0e)'), 3,
|
||||
'coffin with text presentation');
|
||||
strictEqual(await evalWidth('(\u26b0\ufe0f)'), 4,
|
||||
'coffin with emoji presentation');
|
||||
strictEqual(await evalWidth('<E\u0301\ufe0fg\ufe0fa\ufe0fl\ufe0fi\ufe0f\ufe0ft\ufe0fe\u0301\ufe0f>'), 16,
|
||||
'Égalité (using separate acute) emoij_presentation');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,26 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
@@ -30,6 +31,9 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Unicode11Addon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
it('wcwidth V11 emoji test', async () => {
|
||||
await page.evaluate(`
|
||||
window.unicode11 = new Unicode11Addon();
|
||||
window.term.loadAddon(window.unicode11);
|
||||
`);
|
||||
// should have loaded '11'
|
||||
assert.deepEqual((await page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true);
|
||||
// switch should not throw
|
||||
await page.evaluate(`window.term.unicode.activeVersion = '11';`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11');
|
||||
// v6: 10, V11: 20
|
||||
assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual } from 'assert';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Unicode11Addon', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window.unicode11?.dispose();
|
||||
window.unicode11 = new Unicode11Addon();
|
||||
window.term.loadAddon(window.unicode11);
|
||||
`);
|
||||
});
|
||||
|
||||
test('wcwidth V11 emoji test', async () => {
|
||||
// should have loaded '11'
|
||||
deepStrictEqual((await ctx.page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true);
|
||||
// switch should not throw
|
||||
await ctx.page.evaluate(`window.term.unicode.activeVersion = '11';`);
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), '11');
|
||||
// v6: 10, V11: 20
|
||||
deepStrictEqual(await ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,26 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
@@ -30,6 +31,9 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+62
-68
@@ -2,18 +2,11 @@
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
interface ILinkStateData {
|
||||
uri?: string;
|
||||
@@ -30,21 +23,20 @@ interface ILinkStateData {
|
||||
}
|
||||
|
||||
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page, { cols: 40 });
|
||||
});
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx, { cols: 40 });
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`
|
||||
test.describe('WebLinksAddon', () => {
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset()
|
||||
window._linkaddon?.dispose();
|
||||
window.term.reset();
|
||||
window._linkaddon = new window.WebLinksAddon();
|
||||
window._linkaddon = new WebLinksAddon();
|
||||
window.term.loadAddon(window._linkaddon);
|
||||
`);
|
||||
});
|
||||
@@ -71,36 +63,36 @@ describe('WebLinksAddon', () => {
|
||||
'.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw'
|
||||
];
|
||||
for (const tld of countryTlds) {
|
||||
it(tld, async () => await testHostName(`foo${tld}`));
|
||||
test(tld, async () => await testHostName(`foo${tld}`));
|
||||
}
|
||||
it(`.com`, async () => await testHostName(`foo.com`));
|
||||
test(`.com`, async () => await testHostName(`foo.com`));
|
||||
for (const tld of countryTlds) {
|
||||
it(`.com${tld}`, async () => await testHostName(`foo.com${tld}`));
|
||||
test(`.com${tld}`, async () => await testHostName(`foo.com${tld}`));
|
||||
}
|
||||
|
||||
describe('correct buffer offsets & uri', () => {
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`
|
||||
test.describe('correct buffer offsets & uri', () => {
|
||||
test.beforeEach(async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window._linkStateData = {uri:''};
|
||||
window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; };
|
||||
`);
|
||||
});
|
||||
it('all half width', async () => {
|
||||
await writeSync(page, 'aaa http://example.com aaa http://example.com aaa');
|
||||
test('all half width', async () => {
|
||||
await ctx.proxy.write('aaa http://example.com aaa http://example.com aaa');
|
||||
await resetAndHover(5, 0);
|
||||
await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } });
|
||||
await resetAndHover(1, 1);
|
||||
await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } });
|
||||
});
|
||||
it('url after full width', async () => {
|
||||
await writeSync(page, '¥¥¥ http://example.com ¥¥¥ http://example.com aaa');
|
||||
test('url after full width', async () => {
|
||||
await ctx.proxy.write('¥¥¥ http://example.com ¥¥¥ http://example.com aaa');
|
||||
await resetAndHover(8, 0);
|
||||
await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } });
|
||||
await resetAndHover(1, 1);
|
||||
await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } });
|
||||
});
|
||||
it('full width within url and before', async () => {
|
||||
await writeSync(page, '¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥');
|
||||
test('full width within url and before', async () => {
|
||||
await ctx.proxy.write('¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥');
|
||||
await resetAndHover(8, 0);
|
||||
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });
|
||||
await resetAndHover(1, 1);
|
||||
@@ -108,15 +100,15 @@ describe('WebLinksAddon', () => {
|
||||
await resetAndHover(17, 1);
|
||||
await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } });
|
||||
});
|
||||
it('name + password url after full width and combining', async () => {
|
||||
await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path');
|
||||
test('name + password url after full width and combining', async () => {
|
||||
await ctx.proxy.write('¥¥¥cafe\u0301 http://test:password@example.com/some_path');
|
||||
await resetAndHover(12, 0);
|
||||
await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });
|
||||
await resetAndHover(5, 1);
|
||||
await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });
|
||||
});
|
||||
it('url encoded params work properly', async () => {
|
||||
await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path?param=1%202%3');
|
||||
test('url encoded params work properly', async () => {
|
||||
await ctx.proxy.write('¥¥¥cafe\u0301 http://test:password@example.com/some_path?param=1%202%3');
|
||||
await resetAndHover(12, 0);
|
||||
await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });
|
||||
await resetAndHover(5, 1);
|
||||
@@ -125,13 +117,14 @@ describe('WebLinksAddon', () => {
|
||||
});
|
||||
|
||||
// issue #4964
|
||||
it('uppercase in protocol and host, default ports', async () => {
|
||||
const data = ` HTTP://EXAMPLE.COM \\r\\n` +
|
||||
` HTTPS://Example.com \\r\\n` +
|
||||
` HTTP://Example.com:80 \\r\\n` +
|
||||
` HTTP://Example.com:80/staysUpper \\r\\n` +
|
||||
` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`;
|
||||
await writeSync(page, data);
|
||||
test('uppercase in protocol and host, default ports', async () => {
|
||||
await ctx.proxy.write(
|
||||
` HTTP://EXAMPLE.COM \r\n` +
|
||||
` HTTPS://Example.com \r\n` +
|
||||
` HTTP://Example.com:80 \r\n` +
|
||||
` HTTP://Example.com:80/staysUpper \r\n` +
|
||||
` HTTP://Ab:xY@abc.com:80/staysUpper \r\n`
|
||||
);
|
||||
await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`);
|
||||
await pollForLinkAtCell(3, 1, `HTTPS://Example.com`);
|
||||
await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`);
|
||||
@@ -141,14 +134,15 @@ describe('WebLinksAddon', () => {
|
||||
});
|
||||
|
||||
async function testHostName(hostname: string): Promise<void> {
|
||||
const data = ` http://${hostname} \\r\\n` +
|
||||
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
|
||||
` http://${hostname}/colon:test \\r\\n` +
|
||||
` http://${hostname}/colon:test: \\r\\n` +
|
||||
`"http://${hostname}/"\\r\\n` +
|
||||
`\\'http://${hostname}/\\'\\r\\n` +
|
||||
`http://${hostname}/subpath/+/id`;
|
||||
await writeSync(page, data);
|
||||
await ctx.proxy.write(
|
||||
` http://${hostname} \r\n` +
|
||||
` http://${hostname}/a~b#c~d?e~f \r\n` +
|
||||
` http://${hostname}/colon:test \r\n` +
|
||||
` http://${hostname}/colon:test: \r\n` +
|
||||
`"http://${hostname}/"\r\n` +
|
||||
`\'http://${hostname}/\'\r\n` +
|
||||
`http://${hostname}/subpath/+/id`
|
||||
);
|
||||
await pollForLinkAtCell(3, 0, `http://${hostname}`);
|
||||
await pollForLinkAtCell(3, 1, `http://${hostname}/a~b#c~d?e~f`);
|
||||
await pollForLinkAtCell(3, 2, `http://${hostname}/colon:test`);
|
||||
@@ -159,28 +153,28 @@ async function testHostName(hostname: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
|
||||
await page.mouse.move(...(await cellPos(col, row)));
|
||||
await pollFor(page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true);
|
||||
const text = await page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`);
|
||||
assert.deepEqual(text, value);
|
||||
await ctx.page.mouse.move(...(await cellPos(col, row)));
|
||||
await pollFor(ctx.page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true);
|
||||
const text = await ctx.page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`);
|
||||
deepStrictEqual(text, value);
|
||||
}
|
||||
|
||||
async function resetAndHover(col: number, row: number): Promise<void> {
|
||||
await page.mouse.move(0, 0);
|
||||
await page.evaluate(`window._linkStateData = {uri:''};`);
|
||||
await ctx.page.mouse.move(0, 0);
|
||||
await ctx.page.evaluate(`window._linkStateData = {uri:''};`);
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
await page.mouse.move(...(await cellPos(col, row)));
|
||||
await pollFor(page, `!!window._linkStateData.uri.length`, true);
|
||||
await ctx.page.mouse.move(...(await cellPos(col, row)));
|
||||
await pollFor(ctx.page, `!!window._linkStateData.uri.length`, true);
|
||||
}
|
||||
|
||||
async function evalLinkStateData(uri: string, range: any): Promise<void> {
|
||||
const data: ILinkStateData = await page.evaluate(`window._linkStateData`);
|
||||
assert.equal(data.uri, uri);
|
||||
assert.deepEqual(data.range, range);
|
||||
const data: ILinkStateData = await ctx.page.evaluate(`window._linkStateData`);
|
||||
strictEqual(data.uri, uri);
|
||||
deepStrictEqual(data.range, range);
|
||||
}
|
||||
|
||||
async function cellPos(col: number, row: number): Promise<[number, number]> {
|
||||
const coords: any = await page.evaluate(`
|
||||
const coords: any = await ctx.page.evaluate(`
|
||||
(function() {
|
||||
const rect = window.term.element.getBoundingClientRect();
|
||||
const dim = term._core._renderService.dimensions;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -3,20 +3,38 @@
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"es2015"
|
||||
"es2021",
|
||||
],
|
||||
// "downlevelIteration": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node"
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/playwright/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2021",
|
||||
],
|
||||
|
||||
+7
-3
@@ -6,7 +6,7 @@
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const COVERAGE_LINES_THRESHOLD = 60;
|
||||
const COVERAGE_LINES_THRESHOLD = 40;
|
||||
|
||||
// Add `out` to the NODE_PATH so absolute paths can be resolved.
|
||||
const env = { ...process.env };
|
||||
@@ -31,7 +31,6 @@ if (process.argv.length > 2) {
|
||||
}
|
||||
|
||||
const checkCoverage = flagArgs.indexOf('--coverage') >= 0;
|
||||
|
||||
if (checkCoverage) {
|
||||
flagArgs.splice(flagArgs.indexOf('--coverage'), 1);
|
||||
const executable = npmBinScript('nyc');
|
||||
@@ -44,6 +43,7 @@ if (checkCoverage) {
|
||||
{
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
@@ -56,6 +56,7 @@ const run = cp.spawnSync(
|
||||
{
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
@@ -64,4 +65,7 @@ function npmBinScript(script) {
|
||||
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
|
||||
}
|
||||
|
||||
process.exit(run.status);
|
||||
if (run.error) {
|
||||
console.error(run.error);
|
||||
}
|
||||
process.exit(run.status ?? -1);
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
// Add `out` to the NODE_PATH so absolute paths can be resolved.
|
||||
const env = { ...process.env };
|
||||
env.NODE_PATH = path.resolve(__dirname, '../out');
|
||||
|
||||
let testFiles = [
|
||||
'./addons/**/out-test/*api.js',
|
||||
'./out-test/**/*api.js',
|
||||
];
|
||||
|
||||
|
||||
let flagArgs = [];
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
const args = process.argv.slice(2);
|
||||
flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat(val.slice(), []));
|
||||
console.info(flagArgs);
|
||||
// ability to inject particular test files via
|
||||
// yarn test [testFileA testFileB ...]
|
||||
files = args.filter(e => !e.startsWith('--'));
|
||||
if (files.length) {
|
||||
testFiles = files;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : '';
|
||||
env.PORT = 3001;
|
||||
|
||||
const server = cp.spawn('node', ['demo/start'], {
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
stdio: 'pipe'
|
||||
})
|
||||
|
||||
server.stdout.on('data', (data) => {
|
||||
// await for the server to fully start
|
||||
if (data.includes("successfully")) {
|
||||
const run = cp.spawnSync(
|
||||
npmBinScript('mocha'),
|
||||
[...testFiles, ...flagArgs], {
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
|
||||
function npmBinScript(script) {
|
||||
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ?
|
||||
`${script}.cmd` : script));
|
||||
}
|
||||
|
||||
server.kill();
|
||||
|
||||
process.exit(run.status);
|
||||
}
|
||||
});
|
||||
|
||||
server.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
+17
-5
@@ -18,9 +18,18 @@ while (argv.some(e => e.startsWith('--suite='))) {
|
||||
}
|
||||
|
||||
let configs = [
|
||||
{ name: 'core', path: 'out-test/playwright/playwright.config.js' },
|
||||
{ name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' },
|
||||
{ name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' }
|
||||
{ name: 'core', path: 'out-test/playwright/playwright.config.js' },
|
||||
{ name: 'addon-attach', path: 'addons/addon-attach/out-test/playwright.config.js' },
|
||||
{ name: 'addon-canvas', path: 'addons/addon-canvas/out-test/playwright.config.js' },
|
||||
{ name: 'addon-clipboard', path: 'addons/addon-clipboard/out-test/playwright.config.js' },
|
||||
{ name: 'addon-fit', path: 'addons/addon-fit/out-test/playwright.config.js' },
|
||||
{ name: 'addon-image', path: 'addons/addon-image/out-test/playwright.config.js' },
|
||||
{ name: 'addon-search', path: 'addons/addon-search/out-test/playwright.config.js' },
|
||||
{ name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' },
|
||||
{ name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' },
|
||||
{ name: 'addon-unicode11', path: 'addons/addon-unicode11/out-test/playwright.config.js' },
|
||||
{ name: 'addon-web-links', path: 'addons/addon-web-links/out-test/playwright.config.js' },
|
||||
{ name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' }
|
||||
];
|
||||
|
||||
if (suiteFilter) {
|
||||
@@ -40,12 +49,15 @@ async function run() {
|
||||
console.log(`\n\x1b[32m${command}\x1b[0m`, args);
|
||||
const run = cp.spawnSync(command, args, {
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
if (run.status) {
|
||||
process.exit(run.status);
|
||||
|
||||
if (run.error) {
|
||||
console.error(run.error);
|
||||
}
|
||||
process.exit(run.status ?? -1);
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
@@ -157,10 +157,16 @@
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-tree {
|
||||
font-family: monospace;
|
||||
user-select: text;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-tree > div {
|
||||
transform-origin: left;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
|
||||
+7
-10
@@ -34,15 +34,11 @@
|
||||
"lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/",
|
||||
"test": "npm run test-unit",
|
||||
"posttest": "npm run lint",
|
||||
"test-api": "npm run test-api-chromium",
|
||||
"test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
|
||||
"test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
|
||||
"test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
|
||||
"test-playwright": "node ./bin/test_playwright.js --workers=75%",
|
||||
"test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=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-integration": "node ./bin/test_playwright.js --workers=75%",
|
||||
"test-integration-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=ChromeStable\"",
|
||||
"test-integration-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=FirefoxStable\"",
|
||||
"test-integration-webkit": "node ./bin/test_playwright.js --workers=75% \"--project=WebKit\"",
|
||||
"test-integration-debug": "node ./bin/test_playwright.js --workers=1 --headed --timeout=30000",
|
||||
"test-unit": "node ./bin/test.js",
|
||||
"test-unit-coverage": "node ./bin/test.js --coverage",
|
||||
"test-unit-dev": "cross-env NODE_PATH='./out' mocha",
|
||||
@@ -61,6 +57,7 @@
|
||||
"devDependencies": {
|
||||
"@lunapaint/png-codec": "^0.2.0",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@stylistic/eslint-plugin": "^2.3.0",
|
||||
"@types/chai": "^4.2.22",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
@@ -91,7 +88,7 @@
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript": "5.5",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^5.61.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
|
||||
@@ -58,10 +58,11 @@ export class AccessibilityManager extends Disposable {
|
||||
@IRenderService private readonly _renderService: IRenderService
|
||||
) {
|
||||
super();
|
||||
this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div');
|
||||
const doc = this._coreBrowserService.mainDocument;
|
||||
this._accessibilityContainer = doc.createElement('div');
|
||||
this._accessibilityContainer.classList.add('xterm-accessibility');
|
||||
|
||||
this._rowContainer = this._coreBrowserService.mainDocument.createElement('div');
|
||||
this._rowContainer = doc.createElement('div');
|
||||
this._rowContainer.setAttribute('role', 'list');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowElements = [];
|
||||
@@ -75,10 +76,9 @@ export class AccessibilityManager extends Disposable {
|
||||
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
|
||||
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
||||
|
||||
this._refreshRowsDimensions();
|
||||
this._accessibilityContainer.appendChild(this._rowContainer);
|
||||
|
||||
this._liveRegion = this._coreBrowserService.mainDocument.createElement('div');
|
||||
this._liveRegion = doc.createElement('div');
|
||||
this._liveRegion.classList.add('live-region');
|
||||
this._liveRegion.setAttribute('aria-live', 'assertive');
|
||||
this._accessibilityContainer.appendChild(this._liveRegion);
|
||||
@@ -93,12 +93,12 @@ export class AccessibilityManager extends Disposable {
|
||||
this._rowContainer.classList.add('debug');
|
||||
|
||||
// Use a `<div class="xterm">` container so that the css will still apply.
|
||||
this._debugRootContainer = document.createElement('div');
|
||||
this._debugRootContainer = doc.createElement('div');
|
||||
this._debugRootContainer.classList.add('xterm');
|
||||
|
||||
this._debugRootContainer.appendChild(document.createTextNode('------start a11y------'));
|
||||
this._debugRootContainer.appendChild(doc.createTextNode('------start a11y------'));
|
||||
this._debugRootContainer.appendChild(this._accessibilityContainer);
|
||||
this._debugRootContainer.appendChild(document.createTextNode('------end a11y------'));
|
||||
this._debugRootContainer.appendChild(doc.createTextNode('------end a11y------'));
|
||||
|
||||
this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer);
|
||||
} else {
|
||||
@@ -115,9 +115,10 @@ export class AccessibilityManager extends Disposable {
|
||||
this.register(this._terminal.onKey(e => this._handleKey(e.key)));
|
||||
this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
|
||||
this.register(addDisposableDomListener(document, 'selectionchange', () => this._handleSelectionChange()));
|
||||
this.register(addDisposableDomListener(doc, 'selectionchange', () => this._handleSelectionChange()));
|
||||
this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));
|
||||
|
||||
this._refreshRowsDimensions();
|
||||
this._refreshRows();
|
||||
this.register(toDisposable(() => {
|
||||
if (DEBUG) {
|
||||
@@ -192,6 +193,7 @@ export class AccessibilityManager extends Disposable {
|
||||
}
|
||||
element.setAttribute('aria-posinset', posInSet);
|
||||
element.setAttribute('aria-setsize', setSize);
|
||||
this._alignRowWidth(element);
|
||||
}
|
||||
}
|
||||
this._announceCharacters();
|
||||
@@ -270,7 +272,7 @@ export class AccessibilityManager extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = document.getSelection();
|
||||
const selection = this._coreBrowserService.mainDocument.getSelection();
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
@@ -389,19 +391,45 @@ export class AccessibilityManager extends Disposable {
|
||||
this._refreshRowDimensions(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _refreshRowsDimensions(): void {
|
||||
if (!this._renderService.dimensions.css.cell.height) {
|
||||
return;
|
||||
}
|
||||
this._accessibilityContainer.style.width = `${this._renderService.dimensions.css.canvas.width}px`;
|
||||
Object.assign(this._accessibilityContainer.style, {
|
||||
width: `${this._renderService.dimensions.css.canvas.width}px`,
|
||||
fontSize: `${this._terminal.options.fontSize}px`
|
||||
});
|
||||
if (this._rowElements.length !== this._terminal.rows) {
|
||||
this._handleResize(this._terminal.rows);
|
||||
}
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._refreshRowDimensions(this._rowElements[i]);
|
||||
this._alignRowWidth(this._rowElements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshRowDimensions(element: HTMLElement): void {
|
||||
element.style.height = `${this._renderService.dimensions.css.cell.height}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale the width of a row so that each of the character is (mostly) aligned
|
||||
* with the actual rendering. This will allow the screen reader to draw
|
||||
* selection outline at the correct position.
|
||||
*
|
||||
* On top of using the "monospace" font and correct font size, the scaling
|
||||
* here is necessary to handle characters that are not covered by the font
|
||||
* (e.g. CJK).
|
||||
*/
|
||||
private _alignRowWidth(element: HTMLElement): void {
|
||||
element.style.transform = '';
|
||||
const width = element.getBoundingClientRect().width;
|
||||
const lastColumn = this._rowColumns.get(element)?.slice(-1)?.[0];
|
||||
if (!lastColumn) {
|
||||
return;
|
||||
}
|
||||
const targetWidth = lastColumn * this._renderService.dimensions.css.cell.width;
|
||||
element.style.transform = `scaleX(${targetWidth / width})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,12 @@ const enum VectorType {
|
||||
* Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
*/
|
||||
export const powerlineDefinitions: { [index: string]: IVectorShape } = {
|
||||
// Git branch
|
||||
'\u{E0A0}': { d: 'M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655', type: VectorType.FILL },
|
||||
// L N
|
||||
'\u{E0A1}': { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: VectorType.FILL },
|
||||
// Lock
|
||||
'\u{E0A2}': { d: 'M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82', type: VectorType.FILL },
|
||||
// Right triangle solid
|
||||
'\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL, rightPadding: 2 },
|
||||
// Right triangle line
|
||||
|
||||
@@ -20,23 +20,18 @@ export interface IEventEmitter<T, U = void> {
|
||||
}
|
||||
|
||||
export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
|
||||
private _listeners: IListener<T, U>[] = [];
|
||||
private _listeners: Set<IListener<T, U>> = new Set();
|
||||
private _event?: IEvent<T, U>;
|
||||
private _disposed: boolean = false;
|
||||
|
||||
public get event(): IEvent<T, U> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (arg1: T, arg2: U) => any) => {
|
||||
this._listeners.push(listener);
|
||||
this._listeners.add(listener);
|
||||
const disposable = {
|
||||
dispose: () => {
|
||||
if (!this._disposed) {
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
if (this._listeners[i] === listener) {
|
||||
this._listeners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._listeners.delete(listener);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -48,8 +43,8 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
|
||||
|
||||
public fire(arg1: T, arg2: U): void {
|
||||
const queue: IListener<T, U>[] = [];
|
||||
for (let i = 0; i < this._listeners.length; i++) {
|
||||
queue.push(this._listeners[i]);
|
||||
for (const l of this._listeners.values()) {
|
||||
queue.push(l);
|
||||
}
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
queue[i].call(undefined, arg1, arg2);
|
||||
@@ -63,7 +58,7 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
|
||||
|
||||
public clearListeners(): void {
|
||||
if (this._listeners) {
|
||||
this._listeners.length = 0;
|
||||
this._listeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,8 +611,8 @@ export class Buffer implements IBuffer {
|
||||
this._isClearing = true;
|
||||
for (let i = 0; i < this.markers.length; i++) {
|
||||
this.markers[i].dispose();
|
||||
this.markers.splice(i--, 1);
|
||||
}
|
||||
this.markers.length = 0;
|
||||
this._isClearing = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This project contains helpers for the legacy playwright tests. Currently addons use this as a dependency, when all addons have moved over to use `@playright/test` this can be removed.
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as playwright from '@playwright/test';
|
||||
import deepEqual = require('deep-equal');
|
||||
import { ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm';
|
||||
import { deepStrictEqual } from 'assert';
|
||||
|
||||
export async function pollFor<T>(page: playwright.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>, maxDuration?: number): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log('pollFor result: ', result);
|
||||
}
|
||||
|
||||
if (!deepEqual(result, val)) {
|
||||
if (maxDuration === undefined) {
|
||||
maxDuration = 2000;
|
||||
}
|
||||
if (maxDuration <= 0) {
|
||||
deepStrictEqual(result, val, 'pollFor max duration exceeded');
|
||||
}
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, maxDuration! - 10)), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeSync(page: playwright.Page, data: string): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.ready = false;
|
||||
window.term.write('${data}', () => window.ready = true);
|
||||
`);
|
||||
await pollFor(page, 'window.ready', true);
|
||||
}
|
||||
|
||||
export async function timeout(ms: number): Promise<void> {
|
||||
return new Promise<void>(r => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
export async function openTerminal(page: playwright.Page, options: ITerminalOptions & ITerminalInitOnlyOptions = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
|
||||
// HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have
|
||||
// more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453
|
||||
if (testOptions.loadUnicodeGraphemesAddon) {
|
||||
await page.evaluate(`
|
||||
window.unicode = new UnicodeGraphemesAddon();
|
||||
window.term.loadAddon(window.unicode);
|
||||
window.term.unicode.activeVersion = '15-graphemes';
|
||||
`);
|
||||
}
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
}
|
||||
|
||||
export function getBrowserType(): playwright.BrowserType<playwright.WebKitBrowser> | playwright.BrowserType<playwright.ChromiumBrowser> | playwright.BrowserType<playwright.FirefoxBrowser> {
|
||||
// Default to chromium
|
||||
let browserType: playwright.BrowserType<playwright.WebKitBrowser> | playwright.BrowserType<playwright.ChromiumBrowser> | playwright.BrowserType<playwright.FirefoxBrowser> = playwright['chromium'];
|
||||
|
||||
const index = process.argv.indexOf('--browser');
|
||||
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
|
||||
const string = process.argv[index + 1];
|
||||
if (string === 'firefox' || string === 'webkit') {
|
||||
browserType = playwright[string];
|
||||
}
|
||||
}
|
||||
|
||||
return browserType;
|
||||
}
|
||||
|
||||
export function launchBrowser(opts?: playwright.LaunchOptions): Promise<playwright.Browser> {
|
||||
const browserType = getBrowserType();
|
||||
const options: playwright.LaunchOptions = {
|
||||
...opts,
|
||||
headless: process.argv.includes('--headless')
|
||||
};
|
||||
|
||||
const index = process.argv.indexOf('--executablePath');
|
||||
if (index > 0 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
|
||||
options.executablePath = process.argv[index + 1];
|
||||
}
|
||||
|
||||
return browserType.launch(options);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2021"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../../out-test/api",
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../../node_modules/@types/node"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"pretty": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"browser/*": [
|
||||
"../../src/browser/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"../../src/common/*"
|
||||
]
|
||||
},
|
||||
"target": "es2016",
|
||||
"target": "ESNext",
|
||||
"module": "commonjs",
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"references": [
|
||||
{ "path": "./src/browser" },
|
||||
{ "path": "./src/headless" },
|
||||
{ "path": "./test/api" },
|
||||
{ "path": "./test/benchmark" },
|
||||
{ "path": "./test/playwright" },
|
||||
{ "path": "./addons/addon-attach" },
|
||||
|
||||
@@ -429,6 +429,54 @@
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
"@stylistic/eslint-plugin-js@2.3.0", "@stylistic/eslint-plugin-js@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.3.0.tgz#a3faee05863c50c0bb6f879db72b7ee895bfa74e"
|
||||
integrity sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==
|
||||
dependencies:
|
||||
"@types/eslint" "^8.56.10"
|
||||
acorn "^8.11.3"
|
||||
eslint-visitor-keys "^4.0.0"
|
||||
espree "^10.0.1"
|
||||
|
||||
"@stylistic/eslint-plugin-jsx@2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-2.3.0.tgz#f1a01b6dcdf3d6159727eef6ae298107facdb098"
|
||||
integrity sha512-tsQ0IEKB195H6X9A4iUSgLLLKBc8gUBWkBIU8tp1/3g2l8stu+PtMQVV/VmK1+3bem5FJCyvfcZIQ/WF1fsizA==
|
||||
dependencies:
|
||||
"@stylistic/eslint-plugin-js" "^2.3.0"
|
||||
"@types/eslint" "^8.56.10"
|
||||
estraverse "^5.3.0"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
"@stylistic/eslint-plugin-plus@2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-2.3.0.tgz#0ccadea6cb52c7ecb9af61b6f27077ba885ba145"
|
||||
integrity sha512-xboPWGUU5yaPlR+WR57GwXEuY4PSlPqA0C3IdNA/+1o2MuBi95XgDJcZiJ9N+aXsqBXAPIpFFb+WQ7QEHo4f7g==
|
||||
dependencies:
|
||||
"@types/eslint" "^8.56.10"
|
||||
"@typescript-eslint/utils" "^7.12.0"
|
||||
|
||||
"@stylistic/eslint-plugin-ts@2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.3.0.tgz#2c9e047304df2094124a638b273ac02410cc98f1"
|
||||
integrity sha512-wqOR38/uz/0XPnHX68ftp8sNMSAqnYGjovOTN7w00xnjS6Lxr3Sk7q6AaxWWqbMvOj7V2fQiMC5HWAbTruJsCg==
|
||||
dependencies:
|
||||
"@stylistic/eslint-plugin-js" "2.3.0"
|
||||
"@types/eslint" "^8.56.10"
|
||||
"@typescript-eslint/utils" "^7.12.0"
|
||||
|
||||
"@stylistic/eslint-plugin@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.3.0.tgz#e9b411524d94a120dc757c2bc79919088f7385f6"
|
||||
integrity sha512-rtiz6u5gRyyEZp36FcF1/gHJbsbT3qAgXZ1qkad6Nr/xJ9wrSJkiSFFQhpYVTIZ7FJNRJurEcumZDCwN9dEI4g==
|
||||
dependencies:
|
||||
"@stylistic/eslint-plugin-js" "2.3.0"
|
||||
"@stylistic/eslint-plugin-jsx" "2.3.0"
|
||||
"@stylistic/eslint-plugin-plus" "2.3.0"
|
||||
"@stylistic/eslint-plugin-ts" "2.3.0"
|
||||
"@types/eslint" "^8.56.10"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
@@ -492,6 +540,14 @@
|
||||
"@types/estree" "*"
|
||||
"@types/json-schema" "*"
|
||||
|
||||
"@types/eslint@^8.56.10":
|
||||
version "8.56.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
|
||||
integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
"@types/json-schema" "*"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
||||
@@ -712,6 +768,14 @@
|
||||
"@typescript-eslint/types" "6.2.0"
|
||||
"@typescript-eslint/visitor-keys" "6.2.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@7.15.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2"
|
||||
integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.15.0"
|
||||
"@typescript-eslint/visitor-keys" "7.15.0"
|
||||
|
||||
"@typescript-eslint/type-utils@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.2.0.tgz#02b27a3eeb41aa5460d6275d12cce5dd72e1c9fc"
|
||||
@@ -727,6 +791,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.0.tgz#b341a4e6d5f609267306b07afc6f62bcf92b1495"
|
||||
integrity sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA==
|
||||
|
||||
"@typescript-eslint/types@7.15.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc"
|
||||
integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz#4969944b831b481996aa4fbd73c7164ca683b8ef"
|
||||
@@ -740,6 +809,20 @@
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/typescript-estree@7.15.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37"
|
||||
integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.15.0"
|
||||
"@typescript-eslint/visitor-keys" "7.15.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^1.3.0"
|
||||
|
||||
"@typescript-eslint/utils@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.2.0.tgz#606a20e5c13883c2d2bd0538ddc4b96b8d410979"
|
||||
@@ -753,6 +836,16 @@
|
||||
"@typescript-eslint/typescript-estree" "6.2.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
"@typescript-eslint/utils@^7.12.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960"
|
||||
integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "7.15.0"
|
||||
"@typescript-eslint/types" "7.15.0"
|
||||
"@typescript-eslint/typescript-estree" "7.15.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz#71943f42fdaa2ec86dc3222091f41761a49ae71a"
|
||||
@@ -761,6 +854,14 @@
|
||||
"@typescript-eslint/types" "6.2.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@7.15.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66"
|
||||
integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.15.0"
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
@@ -955,6 +1056,11 @@ acorn@^7.1.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
acorn@^8.11.3, acorn@^8.12.0:
|
||||
version "8.12.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
|
||||
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
|
||||
|
||||
acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
|
||||
@@ -1727,6 +1833,11 @@ eslint-visitor-keys@^3.4.3:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
eslint-visitor-keys@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
||||
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
||||
|
||||
eslint@^8.56.0:
|
||||
version "8.56.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15"
|
||||
@@ -1771,6 +1882,15 @@ eslint@^8.56.0:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
espree@^10.0.1:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56"
|
||||
integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==
|
||||
dependencies:
|
||||
acorn "^8.12.0"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^4.0.0"
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
|
||||
@@ -1804,7 +1924,7 @@ estraverse@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||
estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||
@@ -2861,6 +2981,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^9.0.4:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
mocha@^10.1.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8"
|
||||
@@ -3171,6 +3298,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
|
||||
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
|
||||
|
||||
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
@@ -3390,6 +3522,11 @@ semver@^7.3.4, semver@^7.5.3, semver@^7.5.4:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.6.0:
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
|
||||
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
|
||||
|
||||
send@0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
||||
@@ -3696,6 +3833,11 @@ ts-api-utils@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d"
|
||||
integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==
|
||||
|
||||
ts-api-utils@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
|
||||
integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
|
||||
|
||||
ts-loader@^9.3.1:
|
||||
version "9.4.4"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.4.4.tgz#6ceaf4d58dcc6979f84125335904920884b7cee4"
|
||||
@@ -3748,16 +3890,16 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@5.5:
|
||||
version "5.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa"
|
||||
integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==
|
||||
|
||||
typescript@^4.2.3:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
typescript@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
@@ -4027,9 +4169,9 @@ ws@^7.4.6:
|
||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||
|
||||
ws@^8.2.3:
|
||||
version "8.13.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
|
||||
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
||||
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
|
||||
|
||||
xml-name-validator@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
Reference in New Issue
Block a user