| 'use strict'; |
| |
| const common = require('../common'); |
| const assert = require('assert'); |
| const events = require('events'); |
| const { REPLServer } = require('repl'); |
| const { Stream } = require('stream'); |
| const { inspect } = require('util'); |
| |
| common.skipIfInspectorDisabled(); |
| |
| // 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', this.lines[this.lines.length - 1]); |
| return true; |
| } |
| async wait() { |
| this.lines = ['']; |
| for await (const [line] of events.on(this, 'line')) { |
| if (line.includes(PROMPT)) { |
| return this.lines; |
| } |
| } |
| } |
| pause() {} |
| resume() {} |
| } |
| |
| function runAndWait(cmds, repl) { |
| const promise = repl.inputStream.wait(); |
| for (const cmd of cmds) { |
| repl.inputStream.run(cmd); |
| } |
| return promise; |
| } |
| |
| async function tests(options) { |
| const repl = new REPLServer({ |
| prompt: PROMPT, |
| stream: new REPLStream(), |
| ignoreUndefined: true, |
| useColors: true, |
| ...options |
| }); |
| |
| repl.inputStream.run([ |
| 'function foo(x) { return x; }', |
| 'function koo() { console.log("abc"); }', |
| 'a = undefined;', |
| ]); |
| |
| const testCases = [{ |
| input: 'foo', |
| noPreview: '[Function: foo]', |
| preview: [ |
| 'foo', |
| '\x1B[90m[Function: foo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[36m[Function: foo]\x1B[39m', |
| ] |
| }, { |
| input: 'koo', |
| noPreview: '[Function: koo]', |
| preview: [ |
| 'k\x1B[90moo\x1B[39m\x1B[9G', |
| '\x1B[90m[Function: koo]\x1B[39m\x1B[9G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + |
| '\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G', |
| '\x1B[90m[Function: koo]\x1B[39m\x1B[10G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + |
| '\x1B[0Ko', |
| '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[36m[Function: koo]\x1B[39m', |
| ] |
| }, { |
| input: 'a', |
| noPreview: 'repl > ', // No "undefined" output. |
| preview: ['a\r'] // No "undefined" preview. |
| }, { |
| input: " { b: 1 }['b'] === 1", |
| noPreview: '\x1B[33mtrue\x1B[39m', |
| preview: [ |
| " { b: 1 }['b']", |
| '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', |
| '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', |
| '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33mtrue\x1B[39m', |
| ] |
| }, { |
| input: "{ b: 1 }['b'] === 1;", |
| noPreview: '\x1B[33mfalse\x1B[39m', |
| preview: [ |
| "{ b: 1 }['b']", |
| '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', |
| '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', |
| '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', |
| '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33mfalse\x1B[39m', |
| ] |
| }, { |
| input: '{ a: true }', |
| noPreview: '{ a: \x1B[33mtrue\x1B[39m }', |
| preview: [ |
| '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\r', |
| '{ a: \x1B[33mtrue\x1B[39m }', |
| ] |
| }, { |
| input: '{ a: true };', |
| noPreview: '\x1B[33mtrue\x1B[39m', |
| preview: [ |
| '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };', |
| '\x1B[90mtrue\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33mtrue\x1B[39m', |
| ] |
| }, { |
| input: ' \t { a: true};', |
| noPreview: '\x1B[33mtrue\x1B[39m', |
| preview: [ |
| ' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}', |
| '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', |
| '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33mtrue\x1B[39m', |
| ] |
| }, { |
| input: '1n + 2n', |
| noPreview: '\x1B[33m3n\x1B[39m', |
| preview: [ |
| '1n + 2', |
| '\x1B[90mType[39m\x1B[14G\x1B[1A\x1B[1B\x1B[2K\x1B[1An', |
| '\x1B[90m3n\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33m3n\x1B[39m', |
| ] |
| }, { |
| input: '{};1', |
| noPreview: '\x1B[33m1\x1B[39m', |
| preview: [ |
| '{};1', |
| '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '\x1B[33m1\x1B[39m', |
| ] |
| }, { |
| input: 'aaaa', |
| noPreview: 'Uncaught ReferenceError: aaaa is not defined', |
| preview: [ |
| 'aaaa\r', |
| 'Uncaught ReferenceError: aaaa is not defined', |
| ] |
| }, { |
| input: '/0', |
| noPreview: '/0', |
| preview: [ |
| '/0\r', |
| '/0', |
| '^', |
| '', |
| 'Uncaught SyntaxError: Invalid regular expression: missing /', |
| ] |
| }, { |
| input: '{})', |
| noPreview: '{})', |
| preview: [ |
| '{})\r', |
| '{})', |
| ' ^', |
| '', |
| "Uncaught SyntaxError: Unexpected token ')'", |
| ], |
| }, { |
| input: "{ a: '{' }", |
| noPreview: "{ a: \x1B[32m'{'\x1B[39m }", |
| preview: [ |
| "{ a: '{' }\r", |
| "{ a: \x1B[32m'{'\x1B[39m }", |
| ], |
| }, { |
| input: "{'{':0}", |
| noPreview: "{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }", |
| preview: [ |
| "{'{':0}", |
| "\x1B[90m{ '{': 0 }\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r", |
| "{ \x1B[32m'{'\x1B[39m: \x1B[33m0\x1B[39m }", |
| ], |
| }, { |
| input: '{[Symbol.for("{")]: 0 }', |
| noPreview: '{ \x1B[32mSymbol({)\x1B[39m: \x1B[33m0\x1B[39m }', |
| preview: [ |
| '{[Symbol.for("{")]: 0 }\r', |
| '{ \x1B[32mSymbol({)\x1B[39m: \x1B[33m0\x1B[39m }', |
| ], |
| }, { |
| input: '{},{}', |
| noPreview: '{}', |
| preview: [ |
| '{},{}', |
| '\x1B[90m{}\x1B[39m\x1B[13G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| '{}', |
| ], |
| }, { |
| input: '{} //', |
| noPreview: 'repl > ', |
| preview: [ |
| '{} //\r', |
| ], |
| }, { |
| input: '{} //;', |
| noPreview: 'repl > ', |
| preview: [ |
| '{} //;\r', |
| ], |
| }, { |
| input: '{throw 0}', |
| noPreview: 'Uncaught \x1B[33m0\x1B[39m', |
| preview: [ |
| '{throw 0}', |
| '\x1B[90m0\x1B[39m\x1B[17G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', |
| 'Uncaught \x1B[33m0\x1B[39m', |
| ], |
| }]; |
| |
| const hasPreview = repl.terminal && |
| (options.preview !== undefined ? !!options.preview : true); |
| |
| for (const { input, noPreview, preview } of testCases) { |
| console.log(`Testing ${input}`); |
| |
| const toBeRun = input.split('\n'); |
| let lines = await runAndWait(toBeRun, repl); |
| |
| if (hasPreview) { |
| // 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); |
| } else { |
| assert.ok(lines[0].includes(noPreview), lines.map(inspect)); |
| if (preview.length !== 1 || preview[0] !== `${input}\r`) { |
| if (preview[preview.length - 1].includes('Uncaught SyntaxError')) { |
| assert.strictEqual(lines.length, 5); |
| } else { |
| assert.strictEqual(lines.length, 2); |
| } |
| } |
| } |
| } |
| } |
| |
| tests({ terminal: false }); // No preview |
| tests({ terminal: true }); // Preview |
| tests({ terminal: false, preview: false }); // No preview |
| tests({ terminal: false, preview: true }); // No preview |
| tests({ terminal: true, preview: true }); // Preview |