• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// Flags: --expose-internals
4
5const common = require('../common');
6const stream = require('stream');
7const REPL = require('internal/repl');
8const assert = require('assert');
9const fs = require('fs');
10const path = require('path');
11const { inspect } = require('util');
12
13common.skipIfDumbTerminal();
14common.allowGlobals('aaaa');
15
16const tmpdir = require('../common/tmpdir');
17tmpdir.refresh();
18
19const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
20
21// Create an input stream specialized for testing an array of actions
22class ActionStream extends stream.Stream {
23  run(data) {
24    const _iter = data[Symbol.iterator]();
25    const doAction = () => {
26      const next = _iter.next();
27      if (next.done) {
28        // Close the repl. Note that it must have a clean prompt to do so.
29        this.emit('keypress', '', { ctrl: true, name: 'd' });
30        return;
31      }
32      const action = next.value;
33
34      if (typeof action === 'object') {
35        this.emit('keypress', '', action);
36      } else {
37        this.emit('data', `${action}`);
38      }
39      setImmediate(doAction);
40    };
41    doAction();
42  }
43  resume() {}
44  pause() {}
45}
46ActionStream.prototype.readable = true;
47
48// Mock keys
49const ENTER = { name: 'enter' };
50const UP = { name: 'up' };
51const DOWN = { name: 'down' };
52const BACKSPACE = { name: 'backspace' };
53const SEARCH_BACKWARDS = { name: 'r', ctrl: true };
54const SEARCH_FORWARDS = { name: 's', ctrl: true };
55const ESCAPE = { name: 'escape' };
56const CTRL_C = { name: 'c', ctrl: true };
57const DELETE_WORD_LEFT = { name: 'w', ctrl: true };
58
59const prompt = '> ';
60
61// TODO(BridgeAR): Add tests for lines that exceed the maximum columns.
62const tests = [
63  { // Creates few history to navigate for
64    env: { NODE_REPL_HISTORY: defaultHistoryPath },
65    test: [
66      'console.log("foo")', ENTER,
67      'ab = "aaaa"', ENTER,
68      'repl.repl.historyIndex', ENTER,
69      'console.log("foo")', ENTER,
70      'let ba = 9', ENTER,
71      'ab = "aaaa"', ENTER,
72      '555 - 909', ENTER,
73      '{key : {key2 :[] }}', ENTER,
74      'Array(100).fill(1)', ENTER,
75    ],
76    expected: [],
77    clean: false
78  },
79  {
80    env: { NODE_REPL_HISTORY: defaultHistoryPath },
81    showEscapeCodes: true,
82    checkTotal: true,
83    useColors: true,
84    test: [
85      '7',              // 1
86      SEARCH_FORWARDS,
87      SEARCH_FORWARDS,  // 3
88      'a',
89      SEARCH_BACKWARDS, // 5
90      SEARCH_FORWARDS,
91      SEARCH_BACKWARDS, // 7
92      'a',
93      BACKSPACE,        // 9
94      DELETE_WORD_LEFT,
95      'aa',             // 11
96      SEARCH_BACKWARDS,
97      SEARCH_BACKWARDS, // 13
98      SEARCH_BACKWARDS,
99      SEARCH_BACKWARDS, // 15
100      SEARCH_FORWARDS,
101      ESCAPE,           // 17
102      ENTER,
103    ],
104    // A = Cursor n up
105    // B = Cursor n down
106    // C = Cursor n forward
107    // D = Cursor n back
108    // G = Cursor to column n
109    // J = Erase in screen; 0 = right; 1 = left; 2 = total
110    // K = Erase in line; 0 = right; 1 = left; 2 = total
111    expected: [
112      // 0. Start
113      '\x1B[1G', '\x1B[0J',
114      prompt, '\x1B[3G',
115      // 1. '7'
116      '7',
117      // 2. SEARCH FORWARDS
118      '\nfwd-i-search: _', '\x1B[1A', '\x1B[4G',
119      // 3. SEARCH FORWARDS
120      '\x1B[3G', '\x1B[0J',
121      '7\nfwd-i-search: _', '\x1B[1A', '\x1B[4G',
122      // 4. 'a'
123      '\x1B[3G', '\x1B[0J',
124      '7\nfailed-fwd-i-search: a_', '\x1B[1A', '\x1B[4G',
125      // 5. SEARCH BACKWARDS
126      '\x1B[3G', '\x1B[0J',
127      'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
128      '\x1B[1A', '\x1B[6G',
129      // 6. SEARCH FORWARDS
130      '\x1B[3G', '\x1B[0J',
131      '7\nfailed-fwd-i-search: a_', '\x1B[1A', '\x1B[4G',
132      // 7. SEARCH BACKWARDS
133      '\x1B[3G', '\x1B[0J',
134      'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
135      '\x1B[1A', '\x1B[6G',
136      // 8. 'a'
137      '\x1B[3G', '\x1B[0J',
138      'ab = "aa\x1B[4maa\x1B[24m"\nbck-i-search: aa_',
139      '\x1B[1A', '\x1B[11G',
140      // 9. BACKSPACE
141      '\x1B[3G', '\x1B[0J',
142      'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
143      '\x1B[1A', '\x1B[6G',
144      // 10. DELETE WORD LEFT (works as backspace)
145      '\x1B[3G', '\x1B[0J',
146      '7\nbck-i-search: _', '\x1B[1A', '\x1B[4G',
147      // 11. 'a'
148      '\x1B[3G', '\x1B[0J',
149      'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
150      '\x1B[1A', '\x1B[6G',
151      // 11. 'aa' - continued
152      '\x1B[3G', '\x1B[0J',
153      'ab = "aa\x1B[4maa\x1B[24m"\nbck-i-search: aa_',
154      '\x1B[1A', '\x1B[11G',
155      // 12. SEARCH BACKWARDS
156      '\x1B[3G', '\x1B[0J',
157      'ab = "a\x1B[4maa\x1B[24ma"\nbck-i-search: aa_',
158      '\x1B[1A', '\x1B[10G',
159      // 13. SEARCH BACKWARDS
160      '\x1B[3G', '\x1B[0J',
161      'ab = "\x1B[4maa\x1B[24maa"\nbck-i-search: aa_',
162      '\x1B[1A', '\x1B[9G',
163      // 14. SEARCH BACKWARDS
164      '\x1B[3G', '\x1B[0J',
165      '7\nfailed-bck-i-search: aa_', '\x1B[1A', '\x1B[4G',
166      // 15. SEARCH BACKWARDS
167      '\x1B[3G', '\x1B[0J',
168      '7\nfailed-bck-i-search: aa_', '\x1B[1A', '\x1B[4G',
169      // 16. SEARCH FORWARDS
170      '\x1B[3G', '\x1B[0J',
171      'ab = "\x1B[4maa\x1B[24maa"\nfwd-i-search: aa_',
172      '\x1B[1A', '\x1B[9G',
173      // 17. ESCAPE
174      '\x1B[3G', '\x1B[0J',
175      '7',
176      // 18. ENTER
177      '\r\n',
178      '\x1B[33m7\x1B[39m\n',
179      '\x1B[1G', '\x1B[0J',
180      prompt,
181      '\x1B[3G',
182      '\r\n',
183    ],
184    clean: false
185  },
186  {
187    env: { NODE_REPL_HISTORY: defaultHistoryPath },
188    showEscapeCodes: true,
189    skip: !process.features.inspector,
190    checkTotal: true,
191    useColors: false,
192    test: [
193      'fu',              // 1
194      SEARCH_BACKWARDS,
195      '}',               // 3
196      SEARCH_BACKWARDS,
197      CTRL_C,            // 5
198      CTRL_C,
199      '1+1',             // 7
200      ENTER,
201      SEARCH_BACKWARDS,  // 9
202      '+',
203      '\r',              // 11
204      '2',
205      SEARCH_BACKWARDS,  // 13
206      're',
207      UP,                // 15
208      DOWN,
209      SEARCH_FORWARDS,   // 17
210      '\n',
211    ],
212    expected: [
213      '\x1B[1G', '\x1B[0J',
214      prompt, '\x1B[3G',
215      'f', 'u', '\nbck-i-search: _', '\x1B[1A', '\x1B[5G',
216      '\x1B[3G', '\x1B[0J',
217      '{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[21G',
218      '\x1B[3G', '\x1B[0J',
219      '{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[20G',
220      '\x1B[3G', '\x1B[0J',
221      'fu',
222      '\r\n',
223      '\x1B[1G', '\x1B[0J',
224      prompt, '\x1B[3G',
225      '1', '+', '1', '\n// 2', '\x1B[6G', '\x1B[1A',
226      '\x1B[1B', '\x1B[2K', '\x1B[1A',
227      '\r\n',
228      '2\n',
229      '\x1B[1G', '\x1B[0J',
230      prompt, '\x1B[3G',
231      '\nbck-i-search: _', '\x1B[1A',
232      '\x1B[3G', '\x1B[0J',
233      '1+1\nbck-i-search: +_', '\x1B[1A', '\x1B[4G',
234      '\x1B[3G', '\x1B[0J',
235      '1+1', '\x1B[4G',
236      '\x1B[2C',
237      '\r\n',
238      '2\n',
239      '\x1B[1G', '\x1B[0J',
240      prompt, '\x1B[3G',
241      '2',
242      '\nbck-i-search: _', '\x1B[1A', '\x1B[4G',
243      '\x1B[3G', '\x1B[0J',
244      'Array(100).fill(1)\nbck-i-search: r_', '\x1B[1A', '\x1B[5G',
245      '\x1B[3G', '\x1B[0J',
246      'repl.repl.historyIndex\nbck-i-search: re_', '\x1B[1A', '\x1B[8G',
247      '\x1B[3G', '\x1B[0J',
248      'repl.repl.historyIndex', '\x1B[8G',
249      '\x1B[1G', '\x1B[0J',
250      `${prompt}ab = "aaaa"`, '\x1B[14G',
251      '\x1B[1G', '\x1B[0J',
252      `${prompt}repl.repl.historyIndex`, '\x1B[25G', '\n// 8',
253      '\x1B[25G', '\x1B[1A',
254      '\x1B[1B', '\x1B[2K', '\x1B[1A',
255      '\nfwd-i-search: _', '\x1B[1A', '\x1B[25G',
256      '\x1B[3G', '\x1B[0J',
257      'repl.repl.historyIndex',
258      '\r\n',
259      '-1\n',
260      '\x1B[1G', '\x1B[0J',
261      prompt, '\x1B[3G',
262      '\r\n',
263    ],
264    clean: false
265  },
266];
267const numtests = tests.length;
268
269const runTestWrap = common.mustCall(runTest, numtests);
270
271function cleanupTmpFile() {
272  try {
273    // Write over the file, clearing any history
274    fs.writeFileSync(defaultHistoryPath, '');
275  } catch (err) {
276    if (err.code === 'ENOENT') return true;
277    throw err;
278  }
279  return true;
280}
281
282function runTest() {
283  const opts = tests.shift();
284  if (!opts) return; // All done
285
286  const { expected, skip } = opts;
287
288  // Test unsupported on platform.
289  if (skip) {
290    setImmediate(runTestWrap, true);
291    return;
292  }
293
294  const lastChunks = [];
295  let i = 0;
296
297  REPL.createInternalRepl(opts.env, {
298    input: new ActionStream(),
299    output: new stream.Writable({
300      write(chunk, _, next) {
301        const output = chunk.toString();
302
303        if (!opts.showEscapeCodes &&
304            (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
305          return next();
306        }
307
308        lastChunks.push(output);
309
310        if (expected.length && !opts.checkTotal) {
311          try {
312            assert.strictEqual(output, expected[i]);
313          } catch (e) {
314            console.error(`Failed test # ${numtests - tests.length}`);
315            console.error('Last outputs: ' + inspect(lastChunks, {
316              breakLength: 5, colors: true
317            }));
318            throw e;
319          }
320          i++;
321        }
322
323        next();
324      }
325    }),
326    completer: opts.completer,
327    prompt,
328    useColors: opts.useColors || false,
329    terminal: true
330  }, function(err, repl) {
331    if (err) {
332      console.error(`Failed test # ${numtests - tests.length}`);
333      throw err;
334    }
335
336    repl.once('close', () => {
337      if (opts.clean)
338        cleanupTmpFile();
339
340      if (opts.checkTotal) {
341        assert.deepStrictEqual(lastChunks, expected);
342      } else if (expected.length !== i) {
343        console.error(tests[numtests - tests.length - 1]);
344        throw new Error(`Failed test # ${numtests - tests.length}`);
345      }
346
347      setImmediate(runTestWrap, true);
348    });
349
350    if (opts.columns) {
351      Object.defineProperty(repl, 'columns', {
352        value: opts.columns,
353        enumerable: true
354      });
355    }
356    repl.inputStream.run(opts.test);
357  });
358}
359
360// run the tests
361runTest();
362