// @vitest-environment jsdom /** * What the Checkbox page claims, pinned: the keys, the click that never * reaches the row, and the second rendering — no `onChange`, a picture of * the state that is not a control. The last one is what clears axe's * `nested-interactive` on the "inside a row" story (stage 1 of the * migration). */ import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Checkbox } from './Checkbox' afterEach(cleanup) describe('Checkbox', () => { it('is a checkbox that Space toggles, and Enter does not', async () => { const user = userEvent.setup() const onChange = vi.fn() render() const box = screen.getByRole('checkbox', { name: 'Done' }) expect(box.getAttribute('aria-checked')).toBe('false') await user.tab() expect(document.activeElement).toBe(box) await user.keyboard('{Enter}') expect(onChange).not.toHaveBeenCalled() await user.keyboard(' ') expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith(true) }) it('toggles on click, and the click never reaches the row', async () => { const user = userEvent.setup() const onChange = vi.fn() const onRow = vi.fn() render(
, ) await user.click(screen.getByRole('checkbox', { name: 'Done' })) expect(onChange).toHaveBeenCalledWith(false) expect(onRow).not.toHaveBeenCalled() }) it('does nothing when disabled', async () => { const user = userEvent.setup() const onChange = vi.fn() render() const box = screen.getByRole('checkbox', { name: 'Done' }) expect(box.getAttribute('aria-disabled')).toBe('true') await user.click(box) expect(onChange).not.toHaveBeenCalled() }) it('with no onChange is a picture of the state, not a control: the row gets the click', async () => { const user = userEvent.setup() const onRow = vi.fn() const { container } = render( , ) expect(screen.queryByRole('checkbox')).toBeNull() const square = container.querySelector('[aria-hidden="true"]') expect(square).not.toBeNull() expect(container.querySelectorAll('input, [tabindex], [role="checkbox"]')).toHaveLength(0) await user.click(square as HTMLElement) expect(onRow).toHaveBeenCalledTimes(1) }) describe('with label', () => { it('is named by its words, and clicking the words toggles it', async () => { const user = userEvent.setup() const onChange = vi.fn() render() const box = screen.getByRole('checkbox', { name: 'Compare every time' }) await user.click(screen.getByText('Compare every time')) expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith(true) expect(box.getAttribute('aria-checked')).toBe('false') }) it('toggles once, not twice, when the box itself is clicked inside its words', async () => { const user = userEvent.setup() const onChange = vi.fn() render() await user.click(screen.getByRole('checkbox', { name: 'Label' })) expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith(false) }) it('keeps Space on the box', async () => { const user = userEvent.setup() const onChange = vi.fn() render() await user.tab() expect(document.activeElement).toBe(screen.getByRole('checkbox', { name: 'Label' })) await user.keyboard(' ') expect(onChange).toHaveBeenCalledWith(true) }) it('does nothing from its words when disabled, and shows no pointer over them', async () => { const user = userEvent.setup() const onChange = vi.fn() const { container } = render() await user.click(screen.getByText('Label')) expect(onChange).not.toHaveBeenCalled() expect(container.querySelector('label')?.className).not.toContain('cursor-pointer') }) it('keeps className on the box', () => { const { container } = render( {}} label="Label" className="mt-1" />) expect(container.querySelector('label')?.className).not.toContain('mt-1') expect(screen.getByRole('checkbox', { name: 'Label' }).className).toContain('mt-1') }) it('with no onChange draws the words beside the picture, and is still not a control', () => { const { container } = render() expect(screen.queryByRole('checkbox')).toBeNull() expect(container.querySelector('label')).toBeNull() expect(screen.getByText('Label')).not.toBeNull() }) }) describe('with row', () => { const picture = it('is named by its words, and a click anywhere on the row toggles it once', async () => { const user = userEvent.setup() const onChange = vi.fn() render() const box = screen.getByRole('checkbox', { name: 'Item one' }) await user.click(screen.getByText('Item one')) await user.click(screen.getByTestId('picture')) await user.click(box) expect(onChange).toHaveBeenCalledTimes(3) expect(onChange).toHaveBeenNthCalledWith(1, true) }) it('draws the picture, the words, then the box, in one row', () => { const { container } = render( {}} label="Item one" />) const children = [...(container.querySelector('label')?.children ?? [])] expect(children[0]?.getAttribute('data-testid')).toBe('picture') expect(children[1]?.textContent).toBe('Item one') expect(children[2]?.getAttribute('role')).toBe('checkbox') expect(container.querySelector('label')?.className).toContain('h-10') }) it('fills while checked, and on hover', () => { const { container, rerender } = render( {}} label="Item one" />) const row = () => container.querySelector('label')?.className ?? '' expect(row()).not.toContain('bg-bg-selected') expect(row()).toContain('hover:bg-bg-hover') rerender( {}} label="Item one" />) expect(row()).toContain('bg-bg-selected') }) it('does nothing when disabled, and neither fills under the pointer nor points', async () => { const user = userEvent.setup() const onChange = vi.fn() const { container } = render() await user.click(screen.getByText('Item one')) expect(onChange).not.toHaveBeenCalled() const className = container.querySelector('label')?.className ?? '' expect(className).not.toContain('cursor-pointer') expect(className).not.toContain('hover:bg-bg-hover') }) it('keeps Space on the box, and adds no Tab stop of its own', async () => { const user = userEvent.setup() const onChange = vi.fn() render() await user.tab() expect(document.activeElement).toBe(screen.getByRole('checkbox', { name: 'Item one' })) await user.keyboard(' ') expect(onChange).toHaveBeenCalledWith(true) await user.tab() expect(document.activeElement).toBe(document.body) }) it('is ignored with no onChange', () => { const { container } = render() expect(container.querySelector('label')).toBeNull() expect(screen.queryByRole('checkbox')).toBeNull() }) }) })