Move LinkChips and add unit + a11y tests for chips components
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/* 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")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,148 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
import { describe, expect, it, vi, afterEach } from "vitest"
|
||||
import { render, screen, cleanup, fireEvent } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
|
||||
import { ChipButton } from "./ChipButton"
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("ChipButton", () => {
|
||||
describe("rendering", () => {
|
||||
it("renders as a button element", () => {
|
||||
render(<ChipButton>Click me</ChipButton>)
|
||||
const button = screen.getByRole("button", { name: "Click me" })
|
||||
expect(button).toBeTruthy()
|
||||
expect(button.tagName).toBe("BUTTON")
|
||||
})
|
||||
|
||||
it("renders children content", () => {
|
||||
render(<ChipButton>Button Content</ChipButton>)
|
||||
expect(screen.getByText("Button Content")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("renders with multiple children", () => {
|
||||
render(
|
||||
<ChipButton>
|
||||
<span>Icon</span>
|
||||
Label
|
||||
</ChipButton>
|
||||
)
|
||||
expect(screen.getByText("Icon")).toBeTruthy()
|
||||
expect(screen.getByText("Label")).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("variants", () => {
|
||||
it("renders Default variant", () => {
|
||||
render(<ChipButton variant="Default">Default</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Default" })).toBeTruthy()
|
||||
})
|
||||
|
||||
it("renders Outlined variant", () => {
|
||||
render(<ChipButton variant="Outlined">Outlined</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Outlined" })).toBeTruthy()
|
||||
})
|
||||
|
||||
it("renders FilterRounded variant", () => {
|
||||
render(<ChipButton variant="FilterRounded">Filter</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Filter" })).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("selected state", () => {
|
||||
it("renders with selected=false by default", () => {
|
||||
render(<ChipButton>Not Selected</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Not Selected" })).toBeTruthy()
|
||||
})
|
||||
|
||||
it("renders with selected=true", () => {
|
||||
render(<ChipButton selected>Selected</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Selected" })).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("sizes", () => {
|
||||
it("renders Medium size", () => {
|
||||
render(<ChipButton size="Medium">Medium</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Medium" })).toBeTruthy()
|
||||
})
|
||||
|
||||
it("renders Large size (default)", () => {
|
||||
render(<ChipButton size="Large">Large</ChipButton>)
|
||||
expect(screen.getByRole("button", { name: "Large" })).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("props", () => {
|
||||
it("applies custom className", () => {
|
||||
render(<ChipButton className="custom-class">Button</ChipButton>)
|
||||
const button = screen.getByRole("button", { name: "Button" })
|
||||
expect(button.className).toContain("custom-class")
|
||||
})
|
||||
|
||||
it("can be disabled", () => {
|
||||
render(<ChipButton isDisabled>Disabled</ChipButton>)
|
||||
const button = screen.getByRole("button", { name: "Disabled" })
|
||||
expect(button).toHaveProperty("disabled", true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("interactions", () => {
|
||||
it("calls onPress when clicked", () => {
|
||||
const onPress = vi.fn()
|
||||
render(<ChipButton onPress={onPress}>Click me</ChipButton>)
|
||||
|
||||
// React Aria Components Button uses onPress which listens to click events
|
||||
fireEvent.click(screen.getByRole("button", { name: "Click me" }))
|
||||
expect(onPress).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("does not call onPress when disabled", () => {
|
||||
const onPress = vi.fn()
|
||||
render(
|
||||
<ChipButton isDisabled onPress={onPress}>
|
||||
Disabled
|
||||
</ChipButton>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Disabled" }))
|
||||
expect(onPress).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("keyboard navigation", () => {
|
||||
it("is focusable via keyboard", async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<ChipButton>Focusable</ChipButton>)
|
||||
|
||||
const button = screen.getByRole("button", { name: "Focusable" })
|
||||
await user.tab()
|
||||
expect(document.activeElement).toBe(button)
|
||||
})
|
||||
|
||||
it("can be activated with Enter key", async () => {
|
||||
const onPress = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
render(<ChipButton onPress={onPress}>Press Enter</ChipButton>)
|
||||
|
||||
screen.getByRole("button", { name: "Press Enter" })
|
||||
await user.tab()
|
||||
await user.keyboard("{Enter}")
|
||||
expect(onPress).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("can be activated with Space key", async () => {
|
||||
const onPress = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
render(<ChipButton onPress={onPress}>Press Space</ChipButton>)
|
||||
|
||||
screen.getByRole("button", { name: "Press Space" })
|
||||
await user.tab()
|
||||
await user.keyboard(" ")
|
||||
expect(onPress).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user