import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { axe } from 'vitest-axe'
import { Field as BaseField } from '@base-ui/react/field'
import { Switch } from './switch'
describe('Switch', () => {
it('renders with switch role', () => {
render()
expect(screen.getByRole('switch', { name: 'Notifications' })).toBeInTheDocument()
})
it('reflects defaultChecked', () => {
render()
expect(screen.getByRole('switch')).toBeChecked()
})
it('bridges aria-invalid to data-invalid for error styling', () => {
render()
const sw = screen.getByRole('switch')
expect(sw).toHaveAttribute('aria-invalid', 'true')
expect(sw).toHaveAttribute('data-invalid')
})
it('does not set data-invalid when valid', () => {
render()
expect(screen.getByRole('switch')).not.toHaveAttribute('data-invalid')
})
it('keeps Field-context data-invalid when no standalone aria-invalid is set', () => {
render(
,
)
expect(screen.getByRole('switch')).toHaveAttribute('data-invalid')
})
it('toggles when clicked', async () => {
const user = userEvent.setup()
const onCheckedChange = vi.fn()
render()
const sw = screen.getByRole('switch')
expect(sw).not.toBeChecked()
await user.click(sw)
expect(onCheckedChange).toHaveBeenCalledWith(true, expect.anything())
expect(sw).toBeChecked()
})
it('toggles via keyboard (Space)', async () => {
const user = userEvent.setup()
const onCheckedChange = vi.fn()
render()
const sw = screen.getByRole('switch')
sw.focus()
await user.keyboard(' ')
expect(onCheckedChange).toHaveBeenCalledOnce()
expect(sw).toBeChecked()
})
it('does not toggle when disabled', async () => {
const user = userEvent.setup()
const onCheckedChange = vi.fn()
render()
await user.click(screen.getByRole('switch'))
expect(onCheckedChange).not.toHaveBeenCalled()
})
it('honors controlled checked prop', async () => {
const user = userEvent.setup()
const onCheckedChange = vi.fn()
render()
const sw = screen.getByRole('switch')
await user.click(sw)
expect(onCheckedChange).toHaveBeenCalledWith(true, expect.anything())
// Stays unchecked because parent never updates
expect(sw).not.toBeChecked()
})
it('applies size styles to the root', () => {
render()
expect(screen.getByRole('switch').className).toContain('h-6')
})
it('forwards className to the root', () => {
render()
expect(screen.getByRole('switch').className).toContain('custom-class')
})
it('has no accessibility violations', async () => {
const { container } = render()
expect(await axe(container)).toHaveNoViolations()
})
})