feat: advance demo from approval submit

This commit is contained in:
lda
2026-07-09 09:18:44 +07:00 Verified
parent e9d85dd080
commit cf3cac1fc8
2 changed files with 64 additions and 1 deletions
@@ -1,4 +1,4 @@
import { cleanup, render, screen } from "@testing-library/react"; import { act, cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { afterEach, beforeAll, describe, expect, it } from "vitest"; import { afterEach, beforeAll, describe, expect, it } from "vitest";
@@ -194,4 +194,22 @@ describe("PresentationRoute", () => {
await user.click(runButton); await user.click(runButton);
expect(await screen.findByLabelText("workflow.runs.start operation")).toBeInTheDocument(); expect(await screen.findByLabelText("workflow.runs.start operation")).toBeInTheDocument();
}); });
it("submits Scene 10 approval and advances to the resume beat", async () => {
const user = userEvent.setup();
setReplayMode();
window.location.hash = "#scene/interrupt-evidence/approval";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
const submitButton = await screen.findByRole("button", { name: "Submit" });
await waitFor(() => expect(submitButton).toBeEnabled(), { timeout: 10000 });
await act(async () => {
await user.click(submitButton);
});
expect(window.location.hash).toBe("#scene/interrupt-evidence/resume");
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
});
}); });
@@ -12,6 +12,7 @@ import {
} from "./presentation-state.js"; } from "./presentation-state.js";
import { hashForLocation } from "./storyboard-navigation.js"; import { hashForLocation } from "./storyboard-navigation.js";
import type { MainLocation } from "./storyboard.js"; import type { MainLocation } from "./storyboard.js";
import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js";
import "./presentation.css"; import "./presentation.css";
import "./styles/demo-workflow.css"; import "./styles/demo-workflow.css";
@@ -108,6 +109,49 @@ export const PresentationRoute = () => {
dispatch({ type: "set_playback_mode", mode: demo.state.mode }); dispatch({ type: "set_playback_mode", mode: demo.state.mode });
}, [demo.state.mode]); }, [demo.state.mode]);
const selectedIssueIdsForDemo = (
payload: typeof demo.interruptPayload,
): readonly string[] =>
payload?.proposed_issues.map((issue) => issue.id) ?? [];
const [approvalState, setApprovalState] = useState<DemoApprovalUiState>("ready");
const handleSubmitApproval = useCallback(async () => {
const selectedIssueIds = selectedIssueIdsForDemo(demo.interruptPayload);
if (demo.state.phase !== "review" || selectedIssueIds.length === 0) return;
setApprovalState("submitted");
await demo.submitSelectedIssues(selectedIssueIds, "Create the selected issue.");
await demo.next();
dispatch({
type: "jump",
location: {
kind: "main",
sceneId: "interrupt-evidence",
beatId: "resume",
focusPath: [],
},
});
}, [demo]);
const handleCancelApproval = useCallback(async () => {
setApprovalState("cancelled");
}, []);
const approvalActions = useMemo<DemoApprovalActions>(() => ({
state: approvalState,
canSubmit: demo.state.phase === "review" && selectedIssueIdsForDemo(demo.interruptPayload).length > 0,
canCancel: demo.state.phase === "review",
submit: handleSubmitApproval,
cancel: handleCancelApproval,
}), [approvalState, demo.state.phase, demo.interruptPayload, handleSubmitApproval, handleCancelApproval]);
useEffect(() => {
if (demo.state.phase === "ready" || demo.state.phase === "running") {
setApprovalState("ready");
}
}, [demo.state.phase]);
const handleJump = useCallback( const handleJump = useCallback(
(location: MainLocation) => dispatch({ type: "jump", location }), (location: MainLocation) => dispatch({ type: "jump", location }),
[], [],
@@ -131,6 +175,7 @@ export const PresentationRoute = () => {
demo={demo} demo={demo}
evidence={evidence} evidence={evidence}
timelineAgent={timelineAgent} timelineAgent={timelineAgent}
approvalActions={approvalActions}
jump={handleJump} jump={handleJump}
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })} selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })} openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}