xterm.js/test/api/Terminal.api.ts
Daniel Imms 8f97a5a786
Deflake registerLinkProvider tests
The root cause of this flakiness problem was related to the click event
handler. For some reason it would not go off on some links but it would
when clicking elsewhere in the terminal. I tried a bunch of things to
keep the click handler to no avail, instead opting to move to using
mousedown and mouseup events for activating links which comes with the
nice benefit of ensuring that the link on mouseup is the same one on
mousedown.

Fixes #3821
2022-05-19 15:30:12 -07:00

1088 lines
49 KiB
TypeScript

/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { pollFor, timeout, writeSync, openTerminal, launchBrowser } from './TestUtils';
import { Browser, Page } from 'playwright';
import { fail } from 'assert';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('API Integration Tests', function(): void {
before(async () => {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => browser.close());
beforeEach(async () => page.goto(APP));
it('Default options', async () => {
await openTerminal(page);
assert.equal(await page.evaluate(`window.term.cols`), 80);
assert.equal(await page.evaluate(`window.term.rows`), 24);
});
it('Proposed API check', async () => {
await openTerminal(page, { allowProposedApi: false });
await page.evaluate(`
try {
window.term.buffer;
} catch (e) {
window.throwMessage = e.message;
}
`);
await pollFor(page, 'window.throwMessage', 'You must set the allowProposedApi option to true to use proposed API');
});
it('write', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.write('foo');
window.term.write('bar');
window.term.write('文');
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
});
it('write with callback', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.write('foo', () => { window.__x = 'a'; });
window.term.write('bar', () => { window.__x += 'b'; });
window.term.write('文', () => { window.__x += 'c'; });
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'abc');
});
it('write - bytes (UTF8)', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.write(new Uint8Array([102, 111, 111])); // foo
window.term.write(new Uint8Array([98, 97, 114])); // bar
window.term.write(new Uint8Array([230, 150, 135])); // 文
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
});
it('write - bytes (UTF8) with callback', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.write(new Uint8Array([102, 111, 111]), () => { window.__x = 'A'; }); // foo
window.term.write(new Uint8Array([98, 97, 114]), () => { window.__x += 'B'; }); // bar
window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; }); // 文
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'ABC');
});
it('writeln', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.writeln('foo');
window.term.writeln('bar');
window.term.writeln('文');
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
});
it('writeln with callback', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.writeln('foo', () => { window.__x = '1'; });
window.term.writeln('bar', () => { window.__x += '2'; });
window.term.writeln('文', () => { window.__x += '3'; });
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.__x`, '123');
});
it('writeln - bytes (UTF8)', async () => {
await openTerminal(page);
await page.evaluate(`
window.term.writeln(new Uint8Array([102, 111, 111]));
window.term.writeln(new Uint8Array([98, 97, 114]));
window.term.writeln(new Uint8Array([230, 150, 135]));
`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
});
it('paste', async () => {
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push(e));
window.term.paste('foo');
window.term.paste('\\r\\nfoo\\nbar\\r');
window.term.write('\\x1b[?2004h', () => {
window.term.paste('foo');
});
`);
await pollFor(page, `window.calls`, ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']);
});
it('clear', async () => {
await openTerminal(page, { rows: 5 });
await page.evaluate(`
window.term.write('test0');
window.parsed = 0;
for (let i = 1; i < 10; i++) {
window.term.write('\\n\\rtest' + i, () => window.parsed++);
}
`);
await pollFor(page, `window.parsed`, 9);
await page.evaluate(`window.term.clear()`);
await pollFor(page, `window.term.buffer.active.length`, 5);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'test9');
for (let i = 1; i < 5; i++) {
await pollFor(page, `window.term.buffer.active.getLine(${i}).translateToString(true)`, '');
}
});
it('getOption, setOption', async () => {
await openTerminal(page);
assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas');
await page.evaluate(`window.term.setOption('rendererType', 'dom')`);
assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom');
});
describe('options', () => {
it('getter', async () => {
await openTerminal(page);
assert.equal(await page.evaluate(`window.term.options.rendererType`), 'canvas');
assert.equal(await page.evaluate(`window.term.options.cols`), 80);
assert.equal(await page.evaluate(`window.term.options.rows`), 24);
});
it('setter', async () => {
await openTerminal(page);
try {
await page.evaluate('window.term.options.cols = 40');
fail();
} catch {}
try {
await page.evaluate('window.term.options.rows = 20');
fail();
} catch {}
await page.evaluate('window.term.options.scrollback = 1');
assert.equal(await page.evaluate(`window.term.options.scrollback`), 1);
await page.evaluate(`
window.term.options = {
fontSize: 30,
fontFamily: 'Arial'
};
`);
assert.equal(await page.evaluate(`window.term.options.fontSize`), 30);
assert.equal(await page.evaluate(`window.term.options.fontFamily`), 'Arial');
});
it('object.keys return the correct number of options', async () => {
await openTerminal(page);
assert.notEqual(await page.evaluate(`Object.keys(window.term.options).length`), 0);
});
});
describe('renderer', () => {
it('foreground', async () => {
await openTerminal(page, { rendererType: 'dom' });
await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
[
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className
]
`), [
'xterm-fg-0',
'xterm-fg-1',
'xterm-fg-2',
'xterm-fg-3',
'xterm-fg-4',
'xterm-fg-5',
'xterm-fg-6'
]);
});
it('background', async () => {
await openTerminal(page, { rendererType: 'dom' });
await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
[
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className,
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className
]
`), [
'xterm-bg-0',
'xterm-bg-1',
'xterm-bg-2',
'xterm-bg-3',
'xterm-bg-4',
'xterm-bg-5',
'xterm-bg-6'
]);
});
});
it('selection', async () => {
await openTerminal(page, { rows: 5, cols: 5 });
await writeSync(page, `\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), false);
assert.equal(await page.evaluate(`window.term.getSelection()`), '');
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined);
await page.evaluate(`window.term.selectAll()`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), true);
if (process.platform === 'win32') {
assert.equal(await page.evaluate(`window.term.getSelection()`), '\r\n\r\nfoo\r\n\r\nbar\r\n\r\nbaz');
} else {
assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz');
}
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 0, startRow: 0, endColumn: 5, endRow: 6 });
await page.evaluate(`window.term.clearSelection()`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), false);
assert.equal(await page.evaluate(`window.term.getSelection()`), '');
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined);
await page.evaluate(`window.term.select(1, 2, 2)`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), true);
assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo');
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 });
});
it('focus, blur', async () => {
await openTerminal(page);
assert.equal(await page.evaluate(`document.activeElement.className`), '');
await page.evaluate(`window.term.focus()`);
assert.equal(await page.evaluate(`document.activeElement.className`), 'xterm-helper-textarea');
await page.evaluate(`window.term.blur()`);
assert.equal(await page.evaluate(`document.activeElement.className`), '');
});
describe('loadAddon', () => {
it('constructor', async () => {
await openTerminal(page, { cols: 5 });
await page.evaluate(`
window.cols = 0;
window.term.loadAddon({
activate: (t) => window.cols = t.cols,
dispose: () => {}
});
`);
assert.equal(await page.evaluate(`window.cols`), 5);
});
it('dispose (addon)', async () => {
await openTerminal(page);
await page.evaluate(`
window.disposeCalled = false
window.addon = {
activate: () => {},
dispose: () => window.disposeCalled = true
};
window.term.loadAddon(window.addon);
`);
assert.equal(await page.evaluate(`window.disposeCalled`), false);
await page.evaluate(`window.addon.dispose()`);
assert.equal(await page.evaluate(`window.disposeCalled`), true);
});
it('dispose (terminal)', async () => {
await openTerminal(page);
await page.evaluate(`
window.disposeCalled = false
window.term.loadAddon({
activate: () => {},
dispose: () => window.disposeCalled = true
});
`);
assert.equal(await page.evaluate(`window.disposeCalled`), false);
await page.evaluate(`window.term.dispose()`);
assert.equal(await page.evaluate(`window.disposeCalled`), true);
});
});
describe('Events', () => {
it('onCursorMove', async () => {
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onCursorMove(e => window.callCount++);
window.term.write('foo');
`);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.write('bar')`);
await pollFor(page, `window.callCount`, 2);
});
it('onData', async () => {
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push(e));
`);
await page.type('.xterm-helper-textarea', 'foo');
assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']);
});
it('onKey', async () => {
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onKey(e => calls.push(e.key));
`);
await page.type('.xterm-helper-textarea', 'foo');
assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']);
});
it('onLineFeed', async () => {
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onLineFeed(() => callCount++);
window.term.writeln('foo');
`);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.writeln('bar')`);
await pollFor(page, `window.callCount`, 2);
});
it('onScroll', async () => {
await openTerminal(page, { rows: 5 });
await page.evaluate(`
window.calls = [];
window.term.onScroll(e => window.calls.push(e));
for (let i = 0; i < 4; i++) {
window.term.writeln('foo');
}
`);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.writeln('bar')`);
await pollFor(page, `window.calls`, [1]);
await page.evaluate(`window.term.writeln('baz')`);
await pollFor(page, `window.calls`, [1, 2]);
});
it('onSelectionChange', async () => {
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onSelectionChange(() => window.callCount++);
`);
await pollFor(page, `window.callCount`, 0);
await page.evaluate(`window.term.selectAll()`);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.clearSelection()`);
await pollFor(page, `window.callCount`, 2);
});
it('onRender', async function(): Promise<void> {
this.retries(3);
await openTerminal(page);
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
window.term.onRender(e => window.calls.push([e.start, e.end]));
`);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.write('foo')`);
await pollFor(page, `window.calls`, [[0, 0]]);
await page.evaluate(`window.term.write('bar\\n\\nbaz')`);
await pollFor(page, `window.calls`, [[0, 0], [0, 2]]);
});
it('onResize', async () => {
await openTerminal(page);
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
window.term.onResize(e => window.calls.push([e.cols, e.rows]));
`);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.resize(10, 5)`);
await pollFor(page, `window.calls`, [[10, 5]]);
await page.evaluate(`window.term.resize(20, 15)`);
await pollFor(page, `window.calls`, [[10, 5], [20, 15]]);
});
it('onTitleChange', async () => {
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onTitleChange(e => window.calls.push(e));
`);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`);
await pollFor(page, `window.calls`, ['foo']);
});
it('onBell', async () => {
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onBell(() => window.calls.push(true));
`);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.write('\\x07')`);
await pollFor(page, `window.calls`, [true]);
});
});
describe('buffer', () => {
it('cursorX, cursorY', async () => {
await openTerminal(page, { rows: 5, cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0);
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, '\\r');
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, '\\n\\r\\n\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 4);
});
it('viewportY', async () => {
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 1);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 4);
await page.evaluate(`window.term.scrollToTop()`);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
});
it('baseY', async () => {
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 1);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
await page.evaluate(`window.term.scrollToTop()`);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
});
it('length', async () => {
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 6);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 10);
});
describe('getLine', () => {
it('invalid index', async () => {
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(5)`), undefined);
});
it('isWrapped', async () => {
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false);
await writeSync(page, 'f');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), true);
});
it('translateToString', async () => {
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), '');
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'foo ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'foo');
await writeSync(page, 'bar');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).translateToString(true)`), 'r');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1)`), 'ooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1, 3)`), 'oo');
});
it('getCell', async () => {
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(5)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1);
await writeSync(page, 'a文');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), 'a');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getChars()`), '文');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getWidth()`), 2);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getWidth()`), 0);
});
it('clearMarkers', async () => {
await openTerminal(page, { cols: 5 });
await page.evaluate(`
window.disposeStack = [];
`);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.term.addMarker(1)`);
await page.evaluate(`window.term.addMarker(2)`);
await page.evaluate(`window.term.scrollLines(10)`);
await page.evaluate(`window.term.addMarker(3)`);
await page.evaluate(`window.term.addMarker(4)`);
await page.evaluate(`
for (let i = 0; i < window.term.markers.length; ++i) {
const marker = window.term.markers[i];
marker.onDispose(() => window.disposeStack.push(marker));
}`);
await page.evaluate(`window.term.clear()`);
assert.equal(await page.evaluate(`window.disposeStack.length`), 4);
});
});
it('active, normal, alternate', async () => {
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
await writeSync(page, 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);
await writeSync(page, '\\x1b[?47h\\r'); // use alternate screen buffer
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' ');
await writeSync(page, 'alt ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'alt ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0).translateToString()`), 'alt ');
await writeSync(page, '\\x1b[?47l\\r'); // use normal screen buffer
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);
});
});
describe('modes', () => {
it('defaults', async () => {
await openTerminal(page);
assert.deepStrictEqual(await page.evaluate(`window.term.modes`), {
applicationCursorKeysMode: false,
applicationKeypadMode: false,
bracketedPasteMode: false,
insertMode: false,
mouseTrackingMode: 'none',
originMode: false,
reverseWraparoundMode: false,
sendFocusMode: false,
wraparoundMode: true
});
});
it('applicationCursorKeysMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?1h');
assert.strictEqual(await page.evaluate(`window.term.modes.applicationCursorKeysMode`), true);
await writeSync(page, '\\x1b[?1l');
assert.strictEqual(await page.evaluate(`window.term.modes.applicationCursorKeysMode`), false);
});
it('applicationKeypadMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?66h');
assert.strictEqual(await page.evaluate(`window.term.modes.applicationKeypadMode`), true);
await writeSync(page, '\\x1b[?66l');
assert.strictEqual(await page.evaluate(`window.term.modes.applicationKeypadMode`), false);
});
it('bracketedPasteMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?2004h');
assert.strictEqual(await page.evaluate(`window.term.modes.bracketedPasteMode`), true);
await writeSync(page, '\\x1b[?2004l');
assert.strictEqual(await page.evaluate(`window.term.modes.bracketedPasteMode`), false);
});
it('insertMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[4h');
assert.strictEqual(await page.evaluate(`window.term.modes.insertMode`), true);
await writeSync(page, '\\x1b[4l');
assert.strictEqual(await page.evaluate(`window.term.modes.insertMode`), false);
});
it('mouseTrackingMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?9h');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'x10');
await writeSync(page, '\\x1b[?9l');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none');
await writeSync(page, '\\x1b[?1000h');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'vt200');
await writeSync(page, '\\x1b[?1000l');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none');
await writeSync(page, '\\x1b[?1002h');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'drag');
await writeSync(page, '\\x1b[?1002l');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none');
await writeSync(page, '\\x1b[?1003h');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'any');
await writeSync(page, '\\x1b[?1003l');
assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none');
});
it('originMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?6h');
assert.strictEqual(await page.evaluate(`window.term.modes.originMode`), true);
await writeSync(page, '\\x1b[?6l');
assert.strictEqual(await page.evaluate(`window.term.modes.originMode`), false);
});
it('reverseWraparoundMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?45h');
assert.strictEqual(await page.evaluate(`window.term.modes.reverseWraparoundMode`), true);
await writeSync(page, '\\x1b[?45l');
assert.strictEqual(await page.evaluate(`window.term.modes.reverseWraparoundMode`), false);
});
it('sendFocusMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?1004h');
assert.strictEqual(await page.evaluate(`window.term.modes.sendFocusMode`), true);
await writeSync(page, '\\x1b[?1004l');
assert.strictEqual(await page.evaluate(`window.term.modes.sendFocusMode`), false);
});
it('wraparoundMode', async () => {
await openTerminal(page);
await writeSync(page, '\\x1b[?7h');
assert.strictEqual(await page.evaluate(`window.term.modes.wraparoundMode`), true);
await writeSync(page, '\\x1b[?7l');
assert.strictEqual(await page.evaluate(`window.term.modes.wraparoundMode`), false);
});
});
it('dispose', async () => {
await page.evaluate(`
window.term = new Terminal();
window.term.dispose();
`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
it('dispose (opened)', async () => {
await openTerminal(page);
await page.evaluate(`window.term.dispose()`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
it('render when visible after hidden', async () => {
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 page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true);
});
describe('registerDecoration', () => {
describe('bufferDecorations', () => {
it('should register decorations and render them when terminal open is called', async () => {
await page.evaluate(`window.term = new Terminal({})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2);
});
it('should return undefined when the marker has already been disposed of', async () => {
await openTerminal(page);
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.marker.dispose()`);
await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined);
});
it('should throw when a negative x offset is provided', async () => {
await openTerminal(page);
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`
try {
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
} catch (e) {
window.throwMessage = e.message;
}
`);
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
});
});
describe('overviewRulerDecorations', () => {
it('should not add an overview ruler when width is not set', async () => {
await page.evaluate(`window.term = new Terminal({})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0);
});
it('should add an overview ruler when width is set', async () => {
await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1);
});
});
});
describe('registerLinkProvider', () => {
it('should fire provideLinks when hovering cells', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (position, cb) => {
calls.push(position);
cb(undefined);
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 1, 1);
await moveMouseCell(page, dims, 2, 2);
await moveMouseCell(page, dims, 10, 4);
await pollFor(page, `window.calls`, [1, 2, 4]);
await page.evaluate(`window.disposable.dispose()`);
});
it('should fire hover and leave events on the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (position, cb) => {
window.calls.push('provide ' + position);
if (position === 1) {
window.calls.push('match');
cb([{
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: 'bar',
activate: () => window.calls.push('activate'),
hover: () => window.calls.push('hover'),
leave: () => window.calls.push('leave')
}]);
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 1);
await timeout(100);
await moveMouseCell(page, dims, 4, 1);
await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave' ]);
await moveMouseCell(page, dims, 7, 1);
await timeout(100);
await moveMouseCell(page, dims, 8, 1);
await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover', 'leave']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should work fine when hover and leave callbacks are not provided', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (position, cb) => {
window.calls.push('provide ' + position);
if (position === 1) {
window.calls.push('match 1');
cb([{
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: 'bar',
activate: () => window.calls.push('activate')
}]);
} else if (position === 2) {
window.calls.push('match 2');
cb([{
range: { start: { x: 5, y: 2 }, end: { x: 7, y: 2 } },
text: 'bar',
activate: () => window.calls.push('activate')
}]);
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 1);
await pollFor(page, `window.calls`, ['provide 1', 'match 1']);
await moveMouseCell(page, dims, 4, 2);
await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2']);
await moveMouseCell(page, dims, 7, 1);
await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1']);
await moveMouseCell(page, dims, 6, 2);
await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1', 'provide 2', 'match 2']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should fire activate events when clicking the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'a b c');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (y, cb) => {
window.calls.push('provide ' + y);
cb([{
range: { start: { x: 1, y }, end: { x: 80, y } },
text: window.term.buffer.active.getLine(y - 1).translateToString(),
activate: (_, text) => window.calls.push('activate ' + y),
hover: () => window.calls.push('hover ' + y),
leave: () => window.calls.push('leave ' + y)
}]);
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 3, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1']);
await moveMouseCell(page, dims, 1, 2);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2']);
await moveMouseCell(page, dims, 5, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1', 'activate 1']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should work when multiple links are provided on the same line', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (position, cb) => {
window.calls.push('provide ' + position);
if (position === 1) {
cb([{
range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
hover: () => window.calls.push('hover 1-3'),
leave: () => window.calls.push('leave 1-3')
}, {
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
hover: () => window.calls.push('hover 5-7'),
leave: () => window.calls.push('leave 5-7')
}, {
range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
hover: () => window.calls.push('hover 9-11'),
leave: () => window.calls.push('leave 9-11')
}]);
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 2, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3']);
await moveMouseCell(page, dims, 6, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']);
await moveMouseCell(page, dims, 6, 2);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2']);
await moveMouseCell(page, dims, 10, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2', 'provide 1', 'hover 9-11']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should dispose links when hovering away', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLinks: (position, cb) => {
window.calls.push('provide ' + position);
if (position === 1) {
cb([{
range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
dispose: () => window.calls.push('dispose 1-3'),
hover: () => window.calls.push('hover 1-3'),
leave: () => window.calls.push('leave 1-3')
}, {
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
dispose: () => window.calls.push('dispose 5-7'),
hover: () => window.calls.push('hover 5-7'),
leave: () => window.calls.push('leave 5-7')
}, {
range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } },
text: '',
activate: () => window.calls.push('activate'),
dispose: () => window.calls.push('dispose 9-11'),
hover: () => window.calls.push('hover 9-11'),
leave: () => window.calls.push('leave 9-11')
}]);
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 2, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3']);
await moveMouseCell(page, dims, 6, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']);
await moveMouseCell(page, dims, 6, 2);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);
await moveMouseCell(page, dims, 10, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11']);
await moveMouseCell(page, dims, 10, 2);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11', 'leave 9-11', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);
await page.evaluate(`window.disposable.dispose()`);
});
});
});
interface IDimensions {
top: number;
left: number;
renderDimensions: IRenderDimensions;
}
interface IRenderDimensions {
scaledCharWidth: number;
scaledCharHeight: number;
scaledCellWidth: number;
scaledCellHeight: number;
scaledCharLeft: number;
scaledCharTop: number;
scaledCanvasWidth: number;
scaledCanvasHeight: number;
canvasWidth: number;
canvasHeight: number;
actualCellWidth: number;
actualCellHeight: number;
}
async function getDimensions(): Promise<IDimensions> {
return await page.evaluate(`
(function() {
const rect = document.querySelector('.xterm-rows').getBoundingClientRect();
return {
top: rect.top,
left: rect.left,
renderDimensions: window.term._core._renderService.dimensions
};
})();
`);
}
async function getCellCoordinates(dimensions: IDimensions, col: number, row: number): Promise<{ x: number, y: number }> {
return {
x: dimensions.left + dimensions.renderDimensions.scaledCellWidth * (col - 0.5),
y: dimensions.top + dimensions.renderDimensions.scaledCellHeight * (row - 0.5)
};
}
async function moveMouseCell(page: Page, dimensions: IDimensions, col: number, row: number): Promise<void> {
const coords = await getCellCoordinates(dimensions, col, row);
await page.mouse.move(coords.x, coords.y);
}