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