Skip to content

Commit

Permalink
feat(ui): utiliser champ avec le select (#3103)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliebrunetto83 authored Jun 27, 2024
1 parent 57537f7 commit 581c222
Show file tree
Hide file tree
Showing 11 changed files with 725 additions and 576 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import styles
from '~/client/components/features/Accompagnement/FormulaireRecherche/FormulaireRechercheAccompagnement.module.scss';
import { ButtonComponent } from '~/client/components/ui/Button/ButtonComponent';
import { ComboboxCommune } from '~/client/components/ui/Form/Combobox/ComboboxCommune/ComboboxCommune';
import { Option, Select } from '~/client/components/ui/Form/Select/Select';
import { OptionSelect, Select } from '~/client/components/ui/Form/Select/Select';
import { Icon } from '~/client/components/ui/Icon/Icon';
import { useAccompagnementQuery } from '~/client/hooks/useAccompagnementQuery';
import { mapToCommune } from '~/client/hooks/useCommuneQuery';
import { getFormAsQuery } from '~/client/utils/form.util';
import { TypeÉtablissement } from '~/server/etablissement-accompagnement/domain/etablissementAccompagnement';

const typeAccompagnementListe: Option[] = [
const typeAccompagnementListe: OptionSelect[] = [
{ libellé: 'Agences France Travail', valeur: TypeÉtablissement.FRANCE_TRAVAIL },
{ libellé: 'Missions locales', valeur: TypeÉtablissement.MISSION_LOCALE },
{ libellé: 'Info jeunes', valeur: TypeÉtablissement.INFO_JEUNE },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { FormulaireÉtapeLayout } from '~/client/components/layouts/FormulaireEt
import { ButtonComponent } from '~/client/components/ui/Button/ButtonComponent';
import { Champ } from '~/client/components/ui/Form/Champ/Champ';
import { Input } from '~/client/components/ui/Form/Input';
import { Option } from '~/client/components/ui/Form/Select/Select';
import { OptionSelect } from '~/client/components/ui/Form/Select/Select';
import { TextArea } from '~/client/components/ui/Form/TextArea/TextArea';
import { Icon } from '~/client/components/ui/Icon/Icon';
import { Radio } from '~/client/components/ui/Radio/Radio';
Expand Down Expand Up @@ -64,7 +64,7 @@ export enum IsDateDeDebutPrecise {
NON = 'false',
}

const dureeStageList: Option[] = [
const dureeStageList: OptionSelect[] = [
{ libellé: '1 mois', valeur: DUREE_MOIS_EN_JOUR.toString() },
{ libellé: '2 mois', valeur: (2 * DUREE_MOIS_EN_JOUR).toString() },
{ libellé: '3 mois', valeur: (3 * DUREE_MOIS_EN_JOUR).toString() },
Expand Down
4 changes: 1 addition & 3 deletions src/client/components/ui/Form/Champ/Champ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React, { ComponentPropsWithoutRef, useCallback, useEffect, useId, useStat
import { Error } from '~/client/components/ui/Form/Error';
import { Hint } from '~/client/components/ui/Form/Hint';
import { Label } from '~/client/components/ui/Form/Label';
import { useSynchronizedRef } from '~/client/hooks/useSynchronizedRef';

import styles from './Champ.module.scss';
import { ChampContextProvider, useChampContext } from './ChampContext';
Expand Down Expand Up @@ -63,7 +62,6 @@ export const InputChamp: <Props extends ComponentChildrenPropsNecessary>(props:
...rest
}: InputChampProps<Props>, outerRef: React.ForwardedRef<HTMLInputElement>) {
const { errorId, hintId, inputId, setInputId, setErrorMessage, errorMessage } = useChampContext();
const inputRef = useSynchronizedRef(outerRef);

useEffect(() => {
id && setInputId(id);
Expand All @@ -85,12 +83,12 @@ export const InputChamp: <Props extends ComponentChildrenPropsNecessary>(props:

return (<Render
onTouch={onTouch}
ref={inputRef}
aria-describedby={`${ariaDescribedby} ${errorMessage ? errorId : ''} ${hintId}`}
aria-invalid={errorMessage !== ''}
id={inputId}
onInvalid={onInvalid}
onChange={onChange}
ref={outerRef}
{...rest}
/>);
});
Expand Down
3 changes: 2 additions & 1 deletion src/client/components/ui/Form/Select/Select.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ $border-width: inputStyles.$border-width;
border-radius: $border-radius;

& .combobox {
cursor: pointer;
@extend %outlined;
@include utilities.text-interactive-medium;
display: grid;
Expand All @@ -45,7 +46,7 @@ $border-width: inputStyles.$border-width;
background-color: transparent;
}

& .comboboxError {
input:invalid + .combobox[data-touched="true"] {
border-color: $color-error;
border-width: $error-border-width;
}
Expand Down
110 changes: 103 additions & 7 deletions src/client/components/ui/Form/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -720,16 +720,19 @@ describe('<Select />', () => {
expect(onSubmit).not.toHaveBeenCalled();
});

it('lorsque le champ est requis, je vois le message d‘erreur à partir du moment où j‘ai ouvert puis fermé le select', async () => {
it('lorsque le champ est requis et que l‘utilisateur ouvre puis ferme le select, appelle onInvalid et affiche le message d‘erreur', async () => {
const user = userEvent.setup();
const onInvalid = jest.fn();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<Select optionList={options} label={'label'} required/>);
render(<Select optionList={options} label={'label'} required onInvalid={onInvalid}/>);

await user.tab();
await user.keyboard(KeyBoard.ENTER);
await user.keyboard(KeyBoard.ESCAPE);

expect(screen.getByText('Séléctionnez un élément de la liste')).toBeVisible();
expect(onInvalid).toHaveBeenCalledTimes(1);
});

it('lorsque le champ est requis et en erreur, le message d‘erreur est fusionné avec la description accessible', async () => {
Expand All @@ -751,9 +754,10 @@ describe('<Select />', () => {
it('lorsque le champ est requis et en erreur, lorsque l‘utilisateur séléctionne une option le champ n‘est plus en erreur', async () => {
const user = userEvent.setup();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];
const onInvalid = jest.fn();

render(<>
<Select optionList={options} label={'label'} required/>
<Select optionList={options} label={'label'} required onInvalid={onInvalid}/>
</>);

await user.tab();
Expand All @@ -764,10 +768,54 @@ describe('<Select />', () => {
await user.keyboard(KeyBoard.ARROW_DOWN);
await user.keyboard(KeyBoard.ENTER);

expect(onInvalid).toHaveBeenCalledTimes(1);
expect(screen.getByRole('combobox')).toHaveAccessibleDescription('');
expect(screen.queryByText('Séléctionnez un élément de la liste')).not.toBeInTheDocument();
});
});

describe('touched', () => {
it('lorsque l‘utilisateur n‘a pas interragit avec le champ, le select n‘est pas touched', () => {
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];
const onTouch = jest.fn();

render(<Select optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
expect(onTouch).not.toHaveBeenCalled();
expect(combobox).toHaveAttribute('data-touched', 'false');
});

it('lorsque l‘utilisateur ouvre puis ferme le select, le select est touched', async() => {
const user = userEvent.setup();
const onTouch = jest.fn();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<Select optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
await user.click(combobox);
await user.keyboard(KeyBoard.ESCAPE);

expect(onTouch).toHaveBeenCalledWith(true);
expect(combobox).toHaveAttribute('data-touched', 'true');
});

it('lorsque l‘utilisateur ouvre puis selectionne une option, le select est touched', async() => {
const user = userEvent.setup();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];
const onTouch = jest.fn();

render(<Select optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
await user.click(combobox);
await user.click(screen.getByRole('option', { name: 'options 1' }));

expect(onTouch).toHaveBeenCalledWith(true);
expect(combobox).toHaveAttribute('data-touched', 'true');
});
});
});

describe('select choix multiple', () => {
Expand Down Expand Up @@ -1527,16 +1575,19 @@ describe('<Select />', () => {
expect(onSubmit).not.toHaveBeenCalled();
});

it('lorsque le champ est requis, je vois le message d‘erreur à partir du moment où l‘utilisateur a fermé le select sans option séléctionnée', async () => {
it('lorsque le champ est requis et que l‘utilisateur ouvre puis ferme le select sans selectionner d‘option, appelle onInvalid et affiche le message d‘erreur', async () => {
const user = userEvent.setup();
const onInvalid = jest.fn();

const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<Select multiple required optionList={options} label={'label'}/>);
render(<Select multiple required optionList={options} label={'label'} onInvalid={onInvalid}/>);

await user.tab();
await user.keyboard(KeyBoard.ENTER);
await user.keyboard(KeyBoard.ESCAPE);
expect(screen.getByText('Séléctionnez au moins un élément de la liste')).toBeVisible();
expect(onInvalid).toHaveBeenCalledTimes(1);
});

it('lorsque le champ est requis et en erreur, le message d‘erreur est fusionné avec la description accessible', async () => {
Expand All @@ -1556,11 +1607,12 @@ describe('<Select />', () => {
});

it('lorsque le champ est requis et en erreur, lorsque l‘utilisateur séléctionne une option le champ n‘est plus en erreur', async () => {
const onInvalid = jest.fn();
const user = userEvent.setup();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<>
<Select multiple optionList={options} label={'label'} required aria-describedby={'id1'}/>
<Select multiple optionList={options} label={'label'} required aria-describedby={'id1'} onInvalid={onInvalid}/>
<p id={'id1'}>La description accessible</p>
</>);

Expand All @@ -1573,7 +1625,51 @@ describe('<Select />', () => {
await user.keyboard(KeyBoard.ENTER);

expect(screen.queryByText('Séléctionnez au moins un élément de la liste')).not.toBeInTheDocument();
expect(screen.getByRole('combobox')).toHaveAccessibleDescription('La description accessible ');
expect(onInvalid).toHaveBeenCalledTimes(1);
});
});

describe('touched', () => {
it('lorsque l‘utilisateur n‘a pas interragit avec le champ, le select n‘est pas touched', () => {
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];
const onTouch = jest.fn();

render(<Select multiple optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
expect(onTouch).not.toHaveBeenCalled();
expect(combobox).toHaveAttribute('data-touched', 'false');
});

it('lorsque l‘utilisateur ouvre puis ferme le select, le select est touched', async() => {
const user = userEvent.setup();
const onTouch = jest.fn();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<Select multiple optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
await user.click(combobox);
await user.keyboard(KeyBoard.ESCAPE);

expect(onTouch).toHaveBeenCalledTimes(1);
expect(onTouch).toHaveBeenCalledWith(true);
expect(combobox).toHaveAttribute('data-touched', 'true');
});

it('lorsque l‘utilisateur ouvre puis selectionne une option, le select est touched', async() => {
const user = userEvent.setup();
const onTouch = jest.fn();
const options = [{ libellé: 'options 1', valeur: '1' }, { libellé: 'options 2', valeur: '2' }];

render(<Select multiple optionList={options} label={'label'} onTouch={onTouch}/>);

const combobox = screen.getByRole('combobox');
await user.click(combobox);
await user.click(screen.getByRole('option', { name: 'options 1' }));

expect(onTouch).toHaveBeenCalledWith(true);
expect(combobox).toHaveAttribute('data-touched', 'true');
});
});
});
Expand Down
Loading

0 comments on commit 581c222

Please sign in to comment.