diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js new file mode 100644 index 00000000000000..562b79dae846d4 --- /dev/null +++ b/test/parallel/test-repl-history-navigation.js @@ -0,0 +1,139 @@ +'use strict'; + +// Flags: --expose-internals + +const common = require('../common'); +const stream = require('stream'); +const REPL = require('internal/repl'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history'); + +// Create an input stream specialized for testing an array of actions +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); + } else { + this.emit('data', `${action}`); + } + setImmediate(doAction); + }; + setImmediate(doAction); + } + resume() {} + pause() {} +} +ActionStream.prototype.readable = true; + + +// Mock keys +const ENTER = { name: 'enter' }; +const UP = { name: 'up' }; +const DOWN = { name: 'down' }; + +const prompt = '> '; + +const tests = [ + { // creates few history to navigate for + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + test: [ 'let ab = 45', ENTER, + '555 + 909', ENTER, + '{key : {key2 :[] }}', ENTER], + expected: [], + clean: false + }, + { + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + test: [UP, UP, UP, UP, DOWN, DOWN, DOWN], + expected: [prompt, + `${prompt}{key : {key2 :[] }}`, + `${prompt}555 + 909`, + `${prompt}let ab = 45`, + `${prompt}555 + 909`, + `${prompt}{key : {key2 :[] }}`, + prompt], + clean: true + } +]; +const numtests = tests.length; + +const runTestWrap = common.mustCall(runTest, numtests); + +function cleanupTmpFile() { + try { + // Write over the file, clearing any history + fs.writeFileSync(defaultHistoryPath, ''); + } catch (err) { + if (err.code === 'ENOENT') return true; + throw err; + } + return true; +} + +function runTest() { + const opts = tests.shift(); + if (!opts) return; // All done + + const env = opts.env; + const test = opts.test; + const expected = opts.expected; + + REPL.createInternalRepl(env, { + input: new ActionStream(), + output: new stream.Writable({ + write(chunk, _, next) { + const output = chunk.toString(); + + if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) { + return next(); + } + + if (expected.length) { + assert.strictEqual(output, expected[0]); + expected.shift(); + } + + next(); + } + }), + prompt: prompt, + useColors: false, + terminal: true + }, function(err, repl) { + if (err) { + console.error(`Failed test # ${numtests - tests.length}`); + throw err; + } + + repl.once('close', () => { + if (opts.clean) + cleanupTmpFile(); + + if (expected.length !== 0) { + throw new Error(`Failed test # ${numtests - tests.length}`); + } + setImmediate(runTestWrap, true); + }); + + repl.inputStream.run(test); + }); +} + +// run the tests +runTest();