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