PageRenderTime 137ms CodeModel.GetById 37ms app.highlight 88ms RepoModel.GetById 2ms app.codeStats 1ms

/test/parallel/test-readline-interface.js

https://gitlab.com/CORP-RESELLER/node
JavaScript | 439 lines | 364 code | 43 blank | 32 comment | 9 complexity | 135675da526d7f68f514800bb238fbd0 MD5 | raw file
  1// Flags: --expose_internals
  2'use strict';
  3const common = require('../common');
  4const assert = require('assert');
  5const readline = require('readline');
  6const internalReadline = require('internal/readline');
  7const EventEmitter = require('events').EventEmitter;
  8const inherits = require('util').inherits;
  9const Writable = require('stream').Writable;
 10const Readable = require('stream').Readable;
 11
 12function FakeInput() {
 13  EventEmitter.call(this);
 14}
 15inherits(FakeInput, EventEmitter);
 16FakeInput.prototype.resume = function() {};
 17FakeInput.prototype.pause = function() {};
 18FakeInput.prototype.write = function() {};
 19FakeInput.prototype.end = function() {};
 20
 21function isWarned(emitter) {
 22  for (var name in emitter) {
 23    var listeners = emitter[name];
 24    if (listeners.warned) return true;
 25  }
 26  return false;
 27}
 28
 29[ true, false ].forEach(function(terminal) {
 30  var fi;
 31  var rli;
 32  var called;
 33
 34  // disable history
 35  fi = new FakeInput();
 36  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal,
 37                              historySize: 0 });
 38  assert.strictEqual(rli.historySize, 0);
 39
 40  fi.emit('data', 'asdf\n');
 41  assert.deepStrictEqual(rli.history, terminal ? [] : undefined);
 42  rli.close();
 43
 44  // default history size 30
 45  fi = new FakeInput();
 46  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal});
 47  assert.strictEqual(rli.historySize, 30);
 48
 49  fi.emit('data', 'asdf\n');
 50  assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : undefined);
 51  rli.close();
 52
 53  // sending a full line
 54  fi = new FakeInput();
 55  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
 56  called = false;
 57  rli.on('line', function(line) {
 58    called = true;
 59    assert.equal(line, 'asdf');
 60  });
 61  fi.emit('data', 'asdf\n');
 62  assert.ok(called);
 63
 64  // sending a blank line
 65  fi = new FakeInput();
 66  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
 67  called = false;
 68  rli.on('line', function(line) {
 69    called = true;
 70    assert.equal(line, '');
 71  });
 72  fi.emit('data', '\n');
 73  assert.ok(called);
 74
 75  // sending a single character with no newline
 76  fi = new FakeInput();
 77  rli = new readline.Interface(fi, {});
 78  called = false;
 79  rli.on('line', function(line) {
 80    called = true;
 81  });
 82  fi.emit('data', 'a');
 83  assert.ok(!called);
 84  rli.close();
 85
 86  // sending a single character with no newline and then a newline
 87  fi = new FakeInput();
 88  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
 89  called = false;
 90  rli.on('line', function(line) {
 91    called = true;
 92    assert.equal(line, 'a');
 93  });
 94  fi.emit('data', 'a');
 95  assert.ok(!called);
 96  fi.emit('data', '\n');
 97  assert.ok(called);
 98  rli.close();
 99
100  // sending multiple newlines at once
101  fi = new FakeInput();
102  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
103  var expectedLines = ['foo', 'bar', 'baz'];
104  var callCount = 0;
105  rli.on('line', function(line) {
106    assert.equal(line, expectedLines[callCount]);
107    callCount++;
108  });
109  fi.emit('data', expectedLines.join('\n') + '\n');
110  assert.equal(callCount, expectedLines.length);
111  rli.close();
112
113  // sending multiple newlines at once that does not end with a new line
114  fi = new FakeInput();
115  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
116  expectedLines = ['foo', 'bar', 'baz', 'bat'];
117  callCount = 0;
118  rli.on('line', function(line) {
119    assert.equal(line, expectedLines[callCount]);
120    callCount++;
121  });
122  fi.emit('data', expectedLines.join('\n'));
123  assert.equal(callCount, expectedLines.length - 1);
124  rli.close();
125
126  // sending multiple newlines at once that does not end with a new(empty)
127  // line and a `end` event
128  fi = new FakeInput();
129  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
130  expectedLines = ['foo', 'bar', 'baz', ''];
131  callCount = 0;
132  rli.on('line', function(line) {
133    assert.equal(line, expectedLines[callCount]);
134    callCount++;
135  });
136  rli.on('close', function() {
137    callCount++;
138  });
139  fi.emit('data', expectedLines.join('\n'));
140  fi.emit('end');
141  assert.equal(callCount, expectedLines.length);
142  rli.close();
143
144  // sending multiple newlines at once that does not end with a new line
145  // and a `end` event(last line is)
146
147  // \r\n should emit one line event, not two
148  fi = new FakeInput();
149  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
150  expectedLines = ['foo', 'bar', 'baz', 'bat'];
151  callCount = 0;
152  rli.on('line', function(line) {
153    assert.equal(line, expectedLines[callCount]);
154    callCount++;
155  });
156  fi.emit('data', expectedLines.join('\r\n'));
157  assert.equal(callCount, expectedLines.length - 1);
158  rli.close();
159
160  // \r\n should emit one line event when split across multiple writes.
161  fi = new FakeInput();
162  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
163  expectedLines = ['foo', 'bar', 'baz', 'bat'];
164  callCount = 0;
165  rli.on('line', function(line) {
166    assert.equal(line, expectedLines[callCount]);
167    callCount++;
168  });
169  expectedLines.forEach(function(line) {
170    fi.emit('data', line + '\r');
171    fi.emit('data', '\n');
172  });
173  assert.equal(callCount, expectedLines.length);
174  rli.close();
175
176  // \r should behave like \n when alone
177  fi = new FakeInput();
178  rli = new readline.Interface({ input: fi, output: fi, terminal: true });
179  expectedLines = ['foo', 'bar', 'baz', 'bat'];
180  callCount = 0;
181  rli.on('line', function(line) {
182    assert.equal(line, expectedLines[callCount]);
183    callCount++;
184  });
185  fi.emit('data', expectedLines.join('\r'));
186  assert.equal(callCount, expectedLines.length - 1);
187  rli.close();
188
189  // \r at start of input should output blank line
190  fi = new FakeInput();
191  rli = new readline.Interface({ input: fi, output: fi, terminal: true });
192  expectedLines = ['', 'foo' ];
193  callCount = 0;
194  rli.on('line', function(line) {
195    assert.equal(line, expectedLines[callCount]);
196    callCount++;
197  });
198  fi.emit('data', '\rfoo\r');
199  assert.equal(callCount, expectedLines.length);
200  rli.close();
201
202  // \t when there is no completer function should behave like an ordinary
203  //   character
204  fi = new FakeInput();
205  rli = new readline.Interface({ input: fi, output: fi, terminal: true });
206  called = false;
207  rli.on('line', function(line) {
208    assert.equal(line, '\t');
209    assert.strictEqual(called, false);
210    called = true;
211  });
212  fi.emit('data', '\t');
213  fi.emit('data', '\n');
214  assert.ok(called);
215  rli.close();
216
217  // \t does not become part of the input when there is a completer function
218  fi = new FakeInput();
219  var completer = function(line) {
220    return [[], line];
221  };
222  rli = new readline.Interface({
223    input: fi,
224    output: fi,
225    terminal: true,
226    completer: completer
227  });
228  called = false;
229  rli.on('line', function(line) {
230    assert.equal(line, 'foo');
231    assert.strictEqual(called, false);
232    called = true;
233  });
234  for (var character of '\tfo\to\t') {
235    fi.emit('data', character);
236  }
237  fi.emit('data', '\n');
238  assert.ok(called);
239  rli.close();
240
241  // constructor throws if completer is not a function or undefined
242  fi = new FakeInput();
243  assert.throws(function() {
244    readline.createInterface({
245      input: fi,
246      completer: 'string is not valid'
247    });
248  }, function(err) {
249    if (err instanceof TypeError) {
250      if (/Argument "completer" must be a function/.test(err)) {
251        return true;
252      }
253    }
254    return false;
255  });
256
257  // sending a multi-byte utf8 char over multiple writes
258  var buf = Buffer.from('☮', 'utf8');
259  fi = new FakeInput();
260  rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
261  callCount = 0;
262  rli.on('line', function(line) {
263    callCount++;
264    assert.equal(line, buf.toString('utf8'));
265  });
266  [].forEach.call(buf, function(i) {
267    fi.emit('data', Buffer.from([i]));
268  });
269  assert.equal(callCount, 0);
270  fi.emit('data', '\n');
271  assert.equal(callCount, 1);
272  rli.close();
273
274  // Regression test for repl freeze, #1968:
275  // check that nothing fails if 'keypress' event throws.
276  fi = new FakeInput();
277  rli = new readline.Interface({ input: fi, output: fi, terminal: true });
278  var keys = [];
279  fi.on('keypress', function(key) {
280    keys.push(key);
281    if (key === 'X') {
282      throw new Error('bad thing happened');
283    }
284  });
285  try {
286    fi.emit('data', 'fooX');
287  } catch (e) { }
288  fi.emit('data', 'bar');
289  assert.equal(keys.join(''), 'fooXbar');
290  rli.close();
291
292  // calling readline without `new`
293  fi = new FakeInput();
294  rli = readline.Interface({ input: fi, output: fi, terminal: terminal });
295  called = false;
296  rli.on('line', function(line) {
297    called = true;
298    assert.equal(line, 'asdf');
299  });
300  fi.emit('data', 'asdf\n');
301  assert.ok(called);
302  rli.close();
303
304  if (terminal) {
305    // question
306    fi = new FakeInput();
307    rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
308    expectedLines = ['foo'];
309    rli.question(expectedLines[0], function() {
310      rli.close();
311    });
312    var cursorPos = rli._getCursorPos();
313    assert.equal(cursorPos.rows, 0);
314    assert.equal(cursorPos.cols, expectedLines[0].length);
315    rli.close();
316
317    // sending a multi-line question
318    fi = new FakeInput();
319    rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
320    expectedLines = ['foo', 'bar'];
321    rli.question(expectedLines.join('\n'), function() {
322      rli.close();
323    });
324    cursorPos = rli._getCursorPos();
325    assert.equal(cursorPos.rows, expectedLines.length - 1);
326    assert.equal(cursorPos.cols, expectedLines.slice(-1)[0].length);
327    rli.close();
328  }
329
330  // isFullWidthCodePoint() should return false for non-numeric values
331  [true, false, null, undefined, {}, [], 'あ'].forEach((v) => {
332    assert.strictEqual(internalReadline.isFullWidthCodePoint('あ'), false);
333  });
334
335  // wide characters should be treated as two columns.
336  assert.equal(internalReadline.isFullWidthCodePoint('a'.charCodeAt(0)), false);
337  assert.equal(internalReadline.isFullWidthCodePoint('あ'.charCodeAt(0)), true);
338  assert.equal(internalReadline.isFullWidthCodePoint('谢'.charCodeAt(0)), true);
339  assert.equal(internalReadline.isFullWidthCodePoint('고'.charCodeAt(0)), true);
340  assert.equal(internalReadline.isFullWidthCodePoint(0x1f251), true);
341  assert.equal(internalReadline.getStringWidth('abcde'), 5);
342  assert.equal(internalReadline.getStringWidth('古池や'), 6);
343  assert.equal(internalReadline.getStringWidth('ノード.js'), 9);
344  assert.equal(internalReadline.getStringWidth('你好'), 4);
345  assert.equal(internalReadline.getStringWidth('안녕하세요'), 10);
346  assert.equal(internalReadline.getStringWidth('A\ud83c\ude00BC'), 5);
347
348  // check if vt control chars are stripped
349  assert.strictEqual(
350    internalReadline.stripVTControlCharacters('\u001b[31m> \u001b[39m'),
351    '> '
352  );
353  assert.strictEqual(
354    internalReadline.stripVTControlCharacters('\u001b[31m> \u001b[39m> '),
355    '> > '
356  );
357  assert.strictEqual(
358    internalReadline.stripVTControlCharacters('\u001b[31m\u001b[39m'),
359    ''
360  );
361  assert.strictEqual(
362    internalReadline.stripVTControlCharacters('> '),
363    '> '
364  );
365  assert.equal(internalReadline.getStringWidth('\u001b[31m> \u001b[39m'), 2);
366  assert.equal(internalReadline.getStringWidth('\u001b[31m> \u001b[39m> '), 4);
367  assert.equal(internalReadline.getStringWidth('\u001b[31m\u001b[39m'), 0);
368  assert.equal(internalReadline.getStringWidth('> '), 2);
369
370  assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []);
371
372  // check EventEmitter memory leak
373  for (var i = 0; i < 12; i++) {
374    var rl = readline.createInterface({
375      input: process.stdin,
376      output: process.stdout
377    });
378    rl.close();
379    assert.equal(isWarned(process.stdin._events), false);
380    assert.equal(isWarned(process.stdout._events), false);
381  }
382
383  //can create a new readline Interface with a null output arugument
384  fi = new FakeInput();
385  rli = new readline.Interface({input: fi, output: null, terminal: terminal });
386
387  called = false;
388  rli.on('line', function(line) {
389    called = true;
390    assert.equal(line, 'asdf');
391  });
392  fi.emit('data', 'asdf\n');
393  assert.ok(called);
394
395  assert.doesNotThrow(function() {
396    rli.setPrompt('ddd> ');
397  });
398
399  assert.doesNotThrow(function() {
400    rli.prompt();
401  });
402
403  assert.doesNotThrow(function() {
404    rli.write('really shouldnt be seeing this');
405  });
406
407  assert.doesNotThrow(function() {
408    rli.question('What do you think of node.js? ', function(answer) {
409      console.log('Thank you for your valuable feedback:', answer);
410      rli.close();
411    });
412  });
413
414  {
415    const expected = terminal
416      ? ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G']
417      : ['$ '];
418
419    let counter = 0;
420    const output = new Writable({
421      write: common.mustCall((chunk, enc, cb) => {
422        assert.strictEqual(chunk.toString(), expected[counter++]);
423        cb();
424        rl.close();
425      }, expected.length)
426    });
427
428    const rl = readline.createInterface({
429      input: new Readable({ read: () => {} }),
430      output: output,
431      prompt: '$ ',
432      terminal: terminal
433    });
434
435    rl.prompt();
436
437    assert.strictEqual(rl._prompt, '$ ');
438  }
439});