| // 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'); |
| |
| const assert = require('assert'); |
| const readline = require('readline'); |
| const internalReadline = require('internal/readline'); |
| const EventEmitter = require('events').EventEmitter; |
| 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; |
| } |
| |
| { |
| // Default crlfDelay is 100ms |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ input: fi, output: fi }); |
| assert.strictEqual(rli.crlfDelay, 100); |
| rli.close(); |
| } |
| |
| { |
| // Minimum crlfDelay is 100ms |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ input: fi, output: fi, crlfDelay: 0 }); |
| assert.strictEqual(rli.crlfDelay, 100); |
| rli.close(); |
| } |
| |
| { |
| // set crlfDelay to float 100.5ms |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| crlfDelay: 100.5 |
| }); |
| assert.strictEqual(rli.crlfDelay, 100.5); |
| rli.close(); |
| } |
| |
| { |
| // set crlfDelay to 5000ms |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| crlfDelay: 5000 |
| }); |
| assert.strictEqual(rli.crlfDelay, 5000); |
| rli.close(); |
| } |
| |
| [ true, false ].forEach(function(terminal) { |
| // disable history |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal, historySize: 0 } |
| ); |
| assert.strictEqual(rli.historySize, 0); |
| |
| fi.emit('data', 'asdf\n'); |
| assert.deepStrictEqual(rli.history, terminal ? [] : undefined); |
| rli.close(); |
| } |
| |
| // default history size 30 |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| assert.strictEqual(rli.historySize, 30); |
| |
| fi.emit('data', 'asdf\n'); |
| assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : undefined); |
| rli.close(); |
| } |
| |
| // sending a full line |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| let called = false; |
| rli.on('line', function(line) { |
| called = true; |
| assert.strictEqual(line, 'asdf'); |
| }); |
| fi.emit('data', 'asdf\n'); |
| assert.ok(called); |
| } |
| |
| // sending a blank line |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| let called = false; |
| rli.on('line', function(line) { |
| called = true; |
| assert.strictEqual(line, ''); |
| }); |
| fi.emit('data', '\n'); |
| assert.ok(called); |
| } |
| |
| // sending a single character with no newline |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface(fi, {}); |
| let called = false; |
| rli.on('line', function(line) { |
| called = true; |
| }); |
| fi.emit('data', 'a'); |
| assert.ok(!called); |
| rli.close(); |
| } |
| |
| // sending a single character with no newline and then a newline |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| let called = false; |
| rli.on('line', function(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 fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| const expectedLines = ['foo', 'bar', 'baz']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| fi.emit('data', `${expectedLines.join('\n')}\n`); |
| assert.strictEqual(callCount, expectedLines.length); |
| rli.close(); |
| } |
| |
| // sending multiple newlines at once that does not end with a new line |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| const expectedLines = ['foo', 'bar', 'baz', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| fi.emit('data', expectedLines.join('\n')); |
| assert.strictEqual(callCount, expectedLines.length - 1); |
| rli.close(); |
| } |
| |
| // sending multiple newlines at once that does not end with a new(empty) |
| // line and a `end` event |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| const expectedLines = ['foo', 'bar', 'baz', '']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| rli.on('close', function() { |
| callCount++; |
| }); |
| fi.emit('data', expectedLines.join('\n')); |
| fi.emit('end'); |
| assert.strictEqual(callCount, expectedLines.length); |
| 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 fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: true } |
| ); |
| const expectedLines = ['foo', 'bar', 'baz', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| fi.emit('data', expectedLines.join('\r')); |
| assert.strictEqual(callCount, expectedLines.length - 1); |
| rli.close(); |
| } |
| |
| // \r at start of input should output blank line |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: true } |
| ); |
| const expectedLines = ['', 'foo' ]; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| fi.emit('data', '\rfoo\r'); |
| assert.strictEqual(callCount, expectedLines.length); |
| rli.close(); |
| } |
| |
| // Emit two line events when the delay |
| // between \r and \n exceeds crlfDelay |
| { |
| const fi = new FakeInput(); |
| const delay = 200; |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: terminal, |
| crlfDelay: delay |
| }); |
| let callCount = 0; |
| rli.on('line', function(line) { |
| callCount++; |
| }); |
| fi.emit('data', '\r'); |
| setTimeout(common.mustCall(() => { |
| fi.emit('data', '\n'); |
| assert.strictEqual(callCount, 2); |
| rli.close(); |
| }), delay * 2); |
| } |
| |
| // set crlfDelay to `Infinity` is allowed |
| { |
| const fi = new FakeInput(); |
| const delay = 200; |
| const crlfDelay = Infinity; |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: terminal, |
| crlfDelay |
| }); |
| let callCount = 0; |
| rli.on('line', function(line) { |
| callCount++; |
| }); |
| fi.emit('data', '\r'); |
| setTimeout(common.mustCall(() => { |
| fi.emit('data', '\n'); |
| assert.strictEqual(callCount, 1); |
| rli.close(); |
| }), delay); |
| } |
| |
| // \t when there is no completer function should behave like an ordinary |
| // character |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: true } |
| ); |
| let called = false; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, '\t'); |
| assert.strictEqual(called, false); |
| called = true; |
| }); |
| fi.emit('data', '\t'); |
| fi.emit('data', '\n'); |
| assert.ok(called); |
| rli.close(); |
| } |
| |
| // \t does not become part of the input when there is a completer function |
| { |
| const fi = new FakeInput(); |
| const completer = (line) => [[], line]; |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: true, |
| completer: completer |
| }); |
| let called = false; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, 'foo'); |
| assert.strictEqual(called, false); |
| called = true; |
| }); |
| for (const character of '\tfo\to\t') { |
| fi.emit('data', character); |
| } |
| fi.emit('data', '\n'); |
| assert.ok(called); |
| rli.close(); |
| } |
| |
| // constructor throws if completer is not a function or undefined |
| { |
| const fi = new FakeInput(); |
| common.expectsError(function() { |
| readline.createInterface({ |
| input: fi, |
| completer: 'string is not valid' |
| }); |
| }, { |
| type: TypeError, |
| code: 'ERR_INVALID_OPT_VALUE' |
| }); |
| } |
| |
| // constructor throws if historySize is not a positive number |
| { |
| const fi = new FakeInput(); |
| common.expectsError(function() { |
| readline.createInterface({ |
| input: fi, historySize: 'not a number' |
| }); |
| }, { |
| type: RangeError, |
| code: 'ERR_INVALID_OPT_VALUE' |
| }); |
| |
| common.expectsError(function() { |
| readline.createInterface({ |
| input: fi, historySize: -1 |
| }); |
| }, { |
| type: RangeError, |
| code: 'ERR_INVALID_OPT_VALUE' |
| }); |
| |
| common.expectsError(function() { |
| readline.createInterface({ |
| input: fi, historySize: NaN |
| }); |
| }, { |
| type: RangeError, |
| code: 'ERR_INVALID_OPT_VALUE' |
| }); |
| } |
| |
| // duplicate lines are removed from history when |
| // `options.removeHistoryDuplicates` is `true` |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: true, |
| removeHistoryDuplicates: true |
| }); |
| const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(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'); |
| fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar' |
| assert.strictEqual(rli.line, 'bar'); |
| fi.emit('keypress', '.', { name: 'down' }); // 'bat' |
| assert.strictEqual(rli.line, 'bat'); |
| fi.emit('keypress', '.', { name: 'down' }); // '' |
| assert.strictEqual(rli.line, ''); |
| rli.close(); |
| } |
| |
| // duplicate lines are not removed from history when |
| // `options.removeHistoryDuplicates` is `false` |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: true, |
| removeHistoryDuplicates: false |
| }); |
| const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(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(); |
| } |
| |
| // sending a multi-byte utf8 char over multiple writes |
| { |
| const buf = Buffer.from('☮', 'utf8'); |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| let callCount = 0; |
| rli.on('line', function(line) { |
| callCount++; |
| assert.strictEqual(line, buf.toString('utf8')); |
| }); |
| [].forEach.call(buf, function(i) { |
| fi.emit('data', Buffer.from([i])); |
| }); |
| assert.strictEqual(callCount, 0); |
| fi.emit('data', '\n'); |
| assert.strictEqual(callCount, 1); |
| rli.close(); |
| } |
| |
| // Regression test for repl freeze, #1968: |
| // check that nothing fails if 'keypress' event throws. |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: true } |
| ); |
| const keys = []; |
| fi.on('keypress', function(key) { |
| keys.push(key); |
| if (key === 'X') { |
| throw new Error('bad thing happened'); |
| } |
| }); |
| try { |
| fi.emit('data', 'fooX'); |
| } catch { } |
| fi.emit('data', 'bar'); |
| assert.strictEqual(keys.join(''), 'fooXbar'); |
| rli.close(); |
| } |
| |
| // calling readline without `new` |
| { |
| const fi = new FakeInput(); |
| const rli = readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| let called = false; |
| rli.on('line', function(line) { |
| called = true; |
| assert.strictEqual(line, 'asdf'); |
| }); |
| fi.emit('data', 'asdf\n'); |
| assert.ok(called); |
| rli.close(); |
| } |
| |
| // calling the question callback |
| { |
| let called = false; |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| rli.question('foo?', function(answer) { |
| called = true; |
| assert.strictEqual(answer, 'bar'); |
| }); |
| rli.write('bar\n'); |
| assert.ok(called); |
| rli.close(); |
| } |
| |
| if (terminal) { |
| // history is bound |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal, 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 fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| const expectedLines = ['foo']; |
| rli.question(expectedLines[0], function() { |
| rli.close(); |
| }); |
| const cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, expectedLines[0].length); |
| rli.close(); |
| } |
| |
| // sending a multi-line question |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: fi, terminal: terminal } |
| ); |
| const expectedLines = ['foo', 'bar']; |
| rli.question(expectedLines.join('\n'), function() { |
| rli.close(); |
| }); |
| const cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, expectedLines.length - 1); |
| assert.strictEqual(cursorPos.cols, expectedLines.slice(-1)[0].length); |
| rli.close(); |
| } |
| |
| { |
| // Beginning and end of line |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| fi.emit('keypress', '.', { ctrl: true, name: 'a' }); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| fi.emit('keypress', '.', { ctrl: true, name: 'e' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| rli.close(); |
| } |
| |
| { |
| // Back and Forward one character |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| |
| // Back one character |
| fi.emit('keypress', '.', { ctrl: true, name: 'b' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 18); |
| // Back one character |
| fi.emit('keypress', '.', { ctrl: true, name: 'b' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 17); |
| // Forward one character |
| fi.emit('keypress', '.', { ctrl: true, name: 'f' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 18); |
| // Forward one character |
| fi.emit('keypress', '.', { ctrl: true, name: 'f' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| rli.close(); |
| } |
| |
| { |
| // `wordLeft` and `wordRight` |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| fi.emit('keypress', '.', { ctrl: true, name: 'left' }); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 16); |
| fi.emit('keypress', '.', { meta: true, name: 'b' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 10); |
| fi.emit('keypress', '.', { ctrl: true, name: 'right' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 16); |
| fi.emit('keypress', '.', { meta: true, name: 'f' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| rli.close(); |
| } |
| |
| { |
| // `deleteWordLeft` |
| [ |
| { ctrl: true, name: 'w' }, |
| { ctrl: true, name: 'backspace' }, |
| { meta: true, name: 'backspace' } |
| ] |
| .forEach((deleteWordLeftKey) => { |
| let fi = new FakeInput(); |
| let rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| 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 |
| fi = new FakeInput(); |
| rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| 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 fi = new FakeInput(); |
| let rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| 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 |
| fi = new FakeInput(); |
| rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| 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 fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| |
| // Delete left character |
| fi.emit('keypress', '.', { ctrl: true, name: 'h' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 18); |
| rli.on('line', common.mustCall((line) => { |
| assert.strictEqual(line, 'the quick brown fo'); |
| })); |
| fi.emit('data', '\n'); |
| rli.close(); |
| } |
| |
| // deleteRight |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| |
| // Go to the start of the line |
| fi.emit('keypress', '.', { ctrl: true, name: 'a' }); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| |
| // Delete right character |
| fi.emit('keypress', '.', { ctrl: true, name: 'd' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| rli.on('line', common.mustCall((line) => { |
| assert.strictEqual(line, 'he quick brown fox'); |
| })); |
| fi.emit('data', '\n'); |
| rli.close(); |
| } |
| |
| |
| // deleteLineLeft |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 19); |
| |
| // Delete from current to start of line |
| fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| rli.on('line', common.mustCall((line) => { |
| assert.strictEqual(line, ''); |
| })); |
| fi.emit('data', '\n'); |
| rli.close(); |
| } |
| |
| // deleteLineRight |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.emit('data', 'the quick brown fox'); |
| |
| // Go to the start of the line |
| fi.emit('keypress', '.', { ctrl: true, name: 'a' }); |
| let cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| |
| // Delete from current to end of line |
| fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); |
| cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 0); |
| rli.on('line', common.mustCall((line) => { |
| assert.strictEqual(line, ''); |
| })); |
| fi.emit('data', '\n'); |
| rli.close(); |
| } |
| |
| // multi-line cursor position |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| fi.columns = 10; |
| fi.emit('data', 'multi-line text'); |
| const cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 1); |
| assert.strictEqual(cursorPos.cols, 5); |
| rli.close(); |
| } |
| |
| // Clear the whole screen |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| prompt: '', |
| terminal: terminal |
| }); |
| const lines = ['line 1', 'line 2', 'line 3']; |
| fi.emit('data', lines.join('\n')); |
| fi.emit('keypress', '.', { ctrl: true, name: 'l' }); |
| const cursorPos = rli._getCursorPos(); |
| assert.strictEqual(cursorPos.rows, 0); |
| assert.strictEqual(cursorPos.cols, 6); |
| rli.on('line', common.mustCall((line) => { |
| assert.strictEqual(line, 'line 3'); |
| })); |
| fi.emit('data', '\n'); |
| rli.close(); |
| } |
| } |
| |
| // isFullWidthCodePoint() should return false for non-numeric values |
| [true, false, null, undefined, {}, [], 'あ'].forEach((v) => { |
| assert.strictEqual(internalReadline.isFullWidthCodePoint('あ'), false); |
| }); |
| |
| // wide characters should be treated as two columns. |
| assert.strictEqual(internalReadline.isFullWidthCodePoint('a'.charCodeAt(0)), |
| false); |
| assert.strictEqual(internalReadline.isFullWidthCodePoint('あ'.charCodeAt(0)), |
| true); |
| assert.strictEqual(internalReadline.isFullWidthCodePoint('谢'.charCodeAt(0)), |
| true); |
| assert.strictEqual(internalReadline.isFullWidthCodePoint('고'.charCodeAt(0)), |
| true); |
| assert.strictEqual(internalReadline.isFullWidthCodePoint(0x1f251), true); |
| assert.strictEqual(internalReadline.getStringWidth('abcde'), 5); |
| assert.strictEqual(internalReadline.getStringWidth('古池や'), 6); |
| assert.strictEqual(internalReadline.getStringWidth('ノード.js'), 9); |
| assert.strictEqual(internalReadline.getStringWidth('你好'), 4); |
| assert.strictEqual(internalReadline.getStringWidth('안녕하세요'), 10); |
| assert.strictEqual(internalReadline.getStringWidth('A\ud83c\ude00BC'), 5); |
| |
| // check if vt control chars are stripped |
| assert.strictEqual( |
| internalReadline.stripVTControlCharacters('\u001b[31m> \u001b[39m'), |
| '> ' |
| ); |
| assert.strictEqual( |
| internalReadline.stripVTControlCharacters('\u001b[31m> \u001b[39m> '), |
| '> > ' |
| ); |
| assert.strictEqual( |
| internalReadline.stripVTControlCharacters('\u001b[31m\u001b[39m'), |
| '' |
| ); |
| assert.strictEqual( |
| internalReadline.stripVTControlCharacters('> '), |
| '> ' |
| ); |
| assert.strictEqual(internalReadline |
| .getStringWidth('\u001b[31m> \u001b[39m'), 2); |
| assert.strictEqual(internalReadline |
| .getStringWidth('\u001b[31m> \u001b[39m> '), 4); |
| assert.strictEqual(internalReadline |
| .getStringWidth('\u001b[31m\u001b[39m'), 0); |
| assert.strictEqual(internalReadline.getStringWidth('> '), 2); |
| |
| { |
| const fi = new FakeInput(); |
| assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); |
| } |
| |
| // 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); |
| } |
| |
| // can create a new readline Interface with a null output argument |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { input: fi, output: null, terminal: terminal } |
| ); |
| |
| let called = false; |
| rli.on('line', function(line) { |
| called = true; |
| assert.strictEqual(line, 'asdf'); |
| }); |
| fi.emit('data', 'asdf\n'); |
| assert.ok(called); |
| |
| rli.setPrompt('ddd> '); |
| rli.prompt(); |
| rli.write('really shouldnt be seeing this'); |
| rli.question('What do you think of node.js? ', function(answer) { |
| console.log('Thank you for your valuable feedback:', answer); |
| rli.close(); |
| }); |
| } |
| |
| { |
| const expected = terminal ? |
| ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : |
| ['$ ']; |
| |
| let counter = 0; |
| const output = new Writable({ |
| write: common.mustCall((chunk, enc, cb) => { |
| assert.strictEqual(chunk.toString(), expected[counter++]); |
| cb(); |
| rl.close(); |
| }, expected.length) |
| }); |
| |
| const rl = readline.createInterface({ |
| input: new Readable({ read: common.mustCall() }), |
| output: output, |
| prompt: '$ ', |
| terminal: terminal |
| }); |
| |
| rl.prompt(); |
| |
| assert.strictEqual(rl._prompt, '$ '); |
| } |
| }); |
| |
| // 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; |
| |
| [ true, false ].forEach(function(terminal) { |
| // 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 fi = new FakeInput(); |
| const rli = new readline.Interface( |
| { |
| input: fi, |
| output: fi, |
| terminal: terminal, |
| crlfDelay |
| } |
| ); |
| const expectedLines = ['foo', 'bar', 'baz', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| fi.emit('data', expectedLines.join('\r\n')); |
| assert.strictEqual(callCount, expectedLines.length - 1); |
| rli.close(); |
| } |
| |
| // \r\n should emit one line event when split across multiple writes. |
| { |
| const fi = new FakeInput(); |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: terminal, |
| crlfDelay |
| }); |
| const expectedLines = ['foo', 'bar', 'baz', 'bat']; |
| let callCount = 0; |
| rli.on('line', function(line) { |
| assert.strictEqual(line, expectedLines[callCount]); |
| callCount++; |
| }); |
| expectedLines.forEach(function(line) { |
| fi.emit('data', `${line}\r`); |
| fi.emit('data', '\n'); |
| }); |
| assert.strictEqual(callCount, expectedLines.length); |
| rli.close(); |
| } |
| |
| // Emit one line event when the delay between \r and \n is |
| // over the default crlfDelay but within the setting value. |
| { |
| const fi = new FakeInput(); |
| const delay = 125; |
| const rli = new readline.Interface({ |
| input: fi, |
| output: fi, |
| terminal: 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); |
| } |
| }); |