Files
web/packages/design-system/lib/components/ChipButton/ChipButton.a11y.test.tsx

145 lines
4.4 KiB
TypeScript

/* eslint-disable formatjs/no-literal-string-in-jsx */
import { describe, expect, it, vi, afterEach } from "vitest"
import { render, screen, cleanup } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { ChipButton } from "./ChipButton"
afterEach(() => {
cleanup()
})
describe("ChipButton accessibility", () => {
describe("semantic HTML", () => {
it("uses proper button element", () => {
render(<ChipButton>Button</ChipButton>)
const button = screen.getByRole("button")
expect(button.tagName).toBe("BUTTON")
})
it("has accessible button text", () => {
render(<ChipButton>Filter by price</ChipButton>)
expect(
screen.getByRole("button", { name: "Filter by price" })
).toBeTruthy()
})
it("button type defaults to button (not submit)", () => {
render(<ChipButton>Not Submit</ChipButton>)
const button = screen.getByRole("button")
// React Aria Components Button defaults to type="button"
expect(button.getAttribute("type")).toBe("button")
})
})
describe("disabled state", () => {
it("disabled button has disabled attribute", () => {
render(<ChipButton isDisabled>Disabled</ChipButton>)
const button = screen.getByRole("button", { name: "Disabled" })
expect(button).toHaveProperty("disabled", true)
})
it("disabled button is not focusable", async () => {
const user = userEvent.setup()
render(
<div>
<ChipButton isDisabled>Disabled</ChipButton>
<ChipButton>Enabled</ChipButton>
</div>
)
await user.tab()
// Focus should skip disabled button and go to enabled one
expect(document.activeElement).toBe(
screen.getByRole("button", { name: "Enabled" })
)
})
})
describe("keyboard navigation", () => {
it("button is keyboard accessible", async () => {
const user = userEvent.setup()
render(<ChipButton>Accessible Button</ChipButton>)
const button = screen.getByRole("button")
await user.tab()
expect(document.activeElement).toBe(button)
})
it("multiple buttons maintain logical focus order", async () => {
const user = userEvent.setup()
render(
<div>
<ChipButton>First</ChipButton>
<ChipButton>Second</ChipButton>
<ChipButton>Third</ChipButton>
</div>
)
const firstButton = screen.getByRole("button", { name: "First" })
const secondButton = screen.getByRole("button", { name: "Second" })
const thirdButton = screen.getByRole("button", { name: "Third" })
await user.tab()
expect(document.activeElement).toBe(firstButton)
await user.tab()
expect(document.activeElement).toBe(secondButton)
await user.tab()
expect(document.activeElement).toBe(thirdButton)
})
it("Enter key activates button", async () => {
const onPress = vi.fn()
const user = userEvent.setup()
render(<ChipButton onPress={onPress}>Activate</ChipButton>)
await user.tab()
await user.keyboard("{Enter}")
expect(onPress).toHaveBeenCalledTimes(1)
})
it("Space key activates button", async () => {
const onPress = vi.fn()
const user = userEvent.setup()
render(<ChipButton onPress={onPress}>Activate</ChipButton>)
await user.tab()
await user.keyboard(" ")
expect(onPress).toHaveBeenCalledTimes(1)
})
})
describe("screen reader support", () => {
it("button has accessible name from content", () => {
render(<ChipButton>Descriptive Button Text</ChipButton>)
const button = screen.getByRole("button")
expect(button.textContent?.trim().length).toBeGreaterThan(0)
})
it("button with icon and text has accessible name", () => {
render(
<ChipButton>
<span aria-hidden="true"></span>
Selected Filter
</ChipButton>
)
const button = screen.getByRole("button")
expect(button.textContent).toContain("Selected Filter")
})
})
describe("selected state accessibility", () => {
it("selected state is indicated via aria-pressed", () => {
render(
<ChipButton selected aria-pressed={true}>
Selected
</ChipButton>
)
const button = screen.getByRole("button", { name: "Selected" })
expect(button.getAttribute("aria-pressed")).toBe("true")
})
})
})