feat(confidence-engine): v0.54b integrate Investigation Overview UI seam + bounded scroll cleanup
- 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
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user