import React from 'react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, it, expect, vi } from 'vitest' import Select, { Option } from './select' describe('Select', () => { describe('rendering', () => { it('renders select element', () => { render( ) expect(screen.getByRole('combobox')).toBeInTheDocument() }) it('renders with provided id and name', () => { render( ) const select = screen.getByRole('combobox') expect(select).toHaveAttribute('id', 'country') expect(select).toHaveAttribute('name', 'country') }) it('renders children options', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() expect(screen.getByText('United Kingdom')).toBeInTheDocument() expect(screen.getByText('Canada')).toBeInTheDocument() }) it('renders empty option when no children provided', () => { render( ) }).not.toThrow() expect(screen.getByRole('combobox')).toBeInTheDocument() }) it('applies custom styles', () => { render( ) expect(screen.getByRole('combobox')).toHaveStyle({ fontSize: '1.5rem' }) }) }) describe('disabled state', () => { it('applies disabled attribute when disabled', () => { render( ) const select = screen.getByRole('combobox') // useDisabledState hook manages disabled state via aria-disabled // The actual disabled behavior is handled by the hook expect(select).toHaveAttribute('aria-disabled', 'true') }) it('applies aria-disabled when disabled', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-disabled', 'true') }) it('does not call onSelectionChange when disabled', async () => { const user = userEvent.setup() const handleChange = vi.fn() render( ) const select = screen.getByRole('combobox') await user.selectOptions(select, '2') expect(handleChange).not.toHaveBeenCalled() }) }) describe('required state', () => { it('applies required attribute when required', () => { render( ) expect(screen.getByRole('combobox')).toBeRequired() }) it('applies aria-required when required', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-required', 'true') }) }) describe('validation states', () => { it('applies aria-invalid when validation state is invalid', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-invalid', 'true') }) it('does not apply aria-invalid when validation state is valid', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-invalid', 'false') }) it('does not apply aria-invalid when validation state is none', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-invalid', 'false') }) it('associates error message with aria-describedby', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-describedby', 'test-error') }) it('associates hint text with aria-describedby', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-describedby', 'test-hint') }) it('associates both error and hint with aria-describedby', () => { render( ) expect(screen.getByRole('combobox')).toHaveAttribute('aria-describedby', 'test-error test-hint') }) it('does not set aria-describedby when no error or hint provided', () => { render( ) expect(screen.getByRole('combobox')).not.toHaveAttribute('aria-describedby') }) }) describe('selection', () => { it('sets default selected value', () => { render( ) expect(screen.getByRole('combobox')).toHaveValue('2') }) it('calls onSelectionChange when selection changes', async () => { const user = userEvent.setup() const handleChange = vi.fn() render( ) const select = screen.getByRole('combobox') await user.selectOptions(select, '2') expect(handleChange).toHaveBeenCalledOnce() expect(handleChange).toHaveBeenCalledWith( expect.objectContaining({ target: expect.objectContaining({ value: '2' }) }) ) }) }) describe('keyboard interactions', () => { it('calls onEnter when Enter key is pressed', async () => { const user = userEvent.setup() const handleEnter = vi.fn() render( ) const select = screen.getByRole('combobox') select.focus() await user.keyboard('{Enter}') expect(handleEnter).toHaveBeenCalledOnce() }) it('calls both onEnter and onKeyDown when Enter is pressed', async () => { const user = userEvent.setup() const handleEnter = vi.fn() const handleKeyDown = vi.fn() render( ) const select = screen.getByRole('combobox') select.focus() await user.keyboard('{Enter}') expect(handleEnter).toHaveBeenCalledOnce() expect(handleKeyDown).toHaveBeenCalledOnce() }) it('calls only onKeyDown for non-Enter keys', async () => { const user = userEvent.setup() const handleEnter = vi.fn() const handleKeyDown = vi.fn() render( ) const select = screen.getByRole('combobox') select.focus() await user.keyboard('{ArrowDown}') expect(handleKeyDown).toHaveBeenCalledOnce() expect(handleEnter).not.toHaveBeenCalled() }) it('does not call onEnter when disabled', async () => { const user = userEvent.setup() const handleEnter = vi.fn() render( ) const select = screen.getByRole('combobox') select.focus() await user.keyboard('{Enter}') expect(handleEnter).not.toHaveBeenCalled() }) }) describe('event handlers', () => { it('calls onBlur when select loses focus', async () => { const user = userEvent.setup() const handleBlur = vi.fn() render( ) const select = screen.getByRole('combobox') select.focus() await user.tab() expect(handleBlur).toHaveBeenCalledOnce() }) it('calls onPointerDown on pointer interaction', async () => { const user = userEvent.setup() const handlePointerDown = vi.fn() render( ) const select = screen.getByRole('combobox') await user.pointer({ target: select, keys: '[MouseLeft>]' }) expect(handlePointerDown).toHaveBeenCalled() }) }) describe('ref forwarding', () => { it('forwards ref to select element', () => { const ref = vi.fn() render( ) expect(ref).toHaveBeenCalledWith(expect.any(HTMLSelectElement)) }) }) }) describe('Option', () => { describe('rendering', () => { it('renders option element', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() }) it('renders with value attribute', () => { render( ) const option = screen.getByText('United States') expect(option).toHaveAttribute('value', 'us') }) it('uses label as display text', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() }) it('uses children over label when provided', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() expect(screen.queryByText('Ignored')).not.toBeInTheDocument() }) it('uses value as display when label not provided', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() }) }) describe('disabled state', () => { it('applies disabled attribute when disabled', () => { render( ) expect(screen.getByText('United States')).toBeDisabled() }) }) describe('styling variants', () => { it('applies variant data attribute', () => { render( ) expect(screen.getByText('United States')).toHaveAttribute('data-option', 'primary') }) it('applies size data attribute', () => { render( ) expect(screen.getByText('United States')).toHaveAttribute('data-size', 'lg') }) it('applies custom data attributes', () => { render( ) const option = screen.getByText('United States') expect(option).toHaveAttribute('data-highlighted', 'true') expect(option).toHaveAttribute('data-category', 'premium') }) it('accepts classes prop without error', () => { // Test that the Option component accepts the classes prop // The actual className rendering is handled by the UI component expect(() => { render( ) }).not.toThrow() expect(screen.getByText('United States')).toBeInTheDocument() }) it('applies custom styles', () => { render( ) // Browsers convert color values to rgb format expect(screen.getByText('United States')).toHaveStyle({ color: 'rgb(255, 0, 0)' }) }) }) describe('legacy props support', () => { it('supports selectValue prop for backwards compatibility', () => { render( ) const option = screen.getByText('United States') expect(option).toHaveAttribute('value', 'us') }) it('prefers value over selectValue', () => { render( ) const option = screen.getByText('United Kingdom') expect(option).toHaveAttribute('value', 'uk') }) it('prefers label over selectLabel', () => { render( ) expect(screen.getByText('United Kingdom')).toBeInTheDocument() expect(screen.queryByText('Ignored')).not.toBeInTheDocument() }) }) describe('ref forwarding', () => { it('forwards ref to option element', () => { const ref = vi.fn() render( ) expect(ref).toHaveBeenCalledWith(expect.any(HTMLOptionElement)) }) }) describe('compound component', () => { it('is accessible via Select.Option', () => { render( ) expect(screen.getByText('United States')).toBeInTheDocument() }) it('has correct display name', () => { expect(Option.displayName).toBe('Select.Option') }) }) })