import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Checkbox, CheckboxGroup, CheckboxLabel, CheckboxWithLabel } from '../checkbox'
describe('Checkbox Components', () => {
describe('Checkbox Component', () => {
describe('Basic Rendering', () => {
it('renders correctly with default props', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toBeInTheDocument()
expect(checkbox).toHaveAttribute('role', 'checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
})
it('applies custom className', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('custom-checkbox')
})
it('forwards ref correctly', () => {
const ref = React.createRef()
render()
expect(ref.current).toBeInstanceOf(HTMLButtonElement)
})
it('maintains displayName', () => {
expect(Checkbox.displayName).toBe('Checkbox')
})
})
describe('Variants', () => {
it('renders default variant correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('border-border')
expect(checkbox).toHaveClass('bg-background')
})
it('renders outline variant correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('border-border')
expect(checkbox).toHaveClass('bg-transparent')
})
it('renders muted variant correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('border-border')
expect(checkbox).toHaveClass('bg-accent')
})
it('renders ghost variant correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('border-transparent')
expect(checkbox).toHaveClass('bg-transparent')
})
it('uses default variant as default', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('border-border')
expect(checkbox).toHaveClass('bg-background')
})
})
describe('Sizes', () => {
it('renders sm size correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('h-3.5', 'w-3.5')
})
it('renders default size correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('h-4', 'w-4')
})
it('renders md size correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('h-5', 'w-5')
})
it('renders lg size correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('h-6', 'w-6')
})
it('uses default size as default', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('h-4', 'w-4')
})
})
describe('Radius', () => {
it('renders none radius correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('rounded-none')
})
it('renders sm radius correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('rounded-sm')
})
it('renders default radius correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('rounded-sm')
})
it('renders md radius correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('rounded-md')
})
it('renders full radius correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('rounded-full')
})
})
describe('Animation', () => {
it('renders none animation correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).not.toHaveClass('transition-all')
})
it('renders subtle animation correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('transition-all')
})
it('renders default animation correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('transition-all')
})
it('renders bounce animation correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('transition-all')
})
})
describe('States', () => {
it('renders unchecked state correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
expect(checkbox).toHaveAttribute('data-state', 'unchecked')
})
it('renders checked state correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'true')
expect(checkbox).toHaveAttribute('data-state', 'checked')
})
it('renders disabled state correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toBeDisabled()
expect(checkbox).toHaveClass('disabled:cursor-not-allowed')
expect(checkbox).toHaveClass('disabled:opacity-50')
})
it('handles controlled state', () => {
const handleChange = jest.fn()
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'true')
})
})
describe('Indeterminate State', () => {
it('renders indeterminate state correctly', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
expect(checkbox).toHaveAttribute('data-state', 'unchecked')
})
it('shows minus icon when indeterminate and checked', () => {
render()
const checkbox = screen.getByTestId('checkbox')
// Even with checked=true, indeterminate overrides to false
expect(checkbox).toHaveAttribute('aria-checked', 'false')
expect(checkbox).toHaveAttribute('data-state', 'unchecked')
})
it('overrides checked state when indeterminate', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
})
it('updates indeterminate state via prop', () => {
const { rerender } = render()
let checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
rerender()
checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
})
})
describe('Custom Icon', () => {
it('renders custom icon when provided', () => {
render(★} checked data-testid="checkbox" />)
expect(screen.getByTestId('custom-icon')).toBeInTheDocument()
})
it('uses default check icon when no custom icon', () => {
render()
const checkbox = screen.getByTestId('checkbox')
const checkIcon = checkbox.querySelector('svg')
expect(checkIcon).toBeInTheDocument()
})
it('prioritizes indeterminate over custom icon', () => {
render(
★}
data-testid="checkbox"
/>
)
expect(screen.queryByTestId('custom-icon')).not.toBeInTheDocument()
const checkbox = screen.getByTestId('checkbox')
// Check that custom icon is not rendered
expect(screen.queryByTestId('custom-icon')).not.toBeInTheDocument()
// Indeterminate state should show as unchecked
expect(checkbox).toHaveAttribute('aria-checked', 'false')
})
})
describe('Interactions', () => {
it('handles click events', () => {
const handleChange = jest.fn()
render()
const checkbox = screen.getByTestId('checkbox')
fireEvent.click(checkbox)
expect(handleChange).toHaveBeenCalledWith(true)
})
it('does not trigger events when disabled', () => {
const handleChange = jest.fn()
render()
const checkbox = screen.getByTestId('checkbox')
fireEvent.click(checkbox)
expect(handleChange).not.toHaveBeenCalled()
})
it('is focusable for keyboard navigation', () => {
render()
const checkbox = screen.getByTestId('checkbox')
checkbox.focus()
expect(checkbox).toHaveFocus()
})
})
describe('Accessibility', () => {
it('has correct ARIA attributes', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('role', 'checkbox')
expect(checkbox).toHaveAttribute('aria-checked', 'false')
})
it('supports custom ARIA attributes', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveAttribute('aria-label', 'Accept terms')
})
it('has focus-visible styles', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('focus-visible:outline-none')
expect(checkbox).toHaveClass('focus-visible:ring-2')
})
})
})
describe('CheckboxGroup Component', () => {
it('renders correctly with default props', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toBeInTheDocument()
expect(group).toHaveAttribute('role', 'group')
expect(group).toHaveClass('flex', 'flex-col')
})
it('renders horizontal orientation correctly', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveClass('flex-row', 'flex-wrap')
})
it('renders vertical orientation correctly', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveClass('flex-col')
})
it('applies custom spacing', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveStyle({ gap: '2rem' })
})
it('applies custom spacing as number', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveStyle({ gap: '24px' })
})
it('forwards ref correctly', () => {
const ref = React.createRef()
render(
)
expect(ref.current).toBeInstanceOf(HTMLDivElement)
})
it('maintains displayName', () => {
expect(CheckboxGroup.displayName).toBe('CheckboxGroup')
})
})
describe('CheckboxLabel Component', () => {
it('renders correctly with default props', () => {
render(Label text)
const label = screen.getByTestId('checkbox-label')
expect(label).toBeInTheDocument()
expect(label.tagName).toBe('LABEL')
expect(label).toHaveTextContent('Label text')
})
it('applies htmlFor attribute', () => {
render(Label)
const label = screen.getByTestId('checkbox-label')
expect(label).toHaveAttribute('for', 'checkbox-1')
})
it('renders start position correctly', () => {
render(Label)
const label = screen.getByTestId('checkbox-label')
expect(label).toHaveClass('mr-2')
})
it('renders end position correctly', () => {
render(Label)
const label = screen.getByTestId('checkbox-label')
expect(label).toHaveClass('ml-2')
})
it('renders disabled state correctly', () => {
render(Label)
const label = screen.getByTestId('checkbox-label')
expect(label).toHaveClass('cursor-not-allowed', 'opacity-70')
})
it('forwards ref correctly', () => {
const ref = React.createRef()
render(Label)
expect(ref.current).toBeInstanceOf(HTMLLabelElement)
})
it('maintains displayName', () => {
expect(CheckboxLabel.displayName).toBe('CheckboxLabel')
})
})
describe('CheckboxWithLabel Component', () => {
it('renders correctly with default props', () => {
render()
const container = screen.getByTestId('checkbox-with-label').parentElement
expect(container).toHaveClass('flex', 'items-center')
expect(screen.getByText('Accept terms')).toBeInTheDocument()
})
it('renders label at start position', () => {
render()
const container = screen.getByTestId('checkbox-with-label').parentElement
const label = container?.querySelector('label')
const checkbox = container?.querySelector('[role="checkbox"]')
expect(label).toBeInTheDocument()
expect(checkbox).toBeInTheDocument()
// Label should come before checkbox in DOM
const elements = Array.from(container?.children || [])
const labelIndex = elements.indexOf(label!)
const checkboxIndex = elements.indexOf(checkbox!)
expect(labelIndex).toBeLessThan(checkboxIndex)
})
it('renders label at end position', () => {
render()
const container = screen.getByTestId('checkbox-with-label').parentElement
const label = container?.querySelector('label')
const checkbox = container?.querySelector('[role="checkbox"]')
expect(label).toBeInTheDocument()
expect(checkbox).toBeInTheDocument()
// Checkbox should come before label in DOM
const elements = Array.from(container?.children || [])
const labelIndex = elements.indexOf(label!)
const checkboxIndex = elements.indexOf(checkbox!)
expect(checkboxIndex).toBeLessThan(labelIndex)
})
it('applies custom label className', () => {
render(
)
const label = screen.getByText('Accept terms')
expect(label).toHaveClass('custom-label')
})
it('generates unique id when not provided', () => {
render()
const checkbox = screen.getByTestId('checkbox-with-label')
const id = checkbox.getAttribute('id')
expect(id).toMatch(/^checkbox-[a-z0-9]+$/)
})
it('uses provided id', () => {
render()
const checkbox = screen.getByTestId('checkbox-with-label')
expect(checkbox).toHaveAttribute('id', 'custom-id')
const label = screen.getByText('Accept terms')
expect(label).toHaveAttribute('for', 'custom-id')
})
it('forwards ref correctly', () => {
const ref = React.createRef()
render()
expect(ref.current).toBeInstanceOf(HTMLButtonElement)
})
it('passes checkbox props correctly', () => {
render(
)
const checkbox = screen.getByTestId('checkbox-with-label')
expect(checkbox).toHaveAttribute('aria-checked', 'true')
expect(checkbox).toBeDisabled()
expect(checkbox).toHaveClass('bg-transparent')
})
it('maintains displayName', () => {
expect(CheckboxWithLabel.displayName).toBe('CheckboxWithLabel')
})
})
describe('Complex Combinations', () => {
it('renders checkbox with all props correctly', () => {
const handleChange = jest.fn()
render(
)
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toHaveClass('bg-transparent')
expect(checkbox).toHaveClass('h-6', 'w-6')
expect(checkbox).toHaveClass('rounded-full')
expect(checkbox).toHaveClass('transition-all')
expect(checkbox).toHaveClass('custom-class')
expect(checkbox).toHaveAttribute('aria-checked', 'true')
})
it('renders checkbox group with multiple checkboxes', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveClass('flex-row')
expect(group).toHaveStyle({ gap: '1.5rem' })
expect(screen.getByText('Option 1')).toBeInTheDocument()
expect(screen.getByText('Option 2')).toBeInTheDocument()
expect(screen.getByText('Option 3')).toBeInTheDocument()
})
})
describe('Edge Cases', () => {
it('passes through HTML attributes', () => {
render()
const checkbox = screen.getByTestId('checkbox-test')
expect(checkbox).toHaveAttribute('id', 'checkbox-1')
})
it('handles null icon gracefully', () => {
render()
const checkbox = screen.getByTestId('checkbox')
expect(checkbox).toBeInTheDocument()
})
it('handles empty label in CheckboxWithLabel', () => {
render()
const container = screen.getByTestId('checkbox-with-label').parentElement
const label = container?.querySelector('label')
expect(label).toBeInTheDocument()
expect(label).toHaveTextContent('')
})
it('handles zero spacing in CheckboxGroup', () => {
render(
)
const group = screen.getByTestId('checkbox-group')
expect(group).toHaveStyle({ gap: 0 })
})
})
})