• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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