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