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