fix: make active reasoning state visible

This commit is contained in:
2026-08-03 16:09:04 +01:00
parent e2960853ba
commit 0dd15345e4
4 changed files with 341 additions and 11 deletions
+256
View File
@@ -8,6 +8,7 @@ import ReasoningWorkspace, {
useLoadingStatus,
INITIAL_MESSAGES,
UPDATE_MESSAGES,
LoadingOverlay,
} from "@/components/reasoning-workspace.jsx";
import {
ScenarioResultPanels,
@@ -1286,4 +1287,259 @@ describe("ReasoningWorkspace UI", () => {
"What evidence would clarify how the two observations were measured?",
);
});
// ── Loading card UI tests ───────────────────────────────
it("LoadingOverlay shows spinner and heading when isLoading is true", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={0}
currentMessage="Reading your situation"
variant="initial"
/>,
);
expect(html).toContain("Working through your situation");
expect(html).toContain("Reading your situation");
});
it("LoadingOverlay is hidden when isLoading is false", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={false}
elapsed={0}
currentMessage="Reading your situation"
variant="initial"
/>,
);
expect(html).not.toContain("Working through your situation");
});
it("LoadingOverlay shows correct initial status at elapsed 0", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={0}
currentMessage="Reading your situation"
variant="initial"
/>,
);
expect(html).toContain("Reading your situation");
});
it("LoadingOverlay shows correct status at elapsed >= 10", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={12}
currentMessage="Building a structured understanding"
variant="initial"
/>,
);
expect(html).toContain("Building a structured understanding");
});
it("LoadingOverlay shows correct status at elapsed >= 25", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={30}
currentMessage="Identifying what is known and still unclear"
variant="initial"
/>,
);
expect(html).toContain("Identifying what is known and still unclear");
});
it("LoadingOverlay shows correct status at elapsed >= 45", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={50}
currentMessage="Selecting the next useful question"
variant="initial"
/>,
);
expect(html).toContain("Selecting the next useful question");
});
it("LoadingOverlay shows reassurance text after 45s for initial variant", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={60}
currentMessage="Selecting the next useful question"
variant="initial"
/>,
);
expect(html).toContain("This can take around a minute");
});
it("LoadingOverlay hides reassurance text for update variant regardless of elapsed", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={90}
currentMessage="Choosing the next question"
variant="update"
/>,
);
expect(html).not.toContain("This can take around a minute");
});
it("LoadingOverlay displays elapsed time", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={42}
currentMessage="Building a structured understanding"
variant="initial"
/>,
);
expect(html).toContain("This has been running for 42s.");
});
it("LoadingOverlay has aria-busy and role for accessibility", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={0}
currentMessage="Reading your situation"
variant="initial"
/>,
);
expect(html).toContain("aria-busy");
expect(html).toContain("role=\"status\"");
});
it("LoadingOverlay uses aria-live on elapsed time element", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={5}
currentMessage="Reading your situation"
variant="initial"
/>,
);
expect(html).toContain("aria-live");
});
it("scenario-form hides Analyse button and shows loading card during initial analysis", () => {
// We verify ReasoningWorkspace renders LoadingOverlay for initial status=loading
// which is the direct proxy for the scenario form's loading behavior
const html = renderToStaticMarkup(
<ReasoningWorkspace
status="loading"
updateStatus="idle"
result={null}
answer=""
setAnswer={vi.fn()}
onAnswerSubmit={vi.fn()}
/>,
);
// Loading overlay is present
expect(html).toContain("Working through your situation");
expect(html).toContain("Reading your situation");
// No result content appears during loading
expect(html).not.toContain("Your situation");
expect(html).not.toContain("Next question");
});
it("scenario-form shows update loading overlay when updateStatus is loading", () => {
const html = renderToStaticMarkup(
<ReasoningWorkspace
status="success"
updateStatus="loading"
result={makeWorkspaceResult()}
answer="some answer"
setAnswer={vi.fn()}
onAnswerSubmit={vi.fn()}
/>,
);
expect(html).toContain("Working through your situation");
expect(html).toContain("Considering your answer");
});
it("success state restores normal controls — no loading overlay", () => {
const html = renderToStaticMarkup(
<ReasoningWorkspace
status="success"
updateStatus="idle"
result={makeWorkspaceResult()}
answer=""
setAnswer={vi.fn()}
onAnswerSubmit={vi.fn()}
/>,
);
expect(html).not.toContain("Working through your situation");
expect(html).toContain("Next question");
expect(html).toContain("Update situation");
});
it("error state remains visible and shows no loading overlay", () => {
const html = renderToStaticMarkup(
<ReasoningWorkspace
status="error"
updateStatus="idle"
result={{ error: "Invalid start-case request" }}
answer=""
setAnswer={vi.fn()}
onAnswerSubmit={vi.fn()}
/>,
);
expect(html).not.toContain("Working through your situation");
expect(html).toContain("Error: Invalid start-case request");
});
it("spinner renders without breaking layout when reduced-motion is active", () => {
const html = renderToStaticMarkup(
<LoadingOverlay
isLoading={true}
elapsed={3}
currentMessage="Reading your situation"
variant="initial"
/>,
);
// Spinner element present (CSS media query in globals.css handles reduced-motion)
expect(html).toContain("rounded-full");
// No JS animation dependency for rendering
expect(html).not.toContain("animation-delay");
});
it("loading card appears while Analyse button is hidden (duplicate prevention)", () => {
// During status=loading: LoadingOverlay is shown and Analyse button is not rendered
const html = renderToStaticMarkup(
<ReasoningWorkspace
status="loading"
updateStatus="idle"
result={null}
answer=""
setAnswer={vi.fn()}
onAnswerSubmit={vi.fn()}
/>,
);
// Loading card visible (main signal)
expect(html).toContain("Working through your situation");
// No form controls remain during loading
expect(html).not.toContain("Update situation");
});
});