- Wire transient overview state from ScenarioForm to ReasoningWorkspace - Inline rendering of investigation overview below milestone invitation - Remove obsolete scrollIntoView after overview request (scrolled away from rendered content) - All four overview props consumed in ReasoningWorkspace render path - Targeted Vitest: 9/9 PASS (tests/ui/investigation-overview-ui.test.jsx) - Production build: compiles successfully
188 lines
7.0 KiB
React
188 lines
7.0 KiB
React
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();
|
|
});
|
|
});
|