import { render, screen } from "@testing-library/react"; import { http, HttpResponse } from "msw"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { server } from "../test/msw"; import { SessionDocumentsPanel } from "./SessionDocumentsPanel"; import type { SessionSummary } from "../api/types"; function makeClient() { return new QueryClient({ defaultOptions: { queries: { retry: false } } }); } function wrap(ui: React.ReactElement, client = makeClient()) { const Wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); return render(ui, { wrapper: Wrapper }); } const base: SessionSummary = { id: "s1", status: "open", question: "How many ablations?", summary: null, created_at: "2026-01-01T00:00:00Z", updated_at: null, author: null, name: null, group: null, archived: false, }; beforeEach(() => { server.use(http.get("http://localhost:8787/sessions/s1/documents", () => HttpResponse.json([ { phase: "—", key: "question", title: "Original question", format: "text", content: "How many ablations?" }, { phase: "F7", key: "sql", title: "Final SQL", format: "sql", content: "SELECT 1" }, ]), )); }); test("renders the document titles from the API", async () => { wrap(); expect(await screen.findByRole("heading", { name: "Original question" })).toBeInTheDocument(); // "Final SQL" is both the section heading and the SqlViewer block label — target the heading. expect(screen.getByRole("heading", { name: "Final SQL" })).toBeInTheDocument(); }); test("shows Resume for resumable, hides it for finalized", async () => { const { rerender } = wrap(); expect(await screen.findByRole("button", { name: /resume/i })).toBeInTheDocument(); rerender(); expect(screen.queryByRole("button", { name: /resume/i })).not.toBeInTheDocument(); }); test("hides Resume for an archived session", async () => { wrap(); await screen.findByText("Original question"); expect(screen.queryByRole("button", { name: /resume/i })).not.toBeInTheDocument(); }); test("a malformed document renders a fallback without taking down its siblings", async () => { const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); // Valid JSON but missing `candidates` — SchemaLinkingViewer crashes on this shape. // Without a per-document boundary this crash blanks the whole panel (React 18 // unmounts the tree); the good sibling document must still render. // resetHandlers(...) replaces the beforeEach handler so this response is used. server.resetHandlers( http.get("http://localhost:8787/sessions/s1/documents", () => HttpResponse.json([ { phase: "F4", key: "schema", title: "Schema linking", format: "schema-linking", content: '{"joins":[]}' }, { phase: "—", key: "question", title: "Original question", format: "text", content: "SIBLING-SURVIVES" }, ]), ), ); wrap(); // The healthy sibling rendered — the panel did not white-screen. expect(await screen.findByText("SIBLING-SURVIVES")).toBeInTheDocument(); // The broken document degraded to a fallback in place. expect(screen.getByRole("alert")).toBeInTheDocument(); expect(screen.getByText(/couldn't be displayed/i)).toBeInTheDocument(); errorSpy.mockRestore(); });