node/test/parallel/test-repl-tab-complete-require.js
Dario Piotrowicz aee9bc0fc6
test: refactor repl tab complete tests
refactor the test/parallel/test-repl-tab-complete.js file by:
 - making the tests in the file self-contained
   (instead of all of them sharing the same REPL instance and
   constantly calling `.clear` on it)
 - using the test runner with appropriate descriptions to make
   clearer what is being tested
 - extracting some tests in their own js test files
   (to increase isolation of the tests and help with issues such
   as flakiness)

PR-URL: https://github.com/nodejs/node/pull/58636
Reviewed-By: Giovanni Bucci <github@puskin.it>
Reviewed-By: James M Snell <jasnell@gmail.com>
2025-06-15 09:48:49 +00:00

212 lines
6 KiB
JavaScript

'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { builtinModules } = require('module');
const publicModules = builtinModules.filter((lib) => !lib.startsWith('_'));
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('process.chdir is not available in Workers');
}
// We have to change the directory to ../fixtures before requiring repl
// in order to make the tests for completion of node_modules work properly
// since repl modifies module.paths.
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;
}
// Tab completion on require on builtin modules works
{
const replServer = prepareREPL();
replServer.complete(
"require('",
common.mustCall(function(error, data) {
assert.strictEqual(error, null);
publicModules.forEach((lib) => {
assert(
data[0].includes(lib) &&
(lib.startsWith('node:') || data[0].includes(`node:${lib}`)),
`${lib} not found`
);
});
const newModule = 'foobar';
assert(!builtinModules.includes(newModule));
repl.builtinModules.push(newModule);
replServer.complete(
"require('",
common.mustCall((_, [modules]) => {
assert.strictEqual(data[0].length + 1, modules.length);
assert(modules.includes(newModule));
})
);
})
);
}
// Tab completion on require on builtin modules works (with extra spaces and "n" prefix)
{
const replServer = prepareREPL();
replServer.complete(
"require\t( 'n",
common.mustCall(function(error, data) {
assert.strictEqual(error, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], 'n');
// require(...) completions include `node:`-prefixed modules:
let lastIndex = -1;
publicModules
.filter((lib) => !lib.startsWith('node:'))
.forEach((lib, index) => {
lastIndex = data[0].indexOf(`node:${lib}`);
assert.notStrictEqual(lastIndex, -1);
});
assert.strictEqual(data[0][lastIndex + 1], '');
// There is only one Node.js module that starts with n:
assert.strictEqual(data[0][lastIndex + 2], 'net');
assert.strictEqual(data[0][lastIndex + 3], '');
// It's possible to pick up non-core modules too
data[0].slice(lastIndex + 4).forEach((completion) => {
assert.match(completion, /^n/);
});
})
);
}
// Tab completion on require on external modules works
{
const expected = ['@nodejsscope', '@nodejsscope/'];
const replServer = prepareREPL();
// Require calls should handle all types of quotation marks.
for (const quotationMark of ["'", '"', '`']) {
replServer.complete(
'require(`@nodejs',
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [expected, '@nodejs']);
})
);
// Completions should not be greedy in case the quotation ends.
const input = `require(${quotationMark}@nodejsscope${quotationMark}`;
replServer.complete(
input,
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [[], undefined]);
})
);
}
}
{
// Completions should find modules and handle whitespace after the opening bracket.
const replServer = prepareREPL();
replServer.complete(
'require \t("no_ind',
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']);
})
);
}
// Test tab completion for require() relative to the current directory
{
const replServer = prepareREPL();
const cwd = process.cwd();
process.chdir(__dirname);
["require('.", 'require(".'].forEach((input) => {
replServer.complete(
input,
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], '.');
assert.strictEqual(data[0].length, 2);
assert.ok(data[0].includes('./'));
assert.ok(data[0].includes('../'));
})
);
});
["require('..", 'require("..'].forEach((input) => {
replServer.complete(
input,
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(data, [['../'], '..']);
})
);
});
['./', './test-'].forEach((path) => {
[`require('${path}`, `require("${path}`].forEach((input) => {
replServer.complete(
input,
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('./test-repl-tab-complete'));
})
);
});
});
['../parallel/', '../parallel/test-'].forEach((path) => {
[`require('${path}`, `require("${path}`].forEach((input) => {
replServer.complete(
input,
common.mustCall((err, data) => {
assert.strictEqual(err, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(data[0].includes('../parallel/test-repl-tab-complete'));
})
);
});
});
{
const path = '../fixtures/repl-folder-extensions/f';
replServer.complete(
`require('${path}`,
common.mustSucceed((data) => {
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], path);
assert.ok(
data[0].includes('../fixtures/repl-folder-extensions/foo.js')
);
})
);
}
process.chdir(cwd);
}