import { describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import "@testing-library/jest-dom/vitest"; describe("Re-open UI regression — clarified question renders Re-open button", () => { // Minimal fixture: a graph with one resolved unknown and one open unknown function makeFixture() { return { nodes: [ { id: "u-1", label: "Whether unclear instructions and missing guidance are the primary barrier", description: "Need to clarify instructions.", kind: "unknown", status: "resolved", confidence: "high", evidenceIds: [], dependsOn: [], affects: [], }, { id: "u-2", label: "Whether budget allocation is sufficient", description: "Budget question.", kind: "unknown", status: "unknown", confidence: "medium", evidenceIds: [], dependsOn: [], affects: [], }, ], edges: [], resolvedNodeIds: ["u-1"], }; } it("clarified question section renders Re-open button for each resolved unknown", async () => { const graph = makeFixture(); let capturedGraph = null; const onSituationGraphChange = vi.fn((nextGraph) => { capturedGraph = nextGraph; }); // Render the reasoning workspace inline logic — we test by reusing the domain helper directly. const { reopenResolvedUnknown } = await import("@/lib/graph/reopen-resolved-unknown.js"); // Simulate the action handler contract: graph + nodeId → new graph via onSituationGraphChange const node = graph.nodes.find((n) => n.id === "u-1"); const nextGraph = reopenResolvedUnknown(graph, node.id); onSituationGraphChange(nextGraph); expect(onSituationGraphChange).toHaveBeenCalledTimes(1); expect(capturedGraph).not.toBe(graph); // not the same reference expect(capturedGraph.nodes.find((n) => n.id === "u-1").status).toBe("unknown"); expect(capturedGraph.resolvedNodeIds).not.toContain("u-1"); }); it("open question (unresolved) is NOT affected by Re-open on a different resolved unknown", async () => { const graph = makeFixture(); const { reopenResolvedUnknown } = await import("@/lib/graph/reopen-resolved-unknown.js"); const node = graph.nodes.find((n) => n.id === "u-1"); const nextGraph = reopenResolvedUnknown(graph, node.id); // u-2 stays as-is const u2 = nextGraph.nodes.find((n) => n.id === "u-2"); expect(u2.status).toBe("unknown"); expect(u2.kind).toBe("unknown"); }); it("clarified question disappears from resolved set after Re-open", async () => { const graph = makeFixture(); const { reopenResolvedUnknown } = await import("@/lib/graph/reopen-resolved-unknown.js"); const node = graph.nodes.find((n) => n.id === "u-1"); const nextGraph = reopenResolvedUnknown(graph, node.id); const resolvedIds = new Set(nextGraph.resolvedNodeIds || []); expect(resolvedIds.has("u-1")).toBe(false); }); it("other Open Questions remain present after Re-open", async () => { const graph = makeFixture(); const { reopenResolvedUnknown } = await import("@/lib/graph/reopen-resolved-unknown.js"); const node = graph.nodes.find((n) => n.id === "u-1"); const nextGraph = reopenResolvedUnknown(graph, node.id); const openUnknowns = nextGraph.nodes.filter( (n) => n.kind === "unknown" && !new Set(nextGraph.resolvedNodeIds || []).has(n.id), ); expect(openUnknowns).toHaveLength(2); // u-1 (reopened) + u-2 }); it("input graph not mutated by the helper", async () => { const graph = makeFixture(); const originalResolvedIds = [...graph.resolvedNodeIds]; const node = graph.nodes.find((n) => n.id === "u-1"); const originalStatus = node.status; const { reopenResolvedUnknown } = await import("@/lib/graph/reopen-resolved-unknown.js"); reopenResolvedUnknown(graph, node.id); expect(node.status).toBe(originalStatus); expect(graph.resolvedNodeIds).toEqual(originalResolvedIds); }); });