import { describe, expect, it, beforeEach, vi } from "vitest"; import React from "react"; import { render, screen, fireEvent, within } from "@testing-library/react"; import "@testing-library/jest-dom"; vi.mock("next/navigation", () => ({ useRouter: () => ({ push: () => {} }), })); // --------------------------------------------------------------------------- // Mock investigation-storage — shared for entire test file // (vi.mock hoists; all tests share this instance) // --------------------------------------------------------------------------- let mockClearStorage = vi.fn(); let mockLoadResult = null; function setMockSnapshot(snap) { if (snap) { localStorage.setItem( "confidence-engine-investigation", JSON.stringify(snap), ); mockLoadResult = snap; } else { localStorage.removeItem("confidence-engine-investigation"); mockLoadResult = null; } } vi.mock("@/lib/storage/investigation-storage", () => ({ loadInvestigation: () => mockLoadResult, saveInvestigation: () => {}, clearInvestigation: () => { localStorage.removeItem("confidence-engine-investigation"); mockClearStorage(); }, })); function makeSnapshot(overrides = {}) { const situationGraph = { evidence: [ { id: "e1", claim: "Complaints rose.", type: "finding" }, { id: "e2", claim: "Production increased.", type: "finding" }, ], reconstruction: { plausibleInterpretations: ["The denominator may have been narrowed."], }, }; return { scenario: "Complaints increased while production increased.", situationGraph, selectedQuestion: null, summary: "Production quality declined.", updatedAt: new Date().toISOString(), schemaVersion: 1, investigationReport: null, findings: [], ...overrides, }; } async function cleanup() { localStorage.removeItem("confidence-engine-investigation"); } // --------------------------------------------------------------------------- // Portfolio page — pre-confirmation semantics (v0.55/v0.56) // --------------------------------------------------------------------------- describe("Portfolio page (pre-confirmation v0.55/v0.56)", () => { let Portfolio; beforeEach(async () => { const mod = await import("@/app/page.jsx"); Portfolio = mod.default; }); it("shows + Create new investigation when no investigation exists", async () => { setMockSnapshot(null); render(React.createElement(Portfolio)); expect(await screen.findByText(/Create new investigation/i)).toBeInTheDocument(); expect(screen.queryByText(/Open investigation/i)).not.toBeInTheDocument(); expect(screen.queryByText(/Continue investigation/i)).not.toBeInTheDocument(); await cleanup(); }); it("does not show Restart investigation when no investigation exists", async () => { setMockSnapshot(null); render(React.createElement(Portfolio)); expect(screen.queryByText(/Restart investigation/i)).not.toBeInTheDocument(); await cleanup(); }); it("shows Continue investigation card when one persisted investigation exists", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); expect(await screen.findByText(/Continue investigation/i)).toBeInTheDocument(); await cleanup(); }); it("does not show Open investigation text when one persisted investigation exists", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); expect(screen.queryByText(/Open investigation/i)).not.toBeInTheDocument(); await cleanup(); }); it("shows View report only when a persisted report exists", async () => { setMockSnapshot( makeSnapshot({ investigationReport: { understanding: "We understand complaints rose.", hasPlausibleInterpretations: false, }, }), ); render(React.createElement(Portfolio)); expect(await screen.findByText(/View report/i)).toBeInTheDocument(); await cleanup(); }); it("does not show View report when no report persists", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); expect(screen.queryByText(/View report/i)).not.toBeInTheDocument(); await cleanup(); }); it("shows Restart investigation button when one persisted investigation exists", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); expect(await screen.findByText(/Restart investigation/i)).toBeInTheDocument(); await cleanup(); }); it("card does not contain Create new investigation when one persisted investigation exists", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); expect(await screen.findByText(/Create new investigation/i)).toBeInTheDocument(); const card = document.querySelector("section.mb-10"); const btns = Array.from(card.querySelectorAll('a,button')); expect(btns.some(el => /Create new investigation/i.test(el.textContent))).toBe(false); await cleanup(); }); it("View / Continue / Restart semantics unchanged before confirmation (no report)", async () => { setMockSnapshot(makeSnapshot()); render(React.createElement(Portfolio)); // View report absent because investigationReport is null expect(screen.queryByText(/View report/i)).not.toBeInTheDocument(); expect(await screen.findByText(/Continue investigation/i)).toBeInTheDocument(); expect(await screen.findByRole("button", { name: /Restart investigation/i })).toBeInTheDocument(); await cleanup(); }); it("View report present when a persisted report exists", async () => { setMockSnapshot( makeSnapshot({ investigationReport: { understanding: "We understand complaints rose.", hasPlausibleInterpretations: false, }, }), ); render(React.createElement(Portfolio)); expect(await screen.findByText(/View report/i)).toBeInTheDocument(); await cleanup(); }); }); // --------------------------------------------------------------------------- // Restart confirmation flow (v0.57) // --------------------------------------------------------------------------- describe("Restart confirmation flow (v0.57)", () => { let Portfolio; beforeEach(async () => { mockClearStorage = vi.fn(); setMockSnapshot(makeSnapshot()); const mod = await import("@/app/page.jsx"); Portfolio = mod.default; }); it("first Restart click does not call clearInvestigation", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); expect(mockClearStorage).not.toHaveBeenCalled(); }); it("warning dialog title appears on first Restart click", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); expect(await screen.findByRole("heading", { name: /Restart this investigation\?/i })).toBeInTheDocument(); }); it("warning body accurately describes loss on first Restart click", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); expect(await screen.findByText(/Your current investigation, findings, clarified questions, and report will be lost/i)).toBeInTheDocument(); }); it("dialog has accessible role and semantics", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); const dialog = await screen.findByRole("dialog"); expect(dialog).toHaveAttribute("aria-modal", "true"); expect(dialog).toHaveAttribute("aria-labelledby"); }); it("Cancel closes the dialog and does not call clearInvestigation", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); expect(await screen.findByRole("dialog")).toBeInTheDocument(); const cancelBtn = screen.getByRole("button", { name: "Cancel" }); fireEvent.click(cancelBtn); expect(mockClearStorage).not.toHaveBeenCalled(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("persisted card remains after Cancel", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); const cancelBtn = screen.getByRole("button", { name: "Cancel" }); fireEvent.click(cancelBtn); expect(await screen.findByText(/Continue investigation/i)).toBeInTheDocument(); }); it("confirmed Restart calls clearInvestigation exactly once", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); const dialog = await screen.findByRole("dialog"); const dialogRestartBtn = within(dialog).getByRole("button", { name: "Restart investigation" }); fireEvent.click(dialogRestartBtn); expect(mockClearStorage).toHaveBeenCalledTimes(1); }); it("confirmed Restart removes the card from Portfolio state", async () => { render(React.createElement(Portfolio)); expect(await screen.findByText(/Continue investigation/i)).toBeInTheDocument(); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); const dialog = await screen.findByRole("dialog"); const dialogRestartBtn = within(dialog).getByRole("button", { name: "Restart investigation" }); fireEvent.click(dialogRestartBtn); expect(screen.queryByText(/Continue investigation/i)).not.toBeInTheDocument(); }); it("Portfolio-level + Create new investigation remains after confirmed Restart (but no card)", async () => { render(React.createElement(Portfolio)); expect(await screen.findByText(/Create new investigation/i)).toBeInTheDocument(); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); const dialog = await screen.findByRole("dialog"); const dialogRestartBtn = within(dialog).getByRole("button", { name: "Restart investigation" }); fireEvent.click(dialogRestartBtn); // card is gone (no Continue investigation) but the portfolio-level link remains // After restart, existing=null so "Investigations" section disappears, leaving only the standalone + Create new investigation link }); it("mocked clear does not call original storage during tests", async () => { render(React.createElement(Portfolio)); const restartBtn = await screen.findByRole("button", { name: /Restart investigation/i }); fireEvent.click(restartBtn); expect(mockClearStorage).not.toHaveBeenCalled(); }); });