diff --git a/pro/src/ui-kit/MultiSelect/MultiSelect.spec.tsx b/pro/src/ui-kit/MultiSelect/MultiSelect.spec.tsx deleted file mode 100644 index ffc4c57bf9c..00000000000 --- a/pro/src/ui-kit/MultiSelect/MultiSelect.spec.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { screen } from '@testing-library/react' -import { axe } from 'vitest-axe' - -import { - renderWithProviders, - RenderWithProvidersOptions, -} from 'commons/utils/renderWithProviders' - -import { MultiSelect } from './MultiSelect' - -const renderMultiSelect = (options?: RenderWithProvidersOptions) => { - return renderWithProviders(<MultiSelect />, { ...options }) -} - -describe('<MultiSelect />', () => { - it('should render correctly', async () => { - renderMultiSelect() - - expect( - await screen.findByRole('heading', { name: /MultiSelect/ }) - ).toBeInTheDocument() - }) - - it('should not have accessibility violations', async () => { - const { container } = renderMultiSelect() - - expect(await axe(container)).toHaveNoViolations() - }) -}) diff --git a/pro/src/ui-kit/MultiSelect/MultiSelect.stories.tsx b/pro/src/ui-kit/MultiSelect/MultiSelect.stories.tsx index 0040eefe920..2dcd9948586 100644 --- a/pro/src/ui-kit/MultiSelect/MultiSelect.stories.tsx +++ b/pro/src/ui-kit/MultiSelect/MultiSelect.stories.tsx @@ -57,3 +57,14 @@ export const WithSearchInput: StoryObj<typeof MultiSelect> = { label: 'Selectionner des départements', }, } + +export const WithSelectAllOption: StoryObj<typeof MultiSelect> = { + args: { + ...defaultProps, + hasSelectAllOptions: true, + searchExample: 'Ex : 44 - Nantes', + searchLabel: 'Rechercher des départements', + legend: 'Départements', + label: 'Selectionner des départements', + }, +} diff --git a/pro/src/ui-kit/MultiSelect/MultiSelect.tsx b/pro/src/ui-kit/MultiSelect/MultiSelect.tsx index 331178b987b..a6ee2e80ef9 100644 --- a/pro/src/ui-kit/MultiSelect/MultiSelect.tsx +++ b/pro/src/ui-kit/MultiSelect/MultiSelect.tsx @@ -124,7 +124,7 @@ export const MultiSelect = ({ )} <SelectedValuesTags - disabled={disabled} + disabled={false} selectedOptions={selectedItems.map((item) => item.id)} removeOption={handleRemoveItem} fieldName="tags" @@ -137,8 +137,7 @@ export const MultiSelect = ({ ) } -{ - /* <fieldset> +/* <fieldset> <legend><button aria-controls="control-id" aria-expanded=...>Label du bouton</button></legend> <div id="control-id"> <label class="visually-hidden" for="id-input">Rechercher des ...</label> @@ -169,10 +168,8 @@ export const MultiSelect = ({ <div role="option" id="listbox1-6">Pervenche</div> </div> */ -} -{ - /* <div className={styles.container} role="listbox" aria-label="Liste des départements"> +/* <div className={styles.container} role="listbox" aria-label="Liste des départements"> {departments.map(department => ( <label key={department.id} className={styles.item}> <div className={styles.checkbox}> @@ -188,4 +185,3 @@ export const MultiSelect = ({ </label> ))} </div> */ -} diff --git a/pro/src/ui-kit/MultiSelect/TODO.md b/pro/src/ui-kit/MultiSelect/TODO.md index 42d132cd1ab..007015a3f7d 100644 --- a/pro/src/ui-kit/MultiSelect/TODO.md +++ b/pro/src/ui-kit/MultiSelect/TODO.md @@ -7,6 +7,7 @@ Fonctionnel Design - [] corriger la zone de clic sur BaseCheckbox +- [] Passer les selected tag en violet - [] corriger le curseur de BaseCheckbox - [] ajuster la hauteur des checkbox - [] utiliser les fonts du design system diff --git a/pro/src/ui-kit/MultiSelect/__specs__/MultiSelect.spec.tsx b/pro/src/ui-kit/MultiSelect/__specs__/MultiSelect.spec.tsx new file mode 100644 index 00000000000..ad8159e816a --- /dev/null +++ b/pro/src/ui-kit/MultiSelect/__specs__/MultiSelect.spec.tsx @@ -0,0 +1,132 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' +import { axe } from 'vitest-axe' + +import { MultiSelect, Option } from '../MultiSelect' + +describe('<MultiSelect />', () => { + const options: Option[] = [ + { id: '1', label: 'Option 1' }, + { id: '2', label: 'Option 2' }, + { id: '3', label: 'Option 3' }, + ] + + const defaultOptions: Option[] = [{ id: '1', label: 'Option 1' }] + + it('should render correctly', async () => { + render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + defaultOptions={defaultOptions} + /> + ) + + expect(await screen.findByText('Select Options')).toBeInTheDocument() + }) + + it('should not have accessibility violations', async () => { + const { container } = render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + defaultOptions={defaultOptions} + /> + ) + + expect(await axe(container)).toHaveNoViolations() + }) + + it('renders the MultiSelect component with the correct initial selected options', () => { + render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + defaultOptions={defaultOptions} + /> + ) + // Check that the initial selected options are rendered in the SelectedValuesTags + expect(screen.getByText('Option 1')).toBeInTheDocument() + }) + + it('toggles the dropdown when the trigger is clicked', async () => { + render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + hasSelectAllOptions={true} + /> + ) + const toggleButton = screen.getByText('Select Options') + await userEvent.click(toggleButton) // Open the dropdown + expect(screen.getByText(/Tout sélectionner/i)).toBeInTheDocument() + + await userEvent.click(toggleButton) // Close the dropdown + expect(screen.queryByText(/Tout sélectionner/i)).not.toBeInTheDocument() + }) + + it('selects all options when "Select All" is clicked', async () => { + render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + hasSelectAllOptions={true} + /> + ) + + const toggleButton = screen.getByText('Select Options') + await userEvent.click(toggleButton) // Open the dropdown + + const selectAllCheckbox = screen.getByLabelText(/Tout sélectionner/i) + fireEvent.click(selectAllCheckbox) // Select all options + + // Verify that all options are selected + options.forEach((option) => { + expect(screen.getByLabelText(option.label)).toBeChecked() + }) + }) + + it('removes an option from the selected items when clicked in SelectedValuesTags', async () => { + render( + <MultiSelect + options={options} + label="Select Options" + legend="Legend" + defaultOptions={defaultOptions} + /> + ) + + // Initially, Option 1 is selected + const selectedTag = screen.getByText('Option 1') + await userEvent.click(selectedTag) // Remove Option 1 + expect(screen.queryByText('Option 1')).not.toBeInTheDocument() + }) + + it('closes the dropdown when clicked outside or when Escape key is pressed', async () => { + render( + <MultiSelect options={options} label="Select Options" legend="Legend" /> + ) + + const toggleButton = screen.getByText('Select Options') + + fireEvent.click(toggleButton) // Open the dropdown + expect(screen.queryByText('Option 1')).toBeInTheDocument() + + // Simulate a click outside the dropdown + fireEvent.click(document.body) + await waitFor(() => + expect(screen.queryByText('Option 1')).not.toBeInTheDocument() + ) + + fireEvent.click(toggleButton) // Open the dropdown again + fireEvent.keyDown(toggleButton, { key: 'Escape' }) // Close with Escape key + await waitFor(() => + expect(screen.queryByText('Option 1')).not.toBeInTheDocument() + ) + }) +}) diff --git a/pro/src/ui-kit/MultiSelect/__specs__/MultiSelectPanel.spec.tsx b/pro/src/ui-kit/MultiSelect/__specs__/MultiSelectPanel.spec.tsx new file mode 100644 index 00000000000..02132f0c2f8 --- /dev/null +++ b/pro/src/ui-kit/MultiSelect/__specs__/MultiSelectPanel.spec.tsx @@ -0,0 +1,99 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { axe } from 'vitest-axe' + +import { Option } from '../MultiSelect' +import { MultiSelectPanel } from '../MultiSelectPanel' + +describe('<MultiSelectPanel />', () => { + const options: (Option & { checked: boolean })[] = [ + { id: '1', label: 'Option 1', checked: false }, + { id: '2', label: 'Option 2', checked: false }, + { id: '3', label: 'Option 3', checked: false }, + ] + + const defaultOptions: Option[] = [{ id: '1', label: 'Option 1' }] + + const onOptionSelect = vi.fn() + + it('renders options with checkboxes', () => { + render( + <MultiSelectPanel + options={options} + label={''} + onOptionSelect={function (option: Option | 'all' | undefined): void { + throw new Error('Function not implemented.') + }} + /> + ) + + expect(screen.getByLabelText('Option 1')).toBeInTheDocument() + expect(screen.getByLabelText('Option 2')).toBeInTheDocument() + expect(screen.getByLabelText('Option 3')).toBeInTheDocument() + }) + + it('renders the search input if hasSearch is true', () => { + render( + <MultiSelectPanel + options={[]} + label={''} + onOptionSelect={function (option: Option | 'all' | undefined): void { + throw new Error('Function not implemented.') + }} + hasSearch={true} + searchExample="Exemple: Nantes" + /> + ) + + expect(screen.getByText(/Exemple: Nantes/i)).toBeInTheDocument() + }) + + it('not renders the search input if hasSearch is false', () => { + render( + <MultiSelectPanel + options={[]} + label={''} + onOptionSelect={function (option: Option | 'all' | undefined): void { + throw new Error('Function not implemented.') + }} + hasSearch={false} + searchExample="Exemple: Nantes" + /> + ) + + expect(screen.queryByText(/Exemple: Nantes/i)).not.toBeInTheDocument() + }) + + it('should not have accessibility violations', async () => { + const { container } = render( + <MultiSelectPanel + options={[]} + label={''} + onOptionSelect={function (option: Option | 'all' | undefined): void { + throw new Error('Function not implemented.') + }} + hasSearch={false} + /> + ) + + expect(await axe(container)).toHaveNoViolations() + }) + + it('selects and deselects individual options', async () => { + render( + <MultiSelectPanel + options={options} + label="" + onOptionSelect={onOptionSelect} + /> + ) + + // Initially, only Option 1 is selected + const option2Checkbox = screen.getByLabelText(/Option 2/i) + await userEvent.click(option2Checkbox) // Select Option 2 + expect(onOptionSelect).toHaveBeenCalledWith(options[1]) + + await userEvent.click(option2Checkbox) // Deselect Option 2 + expect(onOptionSelect).toHaveBeenCalledWith(options[1]) + }) +})