import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { TaskDrawer } from "./TaskDrawer"; import type { TaskDetail } from "../api"; const DETAIL: TaskDetail = { ok: true, thread_id: "aaaa1111bbbb", short_id: "aaaa1111", task: "remediate CVE-2026-1", status: "done", current_phase: "done", partial: false, timeline: [ { from_phase: null, to_phase: "intake", entered_at: "2026-06-23T00:00:00+00:00", exited_at: "2026-06-23T00:00:01+00:00", duration_s: 1, status: "active", note: null, cost_usd: 0, }, { from_phase: "intake", to_phase: "plan", entered_at: "2026-06-23T00:00:01+00:00", exited_at: null, duration_s: null, status: "active", note: null, cost_usd: 0.12, }, ], qa_history: [{ turn: 0, answer: "scope it" }], review_verdicts: [], plan: { summary: "do the thing" }, cost_by_stage: { plan: 0.12 }, total_usd: 0.12, }; describe("TaskDrawer", () => { it("renders the timeline steps with the task description and spend", () => { render( {}} />, ); expect(screen.getByText("remediate CVE-2026-1")).toBeInTheDocument(); expect(screen.getByText("intake")).toBeInTheDocument(); expect(screen.getByText("plan")).toBeInTheDocument(); expect(screen.getByText("in progress")).toBeInTheDocument(); // open final step expect(screen.getByText(/spend \$0\.12/)).toBeInTheDocument(); }); it("shows a partial-history banner when partial", () => { render( {}} />, ); expect(screen.getByText(/Partial history/)).toBeInTheDocument(); }); it("renders nothing when idle (no detail, not loading)", () => { const { container } = render( {}} />, ); expect(container).toBeEmptyDOMElement(); }); it("renders review findings as markdown (bold, not raw **)", () => { const d: TaskDetail = { ...DETAIL, review_verdicts: [ { verdict: "request_changes", round_index: 1, outcome: "loop_back", findings: "**Missing phase** and\n1. run the tests", }, ], }; render( {}} />, ); expect(screen.getByText("Missing phase").tagName).toBe("STRONG"); expect(screen.getByText("run the tests").closest("li")).not.toBeNull(); expect(screen.getByText("request changes")).toBeInTheDocument(); }); it("renders the plan as summary + phase/step lists", () => { const d: TaskDetail = { ...DETAIL, plan: { summary: "Add a smoke test", phases: [ { name: "Recon", steps: ["read conftest", "read test_graph"] }, ], }, }; render( {}} />, ); expect(screen.getByText("Recon")).toBeInTheDocument(); expect(screen.getByText("read conftest").closest("li")).not.toBeNull(); }); it("renders both string and dict Q&A entries", () => { const d: TaskDetail = { ...DETAIL, qa_history: [ "plain answer text", { turn: 1, answer: "dict answer text" }, ], }; render( {}} />, ); expect(screen.getByText("plain answer text")).toBeInTheDocument(); expect(screen.getByText("dict answer text")).toBeInTheDocument(); }); it("does NOT render raw HTML embedded in agent text (no XSS)", () => { const d: TaskDetail = { ...DETAIL, review_verdicts: [ { verdict: "approve", round_index: 1, outcome: "build", findings: 'before after', }, ], }; const { container } = render( {}} />, ); expect(container.querySelector("img")).toBeNull(); }); });