import { describe, expect, it, vi } from "vitest"; import React from "react"; import { render, screen, waitFor, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; // jsdom does not implement scrollIntoView — mock it globally Element.prototype.scrollIntoView = vi.fn(); // --------------------------------------------------------------------------- // Test target — bounded to ReasoningWorkspace milestone overview seam // --------------------------------------------------------------------------- function makeGraphResult(overrides = {}) { return { success: true, situationGraph: { centralStatement: "Complaints increased while production increased.", currentSummary: "Nodes: 2 observation | Unknowns: 0 resolved", activeUnknownNodeId: null, resolvedNodeIds: ["n-unknown"], nodes: [ { id: "n-1", label: "Complaints up 35%", kind: "observation", status: "supported" }, { id: "n-unknown", label: "Complaint rate denominator", kind: "unknown", status: "resolved" }, ], edges: [], reconstruction: { plausibleInterpretations: [{ id: "i1", description: "Denominator was narrowed" }] }, }, selectedQuestion: null, updatedSituationGraph: null, newlySurfacedNodeIds: [], diagnostics: { modelName: "test", validationStatus: "valid" }, ...overrides, }; } let ReasoningWorkspace; vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (k) => k }), initPromise: Promise.resolve(), })); describe("investigation overview UI seam (v0.54b)", () => { beforeAll(async () => { const mod = await import("@/components/reasoning-workspace.jsx"); ReasoningWorkspace = mod.default; }); function renderWorkspace(overrides = {}) { const props = { scenario: "Complaints increased while production increased.", status: "success", updateStatus: "idle", cuSynthesisLoading: false, currentUnderstanding: "Production quality declined.", result: makeGraphResult(), answer: "", setAnswer: vi.fn(), onAnswerSubmit: vi.fn(), lastSubmittedAnswer: "", onRestart: vi.fn(), focusedContributions: [], onFocusedContribution: vi.fn(), findings: [], onUpdateFindingDisposition: vi.fn(), onUpdateFindingProposition: vi.fn(), onSummaryUpdate: vi.fn(), onImmediateGraphChange: vi.fn(), onSituationGraphChange: vi.fn(), initialPostAnalyseStatus: "success", overviewState: null, setOverviewState: vi.fn(), overviewLoading: false, handleRequestOverview: vi.fn(), ...overrides, }; return render(React.createElement(ReasoningWorkspace, props)); } // 1. zero-Open-Questions milestone renders the review action it("renders Review current understanding button when open unknowns are zero and clarified questions exist", async () => { renderWorkspace(); await waitFor(() => { const btn = screen.getByRole("button", { name: /Review current understanding/i }); expect(btn).toBeInTheDocument(); }); }); // 7. no overview is requested before the user clicks it("does not request overview before user clicks (handleRequestOverview not called)", async () => { const mockHandleRequest = vi.fn(); renderWorkspace({ handleRequestOverview: mockHandleRequest }); expect(mockHandleRequest).not.toHaveBeenCalled(); }); // 2. clicking it calls the overview endpoint once it("calls handleRequestOverview on button click", async () => { const mockHandleRequest = vi.fn(); renderWorkspace({ handleRequestOverview: mockHandleRequest }); const btn = await screen.findByRole("button", { name: /Review current understanding/i }); fireEvent.click(btn); expect(mockHandleRequest).toHaveBeenCalledTimes(1); }); // 3. loading state is visible it("shows loading text while overviewLoading is true", async () => { renderWorkspace({ overviewState: null, overviewLoading: true, handleRequestOverview: vi.fn() }); await waitFor(() => { expect(screen.getByText(/Generating overview/i)).toBeInTheDocument(); }); }); // 4. returned understanding is rendered under the established-understanding section it("renders overview understanding in What we understand section", async () => { renderWorkspace({ overviewState: { success: true, understanding: "We understand that complaints rose alongside production increases.", plausibleInterpretations: null, }, }); await waitFor(() => { const surface = screen.getByTestId("investigation-overview"); expect(surface).toBeInTheDocument(); }); const surface2 = document.querySelector('[data-testid="investigation-overview"]'); expect(surface2.textContent).toContain("What we understand"); expect(surface2.textContent).toContain("We understand that complaints rose alongside production increases."); }); // 5. returned plausibleInterpretations is rendered separately and clearly qualified it("renders plausible interpretations as a separate qualified section", async () => { renderWorkspace({ overviewState: { success: true, understanding: "Production quality declined.", plausibleInterpretations: "The denominator may have been narrowed, explaining the apparent complaint increase.", }, }); const surface = screen.getByTestId("investigation-overview"); expect(surface).toBeInTheDocument(); expect(surface.textContent).toContain("What remains plausible"); expect(surface.textContent).toContain("The denominator may have been narrowed"); }); // 6. existing Current Understanding remains it("existing Current Understanding text remains visible alongside overview", async () => { renderWorkspace({ currentUnderstanding: "Production quality declined.", overviewState: { success: true, understanding: "We understand that complaints rose alongside production increases.", plausibleInterpretations: null, }, }); const cuSurface = document.querySelector('[id="cu-scroll-target"]'); expect(cuSurface).toBeInTheDocument(); expect(cuSurface.textContent).toContain("Production quality declined."); }); // 8. overview response does not mutate Current Understanding it("does not call setCurrentUnderstanding or mutate existing CU", async () => { const mockSetCU = vi.fn(); renderWorkspace({ currentUnderstanding: "Production quality declined.", overviewState: { success: true, understanding: "Overview synthesis result.", plausibleInterpretations: null, }, setCurrentUnderstanding: mockSetCU, }); expect(mockSetCU).not.toHaveBeenCalled(); }); // 9. clarified questions remain visible it("clarified questions remain rendered when overview is shown", async () => { renderWorkspace({ overviewState: { success: true, understanding: "Overview.", plausibleInterpretations: null, }, }); expect(screen.getByText(/Questions we have clarified/i)).toBeInTheDocument(); }); });