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