/** * Terminal Component Tests * * Tests the pure helper functions extracted from the Terminal component: * - getLineClass: maps line types to CSS class names * - getLinePrefix: maps line types to display prefixes * - formatTime: formats a Date as "HH:MM AM/PM" * - isToolContentLong: checks if tool content exceeds collapse threshold * - truncateToolContent: truncates long tool content with ellipsis * * Manual testing checklist: * - [ ] rate-limit lines appear in amber * - [ ] error lines appear in red * - [ ] tool lines appear in purple * - [ ] system lines appear in grey italic * - [ ] user lines appear in cyan * - [ ] assistant lines appear in primary text colour * - [ ] long tool content is collapsed by default with a toggle button */ import { describe, it, expect } from "vitest"; // Mirror functions from Terminal.svelte for isolated testing function getLineClass(type: string): string { switch (type) { case "user": return "terminal-user"; case "assistant": return "terminal-assistant"; case "system": return "terminal-system italic"; case "tool": return "terminal-tool"; case "error": return "terminal-error"; case "thinking": return "terminal-thinking"; case "rate-limit": return "terminal-rate-limit"; case "compact-prompt": return "terminal-compact-prompt"; case "worktree": return "terminal-worktree"; case "config-change": return "terminal-config-change"; default: return "terminal-default"; } } function getLinePrefix(type: string): string { switch (type) { case "user": return ">"; case "assistant": return ""; case "system": return "[system]"; case "tool": return "[tool]"; case "error": return "[error]"; case "rate-limit": return "[rate-limit]"; case "worktree": return "[worktree]"; case "config-change": return "[config]"; default: return ""; } } function formatTime(date: Date): string { return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", }); } const TOOL_COLLAPSE_THRESHOLD = 60; function isToolContentLong(content: string): boolean { return content.length > TOOL_COLLAPSE_THRESHOLD; } function truncateToolContent(content: string): string { return content.slice(0, TOOL_COLLAPSE_THRESHOLD) + "…"; } // --- describe("getLineClass", () => { it("returns terminal-user for user lines", () => { expect(getLineClass("user")).toBe("terminal-user"); }); it("returns terminal-assistant for assistant lines", () => { expect(getLineClass("assistant")).toBe("terminal-assistant"); }); it("returns terminal-system italic for system lines", () => { expect(getLineClass("system")).toBe("terminal-system italic"); }); it("returns terminal-tool for tool lines", () => { expect(getLineClass("tool")).toBe("terminal-tool"); }); it("returns terminal-error for error lines", () => { expect(getLineClass("error")).toBe("terminal-error"); }); it("returns terminal-thinking for thinking lines", () => { expect(getLineClass("thinking")).toBe("terminal-thinking"); }); it("returns terminal-rate-limit for rate-limit lines", () => { expect(getLineClass("rate-limit")).toBe("terminal-rate-limit"); }); it("returns terminal-compact-prompt for compact-prompt lines", () => { expect(getLineClass("compact-prompt")).toBe("terminal-compact-prompt"); }); it("returns terminal-worktree for worktree lines", () => { expect(getLineClass("worktree")).toBe("terminal-worktree"); }); it("returns terminal-config-change for config-change lines", () => { expect(getLineClass("config-change")).toBe("terminal-config-change"); }); it("returns terminal-default for unknown line types", () => { expect(getLineClass("unknown")).toBe("terminal-default"); expect(getLineClass("")).toBe("terminal-default"); expect(getLineClass("random-future-type")).toBe("terminal-default"); }); }); describe("getLinePrefix", () => { it("returns > for user lines", () => { expect(getLinePrefix("user")).toBe(">"); }); it("returns empty string for assistant lines", () => { expect(getLinePrefix("assistant")).toBe(""); }); it("returns [system] for system lines", () => { expect(getLinePrefix("system")).toBe("[system]"); }); it("returns [tool] for tool lines", () => { expect(getLinePrefix("tool")).toBe("[tool]"); }); it("returns [error] for error lines", () => { expect(getLinePrefix("error")).toBe("[error]"); }); it("returns [rate-limit] for rate-limit lines", () => { expect(getLinePrefix("rate-limit")).toBe("[rate-limit]"); }); it("returns empty string for compact-prompt lines (button renders instead)", () => { expect(getLinePrefix("compact-prompt")).toBe(""); }); it("returns [worktree] for worktree lines", () => { expect(getLinePrefix("worktree")).toBe("[worktree]"); }); it("returns [config] for config-change lines", () => { expect(getLinePrefix("config-change")).toBe("[config]"); }); it("returns empty string for thinking lines (no prefix)", () => { expect(getLinePrefix("thinking")).toBe(""); }); it("returns empty string for unknown line types", () => { expect(getLinePrefix("unknown")).toBe(""); expect(getLinePrefix("")).toBe(""); }); }); describe("formatTime", () => { it("formats time in 12-hour format with AM/PM", () => { const date = new Date(2026, 1, 7, 14, 35); const formatted = formatTime(date); expect(formatted).toMatch(/\d{2}:\d{2}\s?(AM|PM)/i); }); it("formats afternoon times correctly", () => { const date = new Date(2026, 1, 7, 14, 35); const formatted = formatTime(date); expect(formatted).toContain("02:35"); expect(formatted.toUpperCase()).toContain("PM"); }); it("formats morning times correctly", () => { const date = new Date(2026, 1, 7, 9, 5); const formatted = formatTime(date); expect(formatted).toContain("09:05"); expect(formatted.toUpperCase()).toContain("AM"); }); it("formats midnight correctly", () => { const date = new Date(2026, 1, 7, 0, 0); const formatted = formatTime(date); expect(formatted).toContain("12:00"); expect(formatted.toUpperCase()).toContain("AM"); }); it("formats noon correctly", () => { const date = new Date(2026, 1, 7, 12, 0); const formatted = formatTime(date); expect(formatted).toContain("12:00"); expect(formatted.toUpperCase()).toContain("PM"); }); }); describe("isToolContentLong", () => { it("returns false for content at or below the threshold", () => { const exactThreshold = "x".repeat(TOOL_COLLAPSE_THRESHOLD); expect(isToolContentLong(exactThreshold)).toBe(false); }); it("returns true for content exceeding the threshold", () => { const overThreshold = "x".repeat(TOOL_COLLAPSE_THRESHOLD + 1); expect(isToolContentLong(overThreshold)).toBe(true); }); it("returns false for short content", () => { expect(isToolContentLong("short")).toBe(false); }); it("returns false for empty content", () => { expect(isToolContentLong("")).toBe(false); }); }); describe("truncateToolContent", () => { it("truncates content to the threshold length with an ellipsis", () => { const long = "x".repeat(100); const result = truncateToolContent(long); expect(result).toBe("x".repeat(TOOL_COLLAPSE_THRESHOLD) + "…"); }); it("keeps content shorter than threshold unchanged (plus ellipsis)", () => { const short = "hello"; const result = truncateToolContent(short); expect(result).toBe("hello…"); }); it("uses the unicode ellipsis character (not three dots)", () => { const long = "x".repeat(100); const result = truncateToolContent(long); expect(result.endsWith("…")).toBe(true); expect(result.endsWith("...")).toBe(false); }); });