test: cover presentation route rehearsal contracts

This commit is contained in:
lda
2026-07-13 00:47:56 +07:00 Verified
parent 0077bab225
commit c9ebc73184
3 changed files with 125 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# Task 3 Report
## Changed Files
- `web/apps/console/src/presentation/presentation-rehearsal.test.ts`
- Added the pure rehearsal storyboard contract.
- Verifies all 14 current scene IDs, expected beat counts, and canonical beat IDs.
- Failure messages identify the missing scene or `scene/beat` pair.
- `web/apps/console/src/presentation/PresentationRoute.test.tsx`
- Added direct-hash coverage for the nine required routes.
- Verifies visible accessible headings and demo-chrome ownership for Scenes 8 through 12.
- Added no-accidental-chrome coverage for title, problem, architecture, evaluation, and conclusion routes.
- Verifies prepared lifecycle assistant-pane ownership, footer ownership, and exactly one run action.
## Deviations
- `#scene/conclusion/questions` intentionally asserts the visible `Thesis contribution` discussion-index heading. That beat renders the examiner discussion index instead of the `Limits and Conclusion` scene caption.
- The typed approval route intentionally checks for the visible demo rail rather than a run button because its chrome is a paused review status with a `Submit` action.
## Bugs
- No production bugs found. No production files were changed.
## Verification
- `pnpm --dir web/apps/console test -- src/presentation/presentation-rehearsal.test.ts src/presentation/PresentationRoute.test.tsx`
- Passed: 2 test files, 67 tests.
- `pnpm --dir web/apps/console typecheck`
- Passed: `tsc -b --pretty false`.
- `git diff --check`
- Passed with no whitespace errors.
- Self-review confirmed the changes are limited to the Task 3 test contracts and this report.
@@ -73,6 +73,62 @@ afterEach(() => {
}); });
describe("PresentationRoute", () => { describe("PresentationRoute", () => {
const directHashRoutes = [
{ hash: "#scene/thesis/title", heading: "Design and Implementation of lda.chat", hasDemoChrome: false },
{ hash: "#scene/architecture/client", heading: "Architecture Zoom", hasDemoChrome: false },
{ hash: "#scene/authoring/diagnose", heading: "Author, Validate, Repair", hasDemoChrome: false },
{ hash: "#scene/prepared-lifecycle/discover", heading: "Prepared Workflow Lifecycle", hasDemoChrome: true },
{ hash: "#scene/run-from-deployment/graph", heading: "Run From Deployment", hasDemoChrome: true },
{ hash: "#scene/typed-human-boundary/approval", heading: "Typed Human Boundary", hasDemoChrome: true },
{ hash: "#scene/resume-output-evidence/trace", heading: "Resume, Output, Evidence", hasDemoChrome: true },
{ hash: "#scene/evaluation/cohort", heading: "Evaluation", hasDemoChrome: false },
{ hash: "#scene/conclusion/questions", heading: "Thesis contribution", hasDemoChrome: false },
] as const;
it.each(directHashRoutes)(
"renders $hash with heading $heading and expected demo chrome ownership",
async ({ hash, heading, hasDemoChrome }) => {
window.location.hash = hash;
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByRole("heading", { name: heading })).toBeInTheDocument();
const demoChrome = screen.queryByTestId("presentation-demo-rail");
expect(demoChrome !== null).toBe(hasDemoChrome);
},
);
it.each([
"#scene/thesis/title",
"#scene/problem/direct-actions",
"#scene/architecture/client",
"#scene/evaluation/cohort",
"#scene/conclusion/questions",
])("does not leak live target or prepared-run chrome onto %s", async (hash) => {
window.location.hash = hash;
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByRole("main", { name: /lda\.chat presentation/i })).toBeInTheDocument();
expect(screen.queryByLabelText("presentation evidence mode")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Run prepared workflow" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Play replay walkthrough" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Retry live service" })).not.toBeInTheDocument();
expect(screen.queryByRole("region", { name: "prepared workflow launch" })).not.toBeInTheDocument();
});
it("keeps prepared lifecycle chat and footer ownership without duplicating a run action", async () => {
window.location.hash = "#scene/prepared-lifecycle/discover";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByRole("complementary", { name: /prepared authoring assistant/i })).toBeInTheDocument();
const footer = screen.getByRole("contentinfo", { name: /presentation footer/i });
expect(within(footer).getByRole("button", { name: /run prepared workflow|play replay walkthrough/i })).toBeInTheDocument();
expect(screen.getAllByRole("button", { name: /run prepared workflow|play replay walkthrough/i })).toHaveLength(1);
expect(screen.queryByRole("region", { name: "prepared workflow launch" })).not.toBeInTheDocument();
});
it("renders the presentation stage entry point", { timeout: 15000 }, async () => { it("renders the presentation stage entry point", { timeout: 15000 }, async () => {
const { PresentationRoute } = await import("./PresentationRoute.js"); const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />); render(<PresentationRoute />);
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { mainScenes } from "./storyboard.js";
const expectedBeats = {
thesis: ["title", "substrate"],
problem: ["direct-actions", "missing-contracts"],
positioning: ["landscape", "lda-position"],
"planner-runtime": ["planner", "runtime", "boundary"],
lifecycle: ["draft", "artifact", "deployment", "run"],
architecture: ["client", "api", "runtime", "node-use"],
authoring: ["discover", "author", "diagnose", "repair"],
"agent-handoff": ["request"],
"prepared-lifecycle": ["discover", "draft", "validate", "artifact", "deployment"],
"run-from-deployment": ["input", "operation", "graph"],
"typed-human-boundary": ["interrupt", "approval"],
"resume-output-evidence": ["resume", "output", "trace"],
evaluation: ["cohort", "validity", "findings"],
conclusion: ["limits", "future", "conclusion", "questions"],
} as const;
describe("presentation rehearsal storyboard", () => {
it("keeps every scene count and canonical beat id aligned with the rehearsal", () => {
for (const [sceneId, beatIds] of Object.entries(expectedBeats)) {
const scene = mainScenes.find((candidate) => candidate.id === sceneId);
expect(scene, `missing rehearsal scene ${sceneId}`).toBeDefined();
if (!scene) continue;
expect(scene.beats, `scene ${sceneId} has the wrong beat count`).toHaveLength(beatIds.length);
for (const beatId of beatIds) {
expect(
scene.beats.map((beat) => beat.id),
`scene ${sceneId} is missing beat ${beatId}`,
).toContain(beatId);
}
}
});
});