diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts new file mode 100644 index 00000000..f970fcc8 --- /dev/null +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from 'xterm'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('Search Tests', function (): void { + this.timeout(200000); + + before(async function (): Promise { + 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 }); + await page.goto(APP); + await openTerminal(); + await page.evaluate(`window.search = new SearchAddon();`); + await page.evaluate(`window.term.loadAddon(window.search);`); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.evaluate(`window.term.reset()`); + }); + + it('Simple Search', async () => { + await writeSync('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(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('package.json pack package.lock'); + await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`); + let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: 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.startColumn, selectionPosition.endColumn + 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.startColumn, selectionPosition.endColumn + 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.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); + }); + it ('Incremental Find Next', async () => { + await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`); + await writeSync('package.jsonc'); + await page.evaluate(`window.search.findNext('pack', {incremental: true})`); + let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: 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.startColumn, selectionPosition.endColumn + 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.startColumn, selectionPosition.endColumn + 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.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); + }); + it ('Simple Regex', async () => { + await writeSync('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'); + }); +}); + +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'); + } +} + +async function writeSync(data: string): Promise { + await page.evaluate(`window.term.write('${data}');`); + while (true) { + if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) { + break; + } + } +} + +function makeData(length: number): string { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; +} diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 8fcfc690..e9f8a9b4 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -57,8 +57,8 @@ export class SearchAddon implements ITerminalAddon { return false; } - let startCol: number = 0; - let startRow = this._terminal.buffer.viewportY; + let startCol = 0; + let startRow = 0; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; @@ -71,20 +71,8 @@ export class SearchAddon implements ITerminalAddon { this._initLinesCache(); - // A row that has isWrapped = false - let findingRow = startRow; - // index of beginning column that _findInLine need to scan. - let cumulativeCols = startCol; - // If startRow is wrapped row, scan for unwrapped row above. - // So we can start matching on wrapped line from long unwrapped line. - let currentLine = this._terminal.buffer.getLine(findingRow); - while (currentLine && currentLine.isWrapped) { - cumulativeCols += this._terminal.cols; - currentLine = this._terminal.buffer.getLine(--findingRow); - } - // Search startRow - let result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + let result = this._findInLine(term, startRow, startCol, searchOptions); // Search from startRow + 1 to end if (!result) { @@ -99,11 +87,9 @@ export class SearchAddon implements ITerminalAddon { } } } - - // Search from the top to the startRow (search the whole startRow again in - // case startCol > 0) - if (!result) { - for (let y = 0; y < findingRow; y++) { + // If we hit the bottom and didn't search from the very top wrap back up + if (!result && startRow !== 0) { + for (let y = 0; y < startRow; y++) { result = this._findInLine(term, y, 0, searchOptions); if (result) { break; @@ -133,61 +119,45 @@ export class SearchAddon implements ITerminalAddon { } const isReverseSearch = true; - let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1; + let startRow = this._terminal.buffer.baseY + this._terminal.rows; let startCol = this._terminal.cols; - + let result: ISearchResult | undefined = undefined; + const incremental = searchOptions ? searchOptions.incremental : false; if (this._terminal.hasSelection()) { - // Start from the selection start if there is a selection const currentSelection = this._terminal.getSelectionPosition()!; + // Start from selection start if there is a selection startRow = currentSelection.startRow; startCol = currentSelection.startColumn; } this._initLinesCache(); - // Search startRow - let result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch); + if (incremental) { + result = this._findInLine(term, startRow, startCol, searchOptions, false); + if (!(result && result.row === startRow && result.col === startCol)) { + result = this._findInLine(term, startRow, startCol, searchOptions, true); + } + } else { + result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch); + } // Search from startRow - 1 to top if (!result) { - // If the line is wrapped line, increase number of columns that is needed to be scanned - // Se we can scan on wrapped line from unwrapped line - let cumulativeCols = this._terminal.cols; - if (this._terminal.buffer.getLine(startRow)!.isWrapped) { - cumulativeCols += startCol; - } + startCol = this._terminal.cols; for (let y = startRow - 1; y >= 0; y--) { - result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); + result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch); if (result) { break; } - // If the current line is wrapped line, increase scanning range, - // preparing for scanning on unwrapped line - const line = this._terminal.buffer.getLine(y); - if (line && line.isWrapped) { - cumulativeCols += this._terminal.cols; - } else { - cumulativeCols = this._terminal.cols; - } } } - - // Search from the bottom to startRow (search the whole startRow again in - // case startCol > 0) - if (!result) { - const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1; - let cumulativeCols = this._terminal.cols; - for (let y = searchFrom; y >= startRow; y--) { - result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); + // If we hit the top and didn't search from the very bottom wrap back down + if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) { + for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) { + result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch); if (result) { break; } - const line = this._terminal.buffer.getLine(y); - if (line && line.isWrapped) { - cumulativeCols += this._terminal.cols; - } else { - cumulativeCols = this._terminal.cols; - } } } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e8b23d34..c784a611 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -62,7 +62,7 @@ jobs: yarn lint displayName: 'Lint' -- job: IntegrationTests +- job: Linux_IntegrationTests pool: vmImage: 'ubuntu-16.04' steps: @@ -81,14 +81,32 @@ jobs: yarn start & sleep 10 yarn test-api --headless - displayName: 'Integration tests' + displayName: 'Linux Integration tests' + +- job: macOS_IntegrationTests + pool: + vmImage: 'xcode9-macos10.13' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '8.x' + displayName: 'Install Node.js' + - script: | + yarn + displayName: 'Install dependencies and build' + - script: | + yarn start & + sleep 10 + yarn test-api --headless + displayName: 'MacOS Integration tests' - job: Release dependsOn: - Linux - macOS - Windows - - IntegrationTests + - Linux_IntegrationTests + - macOS_IntegrationTests condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*'))) pool: vmImage: 'ubuntu-16.04' diff --git a/demo/client.ts b/demo/client.ts index a28a720c..040292e4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -58,11 +58,12 @@ function setPadding(): void { term.fit(); } -function getSearchOptions(): ISearchOptions { +function getSearchOptions(e: KeyboardEvent): ISearchOptions { return { regex: (document.getElementById('regex') as HTMLInputElement).checked, wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, - caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked + caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, + incremental: e.key !== `Enter` }; } @@ -134,15 +135,11 @@ function createTerminal(): void { addDomListener(paddingElement, 'change', setPadding); addDomListener(actionElements.findNext, 'keyup', (e) => { - const searchOptions = getSearchOptions(); - searchOptions.incremental = e.key !== `Enter`; - searchAddon.findNext(actionElements.findNext.value, searchOptions); + searchAddon.findNext(actionElements.findNext.value, getSearchOptions(e)); }); addDomListener(actionElements.findPrevious, 'keyup', (e) => { - if (e.key === `Enter`) { - searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions()); - } + searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions(e)); }); // fit is called within a setTimeout, cols and rows need this. diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2de54396..714257f9 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -556,16 +556,12 @@ describe('Terminal', () => { afterEach(() => term.browser.isMac = originalIsMac); it('should interfere with the alt key on keyDown', () => { - (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 81; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); - (term)._keyDownHandled = false; + assert.equal(term.keyDown(evKeyDown), false); evKeyDown.altKey = true; evKeyDown.keyCode = 192; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); + assert.equal(term.keyDown(evKeyDown), false); }); }); @@ -578,29 +574,22 @@ describe('Terminal', () => { afterEach(() => term.browser.isMac = originalIsMac); it('should not interfere with the alt key on keyDown', () => { - (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 81; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, false); - (term)._keyDownHandled = false; + assert.equal(term.keyDown(evKeyDown), true); evKeyDown.altKey = true; evKeyDown.keyCode = 192; term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, false); + assert.equal(term.keyDown(evKeyDown), true); }); it('should interfere with the alt + arrow keys', () => { - (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 37; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); - (term)._keyDownHandled = false; + assert.equal(term.keyDown(evKeyDown), false); evKeyDown.altKey = true; evKeyDown.keyCode = 39; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); + assert.equal(term.keyDown(evKeyDown), false); }); it('should emit key with alt + key on keyPress', (done) => { @@ -652,32 +641,26 @@ describe('Terminal', () => { afterEach(() => term.browser.isWindows = originalIsWindows); it('should not interfere with the alt + ctrl key on keyDown', () => { - (term)._keyDownHandled = false; evKeyPress.altKey = true; evKeyPress.ctrlKey = true; evKeyPress.keyCode = 81; - term.keyDown(evKeyPress); - assert.equal((term)._keyDownHandled, false); - (term)._keyDownHandled = false; + assert.equal(term.keyDown(evKeyPress), true); evKeyDown.altKey = true; evKeyDown.ctrlKey = true; evKeyDown.keyCode = 81; term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, false); + assert.equal(term.keyDown(evKeyPress), true); }); - it('should interefere with the alt + ctrl + arrow keys', () => { + it('should interfere with the alt + ctrl + arrow keys', () => { evKeyDown.altKey = true; evKeyDown.ctrlKey = true; - (term)._keyDownHandled = false; evKeyDown.keyCode = 37; - term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); - (term)._keyDownHandled = false; + assert.equal(term.keyDown(evKeyDown), false); evKeyDown.keyCode = 39; term.keyDown(evKeyDown); - assert.equal((term)._keyDownHandled, true); + assert.equal(term.keyDown(evKeyDown), false); }); it('should emit key with alt + ctrl + key on keyPress', (done) => { diff --git a/src/Terminal.ts b/src/Terminal.ts index e1e75746..b4b5a8e4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,12 +261,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } public dispose(): void { + if (this._isDisposed) { + return; + } super.dispose(); if (this._windowsMode) { this._windowsMode.dispose(); this._windowsMode = undefined; } - this._renderService.dispose(); + if (this._renderService) { + this._renderService.dispose(); + } this._customKeyEventHandler = null; this.write = () => {}; if (this.element && this.element.parentNode) { @@ -1486,10 +1491,19 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.textarea.value = ''; } - this._keyDownHandled = true; this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); this._coreService.triggerDataEvent(result.key, true); + + // Cancel events when not in screen reader mode so events don't get bubbled up and handled by + // other listeners. When screen reader mode is enabled, this could cause issues if the event + // is handled at a higher level, this is a compromise in order to echo keys to the screen + // reader. + if (!this.optionsService.options.screenReaderMode) { + return this.cancel(event, true); + } + + this._keyDownHandled = true; } private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean { diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts new file mode 100644 index 00000000..266edff7 --- /dev/null +++ b/test/api/MouseTracking.api.ts @@ -0,0 +1,2118 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from 'xterm'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 1024; +const height = 768; + +// adjust terminal row/col size so we can test +// >80 up to 223 and >255 +const fontSize = 6; +const cols = 260; +const rows = 50; + +// for some reason shift gets not caught by selection manager on macos +const noShift = process.platform === 'darwin' ? false : true; + +/** + * Helper functions. + */ +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'); + } +} + +async function resetMouseModes(): Promise { + return await page.evaluate(` + window.term.write('\x1b[?9l\x1b[?1000l\x1b[?1001l\x1b[?1002l\x1b[?1003l'); + window.term.write('\x1b[?1005l\x1b[?1006l\x1b[?1015l'); + `); +} + +async function getReports(encoding: string): Promise { + const reports = await page.evaluate(`window.calls`); + await page.evaluate(`window.calls = [];`); + return reports.map((report: number[]) => parseReport(encoding, report)); +} + +// translate cell positions into pixel offset +// always adds +2 in each direction so we dont end up in the wrong cell +// due to rounding issues +async function cellPos(col: number, row: number): Promise { + const coords = await page.evaluate(` + (function() { + const rect = window.term.element.getBoundingClientRect(); + const dim = term._core._renderService.dimensions; + return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.actualCellWidth, height: dim.actualCellHeight}; + })(); + `); + return [col * coords.width + coords.left + 2, row * coords.height + coords.top + 2]; +} + +/** + * Patched puppeteer functions. + * This is needed to: + * - translate cell positions into pixel positions + * - allow modifiers to be set + * - fake wheel events + */ +async function mouseMove(col: number, row: number): Promise { + const [xPixels, yPixels] = await cellPos(col, row); + return await page.mouse.move(xPixels, yPixels); +} +async function mouseClick(col: number, row: number): Promise { + const [xPixels, yPixels] = await cellPos(col, row); + return await page.mouse.click(xPixels, yPixels); +} +async function mouseDown(button: 'left' | 'right' | 'middle' | undefined): Promise { + return await page.mouse.down({button}); +} +async function mouseUp(button: 'left' | 'right' | 'middle' | undefined): Promise { + return await page.mouse.up({button}); +} +async function wheelUp(): Promise { + const self = (page.mouse as any); + return await page.evaluate(` + window.term.element.dispatchEvent(new WheelEvent('wheel', {clientX: ${self._x}, clientY: ${self._y}, deltaX: 0, deltaY: -10, modifiers: ${self._keyboard._modifiers}})); + `); +} +async function wheelDown(): Promise { + const self = (page.mouse as any); + return await page.evaluate(` + window.term.element.dispatchEvent(new WheelEvent('wheel', {clientX: ${self._x}, clientY: ${self._y}, deltaX: 0, deltaY: 10, modifiers: ${self._keyboard._modifiers}})); + `); +} + +// button definitions +const buttons: {[key: string]: number} = { + '': -1, + left: 0, + middle: 1, + right: 2, + released: 3, + wheelUp: 4, + wheelDown: 5, + wheelLeft: 6, + wheelRight: 7, + aux8: 8, + aux9: 9, + aux10: 10, + aux11: 11, + aux12: 12, + aux13: 13, + aux14: 14, + aux15: 15 +}; +const reverseButtons: any = {}; +for (const el in buttons) { + reverseButtons[buttons[el]] = el; +} + +// extract button data from buttonCode +function evalButtonCode(code: number): any { + if (code > 255) { + return {button: 'invalid', action: 'invalid', modifier: {}}; + } + const modifier = {shift: !!(code & 4), meta: !!(code & 8), control: !!(code & 16)}; + const move = code & 32; + let button = code & 3; + if (code & 128) { + button |= 8; + } + if (code & 64) { + button |= 4 + } + let actionS = 'press'; + let buttonS = reverseButtons[button]; + if (button === 3) { + buttonS = ''; + actionS = 'release'; + } + if (move) { + actionS = 'move'; + } else if (4 <= button && button <= 7) { + buttonS = 'wheel'; + actionS = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right'; + } + return {button: buttonS, action: actionS, modifier}; +} + +// parse a single mouse report +function parseReport(encoding: string, msg: number[]): {state: any; row: number; col: number; } | string { + let sReport: string; + let buttonCode: number; + let row: number; + let col: number; + // unpack msg + const report = String.fromCharCode.apply(null, msg); + // console.log([report]); + // skip non mouse reports + if (!report || report[0] !== '\x1b') { + return report; + } + switch (encoding) { + case 'DEFAULT': + return { + state: evalButtonCode(report.charCodeAt(3) - 32), + col: report.charCodeAt(4) - 32, + row: report.charCodeAt(5) - 32 + }; + case 'UTF8': + // TODO: once the binary patch is in place, + // use UTF8 byte check here + return { + state: evalButtonCode(report.charCodeAt(3) - 32), + col: report.charCodeAt(4) - 32, + row: report.charCodeAt(5) - 32 + }; + case 'SGR': + sReport = report.slice(3, -1); + [buttonCode, col, row] = sReport.split(';').map(el => parseInt(el)); + const state = evalButtonCode(buttonCode); + if (report[report.length - 1] === 'm') { + state.action = 'release'; + } + return {state, row, col}; + case 'URXVT': + sReport = report.slice(2, -1); + [buttonCode, col, row] = sReport.split(';').map(el => parseInt(el)); + return {state: evalButtonCode(buttonCode - 32), row: --row, col: --col}; // FIXME: remove -1 here when fixed! + default: + return { + state: evalButtonCode(report.charCodeAt(3) - 32), + col: report.charCodeAt(4) - 32, + row: report.charCodeAt(5) - 32 + }; + } +} + +/** + * Mouse tracking tests. + */ +describe('Mouse Tracking Tests', function(): void { + this.timeout(30000); + + before(async function(): Promise { + 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); + await openTerminal(); + // patch terminal to get the onData calls + // we encode the msg here to an array of codes to not lose bytes + // (transmission strips non utf8 bytes) + // also resize so we can properly test the edge cases + await page.evaluate(` + window.calls = []; + window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); + window.term.setOption('fontSize', ${fontSize}); + window.term.resize(${cols}, ${rows}); + `); + }); + + describe('DECSET 9 (X10)', async () => { + /** + * X10 protocol: + * - only press events + * - no wheel + * - no move + * - no modifiers + */ + it('default encoding', async () => { + const encoding = 'DEFAULT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?9h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // mouseup should not report + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), []); + await wheelDown(); + assert.deepEqual(await getReports(encoding), []); + + // modifiers + // CTRL + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + + // ALT + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // SHIFT + // note: caught by selection manager + // bug? Why not caught by selection manger on macos? + // bug: no modifier reported + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), []); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + } + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + */ + }); + it('UTF8 encoding', async () => { + const encoding = 'UTF8'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1005h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // mouseup should not report + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), []); + await wheelDown(); + assert.deepEqual(await getReports(encoding), []); + + // modifiers + // CTRL + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + + // ALT + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // SHIFT + // note: caught by selection manager + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), []); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + } + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + */ + }); + it('SGR encoding', async () => { + const encoding = 'SGR'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1006h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // mouseup should not report + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), []); + await wheelDown(); + assert.deepEqual(await getReports(encoding), []); + + // modifiers + // CTRL + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + + // ALT + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // SHIFT + // note: caught by selection manager + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), []); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + } + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + */ + }); + it('URXVT encoding', async () => { + // bug: always reports +1 for row/col (temp. fixed in parseReport to pass tests) + const encoding = 'URXVT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1015h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // mouseup should not report + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), []); + await wheelDown(); + assert.deepEqual(await getReports(encoding), []); + + // modifiers + // CTRL + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + + // ALT + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // SHIFT + // note: caught by selection manager + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), []); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + } + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + */ + }); + }); + describe('DECSET 1000 (VT200 mouse)', () => { + /** + * VT200 protocol: + * - press and release events + * - wheel up/down + * - no move + * - all modifiers + */ + it('default encoding', async () => { + const encoding = 'DEFAULT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1000h');`); + + // test at 0,0 + // bug: release is fired immediately - expected: only press event + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + // bug: release already fired thus no event here - expected: release event + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported, release with wrong coords + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('UTF8 encoding', async () => { + const encoding = 'UTF8'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1005h');`); + + // test at 0,0 + // bug: release is fired immediately - expected: only press event + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + // bug: release already fired thus no event here - expected: release event + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported, release with wrong coords + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('SGR encoding', async () => { + const encoding = 'SGR'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1006h');`); + + // test at 0,0 + // bug: release is fired immediately - expected: only press event + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report + // bug: release already fired thus no event here - expected: release event + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + // bug: release reports wrong button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported, release with wrong coords + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('URXVT encoding', async () => { + const encoding = 'URXVT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1015h');`); + + // test at 0,0 + // bug: release is fired immediately - expected: only press event + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + // bug: release already fired thus no event here - expected: release event + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), []); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported, release with wrong coords + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + }); + describe('DECSET 1002 (xterm with drag)', () => { + /** + * VT200 protocol: + * - press and release events + * - wheel up/down + * - move only on press (drag) + * - all modifiers + * Note: tmux runs this with SGR encoding. + */ + it('default encoding', async () => { + const encoding = 'DEFAULT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1002h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + // bug: completely messed up - wrong modifier, only partially reported + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('UTF8 encoding', async () => { + const encoding = 'UTF8'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1005h');`); + + // test at 0,0 + // bug: release is fired immediately + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + // bug: release already fired thus no event here - expected: release event + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + // bug: completely messed up - wrong modifier, only partially reported + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('SGR encoding', async () => { + const encoding = 'SGR'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1006h');`); + + // test at 0,0 + // bug: release is fired immediately + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + // bug: release reports wrong button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + // bug: completely messed up - wrong modifier, only partially reported + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + it('URXVT encoding', async () => { + const encoding = 'URXVT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1015h');`); + + // test at 0,0 + // bug: release is fired immediately + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should not report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), []); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + // bug: totally broken - reports no modifier, reports wrong button and action + // after fix: removed faulty reports below and uncomment lines + await mouseMove(43, 24); + await getReports(encoding); // clear reports + // await page.keyboard.down('Control'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + // await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + // bug: no modifier reported + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Alt'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + // expected + // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + // bug: modifier not reported for passed events + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Shift'); // defaults to ShiftLeft + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } else { + // bug: completely messed up - wrong modifier, only partially reported + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + ]); + } + + /* + // all modifiers + // bug: this is totally broken with wrong coords and messed up modifiers + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + ]); + */ + }); + }); + describe('DECSET 1003 (xterm any event)', () => { + /** + * VT200 protocol: + * - all events (press, release, wheel, move) + * - all modifiers + */ + // bug: currently same reports as 1002, FIXME: implement tests once fixed + }); + // TODO: move tests with several buttons pressed +});