From 782a914a3b5e0ab4f60ad8fa21dfb59021fa9f63 Mon Sep 17 00:00:00 2001 From: Philip Russell Date: Sat, 24 Feb 2024 08:58:04 +0000 Subject: [PATCH] bun fmt --- test/js/node/readline/readline.node.test.ts | 3564 +++++++++---------- 1 file changed, 1782 insertions(+), 1782 deletions(-) diff --git a/test/js/node/readline/readline.node.test.ts b/test/js/node/readline/readline.node.test.ts index a618221b4091d4..7e33c9f02de958 100644 --- a/test/js/node/readline/readline.node.test.ts +++ b/test/js/node/readline/readline.node.test.ts @@ -62,1954 +62,1954 @@ const writable = new TestWritable(); const input = new FakeInput(); if (0) { -// ---------------------------------------------------------------------------- -// Tests -// ---------------------------------------------------------------------------- + // ---------------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------------- -describe("CSI", () => { - it("should be defined", () => { - assert.ok(CSI); - }); + describe("CSI", () => { + it("should be defined", () => { + assert.ok(CSI); + }); - it("should have all the correct clear sequences", () => { - assert.strictEqual(CSI.kClearToLineBeginning, "\x1b[1K"); - assert.strictEqual(CSI.kClearToLineEnd, "\x1b[0K"); - assert.strictEqual(CSI.kClearLine, "\x1b[2K"); - assert.strictEqual(CSI.kClearScreenDown, "\x1b[0J"); - assert.strictEqual(CSI`1${2}3`, "\x1b[123"); + it("should have all the correct clear sequences", () => { + assert.strictEqual(CSI.kClearToLineBeginning, "\x1b[1K"); + assert.strictEqual(CSI.kClearToLineEnd, "\x1b[0K"); + assert.strictEqual(CSI.kClearLine, "\x1b[2K"); + assert.strictEqual(CSI.kClearScreenDown, "\x1b[0J"); + assert.strictEqual(CSI`1${2}3`, "\x1b[123"); + }); }); -}); -describe("readline.clearScreenDown()", () => { - it("should put clear screen sequence into writable when called", done => { - const { mustCall } = createCallCheckCtx(done); + describe("readline.clearScreenDown()", () => { + it("should put clear screen sequence into writable when called", done => { + const { mustCall } = createCallCheckCtx(done); - assert.strictEqual(readline.clearScreenDown(writable), true); - assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); - assert.strictEqual(readline.clearScreenDown(writable, mustCall()), true); - }); + assert.strictEqual(readline.clearScreenDown(writable), true); + assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); + assert.strictEqual(readline.clearScreenDown(writable, mustCall()), true); + }); - it("should throw on invalid callback", () => { - // Verify that clearScreenDown() throws on invalid callback. - assert.throws(() => { - readline.clearScreenDown(writable, null); - }, /ERR_INVALID_ARG_TYPE/); - }); + it("should throw on invalid callback", () => { + // Verify that clearScreenDown() throws on invalid callback. + assert.throws(() => { + readline.clearScreenDown(writable, null); + }, /ERR_INVALID_ARG_TYPE/); + }); - it("should that clearScreenDown() does not throw on null or undefined stream", done => { - const { mustCall } = createCallCheckCtx(done); - assert.strictEqual( - readline.clearScreenDown( - null, - mustCall(err => { - assert.strictEqual(err, null); - }), - ), - true, - ); - assert.strictEqual(readline.clearScreenDown(undefined, mustCall()), true); + it("should that clearScreenDown() does not throw on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + assert.strictEqual( + readline.clearScreenDown( + null, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.clearScreenDown(undefined, mustCall()), true); + }); }); -}); -describe("readline.clearLine()", () => { - beforeEach(() => { - writable.data = ""; - }); + describe("readline.clearLine()", () => { + beforeEach(() => { + writable.data = ""; + }); - it("should clear to the left of cursor when given -1 as direction", () => { - assert.strictEqual(readline.clearLine(writable, -1), true); - assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); - }); + it("should clear to the left of cursor when given -1 as direction", () => { + assert.strictEqual(readline.clearLine(writable, -1), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + }); - it("should clear to the right of cursor when given 1 as direction", () => { - assert.strictEqual(readline.clearLine(writable, 1), true); - assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd); - }); + it("should clear to the right of cursor when given 1 as direction", () => { + assert.strictEqual(readline.clearLine(writable, 1), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd); + }); - it("should clear whole line when given 0 as direction", () => { - assert.strictEqual(readline.clearLine(writable, 0), true); - assert.deepStrictEqual(writable.data, CSI.kClearLine); - }); + it("should clear whole line when given 0 as direction", () => { + assert.strictEqual(readline.clearLine(writable, 0), true); + assert.deepStrictEqual(writable.data, CSI.kClearLine); + }); - it("should call callback after clearing line", done => { - const { mustCall } = createCallCheckCtx(done); - assert.strictEqual(readline.clearLine(writable, -1, mustCall()), true); - assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); - }); + it("should call callback after clearing line", done => { + const { mustCall } = createCallCheckCtx(done); + assert.strictEqual(readline.clearLine(writable, -1, mustCall()), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + }); - it("should throw on an invalid callback", () => { - // Verify that clearLine() throws on invalid callback. - assert.throws(() => { - readline.clearLine(writable, 0, null); - }, /ERR_INVALID_ARG_TYPE/); - }); + it("should throw on an invalid callback", () => { + // Verify that clearLine() throws on invalid callback. + assert.throws(() => { + readline.clearLine(writable, 0, null); + }, /ERR_INVALID_ARG_TYPE/); + }); - it("should not throw on null or undefined stream", done => { - const { mustCall } = createCallCheckCtx(done); - // Verify that clearLine() does not throw on null or undefined stream. - assert.strictEqual(readline.clearLine(null, 0), true); - assert.strictEqual(readline.clearLine(undefined, 0), true); - assert.strictEqual( - readline.clearLine( - null, - 0, - mustCall(err => { - assert.strictEqual(err, null); - }), - ), - true, - ); - assert.strictEqual(readline.clearLine(undefined, 0, mustCall()), true); - }); -}); - -describe("readline.moveCursor()", () => { - it("shouldn't write when moveCursor(0, 0) is called", done => { - const { mustCall } = createCallCheckCtx(done); - // Nothing is written when moveCursor 0, 0 - [ - [0, 0, ""], - [1, 0, "\x1b[1C"], - [-1, 0, "\x1b[1D"], - [0, 1, "\x1b[1B"], - [0, -1, "\x1b[1A"], - [1, 1, "\x1b[1C\x1b[1B"], - [-1, 1, "\x1b[1D\x1b[1B"], - [-1, -1, "\x1b[1D\x1b[1A"], - [1, -1, "\x1b[1C\x1b[1A"], - ].forEach(set => { - writable.data = ""; - assert.strictEqual(readline.moveCursor(writable, set[0], set[1]), true); - assert.deepStrictEqual(writable.data, set[2]); - writable.data = ""; - assert.strictEqual(readline.moveCursor(writable, set[0], set[1], mustCall()), true); - assert.deepStrictEqual(writable.data, set[2]); + it("should not throw on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Verify that clearLine() does not throw on null or undefined stream. + assert.strictEqual(readline.clearLine(null, 0), true); + assert.strictEqual(readline.clearLine(undefined, 0), true); + assert.strictEqual( + readline.clearLine( + null, + 0, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.clearLine(undefined, 0, mustCall()), true); }); }); - it("should throw on invalid callback", () => { - // Verify that moveCursor() throws on invalid callback. - assert.throws(() => { - readline.moveCursor(writable, 1, 1, null); - }, /ERR_INVALID_ARG_TYPE/); - }); + describe("readline.moveCursor()", () => { + it("shouldn't write when moveCursor(0, 0) is called", done => { + const { mustCall } = createCallCheckCtx(done); + // Nothing is written when moveCursor 0, 0 + [ + [0, 0, ""], + [1, 0, "\x1b[1C"], + [-1, 0, "\x1b[1D"], + [0, 1, "\x1b[1B"], + [0, -1, "\x1b[1A"], + [1, 1, "\x1b[1C\x1b[1B"], + [-1, 1, "\x1b[1D\x1b[1B"], + [-1, -1, "\x1b[1D\x1b[1A"], + [1, -1, "\x1b[1C\x1b[1A"], + ].forEach(set => { + writable.data = ""; + assert.strictEqual(readline.moveCursor(writable, set[0], set[1]), true); + assert.deepStrictEqual(writable.data, set[2]); + writable.data = ""; + assert.strictEqual(readline.moveCursor(writable, set[0], set[1], mustCall()), true); + assert.deepStrictEqual(writable.data, set[2]); + }); + }); - it("should not throw on null or undefined stream", done => { - const { mustCall } = createCallCheckCtx(done); - // Verify that moveCursor() does not throw on null or undefined stream. - assert.strictEqual(readline.moveCursor(null, 1, 1), true); - assert.strictEqual(readline.moveCursor(undefined, 1, 1), true); - assert.strictEqual( - readline.moveCursor( - null, - 1, - 1, - mustCall(err => { - assert.strictEqual(err, null); - }), - ), - true, - ); - assert.strictEqual(readline.moveCursor(undefined, 1, 1, mustCall()), true); - }); -}); + it("should throw on invalid callback", () => { + // Verify that moveCursor() throws on invalid callback. + assert.throws(() => { + readline.moveCursor(writable, 1, 1, null); + }, /ERR_INVALID_ARG_TYPE/); + }); -describe("readline.cursorTo()", () => { - beforeEach(() => { - writable.data = ""; + it("should not throw on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Verify that moveCursor() does not throw on null or undefined stream. + assert.strictEqual(readline.moveCursor(null, 1, 1), true); + assert.strictEqual(readline.moveCursor(undefined, 1, 1), true); + assert.strictEqual( + readline.moveCursor( + null, + 1, + 1, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.moveCursor(undefined, 1, 1, mustCall()), true); + }); }); - it("should not throw on undefined or null as stream", done => { - const { mustCall } = createCallCheckCtx(done); - // Undefined or null as stream should not throw. - assert.strictEqual(readline.cursorTo(null), true); - assert.strictEqual(readline.cursorTo(), true); - assert.strictEqual(readline.cursorTo(null, 1, 1, mustCall()), true); - assert.strictEqual( - readline.cursorTo( - undefined, - 1, - 1, - mustCall(err => { - assert.strictEqual(err, null); - }), - ), - true, - ); - }); + describe("readline.cursorTo()", () => { + beforeEach(() => { + writable.data = ""; + }); - it("should not write if given invalid cursor position - [string, undefined]", () => { - assert.strictEqual(readline.cursorTo(writable, "a"), true); - assert.strictEqual(writable.data, ""); - }); + it("should not throw on undefined or null as stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Undefined or null as stream should not throw. + assert.strictEqual(readline.cursorTo(null), true); + assert.strictEqual(readline.cursorTo(), true); + assert.strictEqual(readline.cursorTo(null, 1, 1, mustCall()), true); + assert.strictEqual( + readline.cursorTo( + undefined, + 1, + 1, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + }); - it("should not write if given invalid cursor position - [string, string]", () => { - assert.strictEqual(readline.cursorTo(writable, "a", "b"), true); - assert.strictEqual(writable.data, ""); - }); + it("should not write if given invalid cursor position - [string, undefined]", () => { + assert.strictEqual(readline.cursorTo(writable, "a"), true); + assert.strictEqual(writable.data, ""); + }); - it("should throw when x is not a number", () => { - assert.throws(() => readline.cursorTo(writable, "a", 1), { - name: "TypeError", - code: "ERR_INVALID_CURSOR_POS", - message: "Cannot set cursor row without setting its column", + it("should not write if given invalid cursor position - [string, string]", () => { + assert.strictEqual(readline.cursorTo(writable, "a", "b"), true); + assert.strictEqual(writable.data, ""); }); - assert.strictEqual(writable.data, ""); - }); - it("should write when given value cursor positions", done => { - const { mustCall } = createCallCheckCtx(done); + it("should throw when x is not a number", () => { + assert.throws(() => readline.cursorTo(writable, "a", 1), { + name: "TypeError", + code: "ERR_INVALID_CURSOR_POS", + message: "Cannot set cursor row without setting its column", + }); + assert.strictEqual(writable.data, ""); + }); - assert.strictEqual(readline.cursorTo(writable, 1, "a"), true); - assert.strictEqual(writable.data, "\x1b[2G"); + it("should write when given value cursor positions", done => { + const { mustCall } = createCallCheckCtx(done); - writable.data = ""; - assert.strictEqual(readline.cursorTo(writable, 1), true); - assert.strictEqual(writable.data, "\x1b[2G"); + assert.strictEqual(readline.cursorTo(writable, 1, "a"), true); + assert.strictEqual(writable.data, "\x1b[2G"); - writable.data = ""; - assert.strictEqual(readline.cursorTo(writable, 1, 2), true); - assert.strictEqual(writable.data, "\x1b[3;2H"); + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1), true); + assert.strictEqual(writable.data, "\x1b[2G"); - writable.data = ""; - assert.strictEqual(readline.cursorTo(writable, 1, 2, mustCall()), true); - assert.strictEqual(writable.data, "\x1b[3;2H"); + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, 2), true); + assert.strictEqual(writable.data, "\x1b[3;2H"); - writable.data = ""; - assert.strictEqual(readline.cursorTo(writable, 1, mustCall()), true); - assert.strictEqual(writable.data, "\x1b[2G"); - }); + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, 2, mustCall()), true); + assert.strictEqual(writable.data, "\x1b[3;2H"); - it("should throw on invalid callback", () => { - // Verify that cursorTo() throws on invalid callback. - assert.throws(() => { - readline.cursorTo(writable, 1, 1, null); - }, /ERR_INVALID_ARG_TYPE/); - }); + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, mustCall()), true); + assert.strictEqual(writable.data, "\x1b[2G"); + }); - it("should throw if x or y is NaN", () => { - // Verify that cursorTo() throws if x or y is NaN. - assert.throws(() => { - readline.cursorTo(writable, NaN); - }, /ERR_INVALID_ARG_VALUE/); + it("should throw on invalid callback", () => { + // Verify that cursorTo() throws on invalid callback. + assert.throws(() => { + readline.cursorTo(writable, 1, 1, null); + }, /ERR_INVALID_ARG_TYPE/); + }); - assert.throws(() => { - readline.cursorTo(writable, 1, NaN); - }, /ERR_INVALID_ARG_VALUE/); + it("should throw if x or y is NaN", () => { + // Verify that cursorTo() throws if x or y is NaN. + assert.throws(() => { + readline.cursorTo(writable, NaN); + }, /ERR_INVALID_ARG_VALUE/); - assert.throws(() => { - readline.cursorTo(writable, NaN, NaN); - }, /ERR_INVALID_ARG_VALUE/); - }); -}); - -describe("readline.emitKeyPressEvents()", () => { - // emitKeypressEvents is thoroughly tested in test-readline-keys.js. - // However, that test calls it implicitly. This is just a quick sanity check - // to verify that it works when called explicitly. - - const expectedSequence = ["f", "o", "o"]; - const expectedKeys = [ - { sequence: "f", name: "f", ctrl: false, meta: false, shift: false }, - { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, - { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, - ]; - - it("should emit the expected sequence when keypress listener added after called", () => { - const stream = new PassThrough(); - const sequence: any[] = []; - const keys: any[] = []; - - readline.emitKeypressEvents(stream); - stream.on("keypress", (s, k) => { - sequence.push(s); - keys.push(k); - }); - stream.write("foo"); - - assert.deepStrictEqual(sequence, expectedSequence); - assert.deepStrictEqual(keys, expectedKeys); - }); + assert.throws(() => { + readline.cursorTo(writable, 1, NaN); + }, /ERR_INVALID_ARG_VALUE/); - it("should emit the expected sequence when keypress listener added before called", () => { - const stream = new PassThrough(); - const sequence: any[] = []; - const keys: any[] = []; + assert.throws(() => { + readline.cursorTo(writable, NaN, NaN); + }, /ERR_INVALID_ARG_VALUE/); + }); + }); - stream.on("keypress", (s, k) => { - sequence.push(s); - keys.push(k); + describe("readline.emitKeyPressEvents()", () => { + // emitKeypressEvents is thoroughly tested in test-readline-keys.js. + // However, that test calls it implicitly. This is just a quick sanity check + // to verify that it works when called explicitly. + + const expectedSequence = ["f", "o", "o"]; + const expectedKeys = [ + { sequence: "f", name: "f", ctrl: false, meta: false, shift: false }, + { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, + { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, + ]; + + it("should emit the expected sequence when keypress listener added after called", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; + + readline.emitKeypressEvents(stream); + stream.on("keypress", (s, k) => { + sequence.push(s); + keys.push(k); + }); + stream.write("foo"); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); }); - readline.emitKeypressEvents(stream); - stream.write("foo"); - assert.deepStrictEqual(sequence, expectedSequence); - assert.deepStrictEqual(keys, expectedKeys); - }); + it("should emit the expected sequence when keypress listener added before called", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; - it("should allow keypress listeners to be removed and added again", () => { - const stream = new PassThrough(); - const sequence: any[] = []; - const keys: any[] = []; - const keypressListener = (s, k) => { - sequence.push(s); - keys.push(k); - }; + stream.on("keypress", (s, k) => { + sequence.push(s); + keys.push(k); + }); + readline.emitKeypressEvents(stream); + stream.write("foo"); - stream.on("keypress", keypressListener); - readline.emitKeypressEvents(stream); - stream.removeListener("keypress", keypressListener); - stream.write("foo"); + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); + }); - assert.deepStrictEqual(sequence, []); - assert.deepStrictEqual(keys, []); + it("should allow keypress listeners to be removed and added again", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; + const keypressListener = (s, k) => { + sequence.push(s); + keys.push(k); + }; - stream.on("keypress", keypressListener); - stream.write("foo"); + stream.on("keypress", keypressListener); + readline.emitKeypressEvents(stream); + stream.removeListener("keypress", keypressListener); + stream.write("foo"); - assert.deepStrictEqual(sequence, expectedSequence); - assert.deepStrictEqual(keys, expectedKeys); - }); -}); - -describe("readline.Interface", () => { - it("should allow valid escapeCodeTimeout to be set", () => { - const fi = new FakeInput(); - const rli = new readline.Interface({ - input: fi, - output: fi, - escapeCodeTimeout: 50, - }); - assert.strictEqual(rli.escapeCodeTimeout, 50); - rli.close(); - }); + assert.deepStrictEqual(sequence, []); + assert.deepStrictEqual(keys, []); - it("should throw on invalid escapeCodeTimeout", () => { - [null, {}, NaN, "50"].forEach(invalidInput => { - assert.throws( - () => { - const fi = new FakeInput(); - const rli = new readline.Interface({ - input: fi, - output: fi, - escapeCodeTimeout: invalidInput, - }); - rli.close(); - }, - { - name: "TypeError", - code: "ERR_INVALID_ARG_VALUE", - }, - ); + stream.on("keypress", keypressListener); + stream.write("foo"); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); }); }); - it("should create valid instances of readline.Interface", () => { - const input = new FakeInput(); - const rl = readline.Interface({ input }); - assert.ok(rl instanceof readline.Interface); - }); + describe("readline.Interface", () => { + it("should allow valid escapeCodeTimeout to be set", () => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: 50, + }); + assert.strictEqual(rli.escapeCodeTimeout, 50); + rli.close(); + }); - it("should call completer when input emits data", done => { - const { mustCall } = createCallCheckCtx(done); - const fi = new FakeInput(); - const rli = new readline.Interface( - fi, - fi, - mustCall(line => [[], line]), - true, - ); - - assert.ok(rli instanceof readline.Interface); - fi.emit("data", "a\t"); - rli.close(); - }); + it("should throw on invalid escapeCodeTimeout", () => { + [null, {}, NaN, "50"].forEach(invalidInput => { + assert.throws( + () => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: invalidInput, + }); + rli.close(); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should create valid instances of readline.Interface", () => { + const input = new FakeInput(); + const rl = readline.Interface({ input }); + assert.ok(rl instanceof readline.Interface); + }); - it("should allow crlfDelay to be set", () => { - [undefined, 50, 0, 100.5, 5000].forEach(crlfDelay => { - const [rli] = getInterface({ crlfDelay }); - assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); + it("should call completer when input emits data", done => { + const { mustCall } = createCallCheckCtx(done); + const fi = new FakeInput(); + const rli = new readline.Interface( + fi, + fi, + mustCall(line => [[], line]), + true, + ); + + assert.ok(rli instanceof readline.Interface); + fi.emit("data", "a\t"); rli.close(); }); - }); - it("should throw if completer is not a function or is undefined", () => { - ["not an array", 123, 123n, {}, true, Symbol(), null].forEach(invalid => { + it("should allow crlfDelay to be set", () => { + [undefined, 50, 0, 100.5, 5000].forEach(crlfDelay => { + const [rli] = getInterface({ crlfDelay }); + assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); + rli.close(); + }); + }); + + it("should throw if completer is not a function or is undefined", () => { + ["not an array", 123, 123n, {}, true, Symbol(), null].forEach(invalid => { + assert.throws( + () => { + readline.createInterface({ + input, + completer: invalid, + }); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should throw if history is not an array", () => { + ["not an array", 123, 123, {}, true, Symbol(), null].forEach(history => { + assert.throws( + () => { + readline.createInterface({ + input, + history, + }); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_TYPE", + }, + ); + }); + }); + + it("should throw if historySize is not a positive number", () => { + ["not a number", -1, NaN, {}, true, Symbol(), null].forEach(historySize => { + assert.throws( + () => { + readline.createInterface({ + input, + historySize, + }); + }, + { + // TODO: Revert to Range error when properly implemented errors with multiple bases + // name: "RangeError", + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should throw on invalid tabSize", () => { + // Check for invalid tab sizes. assert.throws( - () => { - readline.createInterface({ + () => + new readline.Interface({ input, - completer: invalid, - }); - }, - { - name: "TypeError", - code: "ERR_INVALID_ARG_VALUE", - }, + tabSize: 0, + }), + { code: "ERR_OUT_OF_RANGE" }, ); - }); - }); - it("should throw if history is not an array", () => { - ["not an array", 123, 123, {}, true, Symbol(), null].forEach(history => { assert.throws( - () => { - readline.createInterface({ + () => + new readline.Interface({ input, - history, - }); - }, - { - name: "TypeError", - code: "ERR_INVALID_ARG_TYPE", - }, + tabSize: "4", + }), + { code: "ERR_INVALID_ARG_TYPE" }, ); - }); - }); - it("should throw if historySize is not a positive number", () => { - ["not a number", -1, NaN, {}, true, Symbol(), null].forEach(historySize => { assert.throws( - () => { - readline.createInterface({ + () => + new readline.Interface({ input, - historySize, - }); - }, + tabSize: 4.5, + }), { - // TODO: Revert to Range error when properly implemented errors with multiple bases - // name: "RangeError", - name: "TypeError", - code: "ERR_INVALID_ARG_VALUE", + code: "ERR_OUT_OF_RANGE", + // message: + // 'The value of "tabSize" is out of range. ' + + // "It must be an integer. Received 4.5", }, ); }); - }); - it("should throw on invalid tabSize", () => { - // Check for invalid tab sizes. - assert.throws( - () => - new readline.Interface({ - input, - tabSize: 0, - }), - { code: "ERR_OUT_OF_RANGE" }, - ); - - assert.throws( - () => - new readline.Interface({ - input, - tabSize: "4", + // Sending a single character with no newline + it("should not emit line when only a single character sent with no newline", done => { + const { mustNotCall } = createCallCheckCtx(done); + const fi = new FakeInput(); + const rli = new readline.Interface(fi, {}); + rli.on("line", mustNotCall()); + fi.emit("data", "a"); + rli.close(); + }); + + it("should treat \\r like \\n when alone", done => { + const { mustCall } = createCallCheckCtx(done); + // Sending multiple newlines at once that does not end with a new line and a + // `end` event(last line is). \r should behave like \n when alone. + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar", "baz", "bat"]; + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1), + ); + fi.emit("data", expectedLines.join("\r")); + rli.close(); + }); + + // \r at start of input should output blank line + it("should output blank line when \\r at start of input", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["", "foo"]; + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length), + ); + fi.emit("data", "\rfoo\r"); + rli.close(); + }); + + // \t does not become part of the input when there is a completer function + it("should not include \\t in input when there is a completer function", done => { + const { mustCall } = createCallCheckCtx(done); + const completer = line => [[], line]; + const [rli, fi] = getInterface({ terminal: true, completer }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "foo"); }), - { code: "ERR_INVALID_ARG_TYPE" }, - ); - - assert.throws( - () => - new readline.Interface({ - input, - tabSize: 4.5, + ); + for (const character of "\tfo\to\t") { + fi.emit("data", character); + } + fi.emit("data", "\n"); + rli.close(); + }); + + // \t when there is no completer function should behave like an ordinary + // character + it("should treat \\t as an ordinary character when there is no completer function", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "\t"); }), - { - code: "ERR_OUT_OF_RANGE", - // message: - // 'The value of "tabSize" is out of range. ' + - // "It must be an integer. Received 4.5", - }, - ); - }); + ); + fi.emit("data", "\t"); + fi.emit("data", "\n"); + rli.close(); + }); - // Sending a single character with no newline - it("should not emit line when only a single character sent with no newline", done => { - const { mustNotCall } = createCallCheckCtx(done); - const fi = new FakeInput(); - const rli = new readline.Interface(fi, {}); - rli.on("line", mustNotCall()); - fi.emit("data", "a"); - rli.close(); - }); + // Adding history lines should emit the history event with + // the history array + it("should emit history event when adding history lines", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar", "baz", "bat"]; + rli.on( + "history", + mustCall(history => { + const expectedHistory = expectedLines.slice(0, history.length).reverse(); + assert.deepStrictEqual(history, expectedHistory); + }, expectedLines.length), + ); + for (const line of expectedLines) { + fi.emit("data", `${line}\n`); + } + rli.close(); + }); - it("should treat \\r like \\n when alone", done => { - const { mustCall } = createCallCheckCtx(done); - // Sending multiple newlines at once that does not end with a new line and a - // `end` event(last line is). \r should behave like \n when alone. - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ["foo", "bar", "baz", "bat"]; - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length - 1), - ); - fi.emit("data", expectedLines.join("\r")); - rli.close(); - }); + // Altering the history array in the listener should not alter + // the line being processed + it("should not alter the line being processed when history is altered", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLine = "foo"; + rli.on( + "history", + mustCall(history => { + assert.strictEqual(history[0], expectedLine); + history.shift(); + }), + ); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLine); + assert.strictEqual(rli.history.length, 0); + }), + ); + fi.emit("data", `${expectedLine}\n`); + rli.close(); + }); - // \r at start of input should output blank line - it("should output blank line when \\r at start of input", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ["", "foo"]; - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, expectedLines.shift()); - }, expectedLines.length), - ); - fi.emit("data", "\rfoo\r"); - rli.close(); - }); + // Duplicate lines are removed from history when + // `options.removeHistoryDuplicates` is `true` + it("should remove duplicate lines from history when removeHistoryDuplicates is true", () => { + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: true, + }); + const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; + // ['foo', 'baz', 'bar', bat']; + let callCount = 0; + rli.on("line", line => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit("data", `${expectedLines.join("\n")}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'foo' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + fi.emit("keypress", ".", { name: "down" }); // 'baz' + assert.strictEqual(rli.line, "baz"); + assert.strictEqual(rli.historyIndex, 2); + fi.emit("keypress", ".", { name: "n", ctrl: true }); // 'bar' + assert.strictEqual(rli.line, "bar"); + assert.strictEqual(rli.historyIndex, 1); + fi.emit("keypress", ".", { name: "n", ctrl: true }); + assert.strictEqual(rli.line, "bat"); + assert.strictEqual(rli.historyIndex, 0); + // Activate the substring history search. + fi.emit("keypress", ".", { name: "down" }); // 'bat' + assert.strictEqual(rli.line, "bat"); + assert.strictEqual(rli.historyIndex, -1); + // Deactivate substring history search. + fi.emit("keypress", ".", { name: "backspace" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + // Activate the substring history search. + fi.emit("keypress", ".", { name: "down" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "down" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, "bat"); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.strictEqual(rli.historyIndex, 1); + assert.strictEqual(rli.line, "bar"); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.historyIndex, 2); + assert.strictEqual(rli.line, "baz"); + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, "ba"); + // Deactivate substring history search and reset history index. + fi.emit("keypress", ".", { name: "right" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + // Substring history search activated. + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, "bat"); + rli.close(); + }); - // \t does not become part of the input when there is a completer function - it("should not include \\t in input when there is a completer function", done => { - const { mustCall } = createCallCheckCtx(done); - const completer = line => [[], line]; - const [rli, fi] = getInterface({ terminal: true, completer }); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "foo"); - }), - ); - for (const character of "\tfo\to\t") { - fi.emit("data", character); - } - fi.emit("data", "\n"); - rli.close(); - }); + // Duplicate lines are not removed from history when + // `options.removeHistoryDuplicates` is `false` + it("should not remove duplicate lines from history when removeHistoryDuplicates is false", () => { + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: false, + }); + const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; + let callCount = 0; + rli.on("line", line => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit("data", `${expectedLines.join("\n")}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'foo' + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + rli.close(); + }); - // \t when there is no completer function should behave like an ordinary - // character - it("should treat \\t as an ordinary character when there is no completer function", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true }); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "\t"); - }), - ); - fi.emit("data", "\t"); - fi.emit("data", "\n"); - rli.close(); - }); + // Regression test for repl freeze, #1968: + // check that nothing fails if 'keypress' event throws. + it("should not fail if keypress throws", () => { + const [rli, fi] = getInterface({ terminal: true }); + const keys = [] as string[]; + const err = new Error("bad thing happened"); + fi.on("keypress", (key: string) => { + keys.push(key); + if (key === "X") { + throw err; + } + }); + expect(() => fi.emit("data", "fooX")).toThrow(err); + fi.emit("data", "bar"); + assert.strictEqual(keys.join(""), "fooXbar"); + rli.close(); + }); - // Adding history lines should emit the history event with - // the history array - it("should emit history event when adding history lines", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true }); - const expectedLines = ["foo", "bar", "baz", "bat"]; - rli.on( - "history", - mustCall(history => { - const expectedHistory = expectedLines.slice(0, history.length).reverse(); - assert.deepStrictEqual(history, expectedHistory); - }, expectedLines.length), - ); - for (const line of expectedLines) { - fi.emit("data", `${line}\n`); - } - rli.close(); - }); + // History is bound + it("should bind history", () => { + const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); + const lines = ["line 1", "line 2", "line 3"]; + fi.emit("data", lines.join("\n") + "\n"); + assert.strictEqual(rli.history.length, 2); + assert.strictEqual(rli.history[0], "line 3"); + assert.strictEqual(rli.history[1], "line 2"); + }); - // Altering the history array in the listener should not alter - // the line being processed - it("should not alter the line being processed when history is altered", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true }); - const expectedLine = "foo"; - rli.on( - "history", - mustCall(history => { - assert.strictEqual(history[0], expectedLine); - history.shift(); - }), - ); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, expectedLine); - assert.strictEqual(rli.history.length, 0); - }), - ); - fi.emit("data", `${expectedLine}\n`); - rli.close(); - }); + // Question + it("should handle question", () => { + const [rli] = getInterface({ terminal: true }); + const expectedLines = ["foo"]; + rli.question(expectedLines[0], () => rli.close()); + assertCursorRowsAndCols(rli, 0, expectedLines[0].length); + rli.close(); + }); - // Duplicate lines are removed from history when - // `options.removeHistoryDuplicates` is `true` - it("should remove duplicate lines from history when removeHistoryDuplicates is true", () => { - const [rli, fi] = getInterface({ - terminal: true, - removeHistoryDuplicates: true, - }); - const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; - // ['foo', 'baz', 'bar', bat']; - let callCount = 0; - rli.on("line", line => { - assert.strictEqual(line, expectedLines[callCount]); - callCount++; - }); - fi.emit("data", `${expectedLines.join("\n")}\n`); - assert.strictEqual(callCount, expectedLines.length); - fi.emit("keypress", ".", { name: "up" }); // 'bat' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'bar' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'baz' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'foo' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(callCount, 0); - fi.emit("keypress", ".", { name: "down" }); // 'baz' - assert.strictEqual(rli.line, "baz"); - assert.strictEqual(rli.historyIndex, 2); - fi.emit("keypress", ".", { name: "n", ctrl: true }); // 'bar' - assert.strictEqual(rli.line, "bar"); - assert.strictEqual(rli.historyIndex, 1); - fi.emit("keypress", ".", { name: "n", ctrl: true }); - assert.strictEqual(rli.line, "bat"); - assert.strictEqual(rli.historyIndex, 0); - // Activate the substring history search. - fi.emit("keypress", ".", { name: "down" }); // 'bat' - assert.strictEqual(rli.line, "bat"); - assert.strictEqual(rli.historyIndex, -1); - // Deactivate substring history search. - fi.emit("keypress", ".", { name: "backspace" }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, "ba"); - // Activate the substring history search. - fi.emit("keypress", ".", { name: "down" }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, "ba"); - fi.emit("keypress", ".", { name: "down" }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, "ba"); - fi.emit("keypress", ".", { name: "up" }); // 'bat' - assert.strictEqual(rli.historyIndex, 0); - assert.strictEqual(rli.line, "bat"); - fi.emit("keypress", ".", { name: "up" }); // 'bar' - assert.strictEqual(rli.historyIndex, 1); - assert.strictEqual(rli.line, "bar"); - fi.emit("keypress", ".", { name: "up" }); // 'baz' - assert.strictEqual(rli.historyIndex, 2); - assert.strictEqual(rli.line, "baz"); - fi.emit("keypress", ".", { name: "up" }); // 'ba' - assert.strictEqual(rli.historyIndex, 4); - assert.strictEqual(rli.line, "ba"); - fi.emit("keypress", ".", { name: "up" }); // 'ba' - assert.strictEqual(rli.historyIndex, 4); - assert.strictEqual(rli.line, "ba"); - // Deactivate substring history search and reset history index. - fi.emit("keypress", ".", { name: "right" }); // 'ba' - assert.strictEqual(rli.historyIndex, -1); - assert.strictEqual(rli.line, "ba"); - // Substring history search activated. - fi.emit("keypress", ".", { name: "up" }); // 'ba' - assert.strictEqual(rli.historyIndex, 0); - assert.strictEqual(rli.line, "bat"); - rli.close(); - }); + // Sending a multi-line question + it("should handle multi-line questions", () => { + const [rli] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar"]; + rli.question(expectedLines.join("\n"), () => rli.close()); + assertCursorRowsAndCols(rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); + rli.close(); + }); - // Duplicate lines are not removed from history when - // `options.removeHistoryDuplicates` is `false` - it("should not remove duplicate lines from history when removeHistoryDuplicates is false", () => { - const [rli, fi] = getInterface({ - terminal: true, - removeHistoryDuplicates: false, - }); - const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; - let callCount = 0; - rli.on("line", line => { - assert.strictEqual(line, expectedLines[callCount]); - callCount++; - }); - fi.emit("data", `${expectedLines.join("\n")}\n`); - assert.strictEqual(callCount, expectedLines.length); - fi.emit("keypress", ".", { name: "up" }); // 'bat' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'bar' - assert.notStrictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'baz' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'bar' - assert.strictEqual(rli.line, expectedLines[--callCount]); - fi.emit("keypress", ".", { name: "up" }); // 'foo' - assert.strictEqual(rli.line, expectedLines[--callCount]); - assert.strictEqual(callCount, 0); - rli.close(); - }); + it("should handle beginning and end of line", () => { + // Beginning and end of line + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + fi.emit("keypress", ".", { ctrl: true, name: "e" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); + }); - // Regression test for repl freeze, #1968: - // check that nothing fails if 'keypress' event throws. - it("should not fail if keypress throws", () => { - const [rli, fi] = getInterface({ terminal: true }); - const keys = [] as string[]; - const err = new Error("bad thing happened"); - fi.on("keypress", (key: string) => { - keys.push(key); - if (key === "X") { - throw err; - } + it("should handle back and forward one character", () => { + // Back and Forward one character + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Back one character + fi.emit("keypress", ".", { ctrl: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 18); + // Back one character + fi.emit("keypress", ".", { ctrl: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 17); + // Forward one character + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 18); + // Forward one character + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); }); - expect(() => fi.emit("data", "fooX")).toThrow(err); - fi.emit("data", "bar"); - assert.strictEqual(keys.join(""), "fooXbar"); - rli.close(); - }); - // History is bound - it("should bind history", () => { - const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); - const lines = ["line 1", "line 2", "line 3"]; - fi.emit("data", lines.join("\n") + "\n"); - assert.strictEqual(rli.history.length, 2); - assert.strictEqual(rli.history[0], "line 3"); - assert.strictEqual(rli.history[1], "line 2"); - }); + // Back and Forward one astral character + it("should handle going back and forward one astral character", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); - // Question - it("should handle question", () => { - const [rli] = getInterface({ terminal: true }); - const expectedLines = ["foo"]; - rli.question(expectedLines[0], () => rli.close()); - assertCursorRowsAndCols(rli, 0, expectedLines[0].length); - rli.close(); - }); + // Move left one character/code point + fi.emit("keypress", ".", { name: "left" }); + assertCursorRowsAndCols(rli, 0, 0); - // Sending a multi-line question - it("should handle multi-line questions", () => { - const [rli] = getInterface({ terminal: true }); - const expectedLines = ["foo", "bar"]; - rli.question(expectedLines.join("\n"), () => rli.close()); - assertCursorRowsAndCols(rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); - rli.close(); - }); + // Move right one character/code point + fi.emit("keypress", ".", { name: "right" }); + assertCursorRowsAndCols(rli, 0, 2); - it("should handle beginning and end of line", () => { - // Beginning and end of line - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - assertCursorRowsAndCols(rli, 0, 0); - fi.emit("keypress", ".", { ctrl: true, name: "e" }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); - }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "💻"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); - it("should handle back and forward one character", () => { - // Back and Forward one character - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Back one character - fi.emit("keypress", ".", { ctrl: true, name: "b" }); - assertCursorRowsAndCols(rli, 0, 18); - // Back one character - fi.emit("keypress", ".", { ctrl: true, name: "b" }); - assertCursorRowsAndCols(rli, 0, 17); - // Forward one character - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - assertCursorRowsAndCols(rli, 0, 18); - // Forward one character - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); - }); + // Two astral characters left + it("should handle two astral characters left", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); - // Back and Forward one astral character - it("should handle going back and forward one astral character", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "💻"); - - // Move left one character/code point - fi.emit("keypress", ".", { name: "left" }); - assertCursorRowsAndCols(rli, 0, 0); - - // Move right one character/code point - fi.emit("keypress", ".", { name: "right" }); - assertCursorRowsAndCols(rli, 0, 2); - - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "💻"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Move left one character/code point + fi.emit("keypress", ".", { name: "left" }); + assertCursorRowsAndCols(rli, 0, 0); - // Two astral characters left - it("should handle two astral characters left", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "💻"); - - // Move left one character/code point - fi.emit("keypress", ".", { name: "left" }); - assertCursorRowsAndCols(rli, 0, 0); - - fi.emit("data", "🐕"); - assertCursorRowsAndCols(rli, 0, 2); - - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "🐕💻"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + fi.emit("data", "🐕"); + assertCursorRowsAndCols(rli, 0, 2); - // Two astral characters right - it("should handle two astral characters right", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "💻"); - - // Move left one character/code point - fi.emit("keypress", ".", { name: "right" }); - assertCursorRowsAndCols(rli, 0, 2); - - fi.emit("data", "🐕"); - assertCursorRowsAndCols(rli, 0, 4); - - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "💻🐕"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "🐕💻"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); - it("should handle wordLeft and wordRight", () => { - // `wordLeft` and `wordRight` - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - fi.emit("keypress", ".", { ctrl: true, name: "left" }); - assertCursorRowsAndCols(rli, 0, 16); - fi.emit("keypress", ".", { meta: true, name: "b" }); - assertCursorRowsAndCols(rli, 0, 10); - fi.emit("keypress", ".", { ctrl: true, name: "right" }); - assertCursorRowsAndCols(rli, 0, 16); - fi.emit("keypress", ".", { meta: true, name: "f" }); - assertCursorRowsAndCols(rli, 0, 19); - rli.close(); - }); + // Two astral characters right + it("should handle two astral characters right", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Move left one character/code point + fi.emit("keypress", ".", { name: "right" }); + assertCursorRowsAndCols(rli, 0, 2); + + fi.emit("data", "🐕"); + assertCursorRowsAndCols(rli, 0, 4); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "💻🐕"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); - // `deleteWordLeft` - it("should handle deleteWordLeft", done => { - const { mustCall } = createCallCheckCtx(done); - [ - { ctrl: true, name: "w" }, - { ctrl: true, name: "backspace" }, - { meta: true, name: "backspace" }, - ].forEach(deleteWordLeftKey => { - let [rli, fi] = getInterface({ terminal: true, prompt: "" }); + it("should handle wordLeft and wordRight", () => { + // `wordLeft` and `wordRight` + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); fi.emit("data", "the quick brown fox"); fi.emit("keypress", ".", { ctrl: true, name: "left" }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit("keypress", ".", { meta: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 10); + fi.emit("keypress", ".", { ctrl: true, name: "right" }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit("keypress", ".", { meta: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); + }); + + // `deleteWordLeft` + it("should handle deleteWordLeft", done => { + const { mustCall } = createCallCheckCtx(done); + [ + { ctrl: true, name: "w" }, + { ctrl: true, name: "backspace" }, + { meta: true, name: "backspace" }, + ].forEach(deleteWordLeftKey => { + let [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick fox"); + }), + ); + fi.emit("keypress", ".", deleteWordLeftKey); + fi.emit("data", "\n"); + rli.close(); + + // No effect if pressed at beginning of line + [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); + fi.emit("keypress", ".", deleteWordLeftKey); + fi.emit("data", "\n"); + rli.close(); + }); + }); + + // `deleteWordRight` + it("should handle deleteWordRight", done => { + const { mustCall } = createCallCheckCtx(done); + [ + { ctrl: true, name: "delete" }, + { meta: true, name: "delete" }, + { meta: true, name: "d" }, + ].forEach(deleteWordRightKey => { + let [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick fox"); + }), + ); + fi.emit("keypress", ".", deleteWordRightKey); + fi.emit("data", "\n"); + rli.close(); + + // No effect if pressed at end of line + [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); + fi.emit("keypress", ".", deleteWordRightKey); + fi.emit("data", "\n"); + rli.close(); + }); + }); + + // deleteLeft + it("should handle deleteLeft", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete left character + fi.emit("keypress", ".", { ctrl: true, name: "h" }); + assertCursorRowsAndCols(rli, 0, 18); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fo"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteLeft astral character + it("should handle deleteLeft astral character", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + assertCursorRowsAndCols(rli, 0, 2); + // Delete left character + fi.emit("keypress", ".", { ctrl: true, name: "h" }); + assertCursorRowsAndCols(rli, 0, 0); rli.on( "line", mustCall(line => { - assert.strictEqual(line, "the quick fox"); + assert.strictEqual(line, ""); }), ); - fi.emit("keypress", ".", deleteWordLeftKey); fi.emit("data", "\n"); rli.close(); + }); - // No effect if pressed at beginning of line - [rli, fi] = getInterface({ terminal: true, prompt: "" }); + // deleteRight + it("should handle deleteRight", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); fi.emit("data", "the quick brown fox"); + + // Go to the start of the line fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit("keypress", ".", { ctrl: true, name: "d" }); + assertCursorRowsAndCols(rli, 0, 0); rli.on( "line", mustCall(line => { - assert.strictEqual(line, "the quick brown fox"); + assert.strictEqual(line, "he quick brown fox"); }), ); - fi.emit("keypress", ".", deleteWordLeftKey); fi.emit("data", "\n"); rli.close(); }); - }); - // `deleteWordRight` - it("should handle deleteWordRight", done => { - const { mustCall } = createCallCheckCtx(done); - [ - { ctrl: true, name: "delete" }, - { meta: true, name: "delete" }, - { meta: true, name: "d" }, - ].forEach(deleteWordRightKey => { - let [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - fi.emit("keypress", ".", { ctrl: true, name: "left" }); - fi.emit("keypress", ".", { ctrl: true, name: "left" }); + // deleteRight astral character + it("should handle deleteRight of astral characters", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit("keypress", ".", { ctrl: true, name: "d" }); + assertCursorRowsAndCols(rli, 0, 0); rli.on( "line", mustCall(line => { - assert.strictEqual(line, "the quick fox"); + assert.strictEqual(line, ""); }), ); - fi.emit("keypress", ".", deleteWordRightKey); fi.emit("data", "\n"); rli.close(); + }); - // No effect if pressed at end of line - [rli, fi] = getInterface({ terminal: true, prompt: "" }); + // deleteLineLeft + it("should handle deleteLineLeft", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete from current to start of line + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "backspace" }); + assertCursorRowsAndCols(rli, 0, 0); rli.on( "line", mustCall(line => { - assert.strictEqual(line, "the quick brown fox"); + assert.strictEqual(line, ""); }), ); - fi.emit("keypress", ".", deleteWordRightKey); fi.emit("data", "\n"); rli.close(); }); - }); - // deleteLeft - it("should handle deleteLeft", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Delete left character - fi.emit("keypress", ".", { ctrl: true, name: "h" }); - assertCursorRowsAndCols(rli, 0, 18); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "the quick brown fo"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // deleteLineRight + it("should handle deleteLineRight", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); - // deleteLeft astral character - it("should handle deleteLeft astral character", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "💻"); - assertCursorRowsAndCols(rli, 0, 2); - // Delete left character - fi.emit("keypress", ".", { ctrl: true, name: "h" }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, ""); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); - // deleteRight - it("should handle deleteRight", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete right character - fi.emit("keypress", ".", { ctrl: true, name: "d" }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "he quick brown fox"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Delete from current to end of line + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, ""); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); - // deleteRight astral character - it("should handle deleteRight of astral characters", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "💻"); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete right character - fi.emit("keypress", ".", { ctrl: true, name: "d" }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, ""); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // yank + it("should handle yank", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); - // deleteLineLeft - it("should handle deleteLineLeft", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Delete from current to start of line - fi.emit("keypress", ".", { ctrl: true, shift: true, name: "backspace" }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, ""); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward one char + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 1); - // deleteLineRight - it("should handle deleteLineRight", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - assertCursorRowsAndCols(rli, 0, 0); - - // Delete from current to end of line - fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); - assertCursorRowsAndCols(rli, 0, 0); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, ""); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Yank + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 19); - // yank - it("should handle yank", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - // Move forward one char - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - // Delete the right part - fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); - assertCursorRowsAndCols(rli, 0, 1); - - // Yank - fi.emit("keypress", ".", { ctrl: true, name: "y" }); - assertCursorRowsAndCols(rli, 0, 19); - - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "the quick brown fox"); - }), - ); - - fi.emit("data", "\n"); - rli.close(); - }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); - // yank pop - it("should handle yank pop", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - // Move forward one char - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - // Delete the right part - fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); - assertCursorRowsAndCols(rli, 0, 1); - // Yank - fi.emit("keypress", ".", { ctrl: true, name: "y" }); - assertCursorRowsAndCols(rli, 0, 19); - - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - // Move forward four chars - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - fi.emit("keypress", ".", { ctrl: true, name: "f" }); - // Delete the right part - fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); - assertCursorRowsAndCols(rli, 0, 4); - // Go to the start of the line - fi.emit("keypress", ".", { ctrl: true, name: "a" }); - assertCursorRowsAndCols(rli, 0, 0); - - // Yank: 'quick brown fox|the ' - fi.emit("keypress", ".", { ctrl: true, name: "y" }); - // Yank pop: 'he quick brown fox|the' - fi.emit("keypress", ".", { meta: true, name: "y" }); - assertCursorRowsAndCols(rli, 0, 18); - - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "he quick brown foxthe "); - }), - ); - - fi.emit("data", "\n"); - rli.close(); - }); + fi.emit("data", "\n"); + rli.close(); + }); - // Close readline interface - it("Should close readline interface", () => { - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("keypress", ".", { ctrl: true, name: "c" }); - assert.ok(rli.closed); - }); + // yank pop + it("should handle yank pop", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); - // Multi-line input cursor position - it("should handle multi-line input cursors", () => { - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.columns = 10; - fi.emit("data", "multi-line text"); - assertCursorRowsAndCols(rli, 1, 5); - rli.close(); - }); + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward one char + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 1); + // Yank + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 19); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward four chars + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 4); + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); - // Multi-line input cursor position and long tabs - it("should handle long tabs", () => { - const [rli, fi] = getInterface({ - tabSize: 16, - terminal: true, - prompt: "", - }); - fi.columns = 10; - fi.emit("data", "multi-line\ttext \t"); - assert.strictEqual(rli.cursor, 17); - assertCursorRowsAndCols(rli, 3, 2); - rli.close(); - }); + // Yank: 'quick brown fox|the ' + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + // Yank pop: 'he quick brown fox|the' + fi.emit("keypress", ".", { meta: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 18); - // Check for the default tab size. - it("should use the default tab size", () => { - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick\tbrown\tfox"); - assert.strictEqual(rli.cursor, 19); - // The first tab is 7 spaces long, the second one 3 spaces. - assertCursorRowsAndCols(rli, 0, 27); - }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "he quick brown foxthe "); + }), + ); - // Multi-line prompt cursor position - it("should handle multi-line prompt cursor position", () => { - const [rli, fi] = getInterface({ - terminal: true, - prompt: "\nfilledline\nwraping text\n> ", + fi.emit("data", "\n"); + rli.close(); }); - fi.columns = 10; - fi.emit("data", "t"); - assertCursorRowsAndCols(rli, 4, 3); - rli.close(); - }); - // Undo & Redo - it("should undo and redo", () => { - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - fi.emit("data", "the quick brown fox"); - assertCursorRowsAndCols(rli, 0, 19); - - // Delete the last eight chars - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); - - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); - fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); - - assertCursorRowsAndCols(rli, 0, 11); - // Perform undo twice - fi.emit("keypress", ",", { sequence: "\x1F" }); - expect(rli.line).toEqual("the quick brown"); - fi.emit("keypress", ",", { sequence: "\x1F" }); - expect(rli.line).toEqual("the quick brown fox"); - // Perform redo twice - fi.emit("keypress", ",", { sequence: "\x1E" }); - expect(rli.line).toEqual("the quick brown"); - fi.emit("keypress", ",", { sequence: "\x1E" }); - expect(rli.line).toEqual("the quick b"); - fi.emit("data", "\n"); - rli.close(); - }); + // Close readline interface + it("Should close readline interface", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("keypress", ".", { ctrl: true, name: "c" }); + assert.ok(rli.closed); + }); - // Clear the whole screen - it("should clear the whole screen", done => { - const { mustCall } = createCallCheckCtx(done); - const [rli, fi] = getInterface({ terminal: true, prompt: "" }); - const lines = ["line 1", "line 2", "line 3"]; - fi.emit("data", lines.join("\n")); - fi.emit("keypress", ".", { ctrl: true, name: "l" }); - assertCursorRowsAndCols(rli, 0, 6); - rli.on( - "line", - mustCall(line => { - assert.strictEqual(line, "line 3"); - }), - ); - fi.emit("data", "\n"); - rli.close(); - }); + // Multi-line input cursor position + it("should handle multi-line input cursors", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.columns = 10; + fi.emit("data", "multi-line text"); + assertCursorRowsAndCols(rli, 1, 5); + rli.close(); + }); - it("should treat wide characters as two columns", () => { - assert.strictEqual(getStringWidth("a"), 1); - assert.strictEqual(getStringWidth("あ"), 2); - assert.strictEqual(getStringWidth("谢"), 2); - assert.strictEqual(getStringWidth("고"), 2); - assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); - assert.strictEqual(getStringWidth("abcde"), 5); - assert.strictEqual(getStringWidth("古池や"), 6); - assert.strictEqual(getStringWidth("ノード.js"), 9); - assert.strictEqual(getStringWidth("你好"), 4); - assert.strictEqual(getStringWidth("안녕하세요"), 10); - assert.strictEqual(getStringWidth("A\ud83c\ude00BC"), 5); - assert.strictEqual(getStringWidth("👨‍👩‍👦‍👦"), 2); - assert.strictEqual(getStringWidth("🐕𐐷あ💻😀"), 9); - // TODO(BridgeAR): This should have a width of 4. - assert.strictEqual(getStringWidth("⓬⓪"), 2); - assert.strictEqual(getStringWidth("\u0301\u200D\u200E"), 0); - }); + // Multi-line input cursor position and long tabs + it("should handle long tabs", () => { + const [rli, fi] = getInterface({ + tabSize: 16, + terminal: true, + prompt: "", + }); + fi.columns = 10; + fi.emit("data", "multi-line\ttext \t"); + assert.strictEqual(rli.cursor, 17); + assertCursorRowsAndCols(rli, 3, 2); + rli.close(); + }); - // // Check if vt control chars are stripped - // assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); - // assert.strictEqual( - // stripVTControlCharacters('\u001b[31m> \u001b[39m> '), - // '> > ' - // ); - // assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); - // assert.strictEqual(stripVTControlCharacters('> '), '> '); - // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); - // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); - // assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); - // assert.strictEqual(getStringWidth('> '), 2); - - // // Check EventEmitter memory leak - // for (let i = 0; i < 12; i++) { - // const rl = readline.createInterface({ - // input: process.stdin, - // output: process.stdout - // }); - // rl.close(); - // assert.strictEqual(isWarned(process.stdin._events), false); - // assert.strictEqual(isWarned(process.stdout._events), false); - // } - - // [true, false].forEach((terminal) => { - // // Disable history - // { - // const [rli, fi] = getInterface({ terminal, historySize: 0 }); - // assert.strictEqual(rli.historySize, 0); - - // fi.emit('data', 'asdf\n'); - // assert.deepStrictEqual(rli.history, []); - // rli.close(); - // } - - // // Default history size 30 - // { - // const [rli, fi] = getInterface({ terminal }); - // assert.strictEqual(rli.historySize, 30); - - // fi.emit('data', 'asdf\n'); - // assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); - // rli.close(); - // } - - // // Sending a full line - // { - // const [rli, fi] = getInterface({ terminal }); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, 'asdf'); - // })); - // fi.emit('data', 'asdf\n'); - // } - - // // Sending a blank line - // { - // const [rli, fi] = getInterface({ terminal }); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, ''); - // })); - // fi.emit('data', '\n'); - // } - - // // Sending a single character with no newline and then a newline - // { - // const [rli, fi] = getInterface({ terminal }); - // let called = false; - // rli.on('line', (line) => { - // called = true; - // assert.strictEqual(line, 'a'); - // }); - // fi.emit('data', 'a'); - // assert.ok(!called); - // fi.emit('data', '\n'); - // assert.ok(called); - // rli.close(); - // } - - // // Sending multiple newlines at once - // { - // const [rli, fi] = getInterface({ terminal }); - // const expectedLines = ['foo', 'bar', 'baz']; - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, expectedLines.shift()); - // }, expectedLines.length)); - // fi.emit('data', `${expectedLines.join('\n')}\n`); - // rli.close(); - // } - - // // Sending multiple newlines at once that does not end with a new line - // { - // const [rli, fi] = getInterface({ terminal }); - // const expectedLines = ['foo', 'bar', 'baz', 'bat']; - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, expectedLines.shift()); - // }, expectedLines.length - 1)); - // fi.emit('data', expectedLines.join('\n')); - // rli.close(); - // } - - // // Sending multiple newlines at once that does not end with a new(empty) - // // line and a `end` event - // { - // const [rli, fi] = getInterface({ terminal }); - // const expectedLines = ['foo', 'bar', 'baz', '']; - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, expectedLines.shift()); - // }, expectedLines.length - 1)); - // rli.on('close', mustCall()); - // fi.emit('data', expectedLines.join('\n')); - // fi.emit('end'); - // rli.close(); - // } - - // // Sending a multi-byte utf8 char over multiple writes - // { - // const buf = Buffer.from('☮', 'utf8'); - // const [rli, fi] = getInterface({ terminal }); - // let callCount = 0; - // rli.on('line', (line) => { - // callCount++; - // assert.strictEqual(line, buf.toString('utf8')); - // }); - // for (const i of buf) { - // fi.emit('data', Buffer.from([i])); - // } - // assert.strictEqual(callCount, 0); - // fi.emit('data', '\n'); - // assert.strictEqual(callCount, 1); - // rli.close(); - // } - - // // Calling readline without `new` - // { - // const [rli, fi] = getInterface({ terminal }); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, 'asdf'); - // })); - // fi.emit('data', 'asdf\n'); - // rli.close(); - // } - - // // Calling the question callback - // { - // const [rli] = getInterface({ terminal }); - // rli.question('foo?', mustCall((answer) => { - // assert.strictEqual(answer, 'bar'); - // })); - // rli.write('bar\n'); - // rli.close(); - // } - - // // Calling the question callback with abort signal - // { - // const [rli] = getInterface({ terminal }); - // const { signal } = new AbortController(); - // rli.question('foo?', { signal }, mustCall((answer) => { - // assert.strictEqual(answer, 'bar'); - // })); - // rli.write('bar\n'); - // rli.close(); - // } - - // // Calling the question multiple times - // { - // const [rli] = getInterface({ terminal }); - // rli.question('foo?', mustCall((answer) => { - // assert.strictEqual(answer, 'baz'); - // })); - // rli.question('bar?', mustNotCall(() => { - // })); - // rli.write('baz\n'); - // rli.close(); - // } - - // // Calling the promisified question - // { - // const [rli] = getInterface({ terminal }); - // const question = util.promisify(rli.question).bind(rli); - // question('foo?') - // .then(mustCall((answer) => { - // assert.strictEqual(answer, 'bar'); - // })); - // rli.write('bar\n'); - // rli.close(); - // } - - // // Calling the promisified question with abort signal - // { - // const [rli] = getInterface({ terminal }); - // const question = util.promisify(rli.question).bind(rli); - // const { signal } = new AbortController(); - // question('foo?', { signal }) - // .then(mustCall((answer) => { - // assert.strictEqual(answer, 'bar'); - // })); - // rli.write('bar\n'); - // rli.close(); - // } - - // // Aborting a question - // { - // const ac = new AbortController(); - // const signal = ac.signal; - // const [rli] = getInterface({ terminal }); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, 'bar'); - // })); - // rli.question('hello?', { signal }, mustNotCall()); - // ac.abort(); - // rli.write('bar\n'); - // rli.close(); - // } - - // // Aborting a promisified question - // { - // const ac = new AbortController(); - // const signal = ac.signal; - // const [rli] = getInterface({ terminal }); - // const question = util.promisify(rli.question).bind(rli); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, 'bar'); - // })); - // question('hello?', { signal }) - // .then(mustNotCall()) - // .catch(mustCall((error) => { - // assert.strictEqual(error.name, 'AbortError'); - // })); - // ac.abort(); - // rli.write('bar\n'); - // rli.close(); - // } - - // // pre-aborted signal - // { - // const signal = AbortSignal.abort(); - // const [rli] = getInterface({ terminal }); - // rli.pause(); - // rli.on('resume', mustNotCall()); - // rli.question('hello?', { signal }, mustNotCall()); - // rli.close(); - // } - - // // pre-aborted signal promisified question - // { - // const signal = AbortSignal.abort(); - // const [rli] = getInterface({ terminal }); - // const question = util.promisify(rli.question).bind(rli); - // rli.on('resume', mustNotCall()); - // rli.pause(); - // question('hello?', { signal }) - // .then(mustNotCall()) - // .catch(mustCall((error) => { - // assert.strictEqual(error.name, 'AbortError'); - // })); - // rli.close(); - // } - - // // Call question after close - // { - // const [rli, fi] = getInterface({ terminal }); - // rli.question('What\'s your name?', mustCall((name) => { - // assert.strictEqual(name, 'Node.js'); - // rli.close(); - // assert.throws(() => { - // rli.question('How are you?', mustNotCall()); - // }, { - // name: 'Error', - // code: 'ERR_USE_AFTER_CLOSE' - // }); - // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); - // })); - // fi.emit('data', 'Node.js\n'); - // } - - // // Call promisified question after close - // { - // const [rli, fi] = getInterface({ terminal }); - // const question = util.promisify(rli.question).bind(rli); - // question('What\'s your name?').then(mustCall((name) => { - // assert.strictEqual(name, 'Node.js'); - // rli.close(); - // question('How are you?') - // .then(mustNotCall(), expectsError({ - // code: 'ERR_USE_AFTER_CLOSE', - // name: 'Error' - // })); - // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); - // })); - // fi.emit('data', 'Node.js\n'); - // } - - // // Can create a new readline Interface with a null output argument - // { - // const [rli, fi] = getInterface({ output: null, terminal }); - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, 'asdf'); - // })); - // fi.emit('data', 'asdf\n'); - - // rli.setPrompt('ddd> '); - // rli.prompt(); - // rli.write("really shouldn't be seeing this"); - // rli.question('What do you think of node.js? ', (answer) => { - // console.log('Thank you for your valuable feedback:', answer); - // rli.close(); - // }); - // } - - // // Calling the getPrompt method - // { - // const expectedPrompts = ['$ ', '> ']; - // const [rli] = getInterface({ terminal }); - // for (const prompt of expectedPrompts) { - // rli.setPrompt(prompt); - // assert.strictEqual(rli.getPrompt(), prompt); - // } - // } - - // { - // const expected = terminal ? - // ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : - // ['$ ']; - - // const output = new Writable({ - // write: mustCall((chunk, enc, cb) => { - // assert.strictEqual(chunk.toString(), expected.shift()); - // cb(); - // rl.close(); - // }, expected.length) - // }); - - // const rl = readline.createInterface({ - // input: new Readable({ read: mustCall() }), - // output, - // prompt: '$ ', - // terminal - // }); - - // rl.prompt(); - - // assert.strictEqual(rl.getPrompt(), '$ '); - // } - - // { - // const fi = new FakeInput(); - // assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); - // } - - // // Emit two line events when the delay - // // between \r and \n exceeds crlfDelay - // { - // const crlfDelay = 200; - // const [rli, fi] = getInterface({ terminal, crlfDelay }); - // let callCount = 0; - // rli.on('line', () => { - // callCount++; - // }); - // fi.emit('data', '\r'); - // setTimeout(mustCall(() => { - // fi.emit('data', '\n'); - // assert.strictEqual(callCount, 2); - // rli.close(); - // }), crlfDelay + 10); - // } - - // // For the purposes of the following tests, we do not care about the exact - // // value of crlfDelay, only that the behaviour conforms to what's expected. - // // Setting it to Infinity allows the test to succeed even under extreme - // // CPU stress. - // const crlfDelay = Infinity; - - // // Set crlfDelay to `Infinity` is allowed - // { - // const delay = 200; - // const [rli, fi] = getInterface({ terminal, crlfDelay }); - // let callCount = 0; - // rli.on('line', () => { - // callCount++; - // }); - // fi.emit('data', '\r'); - // setTimeout(mustCall(() => { - // fi.emit('data', '\n'); - // assert.strictEqual(callCount, 1); - // rli.close(); - // }), delay); - // } - - // // Sending multiple newlines at once that does not end with a new line - // // and a `end` event(last line is) - - // // \r\n should emit one line event, not two - // { - // const [rli, fi] = getInterface({ terminal, crlfDelay }); - // const expectedLines = ['foo', 'bar', 'baz', 'bat']; - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, expectedLines.shift()); - // }, expectedLines.length - 1)); - // fi.emit('data', expectedLines.join('\r\n')); - // rli.close(); - // } - - // // \r\n should emit one line event when split across multiple writes. - // { - // const [rli, fi] = getInterface({ terminal, crlfDelay }); - // const expectedLines = ['foo', 'bar', 'baz', 'bat']; - // let callCount = 0; - // rli.on('line', mustCall((line) => { - // assert.strictEqual(line, expectedLines[callCount]); - // callCount++; - // }, expectedLines.length)); - // expectedLines.forEach((line) => { - // fi.emit('data', `${line}\r`); - // fi.emit('data', '\n'); - // }); - // rli.close(); - // } - - // // Emit one line event when the delay between \r and \n is - // // over the default crlfDelay but within the setting value. - // { - // const delay = 125; - // const [rli, fi] = getInterface({ terminal, crlfDelay }); - // let callCount = 0; - // rli.on('line', () => callCount++); - // fi.emit('data', '\r'); - // setTimeout(mustCall(() => { - // fi.emit('data', '\n'); - // assert.strictEqual(callCount, 1); - // rli.close(); - // }), delay); - // } - // }); - - // // Ensure that the _wordLeft method works even for large input - // { - // const input = new Readable({ - // read() { - // this.push('\x1B[1;5D'); // CTRL + Left - // this.push(null); - // }, - // }); - // const output = new Writable({ - // write: mustCall((data, encoding, cb) => { - // assert.strictEqual(rl.cursor, rl.line.length - 1); - // cb(); - // }), - // }); - // const rl = new readline.createInterface({ - // input, - // output, - // terminal: true, - // }); - // rl.line = `a${' '.repeat(1e6)}a`; - // rl.cursor = rl.line.length; - // } - - // { - // const fi = new FakeInput(); - // const signal = AbortSignal.abort(); - - // const rl = readline.createInterface({ - // input: fi, - // output: fi, - // signal, - // }); - // rl.on('close', mustCall()); - // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); - // } - - // { - // const fi = new FakeInput(); - // const ac = new AbortController(); - // const { signal } = ac; - // const rl = readline.createInterface({ - // input: fi, - // output: fi, - // signal, - // }); - // assert.strictEqual(getEventListeners(signal, 'abort').length, 1); - // rl.on('close', mustCall()); - // ac.abort(); - // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); - // } - - // { - // const fi = new FakeInput(); - // const ac = new AbortController(); - // const { signal } = ac; - // const rl = readline.createInterface({ - // input: fi, - // output: fi, - // signal, - // }); - // assert.strictEqual(getEventListeners(signal, "abort").length, 1); - // rl.close(); - // assert.strictEqual(getEventListeners(signal, "abort").length, 0); - // } - - // { - // // Constructor throws if signal is not an abort signal - // assert.throws(() => { - // readline.createInterface({ - // input: new FakeInput(), - // signal: {}, - // }); - // }, { - // name: 'TypeError', - // code: 'ERR_INVALID_ARG_TYPE' - // }); - // } -}); - -describe("readline.createInterface()", () => { - it("should emit line when input ends line", done => { - const createDone = createDoneDotAll(done); - const lineDone = createDone(2000); - const { mustCall } = createCallCheckCtx(createDone(2000)); - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, - }); - - rl.on( - "line", - mustCall(data => { - assert.strictEqual(data, "abc"); - lineDone(); - }), - ); - - input.end("abc"); - }); + // Check for the default tab size. + it("should use the default tab size", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick\tbrown\tfox"); + assert.strictEqual(rli.cursor, 19); + // The first tab is 7 spaces long, the second one 3 spaces. + assertCursorRowsAndCols(rli, 0, 27); + }); - it("should not emit line when input ends without newline", done => { - const { mustNotCall } = createCallCheckCtx(done); + // Multi-line prompt cursor position + it("should handle multi-line prompt cursor position", () => { + const [rli, fi] = getInterface({ + terminal: true, + prompt: "\nfilledline\nwraping text\n> ", + }); + fi.columns = 10; + fi.emit("data", "t"); + assertCursorRowsAndCols(rli, 4, 3); + rli.close(); + }); - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, + // Undo & Redo + it("should undo and redo", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete the last eight chars + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); + + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); + + assertCursorRowsAndCols(rli, 0, 11); + // Perform undo twice + fi.emit("keypress", ",", { sequence: "\x1F" }); + expect(rli.line).toEqual("the quick brown"); + fi.emit("keypress", ",", { sequence: "\x1F" }); + expect(rli.line).toEqual("the quick brown fox"); + // Perform redo twice + fi.emit("keypress", ",", { sequence: "\x1E" }); + expect(rli.line).toEqual("the quick brown"); + fi.emit("keypress", ",", { sequence: "\x1E" }); + expect(rli.line).toEqual("the quick b"); + fi.emit("data", "\n"); + rli.close(); }); - rl.on("line", mustNotCall("must not be called before newline")); - input.write("abc"); - }); + // Clear the whole screen + it("should clear the whole screen", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + const lines = ["line 1", "line 2", "line 3"]; + fi.emit("data", lines.join("\n")); + fi.emit("keypress", ".", { ctrl: true, name: "l" }); + assertCursorRowsAndCols(rli, 0, 6); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "line 3"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); - it("should read line by line", done => { - const createDone = createDoneDotAll(done); - const { mustCall } = createCallCheckCtx(createDone(3000)); - const lineDone = createDone(2000); - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, - }); - - rl.on( - "line", - mustCall(data => { - assert.strictEqual(data, "abc"); - lineDone(); - }), - ); - - input.write("abc\n"); - }); + it("should treat wide characters as two columns", () => { + assert.strictEqual(getStringWidth("a"), 1); + assert.strictEqual(getStringWidth("あ"), 2); + assert.strictEqual(getStringWidth("谢"), 2); + assert.strictEqual(getStringWidth("고"), 2); + assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); + assert.strictEqual(getStringWidth("abcde"), 5); + assert.strictEqual(getStringWidth("古池や"), 6); + assert.strictEqual(getStringWidth("ノード.js"), 9); + assert.strictEqual(getStringWidth("你好"), 4); + assert.strictEqual(getStringWidth("안녕하세요"), 10); + assert.strictEqual(getStringWidth("A\ud83c\ude00BC"), 5); + assert.strictEqual(getStringWidth("👨‍👩‍👦‍👦"), 2); + assert.strictEqual(getStringWidth("🐕𐐷あ💻😀"), 9); + // TODO(BridgeAR): This should have a width of 4. + assert.strictEqual(getStringWidth("⓬⓪"), 2); + assert.strictEqual(getStringWidth("\u0301\u200D\u200E"), 0); + }); - it("should support reading-in lines via for await...of loop", async () => { - const sampleTextBuffer = new Buffer.from("Line1\nLine2\nLine3\nLine4"); - const bufferStream = new PassThrough(); + // // Check if vt control chars are stripped + // assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); + // assert.strictEqual( + // stripVTControlCharacters('\u001b[31m> \u001b[39m> '), + // '> > ' + // ); + // assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); + // assert.strictEqual(stripVTControlCharacters('> '), '> '); + // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); + // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); + // assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); + // assert.strictEqual(getStringWidth('> '), 2); + + // // Check EventEmitter memory leak + // for (let i = 0; i < 12; i++) { + // const rl = readline.createInterface({ + // input: process.stdin, + // output: process.stdout + // }); + // rl.close(); + // assert.strictEqual(isWarned(process.stdin._events), false); + // assert.strictEqual(isWarned(process.stdout._events), false); + // } + + // [true, false].forEach((terminal) => { + // // Disable history + // { + // const [rli, fi] = getInterface({ terminal, historySize: 0 }); + // assert.strictEqual(rli.historySize, 0); + + // fi.emit('data', 'asdf\n'); + // assert.deepStrictEqual(rli.history, []); + // rli.close(); + // } + + // // Default history size 30 + // { + // const [rli, fi] = getInterface({ terminal }); + // assert.strictEqual(rli.historySize, 30); + + // fi.emit('data', 'asdf\n'); + // assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); + // rli.close(); + // } + + // // Sending a full line + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + // } + + // // Sending a blank line + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, ''); + // })); + // fi.emit('data', '\n'); + // } + + // // Sending a single character with no newline and then a newline + // { + // const [rli, fi] = getInterface({ terminal }); + // let called = false; + // rli.on('line', (line) => { + // called = true; + // assert.strictEqual(line, 'a'); + // }); + // fi.emit('data', 'a'); + // assert.ok(!called); + // fi.emit('data', '\n'); + // assert.ok(called); + // rli.close(); + // } + + // // Sending multiple newlines at once + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length)); + // fi.emit('data', `${expectedLines.join('\n')}\n`); + // rli.close(); + // } + + // // Sending multiple newlines at once that does not end with a new line + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // fi.emit('data', expectedLines.join('\n')); + // rli.close(); + // } + + // // Sending multiple newlines at once that does not end with a new(empty) + // // line and a `end` event + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz', '']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // rli.on('close', mustCall()); + // fi.emit('data', expectedLines.join('\n')); + // fi.emit('end'); + // rli.close(); + // } + + // // Sending a multi-byte utf8 char over multiple writes + // { + // const buf = Buffer.from('☮', 'utf8'); + // const [rli, fi] = getInterface({ terminal }); + // let callCount = 0; + // rli.on('line', (line) => { + // callCount++; + // assert.strictEqual(line, buf.toString('utf8')); + // }); + // for (const i of buf) { + // fi.emit('data', Buffer.from([i])); + // } + // assert.strictEqual(callCount, 0); + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // } + + // // Calling readline without `new` + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + // rli.close(); + // } + + // // Calling the question callback + // { + // const [rli] = getInterface({ terminal }); + // rli.question('foo?', mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the question callback with abort signal + // { + // const [rli] = getInterface({ terminal }); + // const { signal } = new AbortController(); + // rli.question('foo?', { signal }, mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the question multiple times + // { + // const [rli] = getInterface({ terminal }); + // rli.question('foo?', mustCall((answer) => { + // assert.strictEqual(answer, 'baz'); + // })); + // rli.question('bar?', mustNotCall(() => { + // })); + // rli.write('baz\n'); + // rli.close(); + // } + + // // Calling the promisified question + // { + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // question('foo?') + // .then(mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the promisified question with abort signal + // { + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // const { signal } = new AbortController(); + // question('foo?', { signal }) + // .then(mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Aborting a question + // { + // const ac = new AbortController(); + // const signal = ac.signal; + // const [rli] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'bar'); + // })); + // rli.question('hello?', { signal }, mustNotCall()); + // ac.abort(); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Aborting a promisified question + // { + // const ac = new AbortController(); + // const signal = ac.signal; + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'bar'); + // })); + // question('hello?', { signal }) + // .then(mustNotCall()) + // .catch(mustCall((error) => { + // assert.strictEqual(error.name, 'AbortError'); + // })); + // ac.abort(); + // rli.write('bar\n'); + // rli.close(); + // } + + // // pre-aborted signal + // { + // const signal = AbortSignal.abort(); + // const [rli] = getInterface({ terminal }); + // rli.pause(); + // rli.on('resume', mustNotCall()); + // rli.question('hello?', { signal }, mustNotCall()); + // rli.close(); + // } + + // // pre-aborted signal promisified question + // { + // const signal = AbortSignal.abort(); + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // rli.on('resume', mustNotCall()); + // rli.pause(); + // question('hello?', { signal }) + // .then(mustNotCall()) + // .catch(mustCall((error) => { + // assert.strictEqual(error.name, 'AbortError'); + // })); + // rli.close(); + // } + + // // Call question after close + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.question('What\'s your name?', mustCall((name) => { + // assert.strictEqual(name, 'Node.js'); + // rli.close(); + // assert.throws(() => { + // rli.question('How are you?', mustNotCall()); + // }, { + // name: 'Error', + // code: 'ERR_USE_AFTER_CLOSE' + // }); + // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); + // })); + // fi.emit('data', 'Node.js\n'); + // } + + // // Call promisified question after close + // { + // const [rli, fi] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // question('What\'s your name?').then(mustCall((name) => { + // assert.strictEqual(name, 'Node.js'); + // rli.close(); + // question('How are you?') + // .then(mustNotCall(), expectsError({ + // code: 'ERR_USE_AFTER_CLOSE', + // name: 'Error' + // })); + // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); + // })); + // fi.emit('data', 'Node.js\n'); + // } + + // // Can create a new readline Interface with a null output argument + // { + // const [rli, fi] = getInterface({ output: null, terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + + // rli.setPrompt('ddd> '); + // rli.prompt(); + // rli.write("really shouldn't be seeing this"); + // rli.question('What do you think of node.js? ', (answer) => { + // console.log('Thank you for your valuable feedback:', answer); + // rli.close(); + // }); + // } + + // // Calling the getPrompt method + // { + // const expectedPrompts = ['$ ', '> ']; + // const [rli] = getInterface({ terminal }); + // for (const prompt of expectedPrompts) { + // rli.setPrompt(prompt); + // assert.strictEqual(rli.getPrompt(), prompt); + // } + // } + + // { + // const expected = terminal ? + // ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : + // ['$ ']; + + // const output = new Writable({ + // write: mustCall((chunk, enc, cb) => { + // assert.strictEqual(chunk.toString(), expected.shift()); + // cb(); + // rl.close(); + // }, expected.length) + // }); + + // const rl = readline.createInterface({ + // input: new Readable({ read: mustCall() }), + // output, + // prompt: '$ ', + // terminal + // }); + + // rl.prompt(); + + // assert.strictEqual(rl.getPrompt(), '$ '); + // } + + // { + // const fi = new FakeInput(); + // assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); + // } + + // // Emit two line events when the delay + // // between \r and \n exceeds crlfDelay + // { + // const crlfDelay = 200; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => { + // callCount++; + // }); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 2); + // rli.close(); + // }), crlfDelay + 10); + // } + + // // For the purposes of the following tests, we do not care about the exact + // // value of crlfDelay, only that the behaviour conforms to what's expected. + // // Setting it to Infinity allows the test to succeed even under extreme + // // CPU stress. + // const crlfDelay = Infinity; + + // // Set crlfDelay to `Infinity` is allowed + // { + // const delay = 200; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => { + // callCount++; + // }); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // }), delay); + // } + + // // Sending multiple newlines at once that does not end with a new line + // // and a `end` event(last line is) + + // // \r\n should emit one line event, not two + // { + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // fi.emit('data', expectedLines.join('\r\n')); + // rli.close(); + // } + + // // \r\n should emit one line event when split across multiple writes. + // { + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // let callCount = 0; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines[callCount]); + // callCount++; + // }, expectedLines.length)); + // expectedLines.forEach((line) => { + // fi.emit('data', `${line}\r`); + // fi.emit('data', '\n'); + // }); + // rli.close(); + // } + + // // Emit one line event when the delay between \r and \n is + // // over the default crlfDelay but within the setting value. + // { + // const delay = 125; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => callCount++); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // }), delay); + // } + // }); + + // // Ensure that the _wordLeft method works even for large input + // { + // const input = new Readable({ + // read() { + // this.push('\x1B[1;5D'); // CTRL + Left + // this.push(null); + // }, + // }); + // const output = new Writable({ + // write: mustCall((data, encoding, cb) => { + // assert.strictEqual(rl.cursor, rl.line.length - 1); + // cb(); + // }), + // }); + // const rl = new readline.createInterface({ + // input, + // output, + // terminal: true, + // }); + // rl.line = `a${' '.repeat(1e6)}a`; + // rl.cursor = rl.line.length; + // } + + // { + // const fi = new FakeInput(); + // const signal = AbortSignal.abort(); + + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // rl.on('close', mustCall()); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); + // } + + // { + // const fi = new FakeInput(); + // const ac = new AbortController(); + // const { signal } = ac; + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 1); + // rl.on('close', mustCall()); + // ac.abort(); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); + // } + + // { + // const fi = new FakeInput(); + // const ac = new AbortController(); + // const { signal } = ac; + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // assert.strictEqual(getEventListeners(signal, "abort").length, 1); + // rl.close(); + // assert.strictEqual(getEventListeners(signal, "abort").length, 0); + // } + + // { + // // Constructor throws if signal is not an abort signal + // assert.throws(() => { + // readline.createInterface({ + // input: new FakeInput(), + // signal: {}, + // }); + // }, { + // name: 'TypeError', + // code: 'ERR_INVALID_ARG_TYPE' + // }); + // } + }); + + describe("readline.createInterface()", () => { + it("should emit line when input ends line", done => { + const createDone = createDoneDotAll(done); + const lineDone = createDone(2000); + const { mustCall } = createCallCheckCtx(createDone(2000)); + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on( + "line", + mustCall(data => { + assert.strictEqual(data, "abc"); + lineDone(); + }), + ); - const rl = readline.createInterface({ - input: bufferStream, + input.end("abc"); }); - process.nextTick(() => { - bufferStream.end(sampleTextBuffer); + it("should not emit line when input ends without newline", done => { + const { mustNotCall } = createCallCheckCtx(done); + + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on("line", mustNotCall("must not be called before newline")); + input.write("abc"); }); - const result = []; - for await (const line of rl) result.push(line); - expect(result).toEqual(["Line1", "Line2", "Line3", "Line4"]); - }); + it("should read line by line", done => { + const createDone = createDoneDotAll(done); + const { mustCall } = createCallCheckCtx(createDone(3000)); + const lineDone = createDone(2000); + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on( + "line", + mustCall(data => { + assert.strictEqual(data, "abc"); + lineDone(); + }), + ); - it("should respond to home and end sequences for common pttys ", () => { - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, - }); - - rl.write("foo"); - assert.strictEqual(rl.cursor, 3); - - const key = { - xterm: { - home: ["\x1b[H", { ctrl: true, name: "a" }], - end: ["\x1b[F", { ctrl: true, name: "e" }], - }, - gnome: { - home: ["\x1bOH", { ctrl: true, name: "a" }], - end: ["\x1bOF", { ctrl: true, name: "e" }], - }, - rxvt: { - home: ["\x1b[7", { ctrl: true, name: "a" }], - end: ["\x1b[8", { ctrl: true, name: "e" }], - }, - putty: { - home: ["\x1b[1~", { ctrl: true, name: "a" }], - end: ["\x1b[>~", { ctrl: true, name: "e" }], - }, - }; - - [key.xterm, key.gnome, key.rxvt, key.putty].forEach(key => { - rl.write.apply(rl, key.home); - assert.strictEqual(rl.cursor, 0); - rl.write.apply(rl, key.end); - assert.strictEqual(rl.cursor, 3); + input.write("abc\n"); }); - }); - it("should allow for cursor movement with meta-f and meta-b", () => { - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, - }); - - const key = { - xterm: { - home: ["\x1b[H", { ctrl: true, name: "a" }], - metab: ["\x1bb", { meta: true, name: "b" }], - metaf: ["\x1bf", { meta: true, name: "f" }], - }, - }; - - rl.write("foo bar.hop/zoo"); - rl.write.apply(rl, key.xterm.home); - [ - { cursor: 4, key: key.xterm.metaf }, - { cursor: 7, key: key.xterm.metaf }, - { cursor: 8, key: key.xterm.metaf }, - { cursor: 11, key: key.xterm.metaf }, - { cursor: 12, key: key.xterm.metaf }, - { cursor: 15, key: key.xterm.metaf }, - { cursor: 12, key: key.xterm.metab }, - { cursor: 11, key: key.xterm.metab }, - { cursor: 8, key: key.xterm.metab }, - { cursor: 7, key: key.xterm.metab }, - { cursor: 4, key: key.xterm.metab }, - { cursor: 0, key: key.xterm.metab }, - ].forEach(function (action) { - rl.write.apply(rl, action.key); - assert.strictEqual(rl.cursor, action.cursor); + it("should support reading-in lines via for await...of loop", async () => { + const sampleTextBuffer = new Buffer.from("Line1\nLine2\nLine3\nLine4"); + const bufferStream = new PassThrough(); + + const rl = readline.createInterface({ + input: bufferStream, + }); + + process.nextTick(() => { + bufferStream.end(sampleTextBuffer); + }); + + const result = []; + for await (const line of rl) result.push(line); + expect(result).toEqual(["Line1", "Line2", "Line3", "Line4"]); }); - }); - it("should properly allow for cursor movement with meta-d", () => { - const input = new PassThrough(); - const rl = readline.createInterface({ - terminal: true, - input: input, + it("should respond to home and end sequences for common pttys ", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.write("foo"); + assert.strictEqual(rl.cursor, 3); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + end: ["\x1b[F", { ctrl: true, name: "e" }], + }, + gnome: { + home: ["\x1bOH", { ctrl: true, name: "a" }], + end: ["\x1bOF", { ctrl: true, name: "e" }], + }, + rxvt: { + home: ["\x1b[7", { ctrl: true, name: "a" }], + end: ["\x1b[8", { ctrl: true, name: "e" }], + }, + putty: { + home: ["\x1b[1~", { ctrl: true, name: "a" }], + end: ["\x1b[>~", { ctrl: true, name: "e" }], + }, + }; + + [key.xterm, key.gnome, key.rxvt, key.putty].forEach(key => { + rl.write.apply(rl, key.home); + assert.strictEqual(rl.cursor, 0); + rl.write.apply(rl, key.end); + assert.strictEqual(rl.cursor, 3); + }); }); - const key = { - xterm: { - home: ["\x1b[H", { ctrl: true, name: "a" }], - metad: ["\x1bd", { meta: true, name: "d" }], - }, - }; + it("should allow for cursor movement with meta-f and meta-b", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + metab: ["\x1bb", { meta: true, name: "b" }], + metaf: ["\x1bf", { meta: true, name: "f" }], + }, + }; + + rl.write("foo bar.hop/zoo"); + rl.write.apply(rl, key.xterm.home); + [ + { cursor: 4, key: key.xterm.metaf }, + { cursor: 7, key: key.xterm.metaf }, + { cursor: 8, key: key.xterm.metaf }, + { cursor: 11, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metaf }, + { cursor: 15, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metab }, + { cursor: 11, key: key.xterm.metab }, + { cursor: 8, key: key.xterm.metab }, + { cursor: 7, key: key.xterm.metab }, + { cursor: 4, key: key.xterm.metab }, + { cursor: 0, key: key.xterm.metab }, + ].forEach(function (action) { + rl.write.apply(rl, action.key); + assert.strictEqual(rl.cursor, action.cursor); + }); + }); - rl.write("foo bar.hop/zoo"); - rl.write.apply(rl, key.xterm.home); - ["bar.hop/zoo", ".hop/zoo", "hop/zoo", "/zoo", "zoo", ""].forEach(function (expectedLine) { - rl.write.apply(rl, key.xterm.metad); - assert.strictEqual(rl.cursor, 0); - assert.strictEqual(rl.line, expectedLine); + it("should properly allow for cursor movement with meta-d", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + metad: ["\x1bd", { meta: true, name: "d" }], + }, + }; + + rl.write("foo bar.hop/zoo"); + rl.write.apply(rl, key.xterm.home); + ["bar.hop/zoo", ".hop/zoo", "hop/zoo", "/zoo", "zoo", ""].forEach(function (expectedLine) { + rl.write.apply(rl, key.xterm.metad); + assert.strictEqual(rl.cursor, 0); + assert.strictEqual(rl.line, expectedLine); + }); }); - }); - // TODO: Actual pseudo-tty test - // it("should operate correctly when process.env.DUMB is set", () => { - // process.env.TERM = "dumb"; - // const rl = readline.createInterface({ - // input: process.stdin, - // output: process.stdout, - // }); - // rl.write("text"); - // rl.write(null, { ctrl: true, name: "u" }); - // rl.write(null, { name: "return" }); - // rl.write("text"); - // rl.write(null, { name: "backspace" }); - // rl.write(null, { name: "escape" }); - // rl.write(null, { name: "enter" }); - // rl.write("text"); - // rl.write(null, { ctrl: true, name: "c" }); - // }); -}); -} \ No newline at end of file + // TODO: Actual pseudo-tty test + // it("should operate correctly when process.env.DUMB is set", () => { + // process.env.TERM = "dumb"; + // const rl = readline.createInterface({ + // input: process.stdin, + // output: process.stdout, + // }); + // rl.write("text"); + // rl.write(null, { ctrl: true, name: "u" }); + // rl.write(null, { name: "return" }); + // rl.write("text"); + // rl.write(null, { name: "backspace" }); + // rl.write(null, { name: "escape" }); + // rl.write(null, { name: "enter" }); + // rl.write("text"); + // rl.write(null, { ctrl: true, name: "c" }); + // }); + }); +}