Files
Termix/plugins/ssh-terminal/tests/frontend/lib/terminal-local-echo.test.ts
T

102 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
TerminalLocalEcho,
resolveLocalEchoMode,
} from "../../../src/frontend/lib/terminal-local-echo";
describe("TerminalLocalEcho", () => {
it("renders immediately and suppresses the matching remote echo", () => {
const echo = new TerminalLocalEcho("on");
expect(echo.handleInput("a")).toBe("a");
expect(echo.handleOutput("a")).toBe("");
});
it("rolls back a prediction when remote output differs", () => {
const echo = new TerminalLocalEcho("on");
echo.handleInput("a");
expect(echo.handleOutput("z")).toBe("\x1b[1D\x1b[1Xz");
});
it("only erases the predicted cells during rollback", () => {
const echo = new TerminalLocalEcho("on");
echo.handleInput("a");
echo.handleInput("b");
expect(echo.handleOutput("\x1b[C")).toBe("\x1b[2D\x1b[2X\x1b[C");
});
it.each([
["split prompt", ["Pass", "word: "]],
["sudo prompt", ["[sudo] password for alice: "]],
["ssh-keygen prompt", ["Enter passphrase (empty for no passphrase): "]],
["passwd prompt", ["New password: "]],
])("does not expose input after a %s", (_name, chunks) => {
const echo = new TerminalLocalEcho("on");
for (const chunk of chunks) expect(echo.handleOutput(chunk)).toBe(chunk);
expect(echo.handleInput("s")).toBe("");
});
it("does not treat ordinary password text as a hidden-input prompt", () => {
const echo = new TerminalLocalEcho("on");
expect(echo.handleOutput("Your password has expired\r\n$ ")).toBe(
"Your password has expired\r\n$ ",
);
expect(echo.handleInput("s")).toBe("s");
});
it("does not predict control input, paste, or wide characters", () => {
const echo = new TerminalLocalEcho("on");
expect(echo.handleInput("\t")).toBe("");
expect(echo.handleInput("paste")).toBe("");
expect(echo.handleInput("界")).toBe("");
});
it("enables automatic prediction after repeated slow echoes", () => {
let now = 0;
const echo = new TerminalLocalEcho("auto", () => now, 100);
expect(echo.handleInput("a")).toBe("");
now = 150;
expect(echo.handleOutput("a")).toBe("a");
expect(echo.handleInput("b")).toBe("");
now = 300;
expect(echo.handleOutput("b")).toBe("b");
expect(echo.handleInput("c")).toBe("c");
});
it("waits for outstanding unpredicted input before enabling auto prediction", () => {
let now = 0;
const echo = new TerminalLocalEcho("auto", () => now, 100);
expect(echo.handleInput("d")).toBe("");
now = 150;
expect(echo.handleOutput("d")).toBe("d");
expect(echo.handleInput("o")).toBe("");
expect(echo.handleInput("c")).toBe("");
now = 300;
expect(echo.handleOutput("o")).toBe("o");
expect(echo.handleInput("k")).toBe("");
expect(echo.handleOutput("ck")).toBe("ck");
expect(echo.handleInput("e")).toBe("e");
expect(echo.handleInput("r")).toBe("r");
expect(echo.handleOutput("er")).toBe("");
});
it("rolls back the unmatched suffix after a partially matching output chunk", () => {
const echo = new TerminalLocalEcho("on");
for (const character of "abc") echo.handleInput(character);
expect(echo.handleOutput("abZ")).toBe("\x1b[1D\x1b[1XZ");
expect(echo.handleOutput("c")).toBe("c");
});
it("keeps pending predictions across an empty output chunk", () => {
const echo = new TerminalLocalEcho("on");
expect(echo.handleInput("a")).toBe("a");
expect(echo.handleOutput("")).toBe("");
expect(echo.handleOutput("a")).toBe("");
});
it("uses an explicit host mode before the global mode", () => {
expect(resolveLocalEchoMode("off", "on")).toBe("off");
expect(resolveLocalEchoMode("default", "on")).toBe("on");
expect(resolveLocalEchoMode(undefined, null)).toBe("auto");
});
});