mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
102 lines
3.7 KiB
TypeScript
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");
|
|
});
|
|
});
|