From 84f06a33dc8fe6820b5b6d0c299dfa060a83a7c1 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 27 Sep 2026 12:02:45 -0700 Subject: [PATCH] test(e2e): add Select, FilterChip, dictation, AuthLayout and ChatThread specs --- e2e/chat-thread.spec.ts | 23 ++++++++++++ e2e/controls.spec.ts | 50 +++++++++++++++++++++++++ e2e/fixtures.tsx | 81 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 e2e/chat-thread.spec.ts create mode 100644 e2e/controls.spec.ts diff --git a/e2e/chat-thread.spec.ts b/e2e/chat-thread.spec.ts new file mode 100644 index 00000000..508a5c20 --- /dev/null +++ b/e2e/chat-thread.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from "@playwright/test"; + +test("an agent turn renders its reasoning and tool parts, each expanding on click", async ({ + page, +}) => { + await page.goto("/chat-thread"); + const turn = page.getByRole("article", { name: "Scout replied" }); + await expect(turn.getByText("Three commits landed today.")).toBeVisible(); + + const reasoning = turn.locator("details"); + await expect(reasoning).not.toHaveAttribute("open"); + await reasoning.locator("summary").click(); + await expect(reasoning).toHaveAttribute("open"); + await expect( + reasoning.getByText("Check the commit log first."), + ).toBeVisible(); + + const tool = turn.getByRole("button", { name: /Read today's commits/ }); + await expect(tool).toHaveAttribute("aria-expanded", "false"); + await tool.click(); + await expect(tool).toHaveAttribute("aria-expanded", "true"); + await expect(turn.getByText("3 commits")).toBeVisible(); +}); diff --git a/e2e/controls.spec.ts b/e2e/controls.spec.ts new file mode 100644 index 00000000..1025689e --- /dev/null +++ b/e2e/controls.spec.ts @@ -0,0 +1,50 @@ +import { expect, test } from "@playwright/test"; + +test("Select carries its value, aria wiring and change events to the native control", async ({ + page, +}) => { + await page.goto("/select"); + const select = page.getByRole("combobox", { name: "Letter" }); + await expect(select).toHaveValue("a"); + await expect(select).toHaveAttribute("aria-invalid", "true"); + await expect(select).toHaveAttribute("aria-describedby", "letter-error"); + await select.selectOption("b"); + await expect(select).toHaveValue("b"); + await expect(page.getByRole("status", { name: "Events" })).toHaveText("b"); + await expect(page.getByRole("combobox", { name: "Locked" })).toBeDisabled(); +}); + +test("FilterChip exposes its selected state through aria-pressed", async ({ + page, +}) => { + await page.goto("/filter-chip"); + const chip = page.getByRole("button", { name: "Live" }); + await expect(chip).toHaveAttribute("aria-pressed", "false"); + await chip.click(); + await expect(chip).toHaveAttribute("aria-pressed", "true"); +}); + +test("DictationStatusLine announces a denial with default copy", async ({ + page, +}) => { + await page.goto("/dictation-denied"); + await expect(page.getByRole("alert")).toHaveText( + /Microphone access was blocked\./, + ); +}); + +test.describe("AuthLayout panel slot", () => { + test.use({ viewport: { width: 1280, height: 800 } }); + + test("the decorative panel is empty when panel is omitted", async ({ + page, + }) => { + await page.goto("/auth-layout"); + await expect(page.locator("[aria-hidden=true]").last()).toBeEmpty(); + }); + + test("the panel renders the node it is given", async ({ page }) => { + await page.goto("/auth-layout-panel"); + await expect(page.getByTestId("custom-panel")).toBeVisible(); + }); +}); diff --git a/e2e/fixtures.tsx b/e2e/fixtures.tsx index b20b7e01..6f8ae994 100644 --- a/e2e/fixtures.tsx +++ b/e2e/fixtures.tsx @@ -3,10 +3,16 @@ import type { ReactNode } from "react"; import { ApprovalCard, + AuthLayout, Button, + ChatThread, + DictationStatusLine, + FilterChip, + Select, ThemeProvider, ThemeToggle, type ApprovalRequest, + type ChatMessage, } from "@corbits/react-ui"; import { CommandPalette, @@ -79,6 +85,67 @@ function Palette() { ); } +const CHAT: ChatMessage[] = [ + { + id: "m1", + role: "user", + createdAt: "2026-01-01T09:00:00Z", + parts: [{ type: "text", text: "What changed today?" }], + }, + { + id: "m2", + role: "agent", + createdAt: "2026-01-01T09:00:04Z", + parts: [ + { type: "reasoning", text: "Check the commit log first." }, + { + type: "tool", + toolCallId: "t1", + toolName: "git__log", + label: "Read today's commits", + state: "done", + output: "3 commits", + }, + { type: "text", text: "Three commits landed today." }, + ], + }, +]; + +function SelectField() { + const [value, setValue] = useState("a"); + const { log, record } = useEventLog(); + return ( + <> + + + {log} + + ); +} + +function Chip() { + const [selected, setSelected] = useState(false); + return ( + setSelected(!selected)}> + Live + + ); +} + const SWATCH_TOKENS = [ "background", "foreground", @@ -108,6 +175,20 @@ export const scenarios: Record ReactNode> = { "approval-card": () => , "approval-card-busy": () => , "command-palette": () => , + "chat-thread": () => ( +
+ +
+ ), + select: () => , + "filter-chip": () => , + "dictation-denied": () => , + "auth-layout": () => Form, + "auth-layout-panel": () => ( + custom}> + Form + + ), theme: () => , "theme-toggle": () => (