// @vitest-environment jsdom import { flushSync } from "react-dom"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ThemeProvider } from "@/context/ThemeContext"; import { TaskChatMarker } from "./TaskChatMarker"; import { TaskChatThreadView } from "./TaskChatThreadView"; import { TaskChatPresentationProvider } from "./presentation-mode"; vi.mock("@/lib/router", () => ({ Link: ({ to, children }: { to: string; children: React.ReactNode }) => ( {children} ), })); describe("TaskChatMarker", () => { let container: HTMLDivElement; let root: Root | null = null; beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-09-01T12:05:00.000Z")); container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { flushSync(() => root?.unmount()); root = null; container.remove(); vi.useRealTimers(); }); it.each(["production", "streamlined"] as const)("renders old failure markers without divider rules in %s mode", (mode) => { flushSync(() => root!.render( , )); expect(container.querySelector(".border-dashed, .h-px")).toBeNull(); expect(container.textContent).toContain("Run failed"); expect(container.textContent).toContain("Saved diagnostic"); }); it.each(["production", "streamlined"] as const)("shows only the last adjacent error and retains retry in %s mode", async (mode) => { const retry = vi.fn(); flushSync(() => root!.render( , )); expect(container.textContent).toContain("Run failed"); expect(container.querySelector('[data-testid="task-chat-system-notice"]')).toBeNull(); flushSync(() => container.querySelector('[data-testid="task-chat-run-failed-try-again"]')!.click()); await Promise.resolve(); expect(retry).toHaveBeenCalledWith("latest"); }); it.each(["production", "streamlined"] as const)("keeps the permitted run retry and its pending state on a later recovery notice in %s mode", async (mode) => { const retry = vi.fn(); const renderNotice = (pending: boolean) => flushSync(() => root!.render( , )); renderNotice(true); expect(container.textContent).not.toContain("Run failed"); let button = container.querySelector('[data-testid="task-chat-no-live-path-try-again"]')!; expect(button.disabled).toBe(true); expect(button.textContent).toBe("Trying again..."); flushSync(() => button.click()); expect(retry).not.toHaveBeenCalled(); renderNotice(false); flushSync(() => container.querySelector('button[aria-expanded]')!.click()); button = container.querySelector('[data-testid="task-chat-no-live-path-try-again"]')!; expect(button.disabled).toBe(false); flushSync(() => button.click()); await Promise.resolve(); expect(retry).toHaveBeenCalledExactlyOnceWith("latest"); }); it("renders a failed run as a timestamped disclosure without divider rules", () => { flushSync(() => root!.render( , ), ); const toggle = container.querySelector( '[data-testid="task-chat-collapsible-marker"] button[aria-expanded]', )!; expect(toggle.textContent).toContain("Run failed"); expect(toggle.textContent).toContain("5m ago"); expect( container .querySelector('[data-testid="task-chat-collapsible-marker"]') ?.classList.contains("items-start"), ).toBe(true); expect(container.querySelector(".border-dashed")).toBeNull(); expect(container.textContent).not.toContain("runner_exited"); flushSync(() => toggle.click()); expect(toggle.getAttribute("aria-expanded")).toBe("true"); expect(container.textContent).toContain("runner_exited"); expect( container.querySelector('a[href="/agents/codex/runs/run-1"]') ?.textContent, ).toBe("View run"); }); it("keeps expected cancellation neutral when collapsed and expanded", () => { flushSync(() => root!.render( , ), ); const toggle = container.querySelector( "button[aria-expanded]", )!; expect(toggle.classList).toContain("text-muted-foreground"); expect(container.querySelector(".text-destructive")).toBeNull(); flushSync(() => toggle.click()); expect(container.textContent).toContain("Cancelled by you."); expect(container.querySelector(".text-destructive")).toBeNull(); }); it("keeps Try again available without retry instructions in the detail", async () => { const onTryAgain = vi.fn(); flushSync(() => root!.render( , ), ); const retry = container.querySelector( '[data-testid="task-chat-run-failed-try-again"]', )!; expect(container.textContent).not.toContain( "You can retry this message now", ); flushSync(() => retry.click()); await Promise.resolve(); expect(onTryAgain).toHaveBeenCalledTimes(1); }); it("routes Try again to the failed run retry callback", async () => { const onRetryFailedRun = vi.fn(); flushSync(() => root!.render( , ), ); const retry = container.querySelector( '[data-testid="task-chat-run-failed-try-again"]', )!; expect(retry).not.toBeNull(); flushSync(() => retry.click()); await Promise.resolve(); expect(onRetryFailedRun).toHaveBeenCalledWith("run-1"); }); });