test: cover presentation route rehearsal contracts
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user