From 55d590348c6a39ab4c96fcd7cb9e51a4cb1e111f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 22:57:27 -0700 Subject: [PATCH 1/6] API integration tests Part of #1247 --- azure-pipelines.yml | 11 +++-- demo/client.ts | 10 ++-- demo/server.js | 4 ++ demo/test.html | 12 +++++ package.json | 5 ++ src/public/Terminal.api.ts | 99 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 demo/test.html create mode 100644 src/public/Terminal.api.ts diff --git a/azure-pipelines.yml b/azure-pipelines.yml index db74a4ea..fd65a6a8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,12 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' + - script: | + yarn start & + sleep 5 + yarn test-api --headless + displayName: 'Integration tests' - script: | yarn lint displayName: 'Lint' @@ -39,7 +44,7 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' - script: | yarn lint displayName: 'Lint' @@ -62,7 +67,7 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' - script: | yarn lint displayName: 'Lint' diff --git a/demo/client.ts b/demo/client.ts index 90a913d7..b7409109 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -21,6 +21,7 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; + Terminal?: typeof TerminalType; } declare let window: IWindowWithTerminal; @@ -57,8 +58,6 @@ function getSearchOptions(): ISearchOptions { }; } -createTerminal(); - const disposeRecreateButtonHandler = () => { // If the terminal exists dispose of it, otherwise recreate it if (term) { @@ -74,7 +73,12 @@ const disposeRecreateButtonHandler = () => { } }; -document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); +if (document.location.pathname === '/test') { + window.Terminal = Terminal; +} else { + createTerminal(); + document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); +} function createTerminal(): void { // Clean terminal diff --git a/demo/server.js b/demo/server.js index 758023c7..8270a398 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,6 +16,10 @@ function startServer() { res.sendFile(__dirname + '/index.html'); }); + app.get('/test', function(req, res){ + res.sendFile(__dirname + '/test.html'); + }); + app.get('/style.css', function(req, res){ res.sendFile(__dirname + '/style.css'); }); diff --git a/demo/test.html b/demo/test.html new file mode 100644 index 00000000..275c542d --- /dev/null +++ b/demo/test.html @@ -0,0 +1,12 @@ + + + + xterm.js integration test fixture + + + + +
+ + + diff --git a/package.json b/package.json index 1727af82..615962c4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@types/jsdom": "11.0.1", "@types/mocha": "^2.2.33", "@types/node": "6.0.108", + "@types/puppeteer": "^1.12.4", "@types/webpack": "^4.4.11", "browserify": "^13.3.0", "chai": "3.5.0", @@ -54,6 +55,7 @@ "test-debug": "node --inspect-brk node_modules/.bin/gulp test", "test-suite": "gulp mocha-suite --test", "test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha", + "test-api": "mocha \"**/*.api.js\"", "mocha": "gulp test", "prebuild": "tsc -b ./src/tsconfig.all.json", "build": "gulp build", @@ -61,5 +63,8 @@ "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" + }, + "dependencies": { + "puppeteer": "^1.15.0" } } diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts new file mode 100644 index 00000000..168a9407 --- /dev/null +++ b/src/public/Terminal.api.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from '../Types'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('API Integration Tests', () => { + before(async function(): Promise { + this.timeout(10000); + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.goto(APP); + }); + + it('Default options', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`window.term.cols`), 80); + assert.equal(await page.evaluate(`window.term.rows`), 24); + }); + + it('write', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.term.write('foo'); + window.term.write('bar'); + `); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foobar'); + }); + + it('writeln', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.term.writeln('foo'); + window.term.writeln('bar'); + `); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foo'); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(1, true)`), 'bar'); + }); + + it('clear', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + await page.evaluate(` + window.term.write('test0'); + for (let i = 1; i < 10; i++) { + window.term.write('\\n\\rtest' + i); + } + `); + await page.evaluate(`window.term.clear()`); + assert.equal(await page.evaluate(`window.term._core.buffer.lines.length`), '5'); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'test9'); + for (let i = 1; i < 5; i++) { + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(${i}, true)`), ''); + } + }); + + it('getOption, setOption', async function(): Promise { + this.timeout(10000); + await openTerminal(); + 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'); + }); +}); + +async function openTerminal(options: ITerminalOptions = {}): Promise { + await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + if (options.rendererType === 'dom') { + await page.waitForSelector('.xterm-rows'); + } else { + await page.waitForSelector('.xterm-text-layer'); + } +} From 8a5b6b0356e69ac5433d65929dbb80230d92dd60 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 22:58:23 -0700 Subject: [PATCH 2/6] Move puppeteer to devDependencies --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 615962c4..68d72a1b 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "node-pty": "0.7.6", "nodemon": "1.10.2", "nyc": "^11.8.0", + "puppeteer": "^1.15.0", "sorcery": "^0.10.0", "source-map-loader": "^0.2.4", "ts-loader": "^4.5.0", @@ -63,8 +64,5 @@ "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" - }, - "dependencies": { - "puppeteer": "^1.15.0" } } From 97b176e70109d49637cdb3d60cea51e08e1f03f1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:14:16 -0700 Subject: [PATCH 3/6] Add selection tests --- src/public/Terminal.api.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 168a9407..00a6e6bf 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -86,6 +86,20 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.setOption('rendererType', 'dom')`); assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); }); + + it('selection', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + await page.evaluate(`window.term.write('\\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()`), ''); + await page.evaluate(`window.term.selectAll()`); + assert.equal(await page.evaluate(`window.term.hasSelection()`), true); + assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz'); + await page.evaluate(`window.term.clearSelection()`); + assert.equal(await page.evaluate(`window.term.hasSelection()`), false); + assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From 34ac1bc5029ea7f440acaf904c9c502324d6b1c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:38:45 -0700 Subject: [PATCH 4/6] Fix clearSelection not firing onSelectionChange Fixes #2070 --- src/SelectionManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index dcd60068..71255083 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -245,6 +245,7 @@ export class SelectionManager implements ISelectionManager { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); + this._onSelectionChange.fire(); } /** From 7562948259f1d9ef5776acdc498d9228e27e74e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:47:28 -0700 Subject: [PATCH 5/6] Add tests for all API events --- src/public/Terminal.api.ts | 121 +++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 00a6e6bf..147519f4 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -100,6 +100,127 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); }); + + describe('Events', () => { + it('onCursorMove', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onCursorMove(e => window.callCount++); + window.term.write('foo'); + `); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.write('bar')`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onData', async function(): Promise { + this.timeout(10000); + await openTerminal(); + 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 function(): Promise { + this.timeout(10000); + await openTerminal(); + 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 function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onLineFeed(() => callCount++); + window.term.writeln('foo'); + `); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.writeln('bar')`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onScroll', async function(): Promise { + this.timeout(10000); + await openTerminal({ 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'); + } + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.writeln('bar')`); + assert.deepEqual(await page.evaluate(`window.calls`), [1]); + await page.evaluate(`window.term.writeln('baz')`); + assert.deepEqual(await page.evaluate(`window.calls`), [1, 2]); + }); + + it('onSelectionChange', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onSelectionChange(() => window.callCount++); + `); + assert.equal(await page.evaluate(`window.callCount`), 0); + await page.evaluate(`window.term.selectAll()`); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.clearSelection()`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onRender', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onRender(e => window.calls.push([e.start, e.end])); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.write('foo')`); + assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0]]); + await page.evaluate(`window.term.write('bar\\n\\nbaz')`); + assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0], [0, 2]]); + }); + + it('onResize', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onResize(e => window.calls.push([e.cols, e.rows])); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.resize(10, 5)`); + assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5]]); + await page.evaluate(`window.term.resize(20, 15)`); + assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5], [20, 15]]); + }); + + it('onTitleChange', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onTitleChange(e => window.calls.push(e)); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`); + assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From dfb5d8e663ec92b6935e519358fd2e609128112c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:53:38 -0700 Subject: [PATCH 6/6] Add focus, blur test --- src/public/Terminal.api.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 147519f4..380cc89c 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -101,6 +101,16 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.getSelection()`), ''); }); + it('focus, blur', async function(): Promise { + this.timeout(10000); + await openTerminal(); + 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('Events', () => { it('onCursorMove', async function(): Promise { this.timeout(10000);