• 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', ' // nction',
216      '\x1B[5G', '\x1B[0K',
217      '\nbck-i-search: _', '\x1B[1A', '\x1B[5G',
218      '\x1B[3G', '\x1B[0J',
219      '{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[21G',
220      '\x1B[3G', '\x1B[0J',
221      '{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[20G',
222      '\x1B[3G', '\x1B[0J',
223      'fu',
224      '\r\n',
225      '\x1B[1G', '\x1B[0J',
226      prompt, '\x1B[3G',
227      '1', '+', '1', '\n// 2', '\x1B[6G', '\x1B[1A',
228      '\x1B[1B', '\x1B[2K', '\x1B[1A',
229      '\r\n',
230      '2\n',
231      '\x1B[1G', '\x1B[0J',
232      prompt, '\x1B[3G',
233      '\nbck-i-search: _', '\x1B[1A',
234      '\x1B[3G', '\x1B[0J',
235      '1+1\nbck-i-search: +_', '\x1B[1A', '\x1B[4G',
236      '\x1B[3G', '\x1B[0J',
237      '1+1', '\x1B[4G',
238      '\x1B[2C',
239      '\r\n',
240      '2\n',
241      '\x1B[1G', '\x1B[0J',
242      prompt, '\x1B[3G',
243      '2', '\n// 2', '\x1B[4G', '\x1B[1A',
244      '\x1B[1B', '\x1B[2K', '\x1B[1A',
245      '\nbck-i-search: _', '\x1B[1A', '\x1B[4G',
246      '\x1B[3G', '\x1B[0J',
247      'Array(100).fill(1)\nbck-i-search: r_', '\x1B[1A', '\x1B[5G',
248      '\x1B[3G', '\x1B[0J',
249      'repl.repl.historyIndex\nbck-i-search: re_', '\x1B[1A', '\x1B[8G',
250      '\x1B[3G', '\x1B[0J',
251      'repl.repl.historyIndex', '\x1B[8G',
252      '\x1B[1G', '\x1B[0J',
253      `${prompt}ab = "aaaa"`, '\x1B[14G',
254      '\x1B[1G', '\x1B[0J',
255      `${prompt}repl.repl.historyIndex`, '\x1B[25G', '\n// 8',
256      '\x1B[25G', '\x1B[1A',
257      '\x1B[1B', '\x1B[2K', '\x1B[1A',
258      '\nfwd-i-search: _', '\x1B[1A', '\x1B[25G',
259      '\x1B[3G', '\x1B[0J',
260      'repl.repl.historyIndex',
261      '\r\n',
262      '-1\n',
263      '\x1B[1G', '\x1B[0J',
264      prompt, '\x1B[3G',
265      '\r\n',
266    ],
267    clean: false
268  },
269];
270const numtests = tests.length;
271
272const runTestWrap = common.mustCall(runTest, numtests);
273
274function cleanupTmpFile() {
275  try {
276    // Write over the file, clearing any history
277    fs.writeFileSync(defaultHistoryPath, '');
278  } catch (err) {
279    if (err.code === 'ENOENT') return true;
280    throw err;
281  }
282  return true;
283}
284
285function runTest() {
286  const opts = tests.shift();
287  if (!opts) return; // All done
288
289  const { expected, skip } = opts;
290
291  // Test unsupported on platform.
292  if (skip) {
293    setImmediate(runTestWrap, true);
294    return;
295  }
296
297  const lastChunks = [];
298  let i = 0;
299
300  REPL.createInternalRepl(opts.env, {
301    input: new ActionStream(),
302    output: new stream.Writable({
303      write(chunk, _, next) {
304        const output = chunk.toString();
305
306        if (!opts.showEscapeCodes &&
307            (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
308          return next();
309        }
310
311        lastChunks.push(output);
312
313        if (expected.length && !opts.checkTotal) {
314          try {
315            assert.strictEqual(output, expected[i]);
316          } catch (e) {
317            console.error(`Failed test # ${numtests - tests.length}`);
318            console.error('Last outputs: ' + inspect(lastChunks, {
319              breakLength: 5, colors: true
320            }));
321            throw e;
322          }
323          i++;
324        }
325
326        next();
327      }
328    }),
329    completer: opts.completer,
330    prompt,
331    useColors: opts.useColors || false,
332    terminal: true
333  }, function(err, repl) {
334    if (err) {
335      console.error(`Failed test # ${numtests - tests.length}`);
336      throw err;
337    }
338
339    repl.once('close', () => {
340      if (opts.clean)
341        cleanupTmpFile();
342
343      if (opts.checkTotal) {
344        assert.deepStrictEqual(lastChunks, expected);
345      } else if (expected.length !== i) {
346        console.error(tests[numtests - tests.length - 1]);
347        throw new Error(`Failed test # ${numtests - tests.length}`);
348      }
349
350      setImmediate(runTestWrap, true);
351    });
352
353    if (opts.columns) {
354      Object.defineProperty(repl, 'columns', {
355        value: opts.columns,
356        enumerable: true
357      });
358    }
359    repl.inputStream.run(opts.test);
360  });
361}
362
363// run the tests
364runTest();
365