1'use strict';
2
3const common = require('../common');
4const assert = require('assert');
5const events = require('events');
6const { REPLServer } = require('repl');
7const { Stream } = require('stream');
8const { inspect } = require('util');
9
10common.skipIfInspectorDisabled();
11
12// Ignore terminal settings. This is so the test can be run intact if TERM=dumb.
13process.env.TERM = '';
14const PROMPT = 'repl > ';
15
16class REPLStream extends Stream {
17  readable = true;
18  writable = true;
19
20  constructor() {
21    super();
22    this.lines = [''];
23  }
24  run(data) {
25    for (const entry of data) {
26      this.emit('data', entry);
27    }
28    this.emit('data', '\n');
29  }
30  write(chunk) {
31    const chunkLines = chunk.toString('utf8').split('\n');
32    this.lines[this.lines.length - 1] += chunkLines[0];
33    if (chunkLines.length > 1) {
34      this.lines.push(...chunkLines.slice(1));
35    }
36    this.emit('line', this.lines[this.lines.length - 1]);
37    return true;
38  }
39  async wait() {
40    this.lines = [''];
41    for await (const [line] of events.on(this, 'line')) {
42      if (line.includes(PROMPT)) {
43        return this.lines;
44      }
45    }
46  }
47  pause() {}
48  resume() {}
49}
50
51function runAndWait(cmds, repl) {
52  const promise = repl.inputStream.wait();
53  for (const cmd of cmds) {
54    repl.inputStream.run(cmd);
55  }
56  return promise;
57}
58
59async function tests(options) {
60  const repl = REPLServer({
61    prompt: PROMPT,
62    stream: new REPLStream(),
63    ignoreUndefined: true,
64    useColors: true,
65    ...options
66  });
67
68  repl.inputStream.run([
69    'function foo(x) { return x; }',
70    'function koo() { console.log("abc"); }',
71    'a = undefined;',
72  ]);
73
74  const testCases = [{
75    input: 'foo',
76    noPreview: '[Function: foo]',
77    preview: [
78      'foo',
79      '\x1B[90m[Function: foo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
80      '\x1B[36m[Function: foo]\x1B[39m',
81    ]
82  }, {
83    input: 'koo',
84    noPreview: '[Function: koo]',
85    preview: [
86      'k\x1B[90moo\x1B[39m\x1B[9G',
87      '\x1B[90m[Function: koo]\x1B[39m\x1B[9G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' +
88        '\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G',
89      '\x1B[90m[Function: koo]\x1B[39m\x1B[10G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' +
90        '\x1B[0Ko',
91      '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
92      '\x1B[36m[Function: koo]\x1B[39m',
93    ]
94  }, {
95    input: 'a',
96    noPreview: 'repl > ', // No "undefined" output.
97    preview: ['a\r'] // No "undefined" preview.
98  }, {
99    input: " { b: 1 }['b'] === 1",
100    noPreview: '\x1B[33mtrue\x1B[39m',
101    preview: [
102      " { b: 1 }['b']",
103      '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ',
104      '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1',
105      '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
106      '\x1B[33mtrue\x1B[39m',
107    ]
108  }, {
109    input: "{ b: 1 }['b'] === 1;",
110    noPreview: '\x1B[33mfalse\x1B[39m',
111    preview: [
112      "{ b: 1 }['b']",
113      '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ',
114      '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1',
115      '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;',
116      '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
117      '\x1B[33mfalse\x1B[39m',
118    ]
119  }, {
120    input: '{ a: true }',
121    noPreview: '{ a: \x1B[33mtrue\x1B[39m }',
122    preview: [
123      '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\r',
124      '{ a: \x1B[33mtrue\x1B[39m }',
125    ]
126  }, {
127    input: '{ a: true };',
128    noPreview: '\x1B[33mtrue\x1B[39m',
129    preview: [
130      '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };',
131      '\x1B[90mtrue\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
132      '\x1B[33mtrue\x1B[39m',
133    ]
134  }, {
135    input: ' \t { a: true};',
136    noPreview: '\x1B[33mtrue\x1B[39m',
137    preview: [
138      '  { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}',
139      '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;',
140      '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
141      '\x1B[33mtrue\x1B[39m',
142    ]
143  }, {
144    input: '1n + 2n',
145    noPreview: '\x1B[33m3n\x1B[39m',
146    preview: [
147      '1n + 2',
148      '\x1B[90mType[39m\x1B[14G\x1B[1A\x1B[1B\x1B[2K\x1B[1An',
149      '\x1B[90m3n\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
150      '\x1B[33m3n\x1B[39m',
151    ]
152  }, {
153    input: '{};1',
154    noPreview: '\x1B[33m1\x1B[39m',
155    preview: [
156      '{};1',
157      '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
158      '\x1B[33m1\x1B[39m',
159    ]
160  }];
161
162  const hasPreview = repl.terminal &&
163    (options.preview !== undefined ? !!options.preview : true);
164
165  for (const { input, noPreview, preview } of testCases) {
166    console.log(`Testing ${input}`);
167
168    const toBeRun = input.split('\n');
169    let lines = await runAndWait(toBeRun, repl);
170
171    if (hasPreview) {
172      // Remove error messages. That allows the code to run in different
173      // engines.
174      // eslint-disable-next-line no-control-regex
175      lines = lines.map((line) => line.replace(/Error: .+?\x1B/, ''));
176      assert.strictEqual(lines.pop(), '\x1B[1G\x1B[0Jrepl > \x1B[8G');
177      assert.deepStrictEqual(lines, preview);
178    } else {
179      assert.ok(lines[0].includes(noPreview), lines.map(inspect));
180      if (preview.length !== 1 || preview[0] !== `${input}\r`)
181        assert.strictEqual(lines.length, 2);
182    }
183  }
184}
185
186tests({ terminal: false }); // No preview
187tests({ terminal: true }); // Preview
188tests({ terminal: false, preview: false }); // No preview
189tests({ terminal: false, preview: true }); // No preview
190tests({ terminal: true, preview: true }); // Preview
191