'use strict'; const common = require('../common'); const assert = require('assert'); const { REPLServer } = require('repl'); const { Stream } = require('stream'); if (process.features.inspector) common.skip('test is for node compiled with --without-inspector only'); // Ignore terminal settings. This is so the test can be run intact if TERM=dumb. process.env.TERM = ''; const PROMPT = 'repl > '; class REPLStream extends Stream { readable = true; writable = true; constructor() { super(); this.lines = ['']; } run(data) { for (const entry of data) { this.emit('data', entry); } this.emit('data', '\n'); } write(chunk) { const chunkLines = chunk.toString('utf8').split('\n'); this.lines[this.lines.length - 1] += chunkLines[0]; if (chunkLines.length > 1) { this.lines.push(...chunkLines.slice(1)); } this.emit('line'); return true; } wait() { this.lines = ['']; return new Promise((resolve, reject) => { const onError = (err) => { this.removeListener('line', onLine); reject(err); }; const onLine = () => { if (this.lines[this.lines.length - 1].includes(PROMPT)) { this.removeListener('error', onError); this.removeListener('line', onLine); resolve(this.lines); } }; this.once('error', onError); this.on('line', onLine); }); } pause() { } resume() { } } function runAndWait(cmds, repl) { const promise = repl.inputStream.wait(); for (const cmd of cmds) { repl.inputStream.run(cmd); } return promise; } const repl = new REPLServer({ prompt: PROMPT, stream: new REPLStream(), ignoreUndefined: true, useColors: true, terminal: true, }); repl.inputStream.run([ 'function foo(x) { return x; }', 'function koo() { console.log("abc"); }', 'a = undefined;', 'const r = 5;', ]); const testCases = [{ input: 'foo', preview: [ 'foo\r', '\x1B[36m[Function: foo]\x1B[39m', ] }, { input: 'r', preview: [ 'r\r', '\x1B[33m5\x1B[39m', ] }, { input: 'koo', preview: [ 'koo\r', '\x1B[36m[Function: koo]\x1B[39m', ] }, { input: 'a', preview: ['a\r'] // No "undefined" preview. }, { input: " { b: 1 }['b'] === 1", preview: [ " { b: 1 }['b'] === 1\r", '\x1B[33mtrue\x1B[39m', ] }, { input: "{ b: 1 }['b'] === 1;", preview: [ "{ b: 1 }['b'] === 1;\r", '\x1B[33mfalse\x1B[39m', ] }, { input: '{ a: true }', preview: [ '{ a: true }\r', '{ a: \x1B[33mtrue\x1B[39m }', ] }, { input: '{ a: true };', preview: [ '{ a: true };\r', '\x1B[33mtrue\x1B[39m', ] }, { input: ' \t { a: true};', preview: [ ' { a: true};\r', '\x1B[33mtrue\x1B[39m', ] }, { input: '1n + 2n', preview: [ '1n + 2n\r', '\x1B[33m3n\x1B[39m', ] }, { input: '{};1', preview: [ '{};1\r', '\x1B[33m1\x1B[39m', ], }]; async function runTest() { for (const { input, preview } of testCases) { const toBeRun = input.split('\n'); let lines = await runAndWait(toBeRun, repl); // Remove error messages. That allows the code to run in different // engines. // eslint-disable-next-line no-control-regex lines = lines.map((line) => line.replace(/Error: .+?\x1B/, '')); assert.strictEqual(lines.pop(), '\x1B[1G\x1B[0Jrepl > \x1B[8G'); assert.deepStrictEqual(lines, preview); } } runTest().then(common.mustCall());