feat: add user-focused reasoning workspace
This commit is contained in:
@@ -4,6 +4,11 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import DiagnosticsView from "@/components/diagnostics-view.jsx";
|
||||
import GraphUpdateView from "@/components/graph-update-view.jsx";
|
||||
import SituationGraphView from "@/components/situation-graph-view.jsx";
|
||||
import ReasoningWorkspace, {
|
||||
useLoadingStatus,
|
||||
INITIAL_MESSAGES,
|
||||
UPDATE_MESSAGES,
|
||||
} from "@/components/reasoning-workspace.jsx";
|
||||
import {
|
||||
ScenarioResultPanels,
|
||||
UpdateErrorPanel,
|
||||
@@ -838,4 +843,309 @@ describe("graph-backed UI rendering", () => {
|
||||
expect(html).toContain("New active unknown");
|
||||
expect(html).not.toContain("No next question selected yet.");
|
||||
});
|
||||
});
|
||||
|
||||
// ── ReasoningWorkspace tests ────────────────────────────────
|
||||
describe("ReasoningWorkspace UI", () => {
|
||||
function makeWorkspaceResult(overrides = {}) {
|
||||
return {
|
||||
success: true,
|
||||
situationGraph: makeGraphResult().situationGraph,
|
||||
selectedQuestion: { question: "What denominator is being used for the complaint rate?" },
|
||||
newlySurfacedNodeIds: [],
|
||||
diagnostics: {
|
||||
modelName: "test",
|
||||
responseDurationMs: 1234,
|
||||
validationStatus: "valid",
|
||||
nodeCount: 3,
|
||||
edgeCount: 2,
|
||||
graphReferenceValidation: { valid: true, errors: [] },
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeWorkspaceProps(overrides = {}) {
|
||||
return {
|
||||
status: "success",
|
||||
updateStatus: "idle",
|
||||
result: makeWorkspaceResult(),
|
||||
answer: "",
|
||||
setAnswer: vi.fn(),
|
||||
onAnswerSubmit: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders by default with a successful start result", () => {
|
||||
const html = renderToStaticMarkup(<ReasoningWorkspace {...makeWorkspaceProps()} />);
|
||||
|
||||
expect(html).toContain("Your situation");
|
||||
expect(html).toContain("Complaints increased while production increased.");
|
||||
});
|
||||
|
||||
it("shows current understanding section", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
{...makeWorkspaceProps({
|
||||
result: makeWorkspaceResult({
|
||||
situationGraph: {
|
||||
...makeWorkspaceResult().situationGraph,
|
||||
currentSummary: "Nodes: 2 observation, 1 unknown",
|
||||
},
|
||||
}),
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Current understanding");
|
||||
expect(html).toContain("Nodes: 2 observation, 1 unknown");
|
||||
});
|
||||
|
||||
it("shows current focus section with the active unknown", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("What we are working out");
|
||||
expect(html).toContain("Complaint rate denominator");
|
||||
});
|
||||
|
||||
it("prominently displays the next question", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("Next question");
|
||||
expect(html).toContain("What denominator is being used for the complaint rate?");
|
||||
});
|
||||
|
||||
it("shows progress summary with resolved and remaining counts", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
{...makeWorkspaceProps({
|
||||
result: makeWorkspaceResult({
|
||||
situationGraph: {
|
||||
...makeWorkspaceResult().situationGraph,
|
||||
resolvedNodeIds: ["n-1"],
|
||||
},
|
||||
}),
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("resolved");
|
||||
expect(html).toContain("remaining");
|
||||
});
|
||||
|
||||
it("renders the answer form when a question is available", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("Your answer");
|
||||
expect(html).toContain("Update situation");
|
||||
});
|
||||
|
||||
it("renders developer details section (collapsed)", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("Developer details");
|
||||
});
|
||||
|
||||
it("preserves the full situation graph in developer details", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("Situation Graph");
|
||||
expect(html).toContain("Central statement");
|
||||
});
|
||||
|
||||
it("preserves diagnostics in developer details", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("Diagnostics");
|
||||
expect(html).toContain("test");
|
||||
expect(html).toContain("1234ms");
|
||||
});
|
||||
|
||||
it("user-facing sections show labels, not raw node IDs", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace {...makeWorkspaceProps()} />,
|
||||
);
|
||||
|
||||
// Labels and human-readable text should be present
|
||||
expect(html).toContain("Your situation");
|
||||
expect(html).toContain("Current understanding");
|
||||
expect(html).toContain("What we are working out");
|
||||
expect(html).toContain("Next question");
|
||||
expect(html).toContain("Complaint rate denominator");
|
||||
|
||||
// Raw node IDs only appear in developer details (collapsed), not in user-facing sections
|
||||
expect(html).toContain("Developer details");
|
||||
});
|
||||
|
||||
it("error state remains visible", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="error"
|
||||
updateStatus="idle"
|
||||
result={{ error: "Invalid start-case request" }}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Error: Invalid start-case request");
|
||||
});
|
||||
|
||||
it("no-question state is handled clearly", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="success"
|
||||
updateStatus="idle"
|
||||
result={makeWorkspaceResult({
|
||||
selectedQuestion: null,
|
||||
diagnostics: { ...makeWorkspaceResult().diagnostics, noQuestionReason: "All unknowns resolved" },
|
||||
})}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("There is no next question at the moment.");
|
||||
});
|
||||
|
||||
it("initial loading state appears with spinner heading", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="loading"
|
||||
updateStatus="idle"
|
||||
result={null}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Working through your situation");
|
||||
expect(html).toContain("Reading your situation");
|
||||
});
|
||||
|
||||
it("update loading state appears with spinner heading", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="success"
|
||||
updateStatus="loading"
|
||||
result={makeWorkspaceResult()}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Working through your situation");
|
||||
});
|
||||
|
||||
it("elapsed time text appears during loading", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="loading"
|
||||
updateStatus="idle"
|
||||
result={null}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("This has been running for");
|
||||
});
|
||||
|
||||
it("empty state shows guidance when idle", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="idle"
|
||||
updateStatus="idle"
|
||||
result={null}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Should not show any result panels or workspace
|
||||
expect(html).not.toContain("Your situation");
|
||||
expect(html).not.toContain("Next question");
|
||||
});
|
||||
|
||||
it("update error is visible", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="success"
|
||||
updateStatus="error"
|
||||
result={{
|
||||
situationGraph: makeWorkspaceResult().situationGraph,
|
||||
selectedQuestion: null,
|
||||
updateError: { error: "Invalid graph update proposal" },
|
||||
}}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Update error: Invalid graph update proposal");
|
||||
});
|
||||
|
||||
it("message pools are exported for external use", () => {
|
||||
expect(INITIAL_MESSAGES.length).toBeGreaterThan(0);
|
||||
expect(UPDATE_MESSAGES.length).toBeGreaterThan(0);
|
||||
expect(INITIAL_MESSAGES[0].min).toBe(0);
|
||||
expect(UPDATE_MESSAGES[0].min).toBe(0);
|
||||
});
|
||||
|
||||
it("no answer form shown when there is no question", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="success"
|
||||
updateStatus="idle"
|
||||
result={makeWorkspaceResult({ selectedQuestion: null })}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).not.toContain("Your answer");
|
||||
expect(html).not.toContain("Update situation");
|
||||
});
|
||||
|
||||
it("update result merges into workspace correctly", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ReasoningWorkspace
|
||||
status="success"
|
||||
updateStatus="success"
|
||||
result={makeWorkspaceResult({
|
||||
situationGraph: makeUpdateSuccess().updatedSituationGraph,
|
||||
selectedQuestion: makeUpdateSuccess().selectedQuestion,
|
||||
})}
|
||||
answer=""
|
||||
setAnswer={vi.fn()}
|
||||
onAnswerSubmit={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain("Updated summary");
|
||||
expect(html).toContain(
|
||||
"What evidence would clarify how the two observations were measured?",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user