1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22// Flags: --expose-internals --experimental-abortcontroller 23'use strict'; 24const common = require('../common'); 25common.skipIfDumbTerminal(); 26 27const assert = require('assert'); 28const readline = require('readline'); 29const util = require('util'); 30const { 31 getStringWidth, 32 stripVTControlCharacters 33} = require('internal/util/inspect'); 34const { EventEmitter, getEventListeners } = require('events'); 35const { Writable, Readable } = require('stream'); 36 37class FakeInput extends EventEmitter { 38 resume() {} 39 pause() {} 40 write() {} 41 end() {} 42} 43 44function isWarned(emitter) { 45 for (const name in emitter) { 46 const listeners = emitter[name]; 47 if (listeners.warned) return true; 48 } 49 return false; 50} 51 52function getInterface(options) { 53 const fi = new FakeInput(); 54 const rli = new readline.Interface({ 55 input: fi, 56 output: fi, 57 ...options, 58 }); 59 return [rli, fi]; 60} 61 62function assertCursorRowsAndCols(rli, rows, cols) { 63 const cursorPos = rli.getCursorPos(); 64 assert.strictEqual(cursorPos.rows, rows); 65 assert.strictEqual(cursorPos.cols, cols); 66} 67 68{ 69 const input = new FakeInput(); 70 const rl = readline.Interface({ input }); 71 assert(rl instanceof readline.Interface); 72} 73 74[ 75 undefined, 76 50, 77 0, 78 100.5, 79 5000, 80].forEach((crlfDelay) => { 81 const [rli] = getInterface({ crlfDelay }); 82 assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); 83 rli.close(); 84}); 85 86{ 87 const input = new FakeInput(); 88 89 // Constructor throws if completer is not a function or undefined 90 assert.throws(() => { 91 readline.createInterface({ 92 input, 93 completer: 'string is not valid' 94 }); 95 }, { 96 name: 'TypeError', 97 code: 'ERR_INVALID_OPT_VALUE' 98 }); 99 100 assert.throws(() => { 101 readline.createInterface({ 102 input, 103 completer: '' 104 }); 105 }, { 106 name: 'TypeError', 107 code: 'ERR_INVALID_OPT_VALUE' 108 }); 109 110 assert.throws(() => { 111 readline.createInterface({ 112 input, 113 completer: false 114 }); 115 }, { 116 name: 'TypeError', 117 code: 'ERR_INVALID_OPT_VALUE' 118 }); 119 120 // Constructor throws if history is not an array 121 ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((history) => { 122 assert.throws(() => { 123 readline.createInterface({ 124 input, 125 history, 126 }); 127 }, { 128 name: 'TypeError', 129 code: 'ERR_INVALID_ARG_TYPE' 130 }); 131 }); 132 133 // Constructor throws if historySize is not a positive number 134 ['not a number', -1, NaN, {}, true, Symbol(), null].forEach((historySize) => { 135 assert.throws(() => { 136 readline.createInterface({ 137 input, 138 historySize, 139 }); 140 }, { 141 name: 'RangeError', 142 code: 'ERR_INVALID_OPT_VALUE' 143 }); 144 }); 145 146 // Check for invalid tab sizes. 147 assert.throws( 148 () => new readline.Interface({ 149 input, 150 tabSize: 0 151 }), 152 { 153 message: 'The value of "tabSize" is out of range. ' + 154 'It must be >= 1 && < 4294967296. Received 0', 155 code: 'ERR_OUT_OF_RANGE' 156 } 157 ); 158 159 assert.throws( 160 () => new readline.Interface({ 161 input, 162 tabSize: '4' 163 }), 164 { code: 'ERR_INVALID_ARG_TYPE' } 165 ); 166 167 assert.throws( 168 () => new readline.Interface({ 169 input, 170 tabSize: 4.5 171 }), 172 { 173 code: 'ERR_OUT_OF_RANGE', 174 message: 'The value of "tabSize" is out of range. ' + 175 'It must be an integer. Received 4.5' 176 } 177 ); 178} 179 180// Sending a single character with no newline 181{ 182 const fi = new FakeInput(); 183 const rli = new readline.Interface(fi, {}); 184 rli.on('line', common.mustNotCall()); 185 fi.emit('data', 'a'); 186 rli.close(); 187} 188 189// Sending multiple newlines at once that does not end with a new line and a 190// `end` event(last line is). \r should behave like \n when alone. 191{ 192 const [rli, fi] = getInterface({ terminal: true }); 193 const expectedLines = ['foo', 'bar', 'baz', 'bat']; 194 rli.on('line', common.mustCall((line) => { 195 assert.strictEqual(line, expectedLines.shift()); 196 }, expectedLines.length - 1)); 197 fi.emit('data', expectedLines.join('\r')); 198 rli.close(); 199} 200 201// \r at start of input should output blank line 202{ 203 const [rli, fi] = getInterface({ terminal: true }); 204 const expectedLines = ['', 'foo' ]; 205 rli.on('line', common.mustCall((line) => { 206 assert.strictEqual(line, expectedLines.shift()); 207 }, expectedLines.length)); 208 fi.emit('data', '\rfoo\r'); 209 rli.close(); 210} 211 212// \t does not become part of the input when there is a completer function 213{ 214 const completer = (line) => [[], line]; 215 const [rli, fi] = getInterface({ terminal: true, completer }); 216 rli.on('line', common.mustCall((line) => { 217 assert.strictEqual(line, 'foo'); 218 })); 219 for (const character of '\tfo\to\t') { 220 fi.emit('data', character); 221 } 222 fi.emit('data', '\n'); 223 rli.close(); 224} 225 226// \t when there is no completer function should behave like an ordinary 227// character 228{ 229 const [rli, fi] = getInterface({ terminal: true }); 230 rli.on('line', common.mustCall((line) => { 231 assert.strictEqual(line, '\t'); 232 })); 233 fi.emit('data', '\t'); 234 fi.emit('data', '\n'); 235 rli.close(); 236} 237 238// Adding history lines should emit the history event with 239// the history array 240{ 241 const [rli, fi] = getInterface({ terminal: true }); 242 const expectedLines = ['foo', 'bar', 'baz', 'bat']; 243 rli.on('history', common.mustCall((history) => { 244 const expectedHistory = expectedLines.slice(0, history.length).reverse(); 245 assert.deepStrictEqual(history, expectedHistory); 246 }, expectedLines.length)); 247 for (const line of expectedLines) { 248 fi.emit('data', `${line}\n`); 249 } 250 rli.close(); 251} 252 253// Altering the history array in the listener should not alter 254// the line being processed 255{ 256 const [rli, fi] = getInterface({ terminal: true }); 257 const expectedLine = 'foo'; 258 rli.on('history', common.mustCall((history) => { 259 assert.strictEqual(history[0], expectedLine); 260 history.shift(); 261 })); 262 rli.on('line', common.mustCall((line) => { 263 assert.strictEqual(line, expectedLine); 264 assert.strictEqual(rli.history.length, 0); 265 })); 266 fi.emit('data', `${expectedLine}\n`); 267 rli.close(); 268} 269 270// Duplicate lines are removed from history when 271// `options.removeHistoryDuplicates` is `true` 272{ 273 const [rli, fi] = getInterface({ 274 terminal: true, 275 removeHistoryDuplicates: true 276 }); 277 const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; 278 // ['foo', 'baz', 'bar', bat']; 279 let callCount = 0; 280 rli.on('line', (line) => { 281 assert.strictEqual(line, expectedLines[callCount]); 282 callCount++; 283 }); 284 fi.emit('data', `${expectedLines.join('\n')}\n`); 285 assert.strictEqual(callCount, expectedLines.length); 286 fi.emit('keypress', '.', { name: 'up' }); // 'bat' 287 assert.strictEqual(rli.line, expectedLines[--callCount]); 288 fi.emit('keypress', '.', { name: 'up' }); // 'bar' 289 assert.notStrictEqual(rli.line, expectedLines[--callCount]); 290 assert.strictEqual(rli.line, expectedLines[--callCount]); 291 fi.emit('keypress', '.', { name: 'up' }); // 'baz' 292 assert.strictEqual(rli.line, expectedLines[--callCount]); 293 fi.emit('keypress', '.', { name: 'up' }); // 'foo' 294 assert.notStrictEqual(rli.line, expectedLines[--callCount]); 295 assert.strictEqual(rli.line, expectedLines[--callCount]); 296 assert.strictEqual(callCount, 0); 297 fi.emit('keypress', '.', { name: 'down' }); // 'baz' 298 assert.strictEqual(rli.line, 'baz'); 299 assert.strictEqual(rli.historyIndex, 2); 300 fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar' 301 assert.strictEqual(rli.line, 'bar'); 302 assert.strictEqual(rli.historyIndex, 1); 303 fi.emit('keypress', '.', { name: 'n', ctrl: true }); 304 assert.strictEqual(rli.line, 'bat'); 305 assert.strictEqual(rli.historyIndex, 0); 306 // Activate the substring history search. 307 fi.emit('keypress', '.', { name: 'down' }); // 'bat' 308 assert.strictEqual(rli.line, 'bat'); 309 assert.strictEqual(rli.historyIndex, -1); 310 // Deactivate substring history search. 311 fi.emit('keypress', '.', { name: 'backspace' }); // 'ba' 312 assert.strictEqual(rli.historyIndex, -1); 313 assert.strictEqual(rli.line, 'ba'); 314 // Activate the substring history search. 315 fi.emit('keypress', '.', { name: 'down' }); // 'ba' 316 assert.strictEqual(rli.historyIndex, -1); 317 assert.strictEqual(rli.line, 'ba'); 318 fi.emit('keypress', '.', { name: 'down' }); // 'ba' 319 assert.strictEqual(rli.historyIndex, -1); 320 assert.strictEqual(rli.line, 'ba'); 321 fi.emit('keypress', '.', { name: 'up' }); // 'bat' 322 assert.strictEqual(rli.historyIndex, 0); 323 assert.strictEqual(rli.line, 'bat'); 324 fi.emit('keypress', '.', { name: 'up' }); // 'bar' 325 assert.strictEqual(rli.historyIndex, 1); 326 assert.strictEqual(rli.line, 'bar'); 327 fi.emit('keypress', '.', { name: 'up' }); // 'baz' 328 assert.strictEqual(rli.historyIndex, 2); 329 assert.strictEqual(rli.line, 'baz'); 330 fi.emit('keypress', '.', { name: 'up' }); // 'ba' 331 assert.strictEqual(rli.historyIndex, 4); 332 assert.strictEqual(rli.line, 'ba'); 333 fi.emit('keypress', '.', { name: 'up' }); // 'ba' 334 assert.strictEqual(rli.historyIndex, 4); 335 assert.strictEqual(rli.line, 'ba'); 336 // Deactivate substring history search and reset history index. 337 fi.emit('keypress', '.', { name: 'right' }); // 'ba' 338 assert.strictEqual(rli.historyIndex, -1); 339 assert.strictEqual(rli.line, 'ba'); 340 // Substring history search activated. 341 fi.emit('keypress', '.', { name: 'up' }); // 'ba' 342 assert.strictEqual(rli.historyIndex, 0); 343 assert.strictEqual(rli.line, 'bat'); 344 rli.close(); 345} 346 347// Duplicate lines are not removed from history when 348// `options.removeHistoryDuplicates` is `false` 349{ 350 const [rli, fi] = getInterface({ 351 terminal: true, 352 removeHistoryDuplicates: false 353 }); 354 const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; 355 let callCount = 0; 356 rli.on('line', (line) => { 357 assert.strictEqual(line, expectedLines[callCount]); 358 callCount++; 359 }); 360 fi.emit('data', `${expectedLines.join('\n')}\n`); 361 assert.strictEqual(callCount, expectedLines.length); 362 fi.emit('keypress', '.', { name: 'up' }); // 'bat' 363 assert.strictEqual(rli.line, expectedLines[--callCount]); 364 fi.emit('keypress', '.', { name: 'up' }); // 'bar' 365 assert.notStrictEqual(rli.line, expectedLines[--callCount]); 366 assert.strictEqual(rli.line, expectedLines[--callCount]); 367 fi.emit('keypress', '.', { name: 'up' }); // 'baz' 368 assert.strictEqual(rli.line, expectedLines[--callCount]); 369 fi.emit('keypress', '.', { name: 'up' }); // 'bar' 370 assert.strictEqual(rli.line, expectedLines[--callCount]); 371 fi.emit('keypress', '.', { name: 'up' }); // 'foo' 372 assert.strictEqual(rli.line, expectedLines[--callCount]); 373 assert.strictEqual(callCount, 0); 374 rli.close(); 375} 376 377// Regression test for repl freeze, #1968: 378// check that nothing fails if 'keypress' event throws. 379{ 380 const [rli, fi] = getInterface({ terminal: true }); 381 const keys = []; 382 const err = new Error('bad thing happened'); 383 fi.on('keypress', (key) => { 384 keys.push(key); 385 if (key === 'X') { 386 throw err; 387 } 388 }); 389 assert.throws( 390 () => fi.emit('data', 'fooX'), 391 (e) => { 392 assert.strictEqual(e, err); 393 return true; 394 } 395 ); 396 fi.emit('data', 'bar'); 397 assert.strictEqual(keys.join(''), 'fooXbar'); 398 rli.close(); 399} 400 401// History is bound 402{ 403 const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); 404 const lines = ['line 1', 'line 2', 'line 3']; 405 fi.emit('data', lines.join('\n') + '\n'); 406 assert.strictEqual(rli.history.length, 2); 407 assert.strictEqual(rli.history[0], 'line 3'); 408 assert.strictEqual(rli.history[1], 'line 2'); 409} 410 411// Question 412{ 413 const [rli] = getInterface({ terminal: true }); 414 const expectedLines = ['foo']; 415 rli.question(expectedLines[0], () => rli.close()); 416 assertCursorRowsAndCols(rli, 0, expectedLines[0].length); 417 rli.close(); 418} 419 420// Sending a multi-line question 421{ 422 const [rli] = getInterface({ terminal: true }); 423 const expectedLines = ['foo', 'bar']; 424 rli.question(expectedLines.join('\n'), () => rli.close()); 425 assertCursorRowsAndCols( 426 rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); 427 rli.close(); 428} 429 430{ 431 // Beginning and end of line 432 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 433 fi.emit('data', 'the quick brown fox'); 434 fi.emit('keypress', '.', { ctrl: true, name: 'a' }); 435 assertCursorRowsAndCols(rli, 0, 0); 436 fi.emit('keypress', '.', { ctrl: true, name: 'e' }); 437 assertCursorRowsAndCols(rli, 0, 19); 438 rli.close(); 439} 440 441{ 442 // Back and Forward one character 443 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 444 fi.emit('data', 'the quick brown fox'); 445 assertCursorRowsAndCols(rli, 0, 19); 446 447 // Back one character 448 fi.emit('keypress', '.', { ctrl: true, name: 'b' }); 449 assertCursorRowsAndCols(rli, 0, 18); 450 // Back one character 451 fi.emit('keypress', '.', { ctrl: true, name: 'b' }); 452 assertCursorRowsAndCols(rli, 0, 17); 453 // Forward one character 454 fi.emit('keypress', '.', { ctrl: true, name: 'f' }); 455 assertCursorRowsAndCols(rli, 0, 18); 456 // Forward one character 457 fi.emit('keypress', '.', { ctrl: true, name: 'f' }); 458 assertCursorRowsAndCols(rli, 0, 19); 459 rli.close(); 460} 461 462// Back and Forward one astral character 463{ 464 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 465 fi.emit('data', ''); 466 467 // Move left one character/code point 468 fi.emit('keypress', '.', { name: 'left' }); 469 assertCursorRowsAndCols(rli, 0, 0); 470 471 // Move right one character/code point 472 fi.emit('keypress', '.', { name: 'right' }); 473 assertCursorRowsAndCols(rli, 0, 2); 474 475 rli.on('line', common.mustCall((line) => { 476 assert.strictEqual(line, ''); 477 })); 478 fi.emit('data', '\n'); 479 rli.close(); 480} 481 482// Two astral characters left 483{ 484 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 485 fi.emit('data', ''); 486 487 // Move left one character/code point 488 fi.emit('keypress', '.', { name: 'left' }); 489 assertCursorRowsAndCols(rli, 0, 0); 490 491 fi.emit('data', ''); 492 assertCursorRowsAndCols(rli, 0, 2); 493 494 rli.on('line', common.mustCall((line) => { 495 assert.strictEqual(line, ''); 496 })); 497 fi.emit('data', '\n'); 498 rli.close(); 499} 500 501// Two astral characters right 502{ 503 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 504 fi.emit('data', ''); 505 506 // Move left one character/code point 507 fi.emit('keypress', '.', { name: 'right' }); 508 assertCursorRowsAndCols(rli, 0, 2); 509 510 fi.emit('data', ''); 511 assertCursorRowsAndCols(rli, 0, 4); 512 513 rli.on('line', common.mustCall((line) => { 514 assert.strictEqual(line, ''); 515 })); 516 fi.emit('data', '\n'); 517 rli.close(); 518} 519 520{ 521 // `wordLeft` and `wordRight` 522 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 523 fi.emit('data', 'the quick brown fox'); 524 fi.emit('keypress', '.', { ctrl: true, name: 'left' }); 525 assertCursorRowsAndCols(rli, 0, 16); 526 fi.emit('keypress', '.', { meta: true, name: 'b' }); 527 assertCursorRowsAndCols(rli, 0, 10); 528 fi.emit('keypress', '.', { ctrl: true, name: 'right' }); 529 assertCursorRowsAndCols(rli, 0, 16); 530 fi.emit('keypress', '.', { meta: true, name: 'f' }); 531 assertCursorRowsAndCols(rli, 0, 19); 532 rli.close(); 533} 534 535// `deleteWordLeft` 536[ 537 { ctrl: true, name: 'w' }, 538 { ctrl: true, name: 'backspace' }, 539 { meta: true, name: 'backspace' }, 540].forEach((deleteWordLeftKey) => { 541 let [rli, fi] = getInterface({ terminal: true, prompt: '' }); 542 fi.emit('data', 'the quick brown fox'); 543 fi.emit('keypress', '.', { ctrl: true, name: 'left' }); 544 rli.on('line', common.mustCall((line) => { 545 assert.strictEqual(line, 'the quick fox'); 546 })); 547 fi.emit('keypress', '.', deleteWordLeftKey); 548 fi.emit('data', '\n'); 549 rli.close(); 550 551 // No effect if pressed at beginning of line 552 [rli, fi] = getInterface({ terminal: true, prompt: '' }); 553 fi.emit('data', 'the quick brown fox'); 554 fi.emit('keypress', '.', { ctrl: true, name: 'a' }); 555 rli.on('line', common.mustCall((line) => { 556 assert.strictEqual(line, 'the quick brown fox'); 557 })); 558 fi.emit('keypress', '.', deleteWordLeftKey); 559 fi.emit('data', '\n'); 560 rli.close(); 561}); 562 563// `deleteWordRight` 564[ 565 { ctrl: true, name: 'delete' }, 566 { meta: true, name: 'delete' }, 567 { meta: true, name: 'd' }, 568].forEach((deleteWordRightKey) => { 569 let [rli, fi] = getInterface({ terminal: true, prompt: '' }); 570 fi.emit('data', 'the quick brown fox'); 571 fi.emit('keypress', '.', { ctrl: true, name: 'left' }); 572 fi.emit('keypress', '.', { ctrl: true, name: 'left' }); 573 rli.on('line', common.mustCall((line) => { 574 assert.strictEqual(line, 'the quick fox'); 575 })); 576 fi.emit('keypress', '.', deleteWordRightKey); 577 fi.emit('data', '\n'); 578 rli.close(); 579 580 // No effect if pressed at end of line 581 [rli, fi] = getInterface({ terminal: true, prompt: '' }); 582 fi.emit('data', 'the quick brown fox'); 583 rli.on('line', common.mustCall((line) => { 584 assert.strictEqual(line, 'the quick brown fox'); 585 })); 586 fi.emit('keypress', '.', deleteWordRightKey); 587 fi.emit('data', '\n'); 588 rli.close(); 589}); 590 591// deleteLeft 592{ 593 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 594 fi.emit('data', 'the quick brown fox'); 595 assertCursorRowsAndCols(rli, 0, 19); 596 597 // Delete left character 598 fi.emit('keypress', '.', { ctrl: true, name: 'h' }); 599 assertCursorRowsAndCols(rli, 0, 18); 600 rli.on('line', common.mustCall((line) => { 601 assert.strictEqual(line, 'the quick brown fo'); 602 })); 603 fi.emit('data', '\n'); 604 rli.close(); 605} 606 607// deleteLeft astral character 608{ 609 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 610 fi.emit('data', ''); 611 assertCursorRowsAndCols(rli, 0, 2); 612 // Delete left character 613 fi.emit('keypress', '.', { ctrl: true, name: 'h' }); 614 assertCursorRowsAndCols(rli, 0, 0); 615 rli.on('line', common.mustCall((line) => { 616 assert.strictEqual(line, ''); 617 })); 618 fi.emit('data', '\n'); 619 rli.close(); 620} 621 622// deleteRight 623{ 624 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 625 fi.emit('data', 'the quick brown fox'); 626 627 // Go to the start of the line 628 fi.emit('keypress', '.', { ctrl: true, name: 'a' }); 629 assertCursorRowsAndCols(rli, 0, 0); 630 631 // Delete right character 632 fi.emit('keypress', '.', { ctrl: true, name: 'd' }); 633 assertCursorRowsAndCols(rli, 0, 0); 634 rli.on('line', common.mustCall((line) => { 635 assert.strictEqual(line, 'he quick brown fox'); 636 })); 637 fi.emit('data', '\n'); 638 rli.close(); 639} 640 641// deleteRight astral character 642{ 643 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 644 fi.emit('data', ''); 645 646 // Go to the start of the line 647 fi.emit('keypress', '.', { ctrl: true, name: 'a' }); 648 assertCursorRowsAndCols(rli, 0, 0); 649 650 // Delete right character 651 fi.emit('keypress', '.', { ctrl: true, name: 'd' }); 652 assertCursorRowsAndCols(rli, 0, 0); 653 rli.on('line', common.mustCall((line) => { 654 assert.strictEqual(line, ''); 655 })); 656 fi.emit('data', '\n'); 657 rli.close(); 658} 659 660// deleteLineLeft 661{ 662 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 663 fi.emit('data', 'the quick brown fox'); 664 assertCursorRowsAndCols(rli, 0, 19); 665 666 // Delete from current to start of line 667 fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' }); 668 assertCursorRowsAndCols(rli, 0, 0); 669 rli.on('line', common.mustCall((line) => { 670 assert.strictEqual(line, ''); 671 })); 672 fi.emit('data', '\n'); 673 rli.close(); 674} 675 676// deleteLineRight 677{ 678 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 679 fi.emit('data', 'the quick brown fox'); 680 681 // Go to the start of the line 682 fi.emit('keypress', '.', { ctrl: true, name: 'a' }); 683 assertCursorRowsAndCols(rli, 0, 0); 684 685 // Delete from current to end of line 686 fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); 687 assertCursorRowsAndCols(rli, 0, 0); 688 rli.on('line', common.mustCall((line) => { 689 assert.strictEqual(line, ''); 690 })); 691 fi.emit('data', '\n'); 692 rli.close(); 693} 694 695// Close readline interface 696{ 697 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 698 fi.emit('keypress', '.', { ctrl: true, name: 'c' }); 699 assert(rli.closed); 700} 701 702// Multi-line input cursor position 703{ 704 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 705 fi.columns = 10; 706 fi.emit('data', 'multi-line text'); 707 assertCursorRowsAndCols(rli, 1, 5); 708 rli.close(); 709} 710 711// Multi-line input cursor position and long tabs 712{ 713 const [rli, fi] = getInterface({ tabSize: 16, terminal: true, prompt: '' }); 714 fi.columns = 10; 715 fi.emit('data', 'multi-line\ttext \t'); 716 assert.strictEqual(rli.cursor, 17); 717 assertCursorRowsAndCols(rli, 3, 2); 718 rli.close(); 719} 720 721// Check for the default tab size. 722{ 723 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 724 fi.emit('data', 'the quick\tbrown\tfox'); 725 assert.strictEqual(rli.cursor, 19); 726 // The first tab is 7 spaces long, the second one 3 spaces. 727 assertCursorRowsAndCols(rli, 0, 27); 728} 729 730// Multi-line prompt cursor position 731{ 732 const [rli, fi] = getInterface({ 733 terminal: true, 734 prompt: '\nfilledline\nwraping text\n> ' 735 }); 736 fi.columns = 10; 737 fi.emit('data', 't'); 738 assertCursorRowsAndCols(rli, 4, 3); 739 rli.close(); 740} 741 742// Clear the whole screen 743{ 744 const [rli, fi] = getInterface({ terminal: true, prompt: '' }); 745 const lines = ['line 1', 'line 2', 'line 3']; 746 fi.emit('data', lines.join('\n')); 747 fi.emit('keypress', '.', { ctrl: true, name: 'l' }); 748 assertCursorRowsAndCols(rli, 0, 6); 749 rli.on('line', common.mustCall((line) => { 750 assert.strictEqual(line, 'line 3'); 751 })); 752 fi.emit('data', '\n'); 753 rli.close(); 754} 755 756// Wide characters should be treated as two columns. 757assert.strictEqual(getStringWidth('a'), 1); 758assert.strictEqual(getStringWidth('あ'), 2); 759assert.strictEqual(getStringWidth('谢'), 2); 760assert.strictEqual(getStringWidth('고'), 2); 761assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); 762assert.strictEqual(getStringWidth('abcde'), 5); 763assert.strictEqual(getStringWidth('古池や'), 6); 764assert.strictEqual(getStringWidth('ノード.js'), 9); 765assert.strictEqual(getStringWidth('你好'), 4); 766assert.strictEqual(getStringWidth('안녕하세요'), 10); 767assert.strictEqual(getStringWidth('A\ud83c\ude00BC'), 5); 768assert.strictEqual(getStringWidth(''), 8); 769assert.strictEqual(getStringWidth('あ'), 9); 770// TODO(BridgeAR): This should have a width of 4. 771assert.strictEqual(getStringWidth('⓬⓪'), 2); 772assert.strictEqual(getStringWidth('\u0301\u200D\u200E'), 0); 773 774// Check if vt control chars are stripped 775assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); 776assert.strictEqual( 777 stripVTControlCharacters('\u001b[31m> \u001b[39m> '), 778 '> > ' 779); 780assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); 781assert.strictEqual(stripVTControlCharacters('> '), '> '); 782assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); 783assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); 784assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); 785assert.strictEqual(getStringWidth('> '), 2); 786 787// Check EventEmitter memory leak 788for (let i = 0; i < 12; i++) { 789 const rl = readline.createInterface({ 790 input: process.stdin, 791 output: process.stdout 792 }); 793 rl.close(); 794 assert.strictEqual(isWarned(process.stdin._events), false); 795 assert.strictEqual(isWarned(process.stdout._events), false); 796} 797 798[true, false].forEach((terminal) => { 799 // Disable history 800 { 801 const [rli, fi] = getInterface({ terminal, historySize: 0 }); 802 assert.strictEqual(rli.historySize, 0); 803 804 fi.emit('data', 'asdf\n'); 805 assert.deepStrictEqual(rli.history, []); 806 rli.close(); 807 } 808 809 // Default history size 30 810 { 811 const [rli, fi] = getInterface({ terminal }); 812 assert.strictEqual(rli.historySize, 30); 813 814 fi.emit('data', 'asdf\n'); 815 assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); 816 rli.close(); 817 } 818 819 // Sending a full line 820 { 821 const [rli, fi] = getInterface({ terminal }); 822 rli.on('line', common.mustCall((line) => { 823 assert.strictEqual(line, 'asdf'); 824 })); 825 fi.emit('data', 'asdf\n'); 826 } 827 828 // Sending a blank line 829 { 830 const [rli, fi] = getInterface({ terminal }); 831 rli.on('line', common.mustCall((line) => { 832 assert.strictEqual(line, ''); 833 })); 834 fi.emit('data', '\n'); 835 } 836 837 // Sending a single character with no newline and then a newline 838 { 839 const [rli, fi] = getInterface({ terminal }); 840 let called = false; 841 rli.on('line', (line) => { 842 called = true; 843 assert.strictEqual(line, 'a'); 844 }); 845 fi.emit('data', 'a'); 846 assert.ok(!called); 847 fi.emit('data', '\n'); 848 assert.ok(called); 849 rli.close(); 850 } 851 852 // Sending multiple newlines at once 853 { 854 const [rli, fi] = getInterface({ terminal }); 855 const expectedLines = ['foo', 'bar', 'baz']; 856 rli.on('line', common.mustCall((line) => { 857 assert.strictEqual(line, expectedLines.shift()); 858 }, expectedLines.length)); 859 fi.emit('data', `${expectedLines.join('\n')}\n`); 860 rli.close(); 861 } 862 863 // Sending multiple newlines at once that does not end with a new line 864 { 865 const [rli, fi] = getInterface({ terminal }); 866 const expectedLines = ['foo', 'bar', 'baz', 'bat']; 867 rli.on('line', common.mustCall((line) => { 868 assert.strictEqual(line, expectedLines.shift()); 869 }, expectedLines.length - 1)); 870 fi.emit('data', expectedLines.join('\n')); 871 rli.close(); 872 } 873 874 // Sending multiple newlines at once that does not end with a new(empty) 875 // line and a `end` event 876 { 877 const [rli, fi] = getInterface({ terminal }); 878 const expectedLines = ['foo', 'bar', 'baz', '']; 879 rli.on('line', common.mustCall((line) => { 880 assert.strictEqual(line, expectedLines.shift()); 881 }, expectedLines.length - 1)); 882 rli.on('close', common.mustCall()); 883 fi.emit('data', expectedLines.join('\n')); 884 fi.emit('end'); 885 rli.close(); 886 } 887 888 // Sending a multi-byte utf8 char over multiple writes 889 { 890 const buf = Buffer.from('☮', 'utf8'); 891 const [rli, fi] = getInterface({ terminal }); 892 let callCount = 0; 893 rli.on('line', (line) => { 894 callCount++; 895 assert.strictEqual(line, buf.toString('utf8')); 896 }); 897 for (const i of buf) { 898 fi.emit('data', Buffer.from([i])); 899 } 900 assert.strictEqual(callCount, 0); 901 fi.emit('data', '\n'); 902 assert.strictEqual(callCount, 1); 903 rli.close(); 904 } 905 906 // Calling readline without `new` 907 { 908 const [rli, fi] = getInterface({ terminal }); 909 rli.on('line', common.mustCall((line) => { 910 assert.strictEqual(line, 'asdf'); 911 })); 912 fi.emit('data', 'asdf\n'); 913 rli.close(); 914 } 915 916 // Calling the question callback 917 { 918 const [rli] = getInterface({ terminal }); 919 rli.question('foo?', common.mustCall((answer) => { 920 assert.strictEqual(answer, 'bar'); 921 })); 922 rli.write('bar\n'); 923 rli.close(); 924 } 925 926 // Calling the question multiple times 927 { 928 const [rli] = getInterface({ terminal }); 929 rli.question('foo?', common.mustCall((answer) => { 930 assert.strictEqual(answer, 'baz'); 931 })); 932 rli.question('bar?', common.mustNotCall(() => { 933 })); 934 rli.write('baz\n'); 935 rli.close(); 936 } 937 938 // Calling the promisified question 939 { 940 const [rli] = getInterface({ terminal }); 941 const question = util.promisify(rli.question).bind(rli); 942 question('foo?') 943 .then(common.mustCall((answer) => { 944 assert.strictEqual(answer, 'bar'); 945 })); 946 rli.write('bar\n'); 947 rli.close(); 948 } 949 950 // Aborting a question 951 { 952 const ac = new AbortController(); 953 const signal = ac.signal; 954 const [rli] = getInterface({ terminal }); 955 rli.on('line', common.mustCall((line) => { 956 assert.strictEqual(line, 'bar'); 957 })); 958 rli.question('hello?', { signal }, common.mustNotCall()); 959 ac.abort(); 960 rli.write('bar\n'); 961 rli.close(); 962 } 963 964 // Aborting a promisified question 965 { 966 const ac = new AbortController(); 967 const signal = ac.signal; 968 const [rli] = getInterface({ terminal }); 969 const question = util.promisify(rli.question).bind(rli); 970 rli.on('line', common.mustCall((line) => { 971 assert.strictEqual(line, 'bar'); 972 })); 973 question('hello?', { signal }) 974 .then(common.mustNotCall()) 975 .catch(common.mustCall((error) => { 976 assert.strictEqual(error.name, 'AbortError'); 977 })); 978 ac.abort(); 979 rli.write('bar\n'); 980 rli.close(); 981 } 982 983 // Can create a new readline Interface with a null output argument 984 { 985 const [rli, fi] = getInterface({ output: null, terminal }); 986 rli.on('line', common.mustCall((line) => { 987 assert.strictEqual(line, 'asdf'); 988 })); 989 fi.emit('data', 'asdf\n'); 990 991 rli.setPrompt('ddd> '); 992 rli.prompt(); 993 rli.write("really shouldn't be seeing this"); 994 rli.question('What do you think of node.js? ', (answer) => { 995 console.log('Thank you for your valuable feedback:', answer); 996 rli.close(); 997 }); 998 } 999 1000 // Calling the getPrompt method 1001 { 1002 const expectedPrompts = ['$ ', '> ']; 1003 const [rli] = getInterface({ terminal }); 1004 for (const prompt of expectedPrompts) { 1005 rli.setPrompt(prompt); 1006 assert.strictEqual(rli.getPrompt(), prompt); 1007 } 1008 } 1009 1010 { 1011 const expected = terminal ? 1012 ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : 1013 ['$ ']; 1014 1015 const output = new Writable({ 1016 write: common.mustCall((chunk, enc, cb) => { 1017 assert.strictEqual(chunk.toString(), expected.shift()); 1018 cb(); 1019 rl.close(); 1020 }, expected.length) 1021 }); 1022 1023 const rl = readline.createInterface({ 1024 input: new Readable({ read: common.mustCall() }), 1025 output, 1026 prompt: '$ ', 1027 terminal 1028 }); 1029 1030 rl.prompt(); 1031 1032 assert.strictEqual(rl.getPrompt(), '$ '); 1033 } 1034 1035 { 1036 const fi = new FakeInput(); 1037 assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); 1038 } 1039 1040 // Emit two line events when the delay 1041 // between \r and \n exceeds crlfDelay 1042 { 1043 const crlfDelay = 200; 1044 const [rli, fi] = getInterface({ terminal, crlfDelay }); 1045 let callCount = 0; 1046 rli.on('line', () => { 1047 callCount++; 1048 }); 1049 fi.emit('data', '\r'); 1050 setTimeout(common.mustCall(() => { 1051 fi.emit('data', '\n'); 1052 assert.strictEqual(callCount, 2); 1053 rli.close(); 1054 }), crlfDelay + 10); 1055 } 1056 1057 // For the purposes of the following tests, we do not care about the exact 1058 // value of crlfDelay, only that the behaviour conforms to what's expected. 1059 // Setting it to Infinity allows the test to succeed even under extreme 1060 // CPU stress. 1061 const crlfDelay = Infinity; 1062 1063 // Set crlfDelay to `Infinity` is allowed 1064 { 1065 const delay = 200; 1066 const [rli, fi] = getInterface({ terminal, crlfDelay }); 1067 let callCount = 0; 1068 rli.on('line', () => { 1069 callCount++; 1070 }); 1071 fi.emit('data', '\r'); 1072 setTimeout(common.mustCall(() => { 1073 fi.emit('data', '\n'); 1074 assert.strictEqual(callCount, 1); 1075 rli.close(); 1076 }), delay); 1077 } 1078 1079 // Sending multiple newlines at once that does not end with a new line 1080 // and a `end` event(last line is) 1081 1082 // \r\n should emit one line event, not two 1083 { 1084 const [rli, fi] = getInterface({ terminal, crlfDelay }); 1085 const expectedLines = ['foo', 'bar', 'baz', 'bat']; 1086 rli.on('line', common.mustCall((line) => { 1087 assert.strictEqual(line, expectedLines.shift()); 1088 }, expectedLines.length - 1)); 1089 fi.emit('data', expectedLines.join('\r\n')); 1090 rli.close(); 1091 } 1092 1093 // \r\n should emit one line event when split across multiple writes. 1094 { 1095 const [rli, fi] = getInterface({ terminal, crlfDelay }); 1096 const expectedLines = ['foo', 'bar', 'baz', 'bat']; 1097 let callCount = 0; 1098 rli.on('line', common.mustCall((line) => { 1099 assert.strictEqual(line, expectedLines[callCount]); 1100 callCount++; 1101 }, expectedLines.length)); 1102 expectedLines.forEach((line) => { 1103 fi.emit('data', `${line}\r`); 1104 fi.emit('data', '\n'); 1105 }); 1106 rli.close(); 1107 } 1108 1109 // Emit one line event when the delay between \r and \n is 1110 // over the default crlfDelay but within the setting value. 1111 { 1112 const delay = 125; 1113 const [rli, fi] = getInterface({ terminal, crlfDelay }); 1114 let callCount = 0; 1115 rli.on('line', () => callCount++); 1116 fi.emit('data', '\r'); 1117 setTimeout(common.mustCall(() => { 1118 fi.emit('data', '\n'); 1119 assert.strictEqual(callCount, 1); 1120 rli.close(); 1121 }), delay); 1122 } 1123}); 1124 1125// Ensure that the _wordLeft method works even for large input 1126{ 1127 const input = new Readable({ 1128 read() { 1129 this.push('\x1B[1;5D'); // CTRL + Left 1130 this.push(null); 1131 }, 1132 }); 1133 const output = new Writable({ 1134 write: common.mustCall((data, encoding, cb) => { 1135 assert.strictEqual(rl.cursor, rl.line.length - 1); 1136 cb(); 1137 }), 1138 }); 1139 const rl = new readline.createInterface({ 1140 input, 1141 output, 1142 terminal: true, 1143 }); 1144 rl.line = `a${' '.repeat(1e6)}a`; 1145 rl.cursor = rl.line.length; 1146} 1147 1148{ 1149 const fi = new FakeInput(); 1150 const signal = AbortSignal.abort(); 1151 1152 const rl = readline.createInterface({ 1153 input: fi, 1154 output: fi, 1155 signal, 1156 }); 1157 rl.on('close', common.mustCall()); 1158 assert.strictEqual(getEventListeners(signal, 'abort').length, 0); 1159} 1160 1161{ 1162 const fi = new FakeInput(); 1163 const ac = new AbortController(); 1164 const { signal } = ac; 1165 const rl = readline.createInterface({ 1166 input: fi, 1167 output: fi, 1168 signal, 1169 }); 1170 assert.strictEqual(getEventListeners(signal, 'abort').length, 1); 1171 rl.on('close', common.mustCall()); 1172 ac.abort(); 1173 assert.strictEqual(getEventListeners(signal, 'abort').length, 0); 1174} 1175 1176{ 1177 const fi = new FakeInput(); 1178 const ac = new AbortController(); 1179 const { signal } = ac; 1180 const rl = readline.createInterface({ 1181 input: fi, 1182 output: fi, 1183 signal, 1184 }); 1185 assert.strictEqual(getEventListeners(signal, 'abort').length, 1); 1186 rl.close(); 1187 assert.strictEqual(getEventListeners(signal, 'abort').length, 0); 1188} 1189 1190{ 1191 // Constructor throws if signal is not an abort signal 1192 assert.throws(() => { 1193 readline.createInterface({ 1194 input: new FakeInput(), 1195 signal: {}, 1196 }); 1197 }, { 1198 name: 'TypeError', 1199 code: 'ERR_INVALID_ARG_TYPE' 1200 }); 1201} 1202