Files
zesdex/apps/interfaces/tui/src/tui.test.ts
T

128 lines
4.6 KiB
TypeScript

/**
* Tests for the TUI logic layer: command parsing, action dispatch, and the
* key controller. Mirrors the Rust `#[cfg(test)]` blocks.
*/
import { describe, test, expect } from "bun:test";
import { parseCommand, applyCommand } from "./command.ts";
import { createTuiState, pushTranscript, makeChatMessage } from "./state.ts";
import { applyAction } from "./action.ts";
import { handleKey, decodeKey } from "./controller.ts";
import { Roles } from "@zesdex/domain";
function freshState() {
return createTuiState(["/tmp/zesdex-tui-test"], "/tmp/zesdex-tui-test/sess", "/tmp/zesdex-tui-test/mem");
}
describe("parseCommand", () => {
test("recognises /todo", () => {
expect(parseCommand("/todo")).toEqual({ tag: "TodoOpen" });
});
test("recognises /usage", () => {
expect(parseCommand("/usage")).toEqual({ tag: "UsageOpen" });
});
test("non-slash input is Unknown", () => {
expect(parseCommand("hello")).toEqual({ tag: "Unknown", text: "hello" });
});
test("parses /edit path", () => {
expect(parseCommand("/edit src/a.ts")).toEqual({ tag: "Edit", path: "src/a.ts" });
});
test("parses /clear with arg as Clear, without as ClearConfirm", () => {
expect(parseCommand("/clear foo")).toEqual({ tag: "Clear" });
expect(parseCommand("/clear")).toEqual({ tag: "ClearConfirm" });
});
});
describe("applyCommand", () => {
test("Help opens help overlay", () => {
expect(applyCommand({ tag: "Help" })).toEqual([{ tag: "OpenOverlay", overlay: "help" }]);
});
test("Unknown slash produces SystemNote error", () => {
expect(applyCommand({ tag: "Unknown", text: "/nope" })).toEqual([
{ tag: "SystemNote", kind: "error", message: "Unknown command: /nope" },
]);
});
});
describe("applyAction", () => {
test("SubmitInput pushes user transcript and sets pendingSubmit", () => {
const s = freshState();
applyAction(s, { tag: "SubmitInput", text: "hi" });
const last = s.transcriptCache.messages[s.transcriptCache.messages.length - 1];
expect(last?.role).toBe(Roles.User);
expect(last?.content).toBe("hi");
expect(s.pendingSubmit).toBe("hi");
});
test("ForceQuit sets quit", () => {
const s = freshState();
applyAction(s, { tag: "ForceQuit" });
expect(s.quit).toBe(true);
});
test("QuitConfirm sets overlay", () => {
const s = freshState();
applyAction(s, { tag: "QuitConfirm" });
expect(s.misc.overlay).toBe("quit_confirm");
});
test("AbortTurn sets abort flag", () => {
const s = freshState();
applyAction(s, { tag: "AbortTurn" });
expect(s.abortFlag.value).toBe(true);
});
});
describe("handleKey", () => {
test("Ctrl+C produces QuitConfirm", () => {
const s = freshState();
const actions = handleKey({ code: "Char", value: "c", ctrl: true, alt: false }, s);
expect(actions).toEqual([{ tag: "QuitConfirm" }]);
});
test("typing chars inserts into buffer", () => {
const s = freshState();
handleKey({ code: "Char", value: "h", ctrl: false, alt: false }, s);
handleKey({ code: "Char", value: "i", ctrl: false, alt: false }, s);
expect(s.input.buffer).toBe("hi");
});
test("Enter submits input as SubmitInput", () => {
const s = freshState();
handleKey({ code: "Char", value: "a", ctrl: false, alt: false }, s);
const actions = handleKey({ code: "Enter", ctrl: false, alt: false }, s);
expect(actions).toEqual([{ tag: "SubmitInput", text: "a" }]);
});
test("Ctrl+Y copies last assistant message", () => {
const s = freshState();
pushTranscript(s, makeChatMessage(Roles.User, "hi"));
pushTranscript(s, makeChatMessage(Roles.Assistant, "first"));
pushTranscript(s, makeChatMessage(Roles.Tool, "tool"));
pushTranscript(s, makeChatMessage(Roles.Assistant, "second"));
handleKey({ code: "Char", value: "y", ctrl: true, alt: false }, s);
expect(s.misc.pendingClipboardCopy).toBe("second");
});
test("Ctrl+Y with no assistant pushes info toast", () => {
const s = freshState();
handleKey({ code: "Char", value: "y", ctrl: true, alt: false }, s);
expect(s.misc.pendingClipboardCopy).toBeNull();
expect(s.misc.toasts.length).toBe(1);
});
});
describe("decodeKey", () => {
test("Enter decodes", () => {
expect(decodeKey("\r")).toEqual({ code: "Enter", ctrl: false, alt: false });
});
test("arrow up decodes", () => {
expect(decodeKey("\u001b[A")).toEqual({ code: "ArrowUp", ctrl: false, alt: false });
});
test("ctrl-c decodes to ctrl Char c", () => {
expect(decodeKey("\u0003")).toEqual({ code: "Char", value: "c", ctrl: true, alt: false });
});
test("plain letter decodes", () => {
expect(decodeKey("x")).toEqual({ code: "Char", value: "x", ctrl: false, alt: false });
});
});