veza/apps/web/src/components/ui.backup/select.test.tsx

423 lines
12 KiB
TypeScript

import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { Select } from './select';
describe('Select Component', () => {
const mockOnChange = vi.fn();
const options = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
];
beforeEach(() => {
vi.clearAllMocks();
});
it('renders select trigger correctly', () => {
render(
<Select
options={options}
onChange={mockOnChange}
placeholder="Select..."
/>,
);
expect(screen.getByText('Select...')).toBeInTheDocument();
});
it('opens dropdown when trigger is clicked', async () => {
const user = userEvent.setup();
render(<Select options={options} onChange={mockOnChange} />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Option 1')).toBeInTheDocument();
});
});
it('displays selected value for single select', () => {
render(
<Select options={options} value="option1" onChange={mockOnChange} />,
);
expect(screen.getByText('Option 1')).toBeInTheDocument();
});
it('displays selected count for multi-select', () => {
render(
<Select
options={options}
value={['option1', 'option2']}
onChange={mockOnChange}
multiple
/>,
);
expect(screen.getByText('2 selected')).toBeInTheDocument();
});
it('calls onChange when option is selected in single mode', async () => {
const user = userEvent.setup();
render(<Select options={options} onChange={mockOnChange} />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Option 1')).toBeInTheDocument();
});
const option1 = screen.getByText('Option 1');
await user.click(option1);
expect(mockOnChange).toHaveBeenCalledWith('option1');
});
it('calls onChange when option is selected in multi mode', async () => {
const user = userEvent.setup();
render(<Select options={options} onChange={mockOnChange} multiple />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Option 1')).toBeInTheDocument();
});
const option1 = screen.getByText('Option 1');
await user.click(option1);
expect(mockOnChange).toHaveBeenCalledWith(['option1']);
});
it('toggles option selection in multi mode', async () => {
const user = userEvent.setup();
render(
<Select
options={options}
value={['option1']}
onChange={mockOnChange}
multiple
/>,
);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) => btn.textContent?.includes('1 selected')) ||
triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Option 1')).toBeInTheDocument();
});
const option1 = screen.getByText('Option 1');
await user.click(option1);
expect(mockOnChange).toHaveBeenCalledWith([]);
});
it('filters options when searchable is enabled', async () => {
const user = userEvent.setup();
render(<Select options={options} onChange={mockOnChange} searchable />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
});
// Attendre que les options soient rendues
await waitFor(() => {
expect(screen.getAllByRole('menuitem').length).toBeGreaterThan(0);
});
const searchInput = screen.getByPlaceholderText('Search...');
await user.type(searchInput, 'Option 1');
await waitFor(
() => {
const menuItems = screen.getAllByRole('menuitem');
const option1Item = menuItems.find((item) =>
item.textContent?.includes('Option 1'),
);
expect(option1Item).toBeInTheDocument();
const option2Item = menuItems.find((item) =>
item.textContent?.includes('Option 2'),
);
expect(option2Item).not.toBeInTheDocument();
},
{ timeout: 2000 },
);
});
it('displays grouped options', async () => {
const user = userEvent.setup();
const groupedOptions = [
{ value: 'opt1', label: 'Option 1', group: 'Group 1' },
{ value: 'opt2', label: 'Option 2', group: 'Group 1' },
{ value: 'opt3', label: 'Option 3', group: 'Group 2' },
];
render(<Select options={groupedOptions} onChange={mockOnChange} />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
// Attendre que les options soient rendues
expect(screen.getAllByRole('menuitem').length).toBeGreaterThan(0);
});
// Chercher les labels de groupe
const groupLabels = Array.from(document.querySelectorAll('.text-xs'));
const group1Label = groupLabels.find(
(el) => el.textContent?.trim() === 'GROUP 1',
);
const group2Label = groupLabels.find(
(el) => el.textContent?.trim() === 'GROUP 2',
);
expect(group1Label).toBeInTheDocument();
expect(group2Label).toBeInTheDocument();
});
it('filters grouped options when searching', async () => {
const user = userEvent.setup();
const groupedOptions = [
{ value: 'opt1', label: 'Apple', group: 'Fruits' },
{ value: 'opt2', label: 'Banana', group: 'Fruits' },
{ value: 'opt3', label: 'Carrot', group: 'Vegetables' },
];
render(
<Select options={groupedOptions} onChange={mockOnChange} searchable />,
);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
});
const searchInput = screen.getByPlaceholderText('Search...');
await user.type(searchInput, 'Apple');
expect(screen.getByText('Apple')).toBeInTheDocument();
expect(screen.queryByText('Banana')).not.toBeInTheDocument();
expect(screen.queryByText('Carrot')).not.toBeInTheDocument();
});
it('shows checkmark for selected option in single mode', async () => {
const user = userEvent.setup();
render(
<Select options={options} value="option1" onChange={mockOnChange} />,
);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find(
(btn) =>
btn.textContent?.includes('Option 1') &&
!btn.textContent?.includes('truncate'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
const menuItems = screen.getAllByRole('menuitem');
const option1Item = menuItems.find(
(item) => item.textContent?.trim() === 'Option 1',
);
expect(option1Item).toBeInTheDocument();
expect(option1Item?.querySelector('svg')).toBeInTheDocument();
});
});
it('shows checkbox for multi-select mode', async () => {
const user = userEvent.setup();
render(
<Select
options={options}
value={['option1']}
onChange={mockOnChange}
multiple
/>,
);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) => btn.textContent?.includes('1 selected')) ||
triggers[0];
await user.click(trigger);
await waitFor(() => {
const option1 = screen.getByText('Option 1').closest('[role="menuitem"]');
const checkbox = option1?.querySelector('.border');
expect(checkbox).toBeInTheDocument();
});
});
it('disables option when disabled prop is true', async () => {
const user = userEvent.setup();
const optionsWithDisabled = [
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2', disabled: true },
];
render(<Select options={optionsWithDisabled} onChange={mockOnChange} />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
const option2 = screen.getByText('Option 2').closest('[role="menuitem"]');
expect(option2).toHaveClass('opacity-50');
expect(option2).toHaveClass('cursor-not-allowed');
});
});
it('clears selection when clear button is clicked', async () => {
const user = userEvent.setup();
render(
<Select options={options} value="option1" onChange={mockOnChange} />,
);
const triggers = screen.getAllByRole('button');
const trigger = triggers.find((btn) =>
btn.textContent?.includes('Option 1'),
);
const clearButton = trigger?.querySelector('svg');
expect(clearButton).toBeInTheDocument();
if (clearButton) {
await user.click(clearButton);
expect(mockOnChange).toHaveBeenCalledWith('');
}
});
it('clears all selections in multi mode', async () => {
const user = userEvent.setup();
render(
<Select
options={options}
value={['option1', 'option2']}
onChange={mockOnChange}
multiple
/>,
);
const triggers = screen.getAllByRole('button');
const trigger = triggers.find((btn) =>
btn.textContent?.includes('2 selected'),
);
const clearButton = trigger?.querySelector('svg');
expect(clearButton).toBeInTheDocument();
if (clearButton) {
await user.click(clearButton);
expect(mockOnChange).toHaveBeenCalledWith([]);
}
});
it('displays "No options found" when search yields no results', async () => {
const user = userEvent.setup();
render(<Select options={options} onChange={mockOnChange} searchable />);
const triggers = screen.getAllByRole('button');
const trigger =
triggers.find((btn) =>
btn.textContent?.includes('Select an option...'),
) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
});
const searchInput = screen.getByPlaceholderText('Search...');
await user.type(searchInput, 'nonexistent');
await waitFor(() => {
expect(screen.getByText('No options found')).toBeInTheDocument();
});
});
it('renders hidden input with name attribute', () => {
render(
<Select
options={options}
value="option1"
onChange={mockOnChange}
name="test-select"
/>,
);
const hiddenInput = document.querySelector(
'input[type="hidden"][name="test-select"]',
);
expect(hiddenInput).toBeInTheDocument();
expect(hiddenInput).toHaveValue('option1');
});
it('renders hidden input with comma-separated values for multi-select', () => {
render(
<Select
options={options}
value={['option1', 'option2']}
onChange={mockOnChange}
multiple
name="test-select"
/>,
);
const hiddenInput = document.querySelector(
'input[type="hidden"][name="test-select"]',
);
expect(hiddenInput).toBeInTheDocument();
expect(hiddenInput).toHaveValue('option1,option2');
});
it('disables select when disabled prop is true', () => {
render(<Select options={options} onChange={mockOnChange} disabled />);
// Le Button dans le trigger devrait être disabled
const buttons = document.querySelectorAll('button');
const selectButton = Array.from(buttons).find(
(btn) => btn.textContent?.includes('Select an option...') && btn.disabled,
);
expect(selectButton).toBeInTheDocument();
});
});