test: add new startNewREPLSever testing utility

PR-URL: https://github.com/nodejs/node/pull/59964
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
This commit is contained in:
Dario Piotrowicz 2025-07-20 15:58:10 +01:00 committed by Anna Henningsen
parent c6316f9db9
commit 9ac571d0d5
No known key found for this signature in database
53 changed files with 520 additions and 990 deletions

31
test/common/repl.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
const ArrayStream = require('../common/arraystream');
const repl = require('node:repl');
const assert = require('node:assert');
function startNewREPLServer(replOpts = {}, testingOpts = {}) {
const input = new ArrayStream();
const output = new ArrayStream();
output.accumulator = '';
output.write = (data) => (output.accumulator += `${data}`.replaceAll('\r', ''));
const replServer = repl.start({
prompt: '',
input,
output,
terminal: true,
allowBlockingCompletions: true,
...replOpts,
});
if (!testingOpts.disableDomainErrorAssert) {
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
}
return { replServer, input, output };
}
module.exports = { startNewREPLServer };

View File

@ -22,12 +22,12 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('assert');
const util = require('util');
const repl = require('repl');
const putIn = new ArrayStream();
repl.start('', putIn, null, true);
startNewREPLServer({ input: putIn, output: putIn, useGlobal: true, terminal: false });
test1();

View File

@ -4,19 +4,11 @@ const common = require('../common');
const assert = require('node:assert');
const { describe, test } = require('node:test');
const ArrayStream = require('../common/arraystream');
const repl = require('node:repl');
const { startNewREPLServer } = require('../common/repl');
function runCompletionTests(replInit, tests) {
const stream = new ArrayStream();
const testRepl = repl.start({ stream });
// Some errors are passed to the domain
testRepl._domain.on('error', assert.ifError);
testRepl.write(replInit);
testRepl.write('\n');
const { replServer: testRepl, input } = startNewREPLServer();
input.run([replInit]);
tests.forEach(([query, expectedCompletions]) => {
testRepl.complete(query, common.mustCall((error, data) => {

View File

@ -1,38 +1,28 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const vm = require('vm');
// Create a dummy stream that does nothing.
const stream = new ArrayStream();
const { startNewREPLServer } = require('../common/repl');
// Test context when useGlobal is false.
{
const r = repl.start({
input: stream,
output: stream,
const { replServer, output } = startNewREPLServer({
terminal: false,
useGlobal: false
});
let output = '';
stream.write = function(d) {
output += d;
};
// Ensure that the repl context gets its own "console" instance.
assert(r.context.console);
assert(replServer.context.console);
// Ensure that the repl console instance is not the global one.
assert.notStrictEqual(r.context.console, console);
assert.notStrictEqual(r.context.Object, Object);
assert.notStrictEqual(replServer.context.console, console);
assert.notStrictEqual(replServer.context.Object, Object);
stream.run(['({} instanceof Object)']);
replServer.write('({} instanceof Object)\n');
assert.strictEqual(output, 'true\n> ');
assert.strictEqual(output.accumulator, 'true\n');
const context = r.createContext();
const context = replServer.createContext();
// Ensure that the repl context gets its own "console" instance.
assert(context.console instanceof require('console').Console);
@ -41,44 +31,46 @@ const stream = new ArrayStream();
// Ensure that the repl console instance is writable.
context.console = 'foo';
r.close();
replServer.close();
}
// Test for context side effects.
{
const server = repl.start({ input: stream, output: stream });
const { replServer } = startNewREPLServer({
useGlobal: false
});
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
assert.ok(!replServer.underscoreAssigned);
assert.strictEqual(replServer.lines.length, 0);
// An assignment to '_' in the repl server
server.write('_ = 500;\n');
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
assert.strictEqual(server.last, 500);
replServer.write('_ = 500;\n');
assert.ok(replServer.underscoreAssigned);
assert.strictEqual(replServer.lines.length, 1);
assert.strictEqual(replServer.lines[0], '_ = 500;');
assert.strictEqual(replServer.last, 500);
// Use the server to create a new context
const context = server.createContext();
const context = replServer.createContext();
// Ensure that creating a new context does not
// have side effects on the server
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
assert.strictEqual(server.last, 500);
assert.ok(replServer.underscoreAssigned);
assert.strictEqual(replServer.lines.length, 1);
assert.strictEqual(replServer.lines[0], '_ = 500;');
assert.strictEqual(replServer.last, 500);
// Reset the server context
server.resetContext();
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
replServer.resetContext();
assert.ok(!replServer.underscoreAssigned);
assert.strictEqual(replServer.lines.length, 0);
// Ensure that assigning to '_' in the new context
// does not change the value in our server.
assert.ok(!server.underscoreAssigned);
assert.ok(!replServer.underscoreAssigned);
vm.runInContext('_ = 1000;\n', context);
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
server.close();
assert.ok(!replServer.underscoreAssigned);
assert.strictEqual(replServer.lines.length, 0);
replServer.close();
}

View File

@ -1,44 +1,35 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const { describe, it } = require('node:test');
common.skipIfInspectorDisabled();
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
// Processes some input in a REPL instance and returns a promise that
// resolves to the produced output (as a string).
function getReplRunOutput(input, replOptions) {
function getReplRunOutput(inputStr, replOptions) {
return new Promise((resolve) => {
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
const { replServer, input, output } = startNewREPLServer({ prompt: testingReplPrompt, ...replOptions });
const replServer = repl.start({
input: inputStream,
output: outputStream,
prompt: testingReplPrompt,
...replOptions,
});
output.accumulator = '';
let output = '';
outputStream.write = (chunk) => {
output += chunk;
output.write = (chunk) => {
output.accumulator += chunk;
// The prompt appears after the input has been processed
if (output.includes(testingReplPrompt)) {
if (output.accumulator.includes(testingReplPrompt)) {
replServer.close();
resolve(output);
resolve(output.accumulator);
}
};
inputStream.emit('data', input);
input.emit('data', inputStr);
inputStream.run(['']);
input.run(['']);
});
}

View File

@ -1,42 +1,32 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('assert');
const { describe, it } = require('node:test');
const repl = require('repl');
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
// Processes some input in a REPL instance and returns a promise that
// resolves to the produced output (as a string).
function getReplRunOutput(input, replOptions) {
function getReplRunOutput(inputStr, replOptions) {
return new Promise((resolve) => {
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
const { replServer, input, output } = startNewREPLServer({ prompt: testingReplPrompt, ...replOptions });
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
output.accumulator = '';
const replServer = repl.start({
input: inputStream,
output: outputStream,
prompt: testingReplPrompt,
...replOptions,
});
let output = '';
outputStream.write = (chunk) => {
output += chunk;
output.write = (chunk) => {
output.accumulator += chunk;
// The prompt appears after the input has been processed
if (output.includes(testingReplPrompt)) {
if (output.accumulator.includes(testingReplPrompt)) {
replServer.close();
resolve(output);
resolve(output.accumulator);
}
};
inputStream.emit('data', input);
input.emit('data', inputStr);
inputStream.run(['']);
input.run(['']);
});
}
@ -62,23 +52,23 @@ describe('repl with custom eval', { concurrency: true }, () => {
it('provides a repl context to the eval callback', async () => {
const context = await new Promise((resolve) => {
const r = repl.start({
const { replServer } = startNewREPLServer({
eval: (_cmd, context) => resolve(context),
});
r.context = { foo: 'bar' };
r.write('\n.exit\n');
replServer.context = { foo: 'bar' };
replServer.write('\n.exit\n');
});
assert.strictEqual(context.foo, 'bar');
});
it('provides the global context to the eval callback', async () => {
const context = await new Promise((resolve) => {
const r = repl.start({
useGlobal: true,
const { replServer } = startNewREPLServer({
eval: (_cmd, context) => resolve(context),
useGlobal: true
});
global.foo = 'global_foo';
r.write('\n.exit\n');
replServer.write('\n.exit\n');
});
assert.strictEqual(context.foo, 'global_foo');
@ -88,12 +78,12 @@ describe('repl with custom eval', { concurrency: true }, () => {
it('inherits variables from the global context but does not use it afterwords if `useGlobal` is false', async () => {
global.bar = 'global_bar';
const context = await new Promise((resolve) => {
const r = repl.start({
const { replServer } = startNewREPLServer({
useGlobal: false,
eval: (_cmd, context) => resolve(context),
});
global.baz = 'global_baz';
r.write('\n.exit\n');
replServer.write('\n.exit\n');
});
assert.strictEqual(context.bar, 'global_bar');
@ -111,10 +101,10 @@ describe('repl with custom eval', { concurrency: true }, () => {
*/
it('preserves the original input', async () => {
const cmd = await new Promise((resolve) => {
const r = repl.start({
const { replServer } = startNewREPLServer({
eval: (cmd) => resolve(cmd),
});
r.write('function f() {}\n.exit\n');
replServer.write('function f() {}\n.exit\n');
});
assert.strictEqual(cmd, 'function f() {}\n');
});

View File

@ -1,25 +1,20 @@
'use strict';
require('../common');
const { startNewREPLServer } = require('../common/repl');
const stream = require('stream');
const assert = require('assert');
const repl = require('repl');
let output = '';
const inputStream = new stream.PassThrough();
const outputStream = new stream.PassThrough();
outputStream.on('data', function(d) {
output += d;
});
const r = repl.start({
input: inputStream,
output: outputStream,
terminal: true
});
const { replServer: replServer, input } = startNewREPLServer({ prompt: '> ', terminal: true, output: outputStream });
r.defineCommand('say1', {
replServer.defineCommand('say1', {
help: 'help for say1',
action: function(thing) {
output = '';
@ -28,21 +23,21 @@ r.defineCommand('say1', {
}
});
r.defineCommand('say2', function() {
replServer.defineCommand('say2', function() {
output = '';
this.output.write('hello from say2\n');
this.displayPrompt();
});
inputStream.write('.help\n');
input.run(['.help\n']);
assert.match(output, /\n\.say1 {5}help for say1\n/);
assert.match(output, /\n\.say2\n/);
inputStream.write('.say1 node developer\n');
input.run(['.say1 node developer\n']);
assert.ok(output.startsWith('hello node developer\n'),
`say1 output starts incorrectly: "${output}"`);
assert.ok(output.includes('> '),
`say1 output does not include prompt: "${output}"`);
inputStream.write('.say2 node developer\n');
input.run(['.say2 node developer\n']);
assert.ok(output.startsWith('hello from say2\n'),
`say2 output starts incorrectly: "${output}"`);
assert.ok(output.includes('> '),

View File

@ -21,14 +21,18 @@
'use strict';
require('../common');
const { startNewREPLServer } = require('../common/repl');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const stream = new ArrayStream();
const putIn = new ArrayStream();
repl.start('', putIn);
startNewREPLServer({
input: stream,
output: stream,
terminal: false,
});
putIn.write = function(data) {
stream.write = function(data) {
// Don't use assert for this because the domain might catch it, and
// give a false negative. Don't throw, just print and exit.
if (data === 'OK\n') {
@ -39,7 +43,7 @@ putIn.write = function(data) {
}
};
putIn.run([
stream.run([
'require("domain").create().on("error", function() { console.log("OK") })' +
'.run(function() { throw new Error("threw") })',
]);

View File

@ -2,8 +2,7 @@
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
if (process.env.TERM === 'dumb') {
common.skip('skipping - dumb terminal');
@ -15,30 +14,24 @@ if (process.env.TERM === 'dumb') {
const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G';
const terminalCodeRegex = new RegExp(terminalCode.replace(/\[/g, '\\['), 'g');
function run({ input, output, event, checkTerminalCodes = true }) {
const stream = new ArrayStream();
let found = '';
stream.write = (msg) => found += msg.replace('\r', '');
function run({ input: inputStr, output: outputStr, event, checkTerminalCodes = true }) {
let expected =
`${terminalCode}.editor\n` +
'// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n' +
`${input}${output}\n${terminalCode}`;
`${inputStr}${outputStr}\n${terminalCode}`;
const replServer = repl.start({
const { replServer, input, output } = startNewREPLServer({
prompt: '> ',
terminal: true,
input: stream,
output: stream,
useColors: false
});
stream.emit('data', '.editor\n');
stream.emit('data', input);
input.emit('data', '.editor\n');
input.emit('data', inputStr);
replServer.write('', event);
replServer.close();
let found = output.accumulator;
if (!checkTerminalCodes) {
found = found.replace(terminalCodeRegex, '').replace(/\n/g, '');
expected = expected.replace(terminalCodeRegex, '').replace(/\n/g, '');
@ -79,22 +72,15 @@ const tests = [
tests.forEach(run);
// Auto code alignment for .editor mode
function testCodeAlignment({ input, cursor = 0, line = '' }) {
const stream = new ArrayStream();
const outputStream = new ArrayStream();
stream.write = () => { throw new Error('Writing not allowed!'); };
const replServer = repl.start({
function testCodeAlignment({ input: inputStr, cursor = 0, line = '' }) {
const { replServer, input } = startNewREPLServer({
prompt: '> ',
terminal: true,
input: stream,
output: outputStream,
useColors: false
});
stream.emit('data', '.editor\n');
input.split('').forEach((ch) => stream.emit('data', ch));
input.emit('data', '.editor\n');
inputStr.split('').forEach((ch) => input.emit('data', ch));
// Test the content of current line and the cursor position
assert.strictEqual(line, replServer.line);
assert.strictEqual(cursor, replServer.cursor);

View File

@ -1,27 +1,23 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
{
let evalCalledWithExpectedArgs = false;
let evalCalledWithExpectedArgs = false;
const options = {
eval: common.mustCall((cmd, context) => {
// Assertions here will not cause the test to exit with an error code
// so set a boolean that is checked later instead.
evalCalledWithExpectedArgs = (cmd === '\n');
})
};
const { replServer } = startNewREPLServer({
eval: common.mustCall((cmd, context) => {
// Assertions here will not cause the test to exit with an error code
// so set a boolean that is checked later instead.
evalCalledWithExpectedArgs = (cmd === '\n');
})
});
const r = repl.start(options);
try {
// Empty strings should be sent to the repl's eval function
r.write('\n');
} finally {
r.write('.exit\n');
}
assert(evalCalledWithExpectedArgs);
try {
// Empty strings should be sent to the repl's eval function
replServer.write('\n');
} finally {
replServer.write('.exit\n');
}
assert(evalCalledWithExpectedArgs);

View File

@ -21,28 +21,20 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('assert');
const repl = require('repl');
let terminalExit = 0;
let regularExit = 0;
// Create a dummy stream that does nothing
const stream = new ArrayStream();
function testTerminalMode() {
const r1 = repl.start({
input: stream,
output: stream,
terminal: true
});
const { replServer, input } = startNewREPLServer({ terminal: true });
process.nextTick(function() {
// Manually fire a ^D keypress
stream.emit('data', '\u0004');
input.emit('data', '\u0004');
});
r1.on('exit', function() {
replServer.on('exit', function() {
// Should be fired from the simulated ^D keypress
terminalExit++;
testRegularMode();
@ -50,17 +42,13 @@ function testTerminalMode() {
}
function testRegularMode() {
const r2 = repl.start({
input: stream,
output: stream,
terminal: false
});
const { replServer, input } = startNewREPLServer({ terminal: true });
process.nextTick(function() {
stream.emit('end');
input.emit('end');
});
r2.on('exit', function() {
replServer.on('exit', function() {
// Should be fired from the simulated 'end' event
regularExit++;
});

View File

@ -1,8 +1,7 @@
'use strict';
const common = require('../common');
const repl = require('node:repl');
const stream = require('node:stream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
// This test checks that an eval function returning an error in its callback
@ -13,26 +12,18 @@ const assert = require('node:assert');
const close$ = Promise.withResolvers();
const eval$ = Promise.withResolvers();
const input = new stream.PassThrough();
const output = new stream.PassThrough();
const replServer = repl.start({
input,
output,
const { replServer, output } = startNewREPLServer({
eval(_cmd, _context, _file, cb) {
close$.promise.then(() => {
cb(new Error('Error returned from the eval callback'));
eval$.resolve();
});
},
}, {
disableDomainErrorAssert: true,
});
let outputStr = '';
output.on('data', (data) => {
outputStr += data;
});
input.write('\n');
replServer.write('\n');
replServer.close();
close$.resolve();
@ -41,5 +32,5 @@ const assert = require('node:assert');
await eval$.promise;
assert.match(outputStr, /Uncaught Error: Error returned from the eval callback/);
assert.match(output.accumulator, /Uncaught Error: Error returned from the eval callback/);
})().then(common.mustCall());

View File

@ -2,36 +2,18 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const stream = require('stream');
const { startNewREPLServer } = require('../common/repl');
const r = initRepl();
const { replServer } = startNewREPLServer({
useColors: false,
terminal: false
});
r.input.emit('data', 'function a() { return 42; } (1)\n');
r.input.emit('data', 'a\n');
r.input.emit('data', '.exit\n');
r.once('exit', common.mustCall());
replServer.input.emit('data', 'function a() { return 42; } (1)\n');
replServer.input.emit('data', 'a\n');
replServer.input.emit('data', '.exit\n');
replServer.once('exit', common.mustCall());
const expected = '1\n[Function: a]\n';
const got = r.output.accumulator.join('');
const got = replServer.output.accumulator;
assert.strictEqual(got, expected);
function initRepl() {
const input = new stream();
input.write = input.pause = input.resume = () => {};
input.readable = true;
const output = new stream();
output.writable = true;
output.accumulator = [];
output.write = (data) => output.accumulator.push(data);
return repl.start({
input,
output,
useColors: false,
terminal: false,
prompt: ''
});
}

View File

@ -1,29 +1,22 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
common.skipIfInspectorDisabled();
// This test verifies that the V8 inspector API is usable in the REPL.
const putIn = new ArrayStream();
let output = '';
putIn.write = function(data) {
output += data;
};
const { replServer, input, output } = startNewREPLServer();
const testMe = repl.start('', putIn);
input.run(['const myVariable = 42']);
putIn.run(['const myVariable = 42']);
testMe.complete('myVar', common.mustCall((error, data) => {
replServer.complete('myVar', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [['myVariable'], 'myVar']);
}));
putIn.run([
input.run([
'const inspector = require("inspector")',
'const session = new inspector.Session()',
'session.connect()',
@ -31,5 +24,5 @@ putIn.run([
'session.disconnect()',
]);
assert(output.includes(
assert(output.accumulator.includes(
"null { result: { type: 'number', value: 2, description: '2' } }"));

View File

@ -1,9 +1,7 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
// Regression test for https://github.com/nodejs/node/issues/6802
const input = new ArrayStream();
repl.start({ input, output: process.stdout, useGlobal: true });
const { input } = startNewREPLServer({ useGlobal: true }, { disableDomainErrorAssert: true });
input.run(['let process']);

View File

@ -1,9 +1,8 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
if (process.env.TERM === 'dumb') {
common.skip('skipping - dumb terminal');
@ -24,21 +23,11 @@ const eat = (food) => '<nom nom nom>';
undefined
`;
let accum = '';
const { replServer, output } = startNewREPLServer();
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
outputStream.write = (data) => accum += data.replace('\r', '');
const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
terminal: true,
useColors: false
});
r.write(`${command}\n`);
assert.strictEqual(accum.replace(terminalCodeRegex, ''), expected);
r.close();
replServer.write(`${command}\n`);
assert.strictEqual(
output.accumulator.replace(terminalCodeRegex, ''),
expected
);
replServer.close();

View File

@ -1,9 +1,8 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
if (process.env.TERM === 'dumb') {
common.skip('skipping - dumb terminal');
@ -24,21 +23,8 @@ const eat = (food) => '<nom nom nom>';
undefined
`;
let accum = '';
const { replServer, output } = startNewREPLServer();
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
outputStream.write = (data) => accum += data.replace('\r', '');
const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
terminal: true,
useColors: false
});
r.write(`${command}\n`);
assert.strictEqual(accum.replace(terminalCodeRegex, ''), expected);
r.close();
replServer.write(`${command}\n`);
assert.strictEqual(output.accumulator.replace(terminalCodeRegex, ''), expected);
replServer.close();

View File

@ -1,8 +1,8 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const Stream = require('stream');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
if (process.env.TERM === 'dumb') {
common.skip('skipping - dumb terminal');
@ -20,26 +20,27 @@ tests.forEach(function(test) {
});
function testSloppyMode() {
const cli = initRepl(repl.REPL_MODE_SLOPPY);
const { input, output } = startNewREPLServer({ replMode: repl.REPL_MODE_SLOPPY, terminal: false, prompt: '> ' });
cli.input.emit('data', 'x = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), '> 3\n> ');
cli.output.accumulator.length = 0;
input.emit('data', 'x = 3\n');
assert.strictEqual(output.accumulator, '> 3\n> ');
output.accumulator = '';
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
input.emit('data', 'let y = 3\n');
assert.strictEqual(output.accumulator, 'undefined\n> ');
}
function testStrictMode() {
const cli = initRepl(repl.REPL_MODE_STRICT);
const { input, output } = startNewREPLServer({ replMode: repl.REPL_MODE_STRICT, terminal: false, prompt: '> ' }, {
disableDomainErrorAssert: true,
});
cli.input.emit('data', 'x = 3\n');
assert.match(cli.output.accumulator.join(''),
/ReferenceError: x is not defined/);
cli.output.accumulator.length = 0;
input.emit('data', 'x = 3\n');
assert.match(output.accumulator, /ReferenceError: x is not defined/);
output.accumulator = '';
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
input.emit('data', 'let y = 3\n');
assert.strictEqual(output.accumulator, 'undefined\n> ');
}
function testStrictModeTerminal() {
@ -48,45 +49,21 @@ function testStrictModeTerminal() {
return;
}
// Verify that ReferenceErrors are reported in strict mode previews.
const cli = initRepl(repl.REPL_MODE_STRICT, {
terminal: true
});
const { input, output } = startNewREPLServer({ replMode: repl.REPL_MODE_STRICT, prompt: '> ' });
cli.input.emit('data', 'xyz ');
input.emit('data', 'xyz ');
assert.ok(
cli.output.accumulator.includes('\n// ReferenceError: xyz is not defined')
output.accumulator.includes('\n// ReferenceError: xyz is not defined')
);
}
function testAutoMode() {
const cli = initRepl(repl.REPL_MODE_MAGIC);
const { input, output } = startNewREPLServer({ replMode: repl.REPL_MODE_MAGIC, terminal: false, prompt: '> ' });
cli.input.emit('data', 'x = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), '> 3\n> ');
cli.output.accumulator.length = 0;
input.emit('data', 'x = 3\n');
assert.strictEqual(output.accumulator, '> 3\n> ');
output.accumulator = '';
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
}
function initRepl(mode, options) {
const input = new Stream();
input.write = input.pause = input.resume = () => {};
input.readable = true;
const output = new Stream();
output.write = output.pause = output.resume = function(buf) {
output.accumulator.push(buf);
};
output.accumulator = [];
output.writable = true;
return repl.start({
input: input,
output: output,
useColors: false,
terminal: false,
replMode: mode,
...options
});
input.emit('data', 'let y = 3\n');
assert.strictEqual(output.accumulator, 'undefined\n> ');
}

View File

@ -1,27 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const input = ['const foo = {', '};', 'foo'];
function run({ useColors }) {
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
let output = '';
const { replServer, output } = startNewREPLServer({ useColors });
outputStream.write = (data) => { output += data.replace('\r', ''); };
const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
terminal: true,
useColors
});
r.on('exit', common.mustCall(() => {
const actual = output.split('\n');
replServer.on('exit', common.mustCall(() => {
const actual = output.accumulator.split('\n');
// Validate the output, which contains terminal escape codes.
assert.strictEqual(actual.length, 6);
@ -33,8 +21,8 @@ function run({ useColors }) {
assert.strictEqual(actual[4], '{}');
}));
inputStream.run(input);
r.close();
input.forEach((line) => replServer.write(`${line}\n`));
replServer.close();
}
run({ useColors: true });

View File

@ -1,11 +1,8 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const stream = new ArrayStream();
const replServer = repl.start({ terminal: false, input: stream, output: stream });
const { replServer } = startNewREPLServer();
replServer.setupHistory('/nonexistent/file', common.mustSucceed(() => {
replServer.close();

View File

@ -1,23 +1,13 @@
'use strict';
require('../common');
const repl = require('repl');
const assert = require('assert');
const Stream = require('stream');
const { startNewREPLServer } = require('../common/repl');
const output = new Stream();
let text = '';
output.write = output.pause = output.resume = function(buf) {
text += buf.toString();
};
const { replServer, output } = startNewREPLServer();
const replserver = repl.start({
output: output,
input: process.stdin
});
replserver.emit('line', 'process.nextTick(() => { throw null; })');
replserver.emit('line', '.exit');
replServer.emit('line', 'process.nextTick(() => { throw null; })');
replServer.emit('line', '.exit');
setTimeout(() => {
assert(text.includes('Uncaught null'));
assert(output.accumulator.includes('Uncaught null'));
}, 0);

View File

@ -1,36 +1,18 @@
'use strict';
const common = require('../common');
const repl = require('repl');
const stream = require('stream');
const assert = require('assert');
const { startNewREPLServer } = require('../common/repl');
// Pasting big input should not cause the process to have a huge CPU usage.
const cpuUsage = process.cpuUsage();
const r = initRepl();
r.input.emit('data', 'a'.repeat(2e4) + '\n');
r.input.emit('data', '.exit\n');
const { replServer } = startNewREPLServer({}, { disableDomainErrorAssert: true });
replServer.input.emit('data', 'a'.repeat(2e4) + '\n');
replServer.input.emit('data', '.exit\n');
r.once('exit', common.mustCall(() => {
replServer.once('exit', common.mustCall(() => {
const diff = process.cpuUsage(cpuUsage);
assert.ok(diff.user < 1e6);
}));
function initRepl() {
const input = new stream();
input.write = input.pause = input.resume = () => {};
input.readable = true;
const output = new stream();
output.write = () => {};
output.writable = true;
return repl.start({
input,
output,
terminal: true,
prompt: ''
});
}

View File

@ -1,41 +1,32 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const stackRegExp = /(REPL\d+):[0-9]+:[0-9]+/g;
function run({ command, expected }) {
let accum = '';
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
outputStream.write = (data) => accum += data.replace('\r', '');
const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
const { replServer, output } = startNewREPLServer({
terminal: false,
useColors: false
}, {
disableDomainErrorAssert: true,
});
r.write(`${command}\n`);
replServer.write(`${command}\n`);
if (typeof expected === 'string') {
assert.strictEqual(
accum.replace(stackRegExp, '$1:*:*'),
output.accumulator.replace(stackRegExp, '$1:*:*'),
expected.replace(stackRegExp, '$1:*:*')
);
} else {
assert.match(
accum.replace(stackRegExp, '$1:*:*'),
output.accumulator.replace(stackRegExp, '$1:*:*'),
expected
);
}
r.close();
replServer.close();
}
const origPrepareStackTrace = Error.prepareStackTrace;

View File

@ -1,23 +1,18 @@
'use strict';
require('../common');
const { PassThrough } = require('stream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
{
const input = new PassThrough();
const output = new PassThrough();
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
const r = repl.start({
prompt: '',
input,
output,
writer: String,
terminal: false,
useColors: false
});
const { replServer, output } = startNewREPLServer(
{ prompt: testingReplPrompt },
{ disableDomainErrorAssert: true }
);
r.write('throw new Error("foo[a]")\n');
r.close();
assert.strictEqual(output.read().toString(), 'Uncaught Error: foo[a]\n');
}
replServer.write('throw new Error("foo[a]")\n');
assert.strictEqual(
output.accumulator.split('\n').filter((line) => !line.includes(testingReplPrompt)).join(''),
'Uncaught Error: foo[a]'
);

View File

@ -1,43 +1,34 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const stackRegExp = /(at .*REPL\d+:)[0-9]+:[0-9]+/g;
function run({ command, expected, ...extraREPLOptions }, i) {
let accum = '';
const { replServer, output } = startNewREPLServer(
{
terminal: false,
useColors: false,
...extraREPLOptions
},
{ disableDomainErrorAssert: true }
);
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
outputStream.write = (data) => accum += data.replace('\r', '');
const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
terminal: false,
useColors: false,
...extraREPLOptions
});
r.write(`${command}\n`);
console.log(i);
replServer.write(`${command}\n`);
if (typeof expected === 'string') {
assert.strictEqual(
accum.replace(stackRegExp, '$1*:*'),
output.accumulator.replace(stackRegExp, '$1*:*'),
expected.replace(stackRegExp, '$1*:*')
);
} else {
assert.match(
accum.replace(stackRegExp, '$1*:*'),
output.accumulator.replace(stackRegExp, '$1*:*'),
expected
);
}
r.close();
replServer.close();
}
const tests = [

View File

@ -1,29 +1,21 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
common.skipIfInspectorDisabled();
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
repl.start({
input: inputStream,
output: outputStream,
useGlobal: false,
terminal: true,
useColors: true
});
const { input, output } = startNewREPLServer(
{ useColors: true }, { disableDomainErrorAssert: true }
);
let output = '';
outputStream.write = (chunk) => output += chunk;
output.accumulator = '';
for (const char of ['\\n', '\\v', '\\r']) {
inputStream.emit('data', `"${char}"()`);
input.emit('data', `"${char}"()`);
// Make sure the output is on a single line
assert.strictEqual(output, `"${char}"()\n\x1B[90mTypeError: "\x1B[39m\x1B[9G\x1B[1A`);
inputStream.run(['']);
output = '';
assert.strictEqual(output.accumulator, `"${char}"()\n\x1B[90mTypeError: "\x1B[39m\x1B[7G\x1B[1A`);
input.run(['']);
output.accumulator = '';
}

View File

@ -1,27 +1,17 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
common.skipIfInspectorDisabled();
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
repl.start({
input: inputStream,
output: outputStream,
useGlobal: false,
terminal: true,
useColors: true
});
const { output, input } = startNewREPLServer();
let output = '';
outputStream.write = (chunk) => output += chunk;
output.accumulator = '';
// Input without '\n' triggering actual run.
const input = 'while (true) {}';
inputStream.emit('data', input);
const inputStr = 'while (true) {}';
input.emit('data', inputStr);
// No preview available when timed out.
assert.strictEqual(output, input);
assert.strictEqual(output.accumulator, inputStr);

View File

@ -21,27 +21,19 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const util = require('util');
const { startNewREPLServer } = require('../common/repl');
common.allowGlobals(42);
// Create a dummy stream that does nothing
const dummy = new ArrayStream();
function testReset(cb) {
const r = repl.start({
input: dummy,
output: dummy,
useGlobal: false
});
r.context.foo = 42;
r.on('reset', common.mustCall(function(context) {
const { replServer } = startNewREPLServer();
replServer.context.foo = 42;
replServer.on('reset', common.mustCall(function(context) {
assert(!!context, 'REPL did not emit a context with reset event');
assert.strictEqual(context, r.context, 'REPL emitted incorrect context. ' +
`context is ${util.inspect(context)}, expected ${util.inspect(r.context)}`);
assert.strictEqual(context, replServer.context, 'REPL emitted incorrect context. ' +
`context is ${util.inspect(context)}, expected ${util.inspect(replServer.context)}`);
assert.strictEqual(
context.foo,
undefined,
@ -51,17 +43,13 @@ function testReset(cb) {
context.foo = 42;
cb();
}));
r.resetContext();
replServer.resetContext();
}
function testResetGlobal() {
const r = repl.start({
input: dummy,
output: dummy,
useGlobal: true
});
r.context.foo = 42;
r.on('reset', common.mustCall(function(context) {
const { replServer } = startNewREPLServer({ useGlobal: true });
replServer.context.foo = 42;
replServer.on('reset', common.mustCall(function(context) {
assert.strictEqual(
context.foo,
42,
@ -69,7 +57,7 @@ function testResetGlobal() {
`context.foo is ${context.foo}, expected 42.`
);
}));
r.resetContext();
replServer.resetContext();
}
testReset(common.mustCall(testResetGlobal));

View File

@ -1,30 +1,17 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('node:assert');
const fs = require('node:fs');
const repl = require('node:repl');
const path = require('node:path');
const { startNewREPLServer } = require('../common/repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Test for saving a REPL session in editor mode
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: new ArrayStream(),
allowBlockingCompletions: true,
terminal: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input } = startNewREPLServer();
input.run(['.editor']);

View File

@ -1,28 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('node:assert');
const repl = require('node:repl');
const { startNewREPLServer } = require('../common/repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Test for the appropriate handling of cases in which REPL saves fail
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input, output } = startNewREPLServer({ terminal: false });
// NUL (\0) is disallowed in filenames in UNIX-like operating systems and
// Windows so we can use that to test failed saves.

View File

@ -1,28 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
const repl = require('node:repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Tests that an appropriate error is displayed if the user tries to load a directory instead of a file
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input, output } = startNewREPLServer({ terminal: false });
const dirPath = tmpdir.path;

View File

@ -1,28 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
const repl = require('node:repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Tests that an appropriate error is displayed if the user tries to load a non existent file
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input, output } = startNewREPLServer({ terminal: false });
const filePath = tmpdir.resolve('file.does.not.exist');

View File

@ -1,28 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
const repl = require('node:repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Tests that an appropriate error is displayed if .load is called without a filename
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input, output } = startNewREPLServer({ terminal: false });
output.write = common.mustCall(function(data) {
assert.strictEqual(data, 'The "file" argument must be specified\n');

View File

@ -1,28 +1,15 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
const repl = require('node:repl');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Tests that an appropriate error is displayed if .save is called without a filename
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input, output } = startNewREPLServer({ terminal: false });
output.write = common.mustCall(function(data) {
assert.strictEqual(data, 'The "file" argument must be specified\n');

View File

@ -22,11 +22,9 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const assert = require('node:assert');
const fs = require('node:fs');
const repl = require('node:repl');
const path = require('node:path');
const tmpdir = require('../common/tmpdir');
@ -34,17 +32,7 @@ tmpdir.refresh();
// Tests that a REPL session data can be saved to and loaded from a file
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: new ArrayStream(),
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input } = startNewREPLServer({ terminal: false });
const filePath = path.resolve(tmpdir.path, 'test.save.js');

View File

@ -1,32 +1,32 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
let found = false;
process.on('exit', () => {
assert.strictEqual(found, true);
});
ArrayStream.prototype.write = function(output) {
const { input, output } = startNewREPLServer({}, { disableDomainErrorAssert: true });
output.write = (data) => {
// Matching only on a minimal piece of the stack because the string will vary
// greatly depending on the JavaScript engine. V8 includes `;` because it
// displays the line of code (`var foo bar;`) that is causing a problem.
// ChakraCore does not display the line of code but includes `;` in the phrase
// `Expected ';' `.
if (/;/.test(output))
if (/;/.test(data))
found = true;
};
const putIn = new ArrayStream();
repl.start('', putIn);
let file = fixtures.path('syntax', 'bad_syntax');
if (common.isWindows)
file = file.replace(/\\/g, '\\\\');
putIn.run(['.clear']);
putIn.run([`require('${file}');`]);
input.run(['.clear']);
input.run([`require('${file}');`]);

View File

@ -1,22 +1,11 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { hijackStderr, restoreStderr } = require('../common/hijackstdio');
const assert = require('assert');
const { startNewREPLServer } = require('../common/repl');
const repl = require('repl');
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer, input } = startNewREPLServer();
for (const type of [
Array,

View File

@ -1,27 +1,10 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const { describe, it, before, after } = require('node:test');
const assert = require('assert');
const repl = require('repl');
function prepareREPL() {
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
return { replServer, input };
}
function testCompletion(replServer, { input, expectedCompletions }) {
replServer.complete(
input,
@ -36,7 +19,7 @@ describe('REPL tab object completion on computed properties', () => {
let replServer;
before(() => {
const { replServer: server, input } = prepareREPL();
const { replServer: server, input } = startNewREPLServer();
replServer = server;
input.run([
@ -97,7 +80,7 @@ describe('REPL tab object completion on computed properties', () => {
let replServer;
before(() => {
const { replServer: server, input } = prepareREPL();
const { replServer: server, input } = startNewREPLServer();
replServer = server;
input.run([

View File

@ -1,28 +1,21 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
ArrayStream.prototype.write = () => {};
const putIn = new ArrayStream();
const testMe = repl.start('', putIn);
const { replServer, input } = startNewREPLServer({}, { disableDomainErrorAssert: true });
// https://github.com/nodejs/node/issues/3346
// Tab-completion should be empty
putIn.run(['.clear']);
putIn.run(['function () {']);
testMe.complete('arguments.', common.mustCall((err, completions) => {
input.run(['.clear', 'function () {']);
replServer.complete('arguments.', common.mustCall((err, completions) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(completions, [[], 'arguments.']);
}));
putIn.run(['.clear']);
putIn.run(['function () {']);
putIn.run(['undef;']);
testMe.complete('undef.', common.mustCall((err, completions) => {
input.run(['.clear', 'function () {', 'undef;']);
replServer.complete('undef.', common.mustCall((err, completions) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(completions, [[], 'undef.']);
}));

View File

@ -1,21 +1,14 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const putIn = new ArrayStream();
const { startNewREPLServer } = require('../common/repl');
// To test custom completer function.
// Sync mode.
{
const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' ');
const testCustomCompleterSyncMode = repl.start({
prompt: '',
input: putIn,
output: putIn,
const { replServer } = startNewREPLServer({
completer: function completer(line) {
const hits = customCompletions.filter((c) => c.startsWith(line));
// Show all completions if none found.
@ -25,7 +18,7 @@ const putIn = new ArrayStream();
// On empty line should output all the custom completions
// without complete anything.
testCustomCompleterSyncMode.complete('', common.mustCall((error, data) => {
replServer.complete('', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
customCompletions,
'',
@ -33,7 +26,7 @@ const putIn = new ArrayStream();
}));
// On `a` should output `aaa aa1 aa2` and complete until `aa`.
testCustomCompleterSyncMode.complete('a', common.mustCall((error, data) => {
replServer.complete('a', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
'aaa aa1 aa2'.split(' '),
'a',
@ -45,10 +38,7 @@ const putIn = new ArrayStream();
// Async mode.
{
const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' ');
const testCustomCompleterAsyncMode = repl.start({
prompt: '',
input: putIn,
output: putIn,
const { replServer } = startNewREPLServer({
completer: function completer(line, callback) {
const hits = customCompletions.filter((c) => c.startsWith(line));
// Show all completions if none found.
@ -58,7 +48,7 @@ const putIn = new ArrayStream();
// On empty line should output all the custom completions
// without complete anything.
testCustomCompleterAsyncMode.complete('', common.mustCall((error, data) => {
replServer.complete('', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
customCompletions,
'',
@ -66,7 +56,7 @@ const putIn = new ArrayStream();
}));
// On `a` should output `aaa aa1 aa2` and complete until `aa`.
testCustomCompleterAsyncMode.complete('a', common.mustCall((error, data) => {
replServer.complete('a', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
'aaa aa1 aa2'.split(' '),
'a',

View File

@ -1,9 +1,9 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const path = require('path');
const { startNewREPLServer } = require('../common/repl');
const { isMainThread } = require('worker_threads');
@ -11,17 +11,7 @@ if (!isMainThread) {
common.skip('process.chdir is not available in Workers');
}
const repl = require('repl');
const replServer = repl.start({
prompt: '',
input: new ArrayStream(),
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
const { replServer } = startNewREPLServer();
// Tab completion for files/directories
{

View File

@ -1,29 +1,15 @@
'use strict';
const common = require('../common');
const repl = require('repl');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const { startNewREPLServer } = require('../common/repl');
(async function() {
await runTest();
})().then(common.mustCall());
async function runTest() {
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: output,
allowBlockingCompletions: true,
terminal: true
});
replServer._domain.on('error', (e) => {
assert.fail(`Error in REPL domain: ${e}`);
});
const { replServer } = startNewREPLServer();
await new Promise((resolve, reject) => {
replServer.eval(`

View File

@ -1,7 +1,6 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { builtinModules } = require('module');
@ -19,20 +18,12 @@ if (!isMainThread) {
process.chdir(fixtures.fixturesDir);
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const putIn = new ArrayStream();
const testMe = repl.start({
prompt: '',
input: putIn,
output: process.stdout,
allowBlockingCompletions: true
});
// Some errors are passed to the domain, but do not callback
testMe._domain.on('error', assert.ifError);
const { replServer, input } = startNewREPLServer();
// Tab complete provides built in libs for import()
testMe.complete('import(\'', common.mustSucceed((data) => {
replServer.complete('import(\'', common.mustSucceed((data) => {
publicUnprefixedModules.forEach((lib) => {
assert(
data[0].includes(lib) && data[0].includes(`node:${lib}`),
@ -42,14 +33,14 @@ testMe.complete('import(\'', common.mustSucceed((data) => {
const newModule = 'foobar';
assert(!builtinModules.includes(newModule));
repl.builtinModules.push(newModule);
testMe.complete('import(\'', common.mustSucceed(([modules]) => {
replServer.complete('import(\'', common.mustSucceed(([modules]) => {
assert.strictEqual(data[0].length + 1, modules.length);
assert(modules.includes(newModule) &&
!modules.includes(`node:${newModule}`));
}));
}));
testMe.complete("import\t( 'n", common.mustSucceed((data) => {
replServer.complete("import\t( 'n", common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], 'n');
const completions = data[0];
@ -74,38 +65,37 @@ testMe.complete("import\t( 'n", common.mustSucceed((data) => {
const expected = ['@nodejsscope', '@nodejsscope/'];
// Import calls should handle all types of quotation marks.
for (const quotationMark of ["'", '"', '`']) {
putIn.run(['.clear']);
testMe.complete('import(`@nodejs', common.mustSucceed((data) => {
input.run(['.clear']);
replServer.complete('import(`@nodejs', common.mustSucceed((data) => {
assert.deepStrictEqual(data, [expected, '@nodejs']);
}));
putIn.run(['.clear']);
input.run(['.clear']);
// Completions should not be greedy in case the quotation ends.
const input = `import(${quotationMark}@nodejsscope${quotationMark}`;
testMe.complete(input, common.mustSucceed((data) => {
replServer.complete(`import(${quotationMark}@nodejsscope${quotationMark}`, common.mustSucceed((data) => {
assert.deepStrictEqual(data, [[], undefined]);
}));
}
}
{
putIn.run(['.clear']);
input.run(['.clear']);
// Completions should find modules and handle whitespace after the opening
// bracket.
testMe.complete('import \t("no_ind', common.mustSucceed((data) => {
replServer.complete('import \t("no_ind', common.mustSucceed((data) => {
assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']);
}));
}
// Test tab completion for import() relative to the current directory
{
putIn.run(['.clear']);
input.run(['.clear']);
const cwd = process.cwd();
process.chdir(__dirname);
['import(\'.', 'import(".'].forEach((input) => {
testMe.complete(input, common.mustSucceed((data) => {
replServer.complete(input, common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], '.');
assert.strictEqual(data[0].length, 2);
@ -115,14 +105,14 @@ testMe.complete("import\t( 'n", common.mustSucceed((data) => {
});
['import(\'..', 'import("..'].forEach((input) => {
testMe.complete(input, common.mustSucceed((data) => {
replServer.complete(input, common.mustSucceed((data) => {
assert.deepStrictEqual(data, [['../'], '..']);
}));
});
['./', './test-'].forEach((path) => {
[`import('${path}`, `import("${path}`].forEach((input) => {
testMe.complete(input, common.mustSucceed((data) => {
replServer.complete(input, common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('./test-repl-tab-complete.js'));
@ -132,7 +122,7 @@ testMe.complete("import\t( 'n", common.mustSucceed((data) => {
['../parallel/', '../parallel/test-'].forEach((path) => {
[`import('${path}`, `import("${path}`].forEach((input) => {
testMe.complete(input, common.mustSucceed((data) => {
replServer.complete(input, common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('../parallel/test-repl-tab-complete.js'));
@ -142,7 +132,7 @@ testMe.complete("import\t( 'n", common.mustSucceed((data) => {
{
const path = '../fixtures/repl-folder-extensions/f';
testMe.complete(`import('${path}`, common.mustSucceed((data) => {
replServer.complete(`import('${path}`, common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes(

View File

@ -1,21 +1,17 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const DEFAULT_MAX_LISTENERS = require('events').defaultMaxListeners;
ArrayStream.prototype.write = () => {};
const putIn = new ArrayStream();
const testMe = repl.start('', putIn);
const { replServer, input } = startNewREPLServer();
// https://github.com/nodejs/node/issues/18284
// Tab-completion should not repeatedly add the
// `Runtime.executionContextCreated` listener
process.on('warning', common.mustNotCall());
putIn.run(['async function test() {']);
input.run(['async function test() {']);
for (let i = 0; i < DEFAULT_MAX_LISTENERS; i++) {
testMe.complete('await Promise.resolve()', () => {});
replServer.complete('await Promise.resolve()', () => {});
}

View File

@ -1,26 +1,9 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { describe, it } = require('node:test');
const assert = require('assert');
const repl = require('repl');
function prepareREPL() {
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
return { replServer, input };
}
const { startNewREPLServer } = require('../common/repl');
function getNoResultsFunction() {
return common.mustSucceed((data) => {
@ -44,7 +27,7 @@ describe('REPL tab completion without side effects', () => {
'arr[incCounter()].b',
]) {
it(`does not evaluate with side effects (${code})`, async () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(setup);
replServer.complete(code, getNoResultsFunction());

View File

@ -2,41 +2,28 @@
const common = require('../common');
const assert = require('assert');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
// Tab completion in editor mode
{
const editorStream = new ArrayStream();
const editor = repl.start({
stream: editorStream,
terminal: true,
useColors: false
});
const { replServer, input } = startNewREPLServer();
editorStream.run(['.clear']);
editorStream.run(['.editor']);
input.run(['.clear', '.editor']);
editor.completer('Uin', common.mustCall((_error, data) => {
replServer.completer('Uin', common.mustCall((_error, data) => {
assert.deepStrictEqual(data, [['Uint'], 'Uin']);
}));
editorStream.run(['.clear']);
editorStream.run(['.editor']);
input.run(['.clear', '.editor']);
editor.completer('var log = console.l', common.mustCall((_error, data) => {
replServer.completer('var log = console.l', common.mustCall((_error, data) => {
assert.deepStrictEqual(data, [['console.log'], 'console.l']);
}));
}
// Regression test for https://github.com/nodejs/node/issues/43528
{
const stream = new ArrayStream();
const replServer = repl.start({
input: stream,
output: stream,
terminal: true,
});
const { replServer } = startNewREPLServer();
// Editor mode
replServer.write('.editor\n');

View File

@ -1,7 +1,6 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { builtinModules } = require('module');
@ -19,24 +18,11 @@ if (!isMainThread) {
process.chdir(fixtures.fixturesDir);
const repl = require('repl');
function prepareREPL() {
const replServer = repl.start({
prompt: '',
input: new ArrayStream(),
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
return replServer;
}
const { startNewREPLServer } = require('../common/repl');
// Tab completion on require on builtin modules works
{
const replServer = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
"require('",
@ -65,7 +51,7 @@ function prepareREPL() {
// Tab completion on require on builtin modules works (with extra spaces and "n" prefix)
{
const replServer = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
"require\t( 'n",
@ -98,7 +84,7 @@ function prepareREPL() {
{
const expected = ['@nodejsscope', '@nodejsscope/'];
const replServer = prepareREPL();
const { replServer } = startNewREPLServer();
// Require calls should handle all types of quotation marks.
for (const quotationMark of ["'", '"', '`']) {
@ -124,7 +110,7 @@ function prepareREPL() {
{
// Completions should find modules and handle whitespace after the opening bracket.
const replServer = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'require \t("no_ind',
@ -137,7 +123,7 @@ function prepareREPL() {
// Test tab completion for require() relative to the current directory
{
const replServer = prepareREPL();
const { replServer } = startNewREPLServer();
const cwd = process.cwd();
process.chdir(__dirname);

View File

@ -2,7 +2,7 @@
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
const { describe, it } = require('node:test');
// This test verifies that tab completion works correctly with unary expressions
@ -12,25 +12,20 @@ const { describe, it } = require('node:test');
describe('REPL tab completion with unary expressions', () => {
it('should handle delete operator correctly', (t, done) => {
const r = repl.start({
prompt: '',
input: process.stdin,
output: process.stdout,
terminal: false,
});
const { replServer } = startNewREPLServer({ terminal: false });
// Test delete with member expression
r.complete(
replServer.complete(
'delete globalThis._',
common.mustSucceed((completions) => {
assert.strictEqual(completions[1], 'globalThis._');
// Test delete with identifier
r.complete(
replServer.complete(
'delete globalThis',
common.mustSucceed((completions) => {
assert.strictEqual(completions[1], 'globalThis');
r.close();
replServer.close();
done();
})
);
@ -39,48 +34,33 @@ describe('REPL tab completion with unary expressions', () => {
});
it('should handle typeof operator correctly', (t, done) => {
const r = repl.start({
prompt: '',
input: process.stdin,
output: process.stdout,
terminal: false,
});
const { replServer } = startNewREPLServer({ terminal: false });
r.complete(
replServer.complete(
'typeof globalThis',
common.mustSucceed((completions) => {
assert.strictEqual(completions[1], 'globalThis');
r.close();
replServer.close();
done();
})
);
});
it('should handle void operator correctly', (t, done) => {
const r = repl.start({
prompt: '',
input: process.stdin,
output: process.stdout,
terminal: false,
});
const { replServer } = startNewREPLServer({ terminal: false });
r.complete(
replServer.complete(
'void globalThis',
common.mustSucceed((completions) => {
assert.strictEqual(completions[1], 'globalThis');
r.close();
replServer.close();
done();
})
);
});
it('should handle other unary operators correctly', (t, done) => {
const r = repl.start({
prompt: '',
input: process.stdin,
output: process.stdout,
terminal: false,
});
const { replServer } = startNewREPLServer({ terminal: false });
const unaryOperators = [
'!globalThis',
@ -93,13 +73,13 @@ describe('REPL tab completion with unary expressions', () => {
function testNext() {
if (testIndex >= unaryOperators.length) {
r.close();
replServer.close();
done();
return;
}
const testCase = unaryOperators[testIndex++];
r.complete(
replServer.complete(
testCase,
common.mustSucceed((completions) => {
assert.strictEqual(completions[1], 'globalThis');
@ -112,26 +92,21 @@ describe('REPL tab completion with unary expressions', () => {
});
it('should still evaluate globalThis correctly after unary expression completion', (t, done) => {
const r = repl.start({
prompt: '',
input: process.stdin,
output: process.stdout,
terminal: false,
});
const { replServer } = startNewREPLServer({ terminal: false });
// First trigger completion with delete
r.complete(
replServer.complete(
'delete globalThis._',
common.mustSucceed(() => {
// Then evaluate globalThis
r.eval(
replServer.eval(
'globalThis',
r.context,
replServer.context,
'test.js',
common.mustSucceed((result) => {
assert.strictEqual(typeof result, 'object');
assert.ok(result !== null);
r.close();
replServer.close();
done();
})
);

View File

@ -22,42 +22,25 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const { startNewREPLServer } = require('../common/repl');
const { describe, it } = require('node:test');
const assert = require('assert');
const repl = require('repl');
function getNoResultsFunction() {
return common.mustSucceed((data) => {
assert.deepStrictEqual(data[0], []);
});
}
function prepareREPL() {
const input = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: process.stdout,
allowBlockingCompletions: true,
});
// Some errors are passed to the domain, but do not callback
replServer._domain.on('error', assert.ifError);
return { replServer, input };
}
describe('REPL tab completion (core functionality)', () => {
it('does not break with variable declarations without an initialization', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete('let a', getNoResultsFunction());
replServer.close();
});
it('does not break in an object literal', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var inner = {', 'one:1']);
@ -74,7 +57,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works with optional chaining', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'console?.lo',
@ -102,7 +85,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('returns object completions', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var inner = {', 'one:1']);
@ -119,7 +102,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('does not break in a ternary operator with ()', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var inner = ( true ', '?', '{one: 1} : ']);
@ -129,7 +112,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works on literals', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'``.a',
@ -172,7 +155,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it("does not return a function's local variable", () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var top = function() {', 'var inner = {one:1};', '}']);
@ -182,7 +165,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it("does not return a function's local variable even when the function has parameters", () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var top = function(one, two) {',
@ -198,7 +181,7 @@ describe('REPL tab completion (core functionality)', () => {
it("does not return a function's local variable" +
'even if the scope is nested inside an immediately executed function', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var top = function() {',
@ -216,7 +199,7 @@ describe('REPL tab completion (core functionality)', () => {
it("does not return a function's local variable" +
'even if the scope is nested inside an immediately executed function' +
'(the definition has the params and { on a separate line)', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var top = function() {',
@ -233,7 +216,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('currently does not work, but should not break (local inner)', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var top = function() {',
@ -250,7 +233,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('currently does not work, but should not break (local inner parens next line)', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var top = function() {',
@ -268,7 +251,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works on non-Objects', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var str = "test";']);
@ -283,7 +266,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('should be case-insensitive if member part is lower-case', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
@ -301,7 +284,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('should be case-insensitive if member part is upper-case', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
@ -319,7 +302,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('should not break on spaces', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
const spaceTimeout = setTimeout(function() {
throw new Error('timeout');
@ -338,7 +321,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it(`should pick up the global "toString" object, and any other properties up the "global" object's prototype chain`, () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'toSt',
@ -351,7 +334,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('should make own properties shadow properties on the prototype', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var x = Object.create(null);',
@ -373,7 +356,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works on context properties', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var custom = "test";']);
@ -388,7 +371,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it("doesn't crash REPL with half-baked proxy objects", () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});',
@ -406,7 +389,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('does not include integer members of an Array', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var ary = [1,2,3];']);
@ -423,7 +406,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('does not include integer keys in an object', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var obj = {1:"a","1a":"b",a:"b"};']);
@ -440,7 +423,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('does not try to complete results of non-simple expressions', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['function a() {}']);
@ -450,7 +433,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works when prefixed with spaces', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var obj = {1:"a","1a":"b",a:"b"};']);
@ -467,7 +450,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works inside assignments', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'var log = console.lo',
@ -480,7 +463,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works for defined commands', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
replServer.complete(
'.b',
@ -503,7 +486,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('does not include __defineSetter__ and friends', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run(['var obj = {};']);
@ -522,7 +505,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works with builtin values', () => {
const { replServer } = prepareREPL();
const { replServer } = startNewREPLServer();
replServer.complete(
'I',
@ -555,7 +538,7 @@ describe('REPL tab completion (core functionality)', () => {
});
it('works with lexically scoped variables', () => {
const { replServer, input } = prepareREPL();
const { replServer, input } = startNewREPLServer();
input.run([
'let lexicalLet = true;',

View File

@ -5,40 +5,37 @@
// should throw.
require('../common');
const ArrayStream = require('../common/arraystream');
const repl = require('repl');
const assert = require('assert');
const { startNewREPLServer } = require('../common/repl');
let accum = '';
const { replServer, output } = startNewREPLServer(
{
prompt: '',
terminal: false,
useColors: false,
global: false,
},
{
disableDomainErrorAssert: true
},
);
const output = new ArrayStream();
output.write = (data) => accum += data.replace('\r', '');
const r = repl.start({
prompt: '',
input: new ArrayStream(),
output,
terminal: false,
useColors: false,
global: false
});
r.write(
replServer.write(
'process.nextTick(() => {\n' +
' process.on("uncaughtException", () => console.log("Foo"));\n' +
' throw new TypeError("foobar");\n' +
'});\n'
);
r.write(
replServer.write(
'setTimeout(() => {\n' +
' throw new RangeError("abc");\n' +
'}, 1);console.log()\n'
);
setTimeout(() => {
r.close();
replServer.close();
const len = process.listenerCount('uncaughtException');
process.removeAllListeners('uncaughtException');
assert.strictEqual(len, 0);
assert.match(accum, /ERR_INVALID_REPL_INPUT.*(?!Type)RangeError: abc/s);
assert.match(output.accumulator, /ERR_INVALID_REPL_INPUT.*(?!Type)RangeError: abc/s);
}, 2);

View File

@ -1,23 +1,27 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
const { PassThrough } = require('stream');
const input = new PassThrough();
const output = new PassThrough();
const { startNewREPLServer } = require('../common/repl');
const r = repl.start({
input, output,
eval: common.mustCall((code, context, filename, cb) => {
r.setPrompt('prompt! ');
cb(new Error('err'));
})
});
const { replServer, output } = startNewREPLServer(
{
prompt: '',
terminal: false,
useColors: false,
global: false,
eval: common.mustCall((code, context, filename, cb) => {
replServer.setPrompt('prompt! ');
cb(new Error('err'));
})
},
{
disableDomainErrorAssert: true
},
);
input.end('foo\n');
replServer.write('foo\n');
// The output includes exactly one post-error prompt.
const out = output.read().toString();
assert.match(out, /prompt!/);
assert.doesNotMatch(out, /prompt![\S\s]*prompt!/);
assert.match(output.accumulator, /prompt!/);
assert.doesNotMatch(output.accumulator, /prompt![\S\s]*prompt!/);
output.on('data', common.mustNotCall());

View File

@ -1,38 +1,36 @@
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');
let count = 0;
function run({ command, expected, useColors = false }) {
let accum = '';
const { replServer, output } = startNewREPLServer(
{
prompt: '',
terminal: false,
useColors,
},
{
disableDomainErrorAssert: true
},
);
const output = new ArrayStream();
output.write = (data) => accum += data.replace('\r', '');
replServer.write(`${command}\n`);
const r = repl.start({
prompt: '',
input: new ArrayStream(),
output,
terminal: false,
useColors
});
r.write(`${command}\n`);
if (typeof expected === 'string') {
assert.strictEqual(accum, expected);
assert.strictEqual(output.accumulator, expected);
} else {
assert.match(accum, expected);
assert.match(output.accumulator, expected);
}
// Verify that the repl is still working as expected.
accum = '';
r.write('1 + 1\n');
output.accumulator = '';
replServer.write('1 + 1\n');
// eslint-disable-next-line no-control-regex
assert.strictEqual(accum.replace(/\u001b\[[0-9]+m/g, ''), '2\n');
r.close();
assert.strictEqual(output.accumulator.replace(/\u001b\[[0-9]+m/g, ''), '2\n');
replServer.close();
count++;
}

View File

@ -3,20 +3,25 @@
require('../common');
const assert = require('assert');
const repl = require('repl');
const stream = require('stream');
const { startNewREPLServer } = require('../common/repl');
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
testSloppyMode();
testStrictMode();
testMagicMode();
testResetContext();
testResetContextGlobal();
testMagicMode();
testError();
function testSloppyMode() {
const r = initRepl(repl.REPL_MODE_SLOPPY);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
mode: repl.REPL_MODE_SLOPPY,
});
// Cannot use `let` in sloppy mode
r.write(`_; // initial value undefined
replServer.write(`_; // initial value undefined
var x = 10; // evaluates to undefined
_; // still undefined
y = 10; // evaluates to 10
@ -29,7 +34,7 @@ function testSloppyMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
assertOutput(output, [
'undefined',
'undefined',
'undefined',
@ -46,9 +51,12 @@ function testSloppyMode() {
}
function testStrictMode() {
const r = initRepl(repl.REPL_MODE_STRICT);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
mode: repl.REPL_MODE_STRICT,
});
r.write(`_; // initial value undefined
replServer.write(`_; // initial value undefined
var x = 10; // evaluates to undefined
_; // still undefined
let _ = 20; // use 'let' only in strict mode - evals to undefined
@ -62,7 +70,7 @@ function testStrictMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
assertOutput(output, [
'undefined',
'undefined',
'undefined',
@ -79,9 +87,12 @@ function testStrictMode() {
}
function testMagicMode() {
const r = initRepl(repl.REPL_MODE_MAGIC);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
mode: repl.REPL_MODE_MAGIC,
});
r.write(`_; // initial value undefined
replServer.write(`_; // initial value undefined
x = 10; //
_; // last eval - 10
let _ = 20; // undefined
@ -95,7 +106,7 @@ function testMagicMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
assertOutput(output, [
'undefined',
'10',
'10',
@ -112,9 +123,12 @@ function testMagicMode() {
}
function testResetContext() {
const r = initRepl(repl.REPL_MODE_SLOPPY);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
mode: repl.REPL_MODE_MAGIC,
});
r.write(`_ = 10; // explicitly set to 10
replServer.write(`_ = 10; // explicitly set to 10
_; // 10 from user input
.clear // Clearing context...
_; // remains 10
@ -122,7 +136,7 @@ function testResetContext() {
_; // expect 20
`);
assertOutput(r.output, [
assertOutput(output, [
'Expression assignment to _ now disabled.',
'10',
'10',
@ -134,15 +148,18 @@ function testResetContext() {
}
function testResetContextGlobal() {
const r = initRepl(repl.REPL_MODE_STRICT, true);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
useGlobal: true,
});
r.write(`_ = 10; // explicitly set to 10
replServer.write(`_ = 10; // explicitly set to 10
_; // 10 from user input
.clear // No output because useGlobal is true
_; // remains 10
`);
assertOutput(r.output, [
assertOutput(output, [
'Expression assignment to _ now disabled.',
'10',
'10',
@ -155,9 +172,15 @@ function testResetContextGlobal() {
}
function testError() {
const r = initRepl(repl.REPL_MODE_STRICT);
const { replServer, output } = startNewREPLServer({
prompt: testingReplPrompt,
replMode: repl.REPL_MODE_STRICT,
preview: false,
}, {
disableDomainErrorAssert: true
});
r.write(`_error; // initial value undefined
replServer.write(`_error; // initial value undefined
throw new Error('foo'); // throws error
_error; // shows error
fs.readdirSync('/nonexistent?'); // throws error, sync
@ -168,7 +191,9 @@ function testError() {
`);
setImmediate(() => {
const lines = r.output.accum.trim().split('\n');
const lines = output.accumulator.trim().split('\n').filter(
(line) => !line.includes(testingReplPrompt) || line.includes('Uncaught Error')
);
const expectedLines = [
'undefined',
@ -192,7 +217,7 @@ function testError() {
'undefined',
// The message from the original throw
'Uncaught Error: baz',
/Uncaught Error: baz/,
];
for (const line of lines) {
const expected = expectedLines.shift();
@ -204,14 +229,14 @@ function testError() {
assert.strictEqual(expectedLines.length, 0);
// Reset output, check that '_error' is the asynchronously caught error.
r.output.accum = '';
r.write(`_error.message // show the message
output.accumulator = '';
replServer.write(`_error.message // show the message
_error = 0; // disable auto-assignment
throw new Error('quux'); // new error
_error; // should not see the new error
`);
assertOutput(r.output, [
assertOutput(output, [
"'baz'",
'Expression assignment to _error now disabled.',
'0',
@ -221,27 +246,7 @@ function testError() {
});
}
function initRepl(mode, useGlobal) {
const inputStream = new stream.PassThrough();
const outputStream = new stream.PassThrough();
outputStream.accum = '';
outputStream.on('data', (data) => {
outputStream.accum += data;
});
return repl.start({
input: inputStream,
output: outputStream,
useColors: false,
terminal: false,
prompt: '',
replMode: mode,
useGlobal: useGlobal
});
}
function assertOutput(output, expected) {
const lines = output.accum.trim().split('\n');
const lines = output.accumulator.trim().split('\n').filter((line) => !line.includes(testingReplPrompt));
assert.deepStrictEqual(lines, expected);
}