/** * 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 }); }); });