Skip to content

Commit 6053a8d

Browse files
committed
feat(demo): reorder demo slides to agent, viewer, cli
The Agent video is now the default opening slide; auto-advance cycles Agent -> Viewer -> CLI. Update the demo-slides and DemoSection unit tests for the new order and default tab. Also fix the pre-existing-broken demo e2e: select the CLI tab to mount the terminal player (no longer the default slide), match asciinema's renamed theme class, and assert the mobile cast fits within the page gutters without overflow.
1 parent 8865e6b commit 6053a8d

4 files changed

Lines changed: 68 additions & 47 deletions

File tree

src/components/__tests__/DemoSection.test.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@ afterEach(() => {
2626
});
2727

2828
describe("DemoSection", () => {
29-
it("renders three tabs with CLI selected by default", () => {
29+
it("renders three tabs with Agent selected by default", () => {
3030
render(<DemoSection />);
3131
const tabs = screen.getAllByRole("tab");
32-
expect(tabs.map((t) => t.textContent)).toEqual(["CLI", "Viewer", "Agent"]);
33-
expect(screen.getByRole("tab", { name: "CLI" })).toHaveAttribute("aria-selected", "true");
32+
expect(tabs.map((t) => t.textContent)).toEqual(["Agent", "Viewer", "CLI"]);
33+
expect(screen.getByRole("tab", { name: "Agent" })).toHaveAttribute("aria-selected", "true");
3434
});
3535

36-
it("shows the terminal player on the default tab", () => {
36+
it("plays the agent video with a stop control on the default tab", () => {
3737
render(<DemoSection />);
38-
expect(screen.getByTestId("demo-hero-player")).toBeInTheDocument();
38+
const video = screen.getByTestId("demo-agent-media");
39+
expect(video).toBeInTheDocument();
40+
// Direct mapping: a light page shows the light-named asset.
41+
expect(video).toHaveAttribute("src", "/agent-video-light.mp4");
42+
expect(screen.getByRole("button", { name: /pause demo/i })).toBeInTheDocument();
3943
expect(screen.queryByTestId("demo-viewer-media")).not.toBeInTheDocument();
4044
});
4145

@@ -48,37 +52,35 @@ describe("DemoSection", () => {
4852
expect(media.closest("a")).toHaveAttribute("href", "https://viewer.opentaint.org/");
4953
});
5054

51-
it("switches to the agent slide and plays the agent video with a stop control", () => {
55+
it("switches to the CLI slide and shows the terminal player", () => {
5256
render(<DemoSection />);
53-
fireEvent.click(screen.getByRole("tab", { name: "Agent" }));
54-
const video = screen.getByTestId("demo-agent-media");
55-
expect(video).toBeInTheDocument();
56-
// Direct mapping: a light page shows the light-named asset.
57-
expect(video).toHaveAttribute("src", "/agent-video-light.mp4");
58-
expect(screen.getByRole("button", { name: /pause demo/i })).toBeInTheDocument();
57+
fireEvent.click(screen.getByRole("tab", { name: "CLI" }));
58+
expect(screen.getByRole("tab", { name: "CLI" })).toHaveAttribute("aria-selected", "true");
59+
expect(screen.getByTestId("demo-hero-player")).toBeInTheDocument();
60+
expect(screen.queryByTestId("demo-agent-media")).not.toBeInTheDocument();
5961
});
6062

6163
it("moves between tabs with the right arrow key", () => {
6264
render(<DemoSection />);
63-
const cliTab = screen.getByRole("tab", { name: "CLI" });
64-
cliTab.focus();
65-
fireEvent.keyDown(cliTab, { key: "ArrowRight" });
65+
const agentTab = screen.getByRole("tab", { name: "Agent" });
66+
agentTab.focus();
67+
fireEvent.keyDown(agentTab, { key: "ArrowRight" });
6668
expect(screen.getByRole("tab", { name: "Viewer" })).toHaveAttribute("aria-selected", "true");
6769
});
6870

6971
it("wraps to the last tab when ArrowLeft is pressed on the first tab", () => {
7072
render(<DemoSection />);
71-
const cliTab = screen.getByRole("tab", { name: "CLI" });
72-
cliTab.focus();
73-
fireEvent.keyDown(cliTab, { key: "ArrowLeft" });
74-
expect(screen.getByRole("tab", { name: "Agent" })).toHaveAttribute("aria-selected", "true");
73+
const agentTab = screen.getByRole("tab", { name: "Agent" });
74+
agentTab.focus();
75+
fireEvent.keyDown(agentTab, { key: "ArrowLeft" });
76+
expect(screen.getByRole("tab", { name: "CLI" })).toHaveAttribute("aria-selected", "true");
7577
});
7678

7779
it("moves DOM focus to the newly active tab on arrow navigation", () => {
7880
render(<DemoSection />);
79-
const cliTab = screen.getByRole("tab", { name: "CLI" });
80-
cliTab.focus();
81-
fireEvent.keyDown(cliTab, { key: "ArrowRight" });
81+
const agentTab = screen.getByRole("tab", { name: "Agent" });
82+
agentTab.focus();
83+
fireEvent.keyDown(agentTab, { key: "ArrowRight" });
8284
expect(screen.getByRole("tab", { name: "Viewer" })).toHaveFocus();
8385
});
8486

@@ -136,11 +138,11 @@ describe("DemoSection", () => {
136138
vi.useFakeTimers();
137139
try {
138140
render(<DemoSection />);
139-
fireEvent.click(screen.getByRole("tab", { name: "Agent" }));
141+
fireEvent.click(screen.getByRole("tab", { name: "CLI" }));
140142
act(() => {
141143
vi.advanceTimersByTime(21000);
142144
});
143-
expect(screen.getByRole("tab", { name: "Agent" })).toHaveAttribute("aria-selected", "true");
145+
expect(screen.getByRole("tab", { name: "CLI" })).toHaveAttribute("aria-selected", "true");
144146
} finally {
145147
vi.useRealTimers();
146148
}

src/components/__tests__/demo-slides.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { describe, expect, it } from "vitest";
22
import { DEMO_SLIDES } from "../demo-slides";
33

44
describe("DEMO_SLIDES", () => {
5-
it("lists terminal, viewer, agent in order", () => {
6-
expect(DEMO_SLIDES.map((s) => s.id)).toEqual(["terminal", "viewer", "agent"]);
5+
it("lists agent, viewer, terminal in order", () => {
6+
expect(DEMO_SLIDES.map((s) => s.id)).toEqual(["agent", "viewer", "terminal"]);
77
});
88

99
it("gives the terminal slide the cast kind and no media sources", () => {

src/components/demo-slides.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ export type DemoSlide = {
2323
// picks the source by page theme, so there is no inversion here.
2424
export const DEMO_SLIDES: DemoSlide[] = [
2525
{
26-
id: "terminal",
27-
label: "CLI",
28-
kind: "terminal",
26+
id: "agent",
27+
label: "Agent",
28+
kind: "video",
29+
// The video plays inline; `fallback` doubles as the poster frame and the
30+
// static image shown under prefers-reduced-motion.
31+
sources: { light: "/agent-video-light.mp4", dark: "/agent-video-dark.mp4" },
32+
fallback: { light: "/screen-light-3.png", dark: "/screen-dark-3.png" },
33+
alt: "A coding agent running OpenTaint via the skill",
34+
testId: "demo-agent-media",
2935
},
3036
{
3137
id: "viewer",
@@ -39,14 +45,8 @@ export const DEMO_SLIDES: DemoSlide[] = [
3945
href: "https://viewer.opentaint.org/",
4046
},
4147
{
42-
id: "agent",
43-
label: "Agent",
44-
kind: "video",
45-
// The video plays inline; `fallback` doubles as the poster frame and the
46-
// static image shown under prefers-reduced-motion.
47-
sources: { light: "/agent-video-light.mp4", dark: "/agent-video-dark.mp4" },
48-
fallback: { light: "/screen-light-3.png", dark: "/screen-dark-3.png" },
49-
alt: "A coding agent running OpenTaint via the skill",
50-
testId: "demo-agent-media",
48+
id: "terminal",
49+
label: "CLI",
50+
kind: "terminal",
5151
},
5252
];

tests/demo.spec.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,63 @@
11
import { expect, test } from "@playwright/test";
22

3+
// The CLI/terminal cast is no longer the default slide (the order is
4+
// Agent, Viewer, CLI), so each test selects the CLI tab to mount the player.
5+
const THEME_CLASS = /asciinema-player-theme-opentaint-(light|dark)/;
6+
37
test.describe("landing page demo section", () => {
48
test("hero cast renders with a theme class", async ({ page }) => {
59
await page.goto("/");
10+
await page.getByRole("tab", { name: "CLI" }).click();
611
const hero = page.getByTestId("demo-hero-player");
712
await hero.scrollIntoViewIfNeeded();
813
await expect(hero).toBeVisible();
9-
const cls = (await hero.getAttribute("class")) ?? "";
10-
expect(cls).toMatch(/ap-theme-opentaint-(light|dark)/);
14+
// asciinema mounts inside the container and stamps the theme onto its player.
15+
await expect(hero.locator(".ap-player")).toHaveClass(THEME_CLASS);
1116
});
1217

1318
test("theme toggle swaps the scan panel wrapper class", async ({ page }) => {
1419
await page.goto("/");
20+
await page.getByRole("tab", { name: "CLI" }).click();
1521
const hero = page.getByTestId("demo-hero-player");
1622
await hero.scrollIntoViewIfNeeded();
23+
const player = hero.locator(".ap-player");
24+
await expect(player).toHaveClass(THEME_CLASS);
1725

18-
const initial = (await hero.getAttribute("class")) ?? "";
19-
const initialIsDark = initial.includes("ap-theme-opentaint-dark");
26+
const initial = (await player.getAttribute("class")) ?? "";
27+
const initialIsDark = initial.includes("asciinema-player-theme-opentaint-dark");
2028

2129
await page.locator("[data-theme-toggle]").first().click();
2230

2331
await expect
24-
.poll(async () => (await hero.getAttribute("class")) ?? "")
32+
.poll(async () => (await player.getAttribute("class")) ?? "")
2533
.toContain(
26-
initialIsDark ? "ap-theme-opentaint-light" : "ap-theme-opentaint-dark",
34+
initialIsDark
35+
? "asciinema-player-theme-opentaint-light"
36+
: "asciinema-player-theme-opentaint-dark",
2737
);
2838
});
2939

30-
test("mobile layout shows the scan cast edge-to-edge", async ({ page }) => {
40+
test("mobile layout fits the scan cast within the page without overflow", async ({ page }) => {
3141
await page.setViewportSize({ width: 375, height: 812 });
3242
await page.goto("/");
43+
await page.getByRole("tab", { name: "CLI" }).click();
3344

3445
const hero = page.getByTestId("demo-hero-player");
3546
await hero.scrollIntoViewIfNeeded();
3647
await expect(hero).toBeVisible();
48+
// Wait for the player to lay out before measuring its width.
49+
await expect(hero.locator(".ap-player")).toHaveClass(THEME_CLASS);
3750

3851
const viewportWidth = await page.evaluate(() => window.innerWidth);
3952

40-
const heroWidth = await hero.evaluate((el) => el.getBoundingClientRect().width);
41-
expect(heroWidth).toBeGreaterThanOrEqual(viewportWidth - 1);
53+
// The cast fills the content area symmetrically within the page gutters.
54+
const { left, width } = await hero.evaluate((el) => {
55+
const r = el.getBoundingClientRect();
56+
return { left: r.left, width: r.width };
57+
});
58+
expect(left).toBeGreaterThan(0);
59+
expect(left * 2 + width).toBeGreaterThanOrEqual(viewportWidth - 1);
60+
expect(left * 2 + width).toBeLessThanOrEqual(viewportWidth + 1);
4261

4362
const bodyScrollWidth = await page.evaluate(() => document.body.scrollWidth);
4463
expect(bodyScrollWidth).toBeLessThanOrEqual(viewportWidth + 1);

0 commit comments

Comments
 (0)