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\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko', 96 '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 97 '\x1B[36m[Function: koo]\x1B[39m' 98 ] 99 }, { 100 input: 'a', 101 noPreview: 'repl > ', // No "undefined" output. 102 preview: ['a\r'] // No "undefined" preview. 103 }, { 104 input: " { b: 1 }['b'] === 1", 105 noPreview: '\x1B[33mtrue\x1B[39m', 106 preview: [ 107 " { b: 1 }['b']", 108 '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', 109 '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', 110 '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 111 '\x1B[33mtrue\x1B[39m' 112 ] 113 }, { 114 input: "{ b: 1 }['b'] === 1;", 115 noPreview: '\x1B[33mfalse\x1B[39m', 116 preview: [ 117 "{ b: 1 }['b']", 118 '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', 119 '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', 120 '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', 121 '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 122 '\x1B[33mfalse\x1B[39m' 123 ] 124 }, { 125 input: '{ a: true }', 126 noPreview: '{ a: \x1B[33mtrue\x1B[39m }', 127 preview: [ 128 '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\r', 129 '{ a: \x1B[33mtrue\x1B[39m }' 130 ] 131 }, { 132 input: '{ a: true };', 133 noPreview: '\x1B[33mtrue\x1B[39m', 134 preview: [ 135 '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };', 136 '\x1B[90mtrue\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 137 '\x1B[33mtrue\x1B[39m' 138 ] 139 }, { 140 input: ' \t { a: true};', 141 noPreview: '\x1B[33mtrue\x1B[39m', 142 preview: [ 143 ' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}', 144 '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', 145 '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 146 '\x1B[33mtrue\x1B[39m' 147 ] 148 }, { 149 input: '1n + 2n', 150 noPreview: '\x1B[33m3n\x1B[39m', 151 preview: [ 152 '1n + 2', 153 '\x1B[90mType[39m\x1B[14G\x1B[1A\x1B[1B\x1B[2K\x1B[1An', 154 '\x1B[90m3n\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 155 '\x1B[33m3n\x1B[39m' 156 ] 157 }, { 158 input: '{};1', 159 noPreview: '\x1B[33m1\x1B[39m', 160 preview: [ 161 '{};1', 162 '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', 163 '\x1B[33m1\x1B[39m' 164 ] 165 }]; 166 167 const hasPreview = repl.terminal && 168 (options.preview !== undefined ? !!options.preview : true); 169 170 for (const { input, noPreview, preview } of testCases) { 171 console.log(`Testing ${input}`); 172 173 const toBeRun = input.split('\n'); 174 let lines = await runAndWait(toBeRun, repl); 175 176 if (hasPreview) { 177 // Remove error messages. That allows the code to run in different 178 // engines. 179 // eslint-disable-next-line no-control-regex 180 lines = lines.map((line) => line.replace(/Error: .+?\x1B/, '')); 181 assert.strictEqual(lines.pop(), '\x1B[1G\x1B[0Jrepl > \x1B[8G'); 182 assert.deepStrictEqual(lines, preview); 183 } else { 184 assert.ok(lines[0].includes(noPreview), lines.map(inspect)); 185 if (preview.length !== 1 || preview[0] !== `${input}\r`) 186 assert.strictEqual(lines.length, 2); 187 } 188 } 189} 190 191tests({ terminal: false }); // No preview 192tests({ terminal: true }); // Preview 193tests({ terminal: false, preview: false }); // No preview 194tests({ terminal: false, preview: true }); // No preview 195tests({ terminal: true, preview: true }); // Preview 196