diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-02-10 13:22:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-10 20:22:13 +0000 |
commit | f5e46c9bf2f50d66a953fa133161fc829cecff06 (patch) | |
tree | 8faf2f5831c1c7b11d842cd9908d141082c869a5 /cli/tests/node_compat/test/parallel/test-readline-interface.js | |
parent | d2477f780630a812bfd65e3987b70c0d309385bb (diff) |
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
Diffstat (limited to 'cli/tests/node_compat/test/parallel/test-readline-interface.js')
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-readline-interface.js | 1217 |
1 files changed, 0 insertions, 1217 deletions
diff --git a/cli/tests/node_compat/test/parallel/test-readline-interface.js b/cli/tests/node_compat/test/parallel/test-readline-interface.js deleted file mode 100644 index e8e48dd1e..000000000 --- a/cli/tests/node_compat/test/parallel/test-readline-interface.js +++ /dev/null @@ -1,1217 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file - -// Copyright Joyent and Node contributors. All rights reserved. MIT license. -// Taken from Node 16.13.0 -// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually - -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Flags: --expose-internals -'use strict'; -const common = require('../common'); -common.skipIfDumbTerminal(); - -const assert = require('assert'); -const readline = require('readline'); -const util = require('util'); -const { - getStringWidth, - stripVTControlCharacters -} = require('internal/util/inspect'); -const { EventEmitter, getEventListeners } = require('events'); -const { Writable, Readable } = require('stream'); - -class FakeInput extends EventEmitter { - resume() {} - pause() {} - write() {} - end() {} -} - -function isWarned(emitter) { - for (const name in emitter) { - const listeners = emitter[name]; - if (listeners.warned) return true; - } - return false; -} - -function getInterface(options) { - const fi = new FakeInput(); - const rli = new readline.Interface({ - input: fi, - output: fi, - ...options, - }); - return [rli, fi]; -} - -function assertCursorRowsAndCols(rli, rows, cols) { - const cursorPos = rli.getCursorPos(); - assert.strictEqual(cursorPos.rows, rows); - assert.strictEqual(cursorPos.cols, cols); -} - -{ - const input = new FakeInput(); - const rl = readline.Interface({ input }); - assert(rl instanceof readline.Interface); -} - -[ - undefined, - 50, - 0, - 100.5, - 5000, -].forEach((crlfDelay) => { - const [rli] = getInterface({ crlfDelay }); - assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); - rli.close(); -}); - -{ - const input = new FakeInput(); - - // Constructor throws if completer is not a function or undefined - ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((invalid) => { - assert.throws(() => { - readline.createInterface({ - input, - completer: invalid - }); - }, { - name: 'TypeError', - code: 'ERR_INVALID_ARG_VALUE' - }); - }); - - // Constructor throws if history is not an array - ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((history) => { - assert.throws(() => { - readline.createInterface({ - input, - history, - }); - }, { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE' - }); - }); - - // Constructor throws if historySize is not a positive number - ['not a number', -1, NaN, {}, true, Symbol(), null].forEach((historySize) => { - assert.throws(() => { - readline.createInterface({ - input, - historySize, - }); - }, { - name: 'RangeError', - code: 'ERR_INVALID_ARG_VALUE' - }); - }); - - // Check for invalid tab sizes. - assert.throws( - () => new readline.Interface({ - input, - tabSize: 0 - }), - { - message: 'The value of "tabSize" is out of range. ' + - 'It must be >= 1 && < 4294967296. Received 0', - code: 'ERR_OUT_OF_RANGE' - } - ); - - assert.throws( - () => new readline.Interface({ - input, - tabSize: '4' - }), - { code: 'ERR_INVALID_ARG_TYPE' } - ); - - assert.throws( - () => new readline.Interface({ - input, - tabSize: 4.5 - }), - { - code: 'ERR_OUT_OF_RANGE', - message: 'The value of "tabSize" is out of range. ' + - 'It must be an integer. Received 4.5' - } - ); -} - -// Sending a single character with no newline -{ - const fi = new FakeInput(); - const rli = new readline.Interface(fi, {}); - rli.on('line', common.mustNotCall()); - fi.emit('data', 'a'); - rli.close(); -} - -// Sending multiple newlines at once that does not end with a new line and a -// `end` event(last line is). \r should behave like \n when alone. -{ - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ['foo', 'bar', 'baz', 'bat']; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length - 1)); - fi.emit('data', expectedLines.join('\r')); - rli.close(); -} - -// \r at start of input should output blank line -{ - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ['', 'foo' ]; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length)); - fi.emit('data', '\rfoo\r'); - rli.close(); -} - -// \t does not become part of the input when there is a completer function -{ - const completer = (line) => [[], line]; - const [rli, fi] = getInterface({ terminal: true, completer }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'foo'); - })); - for (const character of '\tfo\to\t') { - fi.emit('data', character); - } - fi.emit('data', '\n'); - rli.close(); -} - -// \t when there is no completer function should behave like an ordinary -// character -{ - const [rli, fi] = getInterface({ terminal: true }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, '\t'); - })); - fi.emit('data', '\t'); - fi.emit('data', '\n'); - rli.close(); -} - -// Adding history lines should emit the history event with -// the history array -{ - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ['foo', 'bar', 'baz', 'bat']; - rli.on('history', common.mustCall((history) => { - const expectedHistory = expectedLines.slice(0, history.length).reverse(); - assert.deepStrictEqual(history, expectedHistory); - }, expectedLines.length)); - for (const line of expectedLines) { - fi.emit('data', `${line}\n`); - } - rli.close(); -} - -// Altering the history array in the listener should not alter -// the line being processed -{ - const [rli, fi] = getInterface({ terminal: true }); - const expectedLine = 'foo'; - rli.on('history', common.mustCall((history) => { - assert.strictEqual(history[0], expectedLine); - history.shift(); - })); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLine); - assert.strictEqual(rli.history.length, 0); - })); - fi.emit('data', `${expectedLine}\n`); - rli.close(); -} - -// Duplicate lines are removed from history when -// `options.removeHistoryDuplicates` is `true` -{ - const [rli, fi] = getInterface({ - terminal: true, - removeHistoryDuplicates: true - }); - const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; - // ['foo', 'baz', 'bar', bat']; - let callCount = 0; - rli.on('line', (line) => { - assert.strictEqual(line, expectedLines[callCount]); - callCount++; - }); - fi.emit('data', `${expectedLines.join('\n')}\n`); - assert.strictEqual(callCount, expectedLines.length); - fi.emit('keypress', '.', { name: 'up' }); // 'bat' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'bar' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'baz' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'foo' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(callCount, 0); - fi.emit('keypress', '.', { name: 'down' }); // 'baz' - assert.strictEqual(rli.line, 'baz'); - assert.strictEqual(rli.historyIndex, 2); - fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar' - assert.strictEqual(rli.line, 'bar'); - assert.strictEqual(rli.historyIndex, 1); - fi.emit('keypress', '.', { name: 'n', ctrl: true }); - assert.strictEqual(rli.line, 'bat'); - assert.strictEqual(rli.historyIndex, 0); - // Activate the substring history search. - fi.emit('keypress', '.', { name: 'down' }); // 'bat' - assert.strictEqual(rli.line, 'bat'); - assert.strictEqual(rli.historyIndex, -1); - // Deactivate substring history search. - fi.emit('keypress', '.', { name: 'backspace' }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, 'ba'); - // Activate the substring history search. - fi.emit('keypress', '.', { name: 'down' }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, 'ba'); - fi.emit('keypress', '.', { name: 'down' }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, 'ba'); - fi.emit('keypress', '.', { name: 'up' }); // 'bat' - assert.strictEqual(rli.historyIndex, 0); - assert.strictEqual(rli.line, 'bat'); - fi.emit('keypress', '.', { name: 'up' }); // 'bar' - assert.strictEqual(rli.historyIndex, 1); - assert.strictEqual(rli.line, 'bar'); - fi.emit('keypress', '.', { name: 'up' }); // 'baz' - assert.strictEqual(rli.historyIndex, 2); - assert.strictEqual(rli.line, 'baz'); - fi.emit('keypress', '.', { name: 'up' }); // 'ba' - assert.strictEqual(rli.historyIndex, 4); - assert.strictEqual(rli.line, 'ba'); - fi.emit('keypress', '.', { name: 'up' }); // 'ba' - assert.strictEqual(rli.historyIndex, 4); - assert.strictEqual(rli.line, 'ba'); - // Deactivate substring history search and reset history index. - fi.emit('keypress', '.', { name: 'right' }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, 'ba'); - // Substring history search activated. - fi.emit('keypress', '.', { name: 'up' }); // 'ba' - assert.strictEqual(rli.historyIndex, 0); - assert.strictEqual(rli.line, 'bat'); - rli.close(); -} - -// Duplicate lines are not removed from history when -// `options.removeHistoryDuplicates` is `false` -{ - const [rli, fi] = getInterface({ - terminal: true, - removeHistoryDuplicates: false - }); - const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; - let callCount = 0; - rli.on('line', (line) => { - assert.strictEqual(line, expectedLines[callCount]); - callCount++; - }); - fi.emit('data', `${expectedLines.join('\n')}\n`); - assert.strictEqual(callCount, expectedLines.length); - fi.emit('keypress', '.', { name: 'up' }); // 'bat' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'bar' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'baz' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'bar' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit('keypress', '.', { name: 'up' }); // 'foo' - assert.strictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(callCount, 0); - rli.close(); -} - -// Regression test for repl freeze, #1968: -// check that nothing fails if 'keypress' event throws. -{ - const [rli, fi] = getInterface({ terminal: true }); - const keys = []; - const err = new Error('bad thing happened'); - fi.on('keypress', (key) => { - keys.push(key); - if (key === 'X') { - throw err; - } - }); - assert.throws( - () => fi.emit('data', 'fooX'), - (e) => { - assert.strictEqual(e, err); - return true; - } - ); - fi.emit('data', 'bar'); - assert.strictEqual(keys.join(''), 'fooXbar'); - rli.close(); -} - -// History is bound -{ - const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); - const lines = ['line 1', 'line 2', 'line 3']; - fi.emit('data', lines.join('\n') + '\n'); - assert.strictEqual(rli.history.length, 2); - assert.strictEqual(rli.history[0], 'line 3'); - assert.strictEqual(rli.history[1], 'line 2'); -} - -// Question -{ - const [rli] = getInterface({ terminal: true }); - const expectedLines = ['foo']; - rli.question(expectedLines[0], () => rli.close()); - assertCursorRowsAndCols(rli, 0, expectedLines[0].length); - rli.close(); -} - -// Sending a multi-line question -{ - const [rli] = getInterface({ terminal: true }); - const expectedLines = ['foo', 'bar']; - rli.question(expectedLines.join('\n'), () => rli.close()); - assertCursorRowsAndCols( - rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); - rli.close(); -} - -{ - // Beginning and end of line - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - fi.emit('keypress', '.', { ctrl: true, name: 'a' }); - assertCursorRowsAndCols(rli, 0, 0); - fi.emit('keypress', '.', { ctrl: true, name: 'e' }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); -} - -{ - // Back and Forward one character - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - assertCursorRowsAndCols(rli, 0, 19); - - // Back one character - fi.emit('keypress', '.', { ctrl: true, name: 'b' }); - assertCursorRowsAndCols(rli, 0, 18); - // Back one character - fi.emit('keypress', '.', { ctrl: true, name: 'b' }); - assertCursorRowsAndCols(rli, 0, 17); - // Forward one character - fi.emit('keypress', '.', { ctrl: true, name: 'f' }); - assertCursorRowsAndCols(rli, 0, 18); - // Forward one character - fi.emit('keypress', '.', { ctrl: true, name: 'f' }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); -} - -// Back and Forward one astral character -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'đť'); - - // Move left one character/code point - fi.emit('keypress', '.', { name: 'left' }); - assertCursorRowsAndCols(rli, 0, 0); - - // Move right one character/code point - fi.emit('keypress', '.', { name: 'right' }); - assertCursorRowsAndCols(rli, 0, 2); - - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'đť'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// Two astral characters left -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'đť'); - - // Move left one character/code point - fi.emit('keypress', '.', { name: 'left' }); - assertCursorRowsAndCols(rli, 0, 0); - - fi.emit('data', 'đ'); - assertCursorRowsAndCols(rli, 0, 2); - - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'đđť'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// Two astral characters right -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'đť'); - - // Move left one character/code point - fi.emit('keypress', '.', { name: 'right' }); - assertCursorRowsAndCols(rli, 0, 2); - - fi.emit('data', 'đ'); - assertCursorRowsAndCols(rli, 0, 4); - - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'đťđ'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -{ - // `wordLeft` and `wordRight` - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - fi.emit('keypress', '.', { ctrl: true, name: 'left' }); - assertCursorRowsAndCols(rli, 0, 16); - fi.emit('keypress', '.', { meta: true, name: 'b' }); - assertCursorRowsAndCols(rli, 0, 10); - fi.emit('keypress', '.', { ctrl: true, name: 'right' }); - assertCursorRowsAndCols(rli, 0, 16); - fi.emit('keypress', '.', { meta: true, name: 'f' }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); -} - -// `deleteWordLeft` -[ - { ctrl: true, name: 'w' }, - { ctrl: true, name: 'backspace' }, - { meta: true, name: 'backspace' }, -].forEach((deleteWordLeftKey) => { - let [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - fi.emit('keypress', '.', { ctrl: true, name: 'left' }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'the quick fox'); - })); - fi.emit('keypress', '.', deleteWordLeftKey); - fi.emit('data', '\n'); - rli.close(); - - // No effect if pressed at beginning of line - [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - fi.emit('keypress', '.', { ctrl: true, name: 'a' }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'the quick brown fox'); - })); - fi.emit('keypress', '.', deleteWordLeftKey); - fi.emit('data', '\n'); - rli.close(); -}); - -// `deleteWordRight` -[ - { ctrl: true, name: 'delete' }, - { meta: true, name: 'delete' }, - { meta: true, name: 'd' }, -].forEach((deleteWordRightKey) => { - let [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - fi.emit('keypress', '.', { ctrl: true, name: 'left' }); - fi.emit('keypress', '.', { ctrl: true, name: 'left' }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'the quick fox'); - })); - fi.emit('keypress', '.', deleteWordRightKey); - fi.emit('data', '\n'); - rli.close(); - - // No effect if pressed at end of line - [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'the quick brown fox'); - })); - fi.emit('keypress', '.', deleteWordRightKey); - fi.emit('data', '\n'); - rli.close(); -}); - -// deleteLeft -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - assertCursorRowsAndCols(rli, 0, 19); - - // Delete left character - fi.emit('keypress', '.', { ctrl: true, name: 'h' }); - assertCursorRowsAndCols(rli, 0, 18); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'the quick brown fo'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// deleteLeft astral character -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'đť'); - assertCursorRowsAndCols(rli, 0, 2); - // Delete left character - fi.emit('keypress', '.', { ctrl: true, name: 'h' }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, ''); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// deleteRight -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - - // Go to the start of the line - fi.emit('keypress', '.', { ctrl: true, name: 'a' }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete right character - fi.emit('keypress', '.', { ctrl: true, name: 'd' }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'he quick brown fox'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// deleteRight astral character -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'đť'); - - // Go to the start of the line - fi.emit('keypress', '.', { ctrl: true, name: 'a' }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete right character - fi.emit('keypress', '.', { ctrl: true, name: 'd' }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, ''); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// deleteLineLeft -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - assertCursorRowsAndCols(rli, 0, 19); - - // Delete from current to start of line - fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, ''); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// deleteLineRight -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick brown fox'); - - // Go to the start of the line - fi.emit('keypress', '.', { ctrl: true, name: 'a' }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete from current to end of line - fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, ''); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// Close readline interface -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('keypress', '.', { ctrl: true, name: 'c' }); - assert(rli.closed); -} - -// Multi-line input cursor position -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.columns = 10; - fi.emit('data', 'multi-line text'); - assertCursorRowsAndCols(rli, 1, 5); - rli.close(); -} - -// Multi-line input cursor position and long tabs -{ - const [rli, fi] = getInterface({ tabSize: 16, terminal: true, prompt: '' }); - fi.columns = 10; - fi.emit('data', 'multi-line\ttext \t'); - assert.strictEqual(rli.cursor, 17); - assertCursorRowsAndCols(rli, 3, 2); - rli.close(); -} - -// Check for the default tab size. -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - fi.emit('data', 'the quick\tbrown\tfox'); - assert.strictEqual(rli.cursor, 19); - // The first tab is 7 spaces long, the second one 3 spaces. - assertCursorRowsAndCols(rli, 0, 27); -} - -// Multi-line prompt cursor position -{ - const [rli, fi] = getInterface({ - terminal: true, - prompt: '\nfilledline\nwraping text\n> ' - }); - fi.columns = 10; - fi.emit('data', 't'); - assertCursorRowsAndCols(rli, 4, 3); - rli.close(); -} - -// Clear the whole screen -{ - const [rli, fi] = getInterface({ terminal: true, prompt: '' }); - const lines = ['line 1', 'line 2', 'line 3']; - fi.emit('data', lines.join('\n')); - fi.emit('keypress', '.', { ctrl: true, name: 'l' }); - assertCursorRowsAndCols(rli, 0, 6); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'line 3'); - })); - fi.emit('data', '\n'); - rli.close(); -} - -// Wide characters should be treated as two columns. -assert.strictEqual(getStringWidth('a'), 1); -assert.strictEqual(getStringWidth('ă'), 2); -assert.strictEqual(getStringWidth('č°˘'), 2); -assert.strictEqual(getStringWidth('ęł '), 2); -assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); -assert.strictEqual(getStringWidth('abcde'), 5); -assert.strictEqual(getStringWidth('ĺ¤ćą ă'), 6); -assert.strictEqual(getStringWidth('ăăźă.js'), 9); -assert.strictEqual(getStringWidth('ä˝ ĺĽ˝'), 4); -assert.strictEqual(getStringWidth('ěë
íě¸ě'), 10); -assert.strictEqual(getStringWidth('A\ud83c\ude00BC'), 5); -assert.strictEqual(getStringWidth('đ¨âđŠâđŚâđŚ'), 8); -assert.strictEqual(getStringWidth('đđˇăđťđ'), 9); -// TODO(BridgeAR): This should have a width of 4. -assert.strictEqual(getStringWidth('âŹâŞ'), 2); -assert.strictEqual(getStringWidth('\u0301\u200D\u200E'), 0); - -// Check if vt control chars are stripped -assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); -assert.strictEqual( - stripVTControlCharacters('\u001b[31m> \u001b[39m> '), - '> > ' -); -assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); -assert.strictEqual(stripVTControlCharacters('> '), '> '); -assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); -assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); -assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); -assert.strictEqual(getStringWidth('> '), 2); - -// FIXME(bartlomieju): this causes hang -// Check EventEmitter memory leak -// for (let i = 0; i < 12; i++) { -// const rl = readline.createInterface({ -// input: process.stdin, -// output: process.stdout -// }); -// rl.close(); -// assert.strictEqual(isWarned(process.stdin._events), false); -// assert.strictEqual(isWarned(process.stdout._events), false); -// } - -[true, false].forEach((terminal) => { - // Disable history - { - const [rli, fi] = getInterface({ terminal, historySize: 0 }); - assert.strictEqual(rli.historySize, 0); - - fi.emit('data', 'asdf\n'); - assert.deepStrictEqual(rli.history, []); - rli.close(); - } - - // Default history size 30 - { - const [rli, fi] = getInterface({ terminal }); - assert.strictEqual(rli.historySize, 30); - - fi.emit('data', 'asdf\n'); - assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); - rli.close(); - } - - // Sending a full line - { - const [rli, fi] = getInterface({ terminal }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'asdf'); - })); - fi.emit('data', 'asdf\n'); - } - - // Sending a blank line - { - const [rli, fi] = getInterface({ terminal }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, ''); - })); - fi.emit('data', '\n'); - } - - // Sending a single character with no newline and then a newline - { - const [rli, fi] = getInterface({ terminal }); - let called = false; - rli.on('line', (line) => { - called = true; - assert.strictEqual(line, 'a'); - }); - fi.emit('data', 'a'); - assert.ok(!called); - fi.emit('data', '\n'); - assert.ok(called); - rli.close(); - } - - // Sending multiple newlines at once - { - const [rli, fi] = getInterface({ terminal }); - const expectedLines = ['foo', 'bar', 'baz']; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length)); - fi.emit('data', `${expectedLines.join('\n')}\n`); - rli.close(); - } - - // Sending multiple newlines at once that does not end with a new line - { - const [rli, fi] = getInterface({ terminal }); - const expectedLines = ['foo', 'bar', 'baz', 'bat']; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length - 1)); - fi.emit('data', expectedLines.join('\n')); - rli.close(); - } - - // Sending multiple newlines at once that does not end with a new(empty) - // line and a `end` event - { - const [rli, fi] = getInterface({ terminal }); - const expectedLines = ['foo', 'bar', 'baz', '']; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length - 1)); - rli.on('close', common.mustCall()); - fi.emit('data', expectedLines.join('\n')); - fi.emit('end'); - rli.close(); - } - - // Sending a multi-byte utf8 char over multiple writes - { - const buf = Buffer.from('âŽ', 'utf8'); - const [rli, fi] = getInterface({ terminal }); - let callCount = 0; - rli.on('line', (line) => { - callCount++; - assert.strictEqual(line, buf.toString('utf8')); - }); - for (const i of buf) { - fi.emit('data', Buffer.from([i])); - } - assert.strictEqual(callCount, 0); - fi.emit('data', '\n'); - assert.strictEqual(callCount, 1); - rli.close(); - } - - // Calling readline without `new` - { - const [rli, fi] = getInterface({ terminal }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'asdf'); - })); - fi.emit('data', 'asdf\n'); - rli.close(); - } - - // Calling the question callback - { - const [rli] = getInterface({ terminal }); - rli.question('foo?', common.mustCall((answer) => { - assert.strictEqual(answer, 'bar'); - })); - rli.write('bar\n'); - rli.close(); - } - - // Calling the question multiple times - { - const [rli] = getInterface({ terminal }); - rli.question('foo?', common.mustCall((answer) => { - assert.strictEqual(answer, 'baz'); - })); - rli.question('bar?', common.mustNotCall(() => { - })); - rli.write('baz\n'); - rli.close(); - } - - // Calling the promisified question - { - const [rli] = getInterface({ terminal }); - const question = util.promisify(rli.question).bind(rli); - question('foo?') - .then(common.mustCall((answer) => { - assert.strictEqual(answer, 'bar'); - })); - rli.write('bar\n'); - rli.close(); - } - - // Aborting a question - { - const ac = new AbortController(); - const signal = ac.signal; - const [rli] = getInterface({ terminal }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'bar'); - })); - rli.question('hello?', { signal }, common.mustNotCall()); - ac.abort(); - rli.write('bar\n'); - rli.close(); - } - - // Aborting a promisified question - { - const ac = new AbortController(); - const signal = ac.signal; - const [rli] = getInterface({ terminal }); - const question = util.promisify(rli.question).bind(rli); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'bar'); - })); - question('hello?', { signal }) - .then(common.mustNotCall()) - .catch(common.mustCall((error) => { - assert.strictEqual(error.name, 'AbortError'); - })); - ac.abort(); - rli.write('bar\n'); - rli.close(); - } - - // pre-aborted signal - { - const signal = AbortSignal.abort(); - const [rli] = getInterface({ terminal }); - rli.pause(); - rli.on('resume', common.mustNotCall()); - rli.question('hello?', { signal }, common.mustNotCall()); - rli.close(); - } - - // pre-aborted signal promisified question - { - const signal = AbortSignal.abort(); - const [rli] = getInterface({ terminal }); - const question = util.promisify(rli.question).bind(rli); - rli.on('resume', common.mustNotCall()); - rli.pause(); - question('hello?', { signal }) - .then(common.mustNotCall()) - .catch(common.mustCall((error) => { - assert.strictEqual(error.name, 'AbortError'); - })); - rli.close(); - } - - // Can create a new readline Interface with a null output argument - { - const [rli, fi] = getInterface({ output: null, terminal }); - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, 'asdf'); - })); - fi.emit('data', 'asdf\n'); - - rli.setPrompt('ddd> '); - rli.prompt(); - rli.write("really shouldn't be seeing this"); - rli.question('What do you think of node.js? ', (answer) => { - console.log('Thank you for your valuable feedback:', answer); - rli.close(); - }); - } - - // Calling the getPrompt method - { - const expectedPrompts = ['$ ', '> ']; - const [rli] = getInterface({ terminal }); - for (const prompt of expectedPrompts) { - rli.setPrompt(prompt); - assert.strictEqual(rli.getPrompt(), prompt); - } - } - - { - const expected = terminal ? - ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : - ['$ ']; - - const output = new Writable({ - write: common.mustCall((chunk, enc, cb) => { - assert.strictEqual(chunk.toString(), expected.shift()); - cb(); - rl.close(); - }, expected.length) - }); - - const rl = readline.createInterface({ - input: new Readable({ read: common.mustCall() }), - output, - prompt: '$ ', - terminal - }); - - rl.prompt(); - - assert.strictEqual(rl.getPrompt(), '$ '); - } - - { - const fi = new FakeInput(); - assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); - } - - // Emit two line events when the delay - // between \r and \n exceeds crlfDelay - { - const crlfDelay = 200; - const [rli, fi] = getInterface({ terminal, crlfDelay }); - let callCount = 0; - rli.on('line', () => { - callCount++; - }); - fi.emit('data', '\r'); - setTimeout(common.mustCall(() => { - fi.emit('data', '\n'); - assert.strictEqual(callCount, 2); - rli.close(); - }), crlfDelay + 10); - } - - // For the purposes of the following tests, we do not care about the exact - // value of crlfDelay, only that the behaviour conforms to what's expected. - // Setting it to Infinity allows the test to succeed even under extreme - // CPU stress. - const crlfDelay = Infinity; - - // Set crlfDelay to `Infinity` is allowed - { - const delay = 200; - const [rli, fi] = getInterface({ terminal, crlfDelay }); - let callCount = 0; - rli.on('line', () => { - callCount++; - }); - fi.emit('data', '\r'); - setTimeout(common.mustCall(() => { - fi.emit('data', '\n'); - assert.strictEqual(callCount, 1); - rli.close(); - }), delay); - } - - // Sending multiple newlines at once that does not end with a new line - // and a `end` event(last line is) - - // \r\n should emit one line event, not two - { - const [rli, fi] = getInterface({ terminal, crlfDelay }); - const expectedLines = ['foo', 'bar', 'baz', 'bat']; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length - 1)); - fi.emit('data', expectedLines.join('\r\n')); - rli.close(); - } - - // \r\n should emit one line event when split across multiple writes. - { - const [rli, fi] = getInterface({ terminal, crlfDelay }); - const expectedLines = ['foo', 'bar', 'baz', 'bat']; - let callCount = 0; - rli.on('line', common.mustCall((line) => { - assert.strictEqual(line, expectedLines[callCount]); - callCount++; - }, expectedLines.length)); - expectedLines.forEach((line) => { - fi.emit('data', `${line}\r`); - fi.emit('data', '\n'); - }); - rli.close(); - } - - // Emit one line event when the delay between \r and \n is - // over the default crlfDelay but within the setting value. - { - const delay = 125; - const [rli, fi] = getInterface({ terminal, crlfDelay }); - let callCount = 0; - rli.on('line', () => callCount++); - fi.emit('data', '\r'); - setTimeout(common.mustCall(() => { - fi.emit('data', '\n'); - assert.strictEqual(callCount, 1); - rli.close(); - }), delay); - } -}); - -// Ensure that the _wordLeft method works even for large input -{ - const input = new Readable({ - read() { - this.push('\x1B[1;5D'); // CTRL + Left - this.push(null); - }, - }); - const output = new Writable({ - write: common.mustCall((data, encoding, cb) => { - assert.strictEqual(rl.cursor, rl.line.length - 1); - cb(); - }), - }); - const rl = new readline.createInterface({ - input, - output, - terminal: true, - }); - rl.line = `a${' '.repeat(1e6)}a`; - rl.cursor = rl.line.length; -} - -// FIXME(bartlomieju): these tests depend on "event_target" module -// { -// const fi = new FakeInput(); -// const signal = AbortSignal.abort(); - -// const rl = readline.createInterface({ -// input: fi, -// output: fi, -// signal, -// }); -// rl.on('close', common.mustCall()); -// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); -// } - -// { -// const fi = new FakeInput(); -// const ac = new AbortController(); -// const { signal } = ac; -// const rl = readline.createInterface({ -// input: fi, -// output: fi, -// signal, -// }); -// assert.strictEqual(getEventListeners(signal, 'abort').length, 1); -// rl.on('close', common.mustCall()); -// ac.abort(); -// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); -// } - -// { -// const fi = new FakeInput(); -// const ac = new AbortController(); -// const { signal } = ac; -// const rl = readline.createInterface({ -// input: fi, -// output: fi, -// signal, -// }); -// assert.strictEqual(getEventListeners(signal, 'abort').length, 1); -// rl.close(); -// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); -// } - -{ - // Constructor throws if signal is not an abort signal - assert.throws(() => { - readline.createInterface({ - input: new FakeInput(), - signal: {}, - }); - }, { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE' - }); -} |