'use strict'; require('../common'); const ArrayStream = require('../common/arraystream'); const assert = require('assert'); const { stripVTControlCharacters } = require('internal/readline'); const repl = require('repl'); // Flags: --expose-internals --experimental-repl-await const PROMPT = 'await repl > '; class REPLStream extends ArrayStream { constructor() { super(); this.waitingForResponse = false; this.lines = ['']; } write(chunk, encoding, callback) { if (Buffer.isBuffer(chunk)) { chunk = chunk.toString(encoding); } const chunkLines = stripVTControlCharacters(chunk).split('\n'); this.lines[this.lines.length - 1] += chunkLines[0]; if (chunkLines.length > 1) { this.lines.push(...chunkLines.slice(1)); } this.emit('line'); if (callback) callback(); return true; } wait(lookFor = PROMPT) { if (this.waitingForResponse) { throw new Error('Currently waiting for response to another command'); } 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(lookFor)) { this.removeListener('error', onError); this.removeListener('line', onLine); resolve(this.lines); } }; this.once('error', onError); this.on('line', onLine); }); } } const putIn = new REPLStream(); const testMe = repl.start({ prompt: PROMPT, stream: putIn, terminal: true, useColors: false, breakEvalOnSigint: true }); function runAndWait(cmds, lookFor) { const promise = putIn.wait(lookFor); for (const cmd of cmds) { if (typeof cmd === 'string') { putIn.run([cmd]); } else { testMe.write('', cmd); } } return promise; } async function ordinaryTests() { // These tests were created based on // https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/http/tests/devtools/console/console-top-level-await.js?rcl=5d0ea979f0ba87655b7ef0e03b58fa3c04986ba6 putIn.run([ 'function foo(x) { return x; }', 'function koo() { return Promise.resolve(4); }' ]); const testCases = [ [ 'await Promise.resolve(0)', '0' ], [ '{ a: await Promise.resolve(1) }', '{ a: 1 }' ], [ '_', '{ a: 1 }' ], [ 'let { a, b } = await Promise.resolve({ a: 1, b: 2 }), f = 5;', 'undefined' ], [ 'a', '1' ], [ 'b', '2' ], [ 'f', '5' ], [ 'let c = await Promise.resolve(2)', 'undefined' ], [ 'c', '2' ], [ 'let d;', 'undefined' ], [ 'd', 'undefined' ], [ 'let [i, { abc: { k } }] = [0, { abc: { k: 1 } }];', 'undefined' ], [ 'i', '0' ], [ 'k', '1' ], [ 'var l = await Promise.resolve(2);', 'undefined' ], [ 'l', '2' ], [ 'foo(await koo())', '4' ], [ '_', '4' ], [ 'const m = foo(await koo());', 'undefined' ], [ 'm', '4' ], [ 'const n = foo(await\nkoo());', 'undefined' ], [ 'n', '4' ], // eslint-disable-next-line no-template-curly-in-string [ '`status: ${(await Promise.resolve({ status: 200 })).status}`', "'status: 200'"], [ 'for (let i = 0; i < 2; ++i) await i', 'undefined' ], [ 'for (let i = 0; i < 2; ++i) { await i }', 'undefined' ], [ 'await 0', '0' ], [ 'await 0; function foo() {}', 'undefined' ], [ 'foo', '[Function: foo]' ], [ 'class Foo {}; await 1;', '1' ], [ 'Foo', '[Function: Foo]' ], [ 'if (await true) { function bar() {}; }', 'undefined' ], [ 'bar', '[Function: bar]' ], [ 'if (await true) { class Bar {}; }', 'undefined' ], [ 'Bar', 'ReferenceError: Bar is not defined', { line: 1 } ], [ 'await 0; function* gen(){}', 'undefined' ], [ 'for (var i = 0; i < 10; ++i) { await i; }', 'undefined' ], [ 'i', '10' ], [ 'for (let j = 0; j < 5; ++j) { await j; }', 'undefined' ], [ 'j', 'ReferenceError: j is not defined', { line: 1 } ], [ 'gen', '[GeneratorFunction: gen]' ], [ 'return 42; await 5;', 'SyntaxError: Illegal return statement', { line: 4 } ], [ 'let o = await 1, p', 'undefined' ], [ 'p', 'undefined' ], [ 'let q = 1, s = await 2', 'undefined' ], [ 's', '2' ], [ 'for await (let i of [1,2,3]) console.log(i)', 'undefined', { line: 3 } ] ]; for (const [input, expected, options = {}] of testCases) { console.log(`Testing ${input}`); const toBeRun = input.split('\n'); const lines = await runAndWait(toBeRun); if ('line' in options) { assert.strictEqual(lines[toBeRun.length + options.line], expected); } else { const echoed = toBeRun.map((a, i) => `${i > 0 ? '... ' : ''}${a}\r`); assert.deepStrictEqual(lines, [...echoed, expected, PROMPT]); } } } async function ctrlCTest() { putIn.run([ `const timeout = (msecs) => new Promise((resolve) => { setTimeout(resolve, msecs).unref(); });` ]); console.log('Testing Ctrl+C'); assert.deepStrictEqual(await runAndWait([ 'await timeout(100000)', { ctrl: true, name: 'c' } ]), [ 'await timeout(100000)\r', 'Thrown:', 'Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' + 'Script execution was interrupted by `SIGINT`', PROMPT ]); } async function main() { await ordinaryTests(); await ctrlCTest(); } main();