node/test/parallel/test-repl-multiline-navigation.js
Giovanni Bucci af85f3f169
repl: fix multiline history editing string order
PR-URL: https://github.com/nodejs/node/pull/57874
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2025-04-16 15:01:38 +00:00

193 lines
5.5 KiB
JavaScript

'use strict';
// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const repl = require('internal/repl');
const stream = require('stream');
class ActionStream extends stream.Stream {
run(data) {
const _iter = data[Symbol.iterator]();
const doAction = () => {
const next = _iter.next();
if (next.done) {
// Close the repl. Note that it must have a clean prompt to do so.
this.emit('keypress', '', { ctrl: true, name: 'd' });
return;
}
const action = next.value;
if (typeof action === 'object') {
this.emit('keypress', '', action);
}
setImmediate(doAction);
};
doAction();
}
write(chunk) {
const chunkLines = chunk.toString('utf8').split('\n');
this.lines[this.lines.length - 1] += chunkLines[0];
if (chunkLines.length > 1) {
this.lines.push(...chunkLines.slice(1));
}
this.emit('line', this.lines[this.lines.length - 1]);
return true;
}
resume() {}
pause() {}
}
ActionStream.prototype.readable = true;
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
// Make sure the cursor is at the right places.
// If the cursor is at the end of a long line and the down key is pressed,
// Move the cursor to the end of the next line, if shorter.
const checkResults = common.mustSucceed((r) => {
r.write('let str = `');
r.input.run([{ name: 'enter' }]);
r.write('111');
r.input.run([{ name: 'enter' }]);
r.write('22222222222222');
r.input.run([{ name: 'enter' }]);
r.write('3`');
r.input.run([{ name: 'enter' }]);
r.input.run([{ name: 'up' }]);
assert.strictEqual(r.cursor, 33);
r.input.run([{ name: 'up' }]);
assert.strictEqual(r.cursor, 18);
for (let i = 0; i < 5; i++) {
r.input.run([{ name: 'right' }]);
}
r.input.run([{ name: 'up' }]);
assert.strictEqual(r.cursor, 15);
r.input.run([{ name: 'up' }]);
for (let i = 0; i < 5; i++) {
r.input.run([{ name: 'right' }]);
}
assert.strictEqual(r.cursor, 8);
r.input.run([{ name: 'down' }]);
assert.strictEqual(r.cursor, 15);
r.input.run([{ name: 'down' }]);
assert.strictEqual(r.cursor, 19);
});
repl.createInternalRepl(
{ NODE_REPL_HISTORY: historyPath },
{
terminal: true,
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
next();
}
}),
},
checkResults
);
}
{
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
// If the last command errored and the user is trying to edit it,
// The errored line should be removed from history
const checkResults = common.mustSucceed((r) => {
r.write('let lineWithMistake = `I have some');
r.input.run([{ name: 'enter' }]);
r.write('problem with` my syntax\'');
r.input.run([{ name: 'enter' }]);
r.input.run([{ name: 'up' }]);
r.input.run([{ name: 'backspace' }]);
r.write('`');
for (let i = 0; i < 11; i++) {
r.input.run([{ name: 'left' }]);
}
r.input.run([{ name: 'backspace' }]);
r.input.run([{ name: 'enter' }]);
assert.strictEqual(r.history.length, 1);
// Check that the line is properly set in the history structure
assert.strictEqual(r.history[0], 'problem with my syntax`\rlet lineWithMistake = `I have some');
assert.strictEqual(r.line, '');
r.input.run([{ name: 'up' }]);
// Check that the line is properly displayed
assert.strictEqual(r.line, 'let lineWithMistake = `I have some\nproblem with my syntax`');
});
repl.createInternalRepl(
{ NODE_REPL_HISTORY: historyPath },
{
terminal: true,
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
next();
}
}),
},
checkResults
);
}
{
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
const outputBuffer = [];
// Test that the REPL preview is properly shown on multiline commands
// And deleted when enter is pressed
const checkResults = common.mustSucceed((r) => {
r.write('Array(100).fill(');
r.input.run([{ name: 'enter' }]);
r.write('123');
r.input.run([{ name: 'enter' }]);
r.write(')');
r.input.run([{ name: 'enter' }]);
r.input.run([{ name: 'up' }]);
r.input.run([{ name: 'up' }]);
assert.deepStrictEqual(r.last, new Array(100).fill(123));
r.input.run([{ name: 'enter' }]);
assert.strictEqual(outputBuffer.includes('[\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123,\n' +
' 123\n' +
']\n'), true);
});
repl.createInternalRepl(
{ NODE_REPL_HISTORY: historyPath },
{
preview: true,
terminal: true,
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
// Store each chunk in the buffer
outputBuffer.push(chunk.toString());
next();
}
}),
},
checkResults
);
}