test: add MultiSelect test file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
109
src/design-system/composites/MultiSelect/MultiSelect.test.tsx
Normal file
109
src/design-system/composites/MultiSelect/MultiSelect.test.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import { MultiSelect } from './MultiSelect'
|
||||||
|
|
||||||
|
const OPTIONS = [
|
||||||
|
{ value: 'admin', label: 'ADMIN' },
|
||||||
|
{ value: 'editor', label: 'EDITOR' },
|
||||||
|
{ value: 'viewer', label: 'VIEWER' },
|
||||||
|
{ value: 'operator', label: 'OPERATOR' },
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('MultiSelect', () => {
|
||||||
|
it('renders trigger with placeholder', () => {
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} />)
|
||||||
|
expect(screen.getByText('Select...')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders trigger with custom placeholder', () => {
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} placeholder="Add roles..." />)
|
||||||
|
expect(screen.getByText('Add roles...')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows selected count on trigger', () => {
|
||||||
|
render(<MultiSelect options={OPTIONS} value={['admin', 'editor']} onChange={vi.fn()} />)
|
||||||
|
expect(screen.getByText('2 selected')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens dropdown on trigger click', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
expect(screen.getByText('ADMIN')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('EDITOR')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows checkboxes for pre-selected values', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={['admin']} onChange={vi.fn()} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
const adminCheckbox = screen.getByRole('checkbox', { name: 'ADMIN' })
|
||||||
|
expect(adminCheckbox).toBeChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters options by search text', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
await user.type(screen.getByPlaceholderText('Search...'), 'adm')
|
||||||
|
expect(screen.getByText('ADMIN')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('EDITOR')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onChange with selected values on Apply', async () => {
|
||||||
|
const onChange = vi.fn()
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={onChange} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
await user.click(screen.getByRole('checkbox', { name: 'ADMIN' }))
|
||||||
|
await user.click(screen.getByRole('checkbox', { name: 'VIEWER' }))
|
||||||
|
await user.click(screen.getByRole('button', { name: /Apply/ }))
|
||||||
|
expect(onChange).toHaveBeenCalledWith(['admin', 'viewer'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('discards pending changes on Escape', async () => {
|
||||||
|
const onChange = vi.fn()
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={onChange} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
await user.click(screen.getByRole('checkbox', { name: 'ADMIN' }))
|
||||||
|
await user.keyboard('{Escape}')
|
||||||
|
expect(onChange).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes dropdown on outside click without applying', async () => {
|
||||||
|
const onChange = vi.fn()
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(
|
||||||
|
<div>
|
||||||
|
<MultiSelect options={OPTIONS} value={[]} onChange={onChange} />
|
||||||
|
<button>Outside</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
await user.click(screen.getByRole('checkbox', { name: 'ADMIN' }))
|
||||||
|
await user.click(screen.getByText('Outside'))
|
||||||
|
expect(onChange).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables trigger when disabled prop is set', () => {
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} disabled />)
|
||||||
|
expect(screen.getByRole('combobox')).toHaveAttribute('aria-disabled', 'true')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides search input when searchable is false', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={[]} onChange={vi.fn()} searchable={false} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
expect(screen.queryByPlaceholderText('Search...')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows Apply button with count of pending changes', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<MultiSelect options={OPTIONS} value={['admin']} onChange={vi.fn()} />)
|
||||||
|
await user.click(screen.getByRole('combobox'))
|
||||||
|
await user.click(screen.getByRole('checkbox', { name: 'EDITOR' }))
|
||||||
|
expect(screen.getByRole('button', { name: /Apply \(2\)/ })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user