Skip to content

Commit a3fe2c9

Browse files
committed
Align tool action group semantics (#263)
1 parent b570118 commit a3fe2c9

7 files changed

Lines changed: 20 additions & 16 deletions

File tree

docs/accessibility/bf-036-bf-038-keyboard-visual-qa.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Scope: representative navigation, discovery, tool action, upload, and output sur
3131
## Automated Coverage
3232

3333
- `tests/guards/focus-visible-guard.test.ts` prevents removing visible focus rings from app/component surfaces.
34-
- `tests/guards/shared-a11y-surfaces.test.ts` guards All Tools dialog focus trap/restore behavior, shared action toolbar names, and Pipeline Builder run status.
34+
- `tests/guards/shared-a11y-surfaces.test.ts` guards All Tools dialog focus trap/restore behavior, shared action-group names, and Pipeline Builder run status.
3535
- `tests/guards/a11y-mobile-baseline.test.ts` guards skip-link behavior, live toast feedback, mobile touch target baseline, and core mobile routes.
3636
- `tests/guards/all-tools-performance-a11y-guard.test.ts` guards disabled explanations plus long-text/mobile containment on All Tools.
3737
- `tests/guards/theme-contrast-token-guard.test.ts` checks light/dark foreground and muted text contrast tokens against WCAG AA.

docs/accessibility/bf-037-screen-reader-qa.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Scope: representative dynamic tool interfaces for BF-037.
1717
- All Tools: search exposes the result-count status as its accessible description; filtered result counts announce through a polite live region; the mobile filter drawer has a dialog name, description, trapped focus, Escape close, and focus restoration.
1818
- Pipeline Builder: final output is label-associated; run-log status announces through a polite live region; the run-log table has an accessible name; run failures expose an alert.
1919
- External-request media flow: confirmation panel identifies the destination/purpose/data sent; preview/download actions remain disabled with accessible descriptions until explicit confirmation.
20-
- Shared tool actions: action groups expose a toolbar label; disabled actions use visible button names plus programmatic descriptions, without exposing tool payloads.
20+
- Shared tool actions: action groups expose a named group container; disabled actions use visible button names plus programmatic descriptions, without exposing tool payloads.
2121
- Copy, download, export, save, and share feedback: toast title/description text is mirrored into the shared polite status region so success and failure states are announced consistently.
2222

2323
## Automated Coverage

src/features/tool-shell/tool-action-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export function ToolActionBar({
273273
return (
274274
<div
275275
className={joinClasses("flex w-full flex-wrap items-center gap-2 sm:w-auto", className)}
276-
role="toolbar"
276+
role="group"
277277
aria-label={t.common.tool_actions}
278278
>
279279
{primaryActions.map((action) => {

tests/component/http-request-builder-page.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe("HttpRequestBuilderPage", () => {
126126

127127
expect(screen.getByRole("group", { name: "Add to favorites / Remove from favorites" })).toHaveAttribute("data-tool-global-actions")
128128
expect(screen.getByRole("button", { name: "Add to favorites" })).toBeInTheDocument()
129-
expect(screen.getByRole("toolbar", { name: "Tool actions" })).toBeInTheDocument()
129+
expect(screen.getByRole("group", { name: "Tool actions" })).toBeInTheDocument()
130130

131131
fireEvent.click(screen.getByRole("button", { name: "Add Header" }))
132132
fireEvent.change(screen.getAllByRole("textbox", { name: "Header name" })[1], {

tests/component/phase3-pipeline-builder-page.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe("phase 3 pipeline builder page", () => {
104104

105105
expect(screen.getByRole("heading", { name: "Pipeline Builder" })).toBeInTheDocument()
106106
expect(screen.getByRole("heading", { name: "Build a recipe in three moves" })).toBeInTheDocument()
107-
expect(screen.getByRole("toolbar", { name: "Tool actions" })).toBeInTheDocument()
107+
expect(screen.getByRole("group", { name: "Tool actions" })).toBeInTheDocument()
108108
expect(screen.getByLabelText("Initial input")).toBeInTheDocument()
109109
expect(screen.getByLabelText("Final output")).toBeInTheDocument()
110110
expect(screen.getAllByText("Steps").length).toBeGreaterThan(0)
@@ -310,13 +310,13 @@ describe("phase 3 pipeline builder page", () => {
310310
expect(screen.getByRole("button", { name: "Export structure only" })).toBeInTheDocument()
311311
}, 10_000)
312312

313-
it("restores privacy dialog focus for toolbar and inspector actions", async () => {
313+
it("restores privacy dialog focus for action-bar and inspector actions", async () => {
314314
renderWithEnglish(<PipelineBuilderPage />)
315315

316-
const toolbar = screen.getByRole("toolbar", { name: "Tool actions" })
317-
const toolbarExport = within(toolbar).getByRole("button", { name: "Export JSON" })
318-
toolbarExport.focus()
319-
fireEvent.click(toolbarExport)
316+
const actionGroup = screen.getByRole("group", { name: "Tool actions" })
317+
const actionGroupExport = within(actionGroup).getByRole("button", { name: "Export JSON" })
318+
actionGroupExport.focus()
319+
fireEvent.click(actionGroupExport)
320320

321321
const firstDialog = await screen.findByRole("dialog", { name: "Privacy preview" })
322322
expect(firstDialog).toHaveAccessibleDescription("Export a structure-only JSON recipe file.")
@@ -339,14 +339,14 @@ describe("phase 3 pipeline builder page", () => {
339339
fireEvent.keyDown(document, { key: "Escape" })
340340

341341
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Privacy preview" })).not.toBeInTheDocument())
342-
await waitFor(() => expect(toolbarExport).toHaveFocus())
342+
await waitFor(() => expect(actionGroupExport).toHaveFocus())
343343

344-
fireEvent.click(toolbarExport)
344+
fireEvent.click(actionGroupExport)
345345
const cancelDialog = await screen.findByRole("dialog", { name: "Privacy preview" })
346346
fireEvent.click(within(cancelDialog).getByRole("button", { name: "Cancel" }))
347347

348348
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Privacy preview" })).not.toBeInTheDocument())
349-
await waitFor(() => expect(toolbarExport).toHaveFocus())
349+
await waitFor(() => expect(actionGroupExport).toHaveFocus())
350350

351351
const inspector = screen.getByRole("complementary", { name: "Recipe inspector" })
352352
const inspectorExport = within(inspector).getByRole("button", { name: "Export JSON" })

tests/component/tool-action-bar.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ describe("ToolActionBar", () => {
4545

4646
render(<ToolActionBar actions={actions} />)
4747

48-
const toolbar = screen.getByRole("toolbar", { name: "Tool actions" })
49-
const labels = within(toolbar).getAllByRole("button").map((button) => button.textContent)
48+
const actionGroup = screen.getByRole("group", { name: "Tool actions" })
49+
const actionButtons = within(actionGroup).getAllByRole("button")
50+
const labels = actionButtons.map((button) => button.textContent)
5051

52+
expect(screen.queryByRole("toolbar", { name: "Tool actions" })).not.toBeInTheDocument()
53+
expect(actionButtons.filter((button) => !button.hasAttribute("disabled")).every((button) => button.tabIndex === 0)).toBe(true)
5154
expect(labels).toEqual(["Sample", "Clear", "Format", "Copy", "Download"])
5255
expect(screen.getByRole("button", { name: "Download", description: "Nothing to download." })).toHaveAttribute("title", "Download: Nothing to download.")
5356
})

tests/guards/shared-a11y-surfaces.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ describe("shared accessibility surfaces", () => {
2323
it("keeps the shared tool action bar named and described", () => {
2424
const source = readSource("src/features/tool-shell/tool-action-bar.tsx")
2525

26-
expect(source).toContain("role=\"toolbar\"")
26+
expect(source).toContain("role=\"group\"")
27+
expect(source).not.toContain("role=\"toolbar\"")
2728
expect(source).toContain("aria-label={t.common.tool_actions}")
2829
expect(source).toContain("aria-describedby={disabledDescriptionId}")
2930
expect(source).toContain("aria-describedby={handoffDisabledDescriptionId}")

0 commit comments

Comments
 (0)