diff --git a/cypress/e2e/stage.cy.ts b/cypress/e2e/stage.cy.ts index d750302228..aa499dc39d 100644 --- a/cypress/e2e/stage.cy.ts +++ b/cypress/e2e/stage.cy.ts @@ -15,8 +15,7 @@ describe('Recherche de stages', () => { cy.intercept({ pathname: '/multi-search' }, stageNonFiltreeResponse).as('facets'); cy.wait('@facets'); - // FIXME (GAFI 06-11-2023): Devrait être role combobox - cy.findByRole('button', { name: /Domaines/i }).click(); + cy.findByRole('combobox', { name: /Domaines/i }).click(); cy.findAllByRole('option').first().should('contain.text', 'Achats'); cy.findAllByRole('option').eq(1).should('contain.text', 'Production'); diff --git a/src/client/components/features/Evenement/FormulaireRecherche/FormulaireRechercheEvenement.tsx b/src/client/components/features/Evenement/FormulaireRecherche/FormulaireRechercheEvenement.tsx index 3a3aa190b5..52e87735be 100644 --- a/src/client/components/features/Evenement/FormulaireRecherche/FormulaireRechercheEvenement.tsx +++ b/src/client/components/features/Evenement/FormulaireRecherche/FormulaireRechercheEvenement.tsx @@ -2,15 +2,15 @@ import React from 'react'; import styles from '~/client/components/features/Evenement/FormulaireRecherche/FormulaireRechercheEvenement.module.scss'; -import { MeilisearchComboboxLocalisation } from '~/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation'; -import { MeilisearchCustomSearchBox } from '~/client/components/ui/Meilisearch/MeilisearchCustomSearchBox'; +import { MeilisearchComboboxLocalisation } from '~/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation'; +import { MeilisearchInput } from '~/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput'; const LIMIT_MAX_FACETS = 10000; export function FormulaireRechercheEvenement() { return (
event.preventDefault()}> - = ['name:asc']; export function FormulaireRechercheFicheMetier() { return ( event.preventDefault()}> - - + + /> ); } diff --git a/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.test.tsx b/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.test.tsx index 16b2d54954..2e606c5e5d 100644 --- a/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.test.tsx +++ b/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.test.tsx @@ -10,79 +10,37 @@ import { import { generateRefinementListItem, mockUseRefinementList, -} from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +} from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; import { mockLargeScreen, mockSmallScreen } from '~/client/components/window.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires const spyed = jest.spyOn(require('react-instantsearch'), 'useRefinementList'); -let refineMock: jest.Mock; - describe('FormulaireRechercheAnnonceLogement', () => { - describe('en Desktop', () => { - beforeEach(() => { - mockLargeScreen(); - }); - beforeEach(() => { - // GIVEN - refineMock = jest.fn(); - spyed.mockImplementation(() => mockUseRefinementList({ - items: [ generateRefinementListItem({ label: 'exemple', value: 'exemple' }) ], - refine: refineMock, - })); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); + it('affiche un formulaire', () => { + render(); - it('affiche un formulaire', () => { - render(); - - const form = screen.getByRole('search'); - expect(form).toBeInTheDocument(); - }); - - it('n‘affiche pas de bouton pour filtrer la recherche', () => { - render(); - - const buttonFiltreMobile = screen.getByTestId('bouton-filtrer-recherche-mobile'); - expect(buttonFiltreMobile).toBeInTheDocument(); - }); - - it('affiche le champ ville dans le formulaire', () => { - render(); - - const inputVille = screen.getByRole('textbox', { name: 'Ville' }); - expect(inputVille).toBeInTheDocument(); - }); - - it('affiche le champ type d‘offre dans le formulaire', () => { - render(); - - const buttonTypeOffre = screen.getByRole('button', { name: 'Type d‘offre' }); - expect(buttonTypeOffre).toBeInTheDocument(); - }); - - it('affiche le champ type de bien dans le formulaire', () => { - render(); - - const buttonTypeOffre = screen.getByRole('button', { name: 'Type de bien' }); - expect(buttonTypeOffre).toBeInTheDocument(); - }); - - it('affiche le champ prix dans le formulaire', () => { - render(); + const form = screen.getByRole('search'); + expect(form).toBeVisible(); + }); - const buttonPrix = screen.getByRole('button', { name: 'Prix' }); - expect(buttonPrix).toBeInTheDocument(); - }); + beforeEach(() => { + mockLargeScreen(); + spyed.mockImplementation(() => mockUseRefinementList({ + items: [generateRefinementListItem({ label: 'exemple', value: 'exemple' })], + refine: jest.fn(), + })); + }); - it('affiche le champ surface dans le formulaire', () => { + describe('en Desktop', () => { + it('affiche les champs de recherche', () => { render(); - const buttonSurface = screen.getByRole('button', { name: 'Surface (m²)' }); - expect(buttonSurface).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Ville' })).toBeVisible(); + expect(screen.getByRole('combobox', { name: 'Type d‘offre' })).toBeVisible(); + expect(screen.getByRole('combobox', { name: 'Type de bien' })).toBeVisible(); + expect(screen.getByRole('button', { name: 'Surface (m²)' })).toBeVisible(); + expect(screen.getByRole('button', { name: 'Prix' })).toBeVisible(); }); }); @@ -91,60 +49,17 @@ describe('FormulaireRechercheAnnonceLogement', () => { mockSmallScreen(); }); - afterEach(() => { - jest.clearAllMocks(); - }); - - it('affiche un formulaire', () => { - render(); - - const form = screen.getByRole('search'); - expect(form).toBeInTheDocument(); - }); - - it('affiche le champ ville dans le formulaire', () => { + it('affiche uniquement le champ ville dans le formulaire', () => { render(); - const inputVille = screen.getByRole('textbox', { name: 'Ville' }); - expect(inputVille).toBeInTheDocument(); - expect(inputVille).toBeVisible(); + expect(screen.getByRole('textbox', { name: 'Ville' })).toBeVisible(); }); it('affiche un bouton pour filtrer la recherche', () => { render(); const buttonFiltre = screen.getByRole('button', { name: 'Filtrer ma recherche' }); - expect(buttonFiltre).toBeInTheDocument(); - }); - - it('n‘affiche pas le champ type d‘offre dans le formulaire', () => { - render(); - - const buttonTypeOffreDesktop = screen.getByTestId('input-type-offre-desktop'); - expect(buttonTypeOffreDesktop).toBeInTheDocument(); - - }); - - it('n‘affiche pas le champ type de bien dans le formulaire', () => { - render(); - - const buttonTypeBienDesktop = screen.getByTestId('input-type-bien-desktop'); - expect(buttonTypeBienDesktop).toBeInTheDocument(); - - }); - - it('n‘affiche pas le champ prix dans le formulaire', () => { - render(); - - const buttonPrixDesktop = screen.getByTestId('input-prix-desktop'); - expect(buttonPrixDesktop).toBeInTheDocument(); - }); - - it('n‘affiche pas le champ surface dans le formulaire', () => { - render(); - - const buttonSurfaceDesktop = screen.getByTestId('input-surface-desktop'); - expect(buttonSurfaceDesktop).toBeInTheDocument(); + expect(buttonFiltre).toBeVisible(); }); describe('quand l‘utilisateur ouvre les filtres de recherche', () => { diff --git a/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.tsx b/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.tsx index cea324f1a1..063115b749 100644 --- a/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.tsx +++ b/src/client/components/features/Logement/FormulaireRecherche/FormulaireRechercheAnnonceLogement.tsx @@ -5,15 +5,15 @@ import styles import { ButtonComponent } from '~/client/components/ui/Button/ButtonComponent'; import { FilterAccordion } from '~/client/components/ui/FilterAccordion/FilterAccordion'; import { Icon } from '~/client/components/ui/Icon/Icon'; -import { MeilisearchCustomRangeInput } from '~/client/components/ui/Meilisearch/MeilisearchCustomRangeInput'; import { - MeilisearchCustomRangeInputForModal, -} from '~/client/components/ui/Meilisearch/MeilisearchCustomRangeInputForModal'; -import { MeilisearchCustomRefinementList } from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementList'; + MeilisearchCheckboxList, +} from '~/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList'; +import { MeilisearchInput } from '~/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput'; +import { MeilisearchRange } from '~/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange'; import { - MeilisearchCustomRefinementListForModal, -} from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementListForModal'; -import { MeilisearchCustomSearchBox } from '~/client/components/ui/Meilisearch/MeilisearchCustomSearchBox'; + MeilisearchRangeForModal, +} from '~/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRangeForModal'; +import { MeilisearchSelectMultiple } from '~/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple'; import { ModalComponent } from '~/client/components/ui/Modal/ModalComponent'; export const PRIX_MINIMUM = 0; @@ -31,27 +31,25 @@ export function FormulaireRechercheAnnonceLogement() { className={styles.RechercherLogementForm} role="search" onSubmit={(event) => event.preventDefault()}> - - - - -
} iconPosition="right" label="Filtrer ma recherche" - data-testid="bouton-filtrer-recherche-mobile" onClick={() => setIsFiltresAvancésMobileOpen(true)} /> - - - - event.preventDefault()}> - - - } skeletonRepeat={nombreDeSkeleton} - pagination={} + pagination={} isLoading={isInstantSearchLoading} isAffichageListeDeResultatsDesktopDirectionRow={isAffichageListeDeResultatsDesktopDirectionRow} /> diff --git a/src/client/components/layouts/InstantSearch/ListeDesResultats.test.tsx b/src/client/components/layouts/InstantSearch/ListeDesResultats.test.tsx index 4d67962449..0bb5538b95 100644 --- a/src/client/components/layouts/InstantSearch/ListeDesResultats.test.tsx +++ b/src/client/components/layouts/InstantSearch/ListeDesResultats.test.tsx @@ -10,8 +10,8 @@ import { import React from 'react'; import { ListeDesResultats } from '~/client/components/layouts/InstantSearch/ListeDesResultats'; -import { MeiliSearchCustomPagination } from '~/client/components/ui/Meilisearch/MeiliSearchCustomPagination'; -import { mockUsePagination } from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +import { MeiliSearchPagination } from '~/client/components/ui/Meilisearch/MeilisearchPagination/MeiliSearchPagination'; +import { mockUsePagination } from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; import { mockLargeScreen } from '~/client/components/window.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -40,7 +40,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, @@ -60,7 +60,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={4} />, @@ -79,7 +79,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, @@ -100,7 +100,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={false} skeletonRepeat={3} />, @@ -125,7 +125,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, @@ -149,7 +149,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, @@ -171,7 +171,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, @@ -187,7 +187,7 @@ describe('ListeDesResultats Component', () => { } - pagination={ null}/>} + pagination={ null}/>} isAffichageListeDeResultatsDesktopDirectionRow={true} skeletonRepeat={2} />, diff --git a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementListForModal.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.test.tsx similarity index 76% rename from src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementListForModal.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.test.tsx index 8de6345275..f7e55ef009 100644 --- a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementListForModal.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.test.tsx @@ -8,18 +8,18 @@ import { import { userEvent } from '@testing-library/user-event'; import React from 'react'; -import { MeilisearchCustomRefinementListForModal } from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementListForModal'; +import { MeilisearchCheckboxList } from '~/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList'; import { generateRefinementListItem, mockUseRefinementList, -} from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +} from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; // eslint-disable-next-line @typescript-eslint/no-var-requires const spyed = jest.spyOn(require('react-instantsearch'), 'useRefinementList'); let refineMock: jest.Mock; -describe('MeilisearchCustomRefinementListForModal', () => { +describe('', () => { beforeEach(() => { // GIVEN refineMock = jest.fn(); @@ -35,7 +35,7 @@ describe('MeilisearchCustomRefinementListForModal', () => { }); it('affiche la liste des checkbox', async () => { - render(); + render(); const optionList = screen.getAllByRole('checkbox'); expect(optionList).toHaveLength(3); @@ -45,7 +45,7 @@ describe('MeilisearchCustomRefinementListForModal', () => { it('appelle la méthode refine une fois', async () => { const user = userEvent.setup(); - render(); + render(); const option = screen.getByLabelText('Studio'); await user.click(option); @@ -55,7 +55,7 @@ describe('MeilisearchCustomRefinementListForModal', () => { it('appelle la méthode refine avec la valeur "studio"', async () => { const user = userEvent.setup(); - render(); + render(); const option = screen.getByLabelText('Studio'); await user.click(option); @@ -74,7 +74,7 @@ describe('MeilisearchCustomRefinementListForModal', () => { })); }); it('affiche un message informatif dans à la place de la liste de suggestions', async () => { - render(); + render(); const messageInformatif = screen.getByText('Malheureusement ce champ de recherche ne peut pas être affiché pour le moment.'); expect(messageInformatif).toBeInTheDocument(); }); diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementListForModal.tsx b/src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.tsx similarity index 61% rename from src/client/components/ui/Meilisearch/MeilisearchCustomRefinementListForModal.tsx rename to src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.tsx index 23884f39d6..46794f0d7f 100644 --- a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementListForModal.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchCheckboxList/MeilisearchCheckboxList.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'; -import { Checkbox } from '~/client/components/ui/Checkbox/Checkbox'; -import { getCapitalizedItems } from '~/client/components/ui/Meilisearch/getCapitalizedItems'; +import { Checkbox } from '../../Checkbox/Checkbox'; +import { getCapitalizedItems } from '../getCapitalizedItems'; -interface MeilisearchCustomRefinementListProps { +interface MeilisearchCheckboxListProps { label: string } -export function MeilisearchCustomRefinementListForModal(props: UseRefinementListProps & MeilisearchCustomRefinementListProps) { +export function MeilisearchCheckboxList(props: UseRefinementListProps & MeilisearchCheckboxListProps) { const { refine, items } = useRefinementList(props); if (items.length === 0) return

Malheureusement ce champ de recherche ne peut pas être affiché pour le moment.

; diff --git a/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.test.tsx similarity index 97% rename from src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.test.tsx index d5efae8ae8..99892a674a 100644 --- a/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.test.tsx @@ -6,11 +6,11 @@ import { render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import { KeyBoard } from '~/client/components/keyboard/keyboard.enum'; -import { MeilisearchComboboxLocalisation } from '~/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation'; +import { MeilisearchComboboxLocalisation } from '~/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation'; import { generateRefinementListItem, mockUseRefinementList, -} from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +} from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; import { mockScrollIntoView } from '~/client/components/window.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.tsx b/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.tsx similarity index 96% rename from src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.tsx rename to src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.tsx index 7ab8d49020..e3f0f4582a 100644 --- a/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchComboboxLocalisation/MeilisearchComboboxLocalisation.tsx @@ -3,8 +3,8 @@ import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'; import { KeyBoard } from '~/client/components/keyboard/keyboard.enum'; -import { Champ } from '../Form/Champ/Champ'; -import { Combobox } from '../Form/Combobox'; +import { Champ } from '../../Form/Champ/Champ'; +import { Combobox } from '../../Form/Combobox'; const MESSAGE_PAS_DE_RESULTAT = 'Aucune proposition ne correspond à votre saisie. Vérifiez que votre saisie correspond bien à un lieu. Exemple : Paris, Marseille …'; const NOMBRE_RESULTAT_MAXIMUM = 20; diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.module.scss b/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.module.scss deleted file mode 100644 index 3b51d6759e..0000000000 --- a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.module.scss +++ /dev/null @@ -1,96 +0,0 @@ -@use "@styles/utilities-deprecated"; -@use "@styles/utilities"; -@use "@styles/components/form/variables"; - -.selectContainer { - position: relative; - margin-top: 0.5rem; -} - -.button { - @include utilities.text-interactive-medium; - display: flex; - width: 100%; - justify-content: space-between; - align-items: center; - color: utilities-deprecated.$color-mention-grey; - padding: 6px 6px 6px 12px; - border: 1px solid utilities-deprecated.$color-on-background; - border-radius: 20px; - height: 2.5rem; - - &:focus, &[aria-expanded=true] { - border: 2px solid utilities-deprecated.$color-primary; - } -} - -.options { - z-index: 15; - position: absolute; - padding: 8px; - width: 100%; - border: 1px solid utilities-deprecated.$color-separator; - border-radius: 20px; - background-color: utilities-deprecated.$color-background; - margin: 4px 0 0 0; - max-height: 300px; - overflow-y: auto; -} - -.option { - list-style-type: none; -} - -.option:not(:last-child) { - border-bottom: 1px solid utilities-deprecated.$color-separator; - border-radius: 0; -} - -.label{ - @extend %label-champ; -} - -.checkbox { - position: relative; - - input[type=checkbox] { - position: absolute; - margin: 0; - opacity: 0; - top: 50%; - transform: translateY(-50%); - } - - input[type=checkbox] + span { - position: relative; - padding: 0.75rem 0 .75rem 2rem; - -webkit-tap-highlight-color: transparent; - display: block; - flex-direction: row; - align-items: center; - justify-content: flex-start; - flex-wrap: wrap; - } - - input[type=checkbox] + span::before { - content: ""; - display: block; - position: absolute; - top: 0; - left: 0.5rem; - margin-top: 1rem; - width: 1rem; - height: 1rem; - margin-right: 0.5rem; - background-size: 1rem; - background-position: center; - background-repeat: no-repeat; - border-radius: 0.25rem; - box-shadow: inset 0 0 0 1px utilities-deprecated.$color-on-surface; - } - - input[type=checkbox]:checked + span::before { - background-color: utilities-deprecated.$color-primary; - background-image: url("data:image/svg+xml;charset=utf8,"); - } -} diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx b/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx deleted file mode 100644 index 1a2c5f778f..0000000000 --- a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import classNames from 'classnames'; -import React, { - FocusEvent, - useEffect, - useRef, - useState, -} from 'react'; -import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'; -import { v4 as uuidv4 } from 'uuid'; - -import { KeyBoard } from '~/client/components/keyboard/keyboard.enum'; -import { - handleKeyBoardInteraction, -} from '~/client/components/keyboard/select.keyboard'; -import { Checkbox } from '~/client/components/ui/Checkbox/Checkbox'; -import { Icon } from '~/client/components/ui/Icon/Icon'; -import { getCapitalizedItems } from '~/client/components/ui/Meilisearch/getCapitalizedItems'; -import styles from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.module.scss'; - -interface MeilisearchCustomRefinementListProps extends React.ComponentPropsWithoutRef<'div'> { - label: string - 'data-testid'?: string // FIXME (SULI 29-03-2024): a été ajouté pour faire passer des tests car pas de CSS inclus dans le JSDOM -} - -export function MeilisearchCustomRefinementList(props: UseRefinementListProps & MeilisearchCustomRefinementListProps) { - const { refine, items } = useRefinementList(props); - const { label, className } = props; - - const [isOptionsOpen, setIsOptionsOpen] = useState(false); - const buttonLabel = 'Sélectionnez vos choix'; - const labelledBy = useRef(uuidv4()); - const buttonRef = useRef(null); - const listBoxRef = useRef(null); - - useEffect(function setFocusOnOpen() { - if (isOptionsOpen && items.length > 0) { - const currentItem = listBoxRef.current; - const firstElement = currentItem?.getElementsByTagName('li')[0]; - firstElement?.focus(); - } - }, [isOptionsOpen, items.length]); - - function changeFocusToButtonElement(){ - buttonRef.current?.focus(); - } - - function handleKeyDown (event: React.KeyboardEvent){ - const currentItem = event.currentTarget; - const updateValues = () => { - const currentInput = currentItem.querySelector('input'); - if (currentInput === null) return; - refine(currentInput.value); - }; - handleKeyBoardInteraction(event, currentItem, updateValues); - - if (event.key === KeyBoard.ESCAPE) { - changeFocusToButtonElement(); - setIsOptionsOpen(false); - } - } - - function onBlur(event: FocusEvent) { - if (!event.currentTarget.contains(event.relatedTarget)) { - setIsOptionsOpen(false); - } - } - - const renderOptionList = () => ( -
    - {items.length > 0 && items.map((item) => ( -
  • - refine(item.value)} - /> -
  • - ))} -
- ); - - - if (items.length === 0) return null; - return ( -
- {label} -
- - {isOptionsOpen && renderOptionList()} -
-
- ); -} diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomSearchBox.module.scss b/src/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput.module.scss similarity index 100% rename from src/client/components/ui/Meilisearch/MeilisearchCustomSearchBox.module.scss rename to src/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput.module.scss diff --git a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomSearchBox.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput.test.tsx similarity index 88% rename from src/client/components/ui/Meilisearch/tests/MeilisearchCustomSearchBox.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput.test.tsx index 4fb212a41e..5af234bb76 100644 --- a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomSearchBox.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput.test.tsx @@ -8,15 +8,15 @@ import { } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; -import { MeilisearchCustomSearchBox } from '~/client/components/ui/Meilisearch/MeilisearchCustomSearchBox'; +import { MeilisearchInput } from '~/client/components/ui/Meilisearch/MeilisearchInput/MeilisearchInput'; // eslint-disable-next-line @typescript-eslint/no-var-requires jest.spyOn(require('react-instantsearch'), 'useSearchBox'); -describe('MeilisearchCustomSearchBox Component', () => { +describe('', () => { it('ne retourne pas de form', () => { render( - { it('contient un input associé à label', () => { render( - { it('ne contient pas de button reset quand le champ est vide', () => { render( - { it('contient un button reset quand le champ est rempli', async() => { render( - { it('rend le titre du bouton reset par défaut quand celui ci n‘est pas précisé', async () => { render( - { it('rend le titre du bouton reset passé en paramètre quand celui ci est précisé', async () => { render( - , 'className'> { id?: string @@ -23,7 +22,7 @@ interface MeilisearchCustomSearchBoxProps extends Pick { +export const MeilisearchInput = (props: MeilisearchCustomSearchBoxProps & UseSearchBoxProps) => { const { label, name, diff --git a/src/client/components/ui/Meilisearch/MeilisearchInputRefinement.module.scss b/src/client/components/ui/Meilisearch/MeilisearchInputRefinement.module.scss deleted file mode 100644 index 4ea086d9b3..0000000000 --- a/src/client/components/ui/Meilisearch/MeilisearchInputRefinement.module.scss +++ /dev/null @@ -1,65 +0,0 @@ -@use "@styles/utilities-deprecated"; -@use "@styles/utilities"; -@use "@styles/components/form/variables"; - -.suggestionList { - z-index: 15; - position: absolute; - padding: 8px; - border: 1px solid utilities-deprecated.$color-separator; - border-radius: 20px; - background-color: utilities-deprecated.$color-background; - margin: 4px 0 0 0; - max-height: 300px; - overflow-y: auto; - width: 100%; -} - -.suggestionList > li { - padding-left: 0.5rem; - cursor: pointer; -} - -.suggestionList > li:hover, .hover, .suggestionList > li[aria-selected=true] { - background-color: rgba(3, 15, 143, .05); -} - -.suggestionList > li:first-letter { - text-transform: capitalize; -} - -.aucunRésultat { - padding: 0.5rem; -} - -.formInput { - position: relative; - - & .label { - @extend %label-champ; - } -} - -.formControl { - &Input { - @include utilities.text-interactive-medium; - display: flex; - align-items: center; - width: 100%; - height: 2.5rem; - padding: 0 16px; - border-radius: 1.25rem; - border: solid 1px utilities-deprecated.$color-on-background; - background-color: utilities-deprecated.$color-background; - margin-top: 0.5rem; - - &:active, &:focus { - border-color: utilities-deprecated.$color-primary; - } - - &::placeholder { - color: utilities-deprecated.$deep-neutral-grey; - opacity: 1; - } - } -} diff --git a/src/client/components/ui/Meilisearch/MeiliSearchCustomPagination.tsx b/src/client/components/ui/Meilisearch/MeilisearchPagination/MeiliSearchPagination.tsx similarity index 81% rename from src/client/components/ui/Meilisearch/MeiliSearchCustomPagination.tsx rename to src/client/components/ui/Meilisearch/MeilisearchPagination/MeiliSearchPagination.tsx index 7f3d90b827..4a279c9c3e 100644 --- a/src/client/components/ui/Meilisearch/MeiliSearchCustomPagination.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchPagination/MeiliSearchPagination.tsx @@ -5,14 +5,14 @@ import { usePagination } from 'react-instantsearch'; import type { UsePaginationProps } from 'react-instantsearch-core/dist/es/connectors/usePagination'; // NOTE (BRUJ 06/05/2024): Pour éviter les hydratation mismatch lié au usebreakpoint on désactive le srr sur des composants spécifiques cf https://nextjs.org/docs/messages/react-hydration-error#solution-2-disabling-ssr-on-specific-components -const CommonPagination = dynamic(() => import('../Pagination/CommonPagination').then((mod) => mod.CommonPagination), { ssr: false }); +const CommonPagination = dynamic(() => import('../../Pagination/CommonPagination').then((mod) => mod.CommonPagination), { ssr: false }); -interface MeiliSearchCustomPaginationProps extends Pick, 'className'> { +interface MeilisearchPaginationProps extends Pick, 'className'> { numberOfResultPerPage: number onPageChange: () => void } -export function MeiliSearchCustomPagination(props: MeiliSearchCustomPaginationProps & UsePaginationProps) { +export function MeiliSearchPagination(props: MeilisearchPaginationProps & UsePaginationProps) { const { numberOfResultPerPage, onPageChange, className } = props; const { currentRefinement, diff --git a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomPagination.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchPagination/MeilisearchPagination.test.tsx similarity index 80% rename from src/client/components/ui/Meilisearch/tests/MeilisearchCustomPagination.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchPagination/MeilisearchPagination.test.tsx index 0fad03e7cc..342e22638a 100644 --- a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomPagination.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchPagination/MeilisearchPagination.test.tsx @@ -7,8 +7,8 @@ import { render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import React from 'react'; -import { MeiliSearchCustomPagination } from '~/client/components/ui/Meilisearch/MeiliSearchCustomPagination'; -import { mockUsePagination } from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +import { MeiliSearchPagination } from '~/client/components/ui/Meilisearch/MeilisearchPagination/MeiliSearchPagination'; +import { mockUsePagination } from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; import { mockLargeScreen } from '~/client/components/window.mock'; declare type CreateURL = (value: TValue) => string; @@ -26,7 +26,7 @@ const ALLER_A_LA_DERNIERE_PAGE = 'Aller à la dernière page'; const mockFunctionScrollToTopOfListeDesResultats = jest.fn(); -describe('MeilisearchCustomPagination', () => { +describe('', () => { beforeEach(() => { mockLargeScreen(); }); @@ -46,7 +46,7 @@ describe('MeilisearchCustomPagination', () => { it('Revenir à la première page et Revenir à la page précédente doivent etre disable et Aller à la page suivante et Aller à la dernière page doivent etre enable', () => { // WHEN render( - , + , ); // THEN expect(screen.getByRole('link', { name: REVENIR_A_LA_PREMIERE_PAGE }).getAttribute('aria-disabled')).toBe('true'); @@ -65,7 +65,7 @@ describe('MeilisearchCustomPagination', () => { it('Revenir à la première page, Revenir à la page précédente, Aller à la page suivante, Aller à la dernière page doivent etre enable', async () => { // WHEN render( - , + , ); // THEN expect(screen.getByRole('link', { name: REVENIR_A_LA_PREMIERE_PAGE }).getAttribute('aria-disabled')).toBe('false'); @@ -77,7 +77,7 @@ describe('MeilisearchCustomPagination', () => { it('affiche "…" dans le document', async () => { // WHEN render( - , + , ); // THEN expect(screen.getByText('…')).toBeInTheDocument(); @@ -93,7 +93,7 @@ describe('MeilisearchCustomPagination', () => { it('n’affiche pas "…" dans le document', async () => { // WHEN render( - , + , ); // THEN expect(screen.queryByText('…')).not.toBeInTheDocument(); @@ -109,7 +109,7 @@ describe('MeilisearchCustomPagination', () => { it('n’affiche pas "…" dans le document', async () => { // WHEN render( - , + , ); // THEN expect(screen.queryByText('…')).not.toBeInTheDocument(); @@ -125,7 +125,7 @@ describe('MeilisearchCustomPagination', () => { it('Revenir à la première page, Revenir à la page précédente doivent etre enable et Aller à la page suivante, Aller à la dernière page doivent etre disable', async () => { // WHEN render( - , + , ); // THEN expect(screen.getByRole('link', { name: REVENIR_A_LA_PREMIERE_PAGE }).getAttribute('aria-disabled')).toBe('false'); @@ -137,7 +137,7 @@ describe('MeilisearchCustomPagination', () => { it('n’affiche pas "…" dans le document', async () => { // WHEN render( - , + , ); // THEN expect(screen.queryByText('…')).not.toBeInTheDocument(); @@ -166,7 +166,7 @@ describe('MeilisearchCustomPagination', () => { it('affiche une liste', () => { // WHEN render( - , + , ); // THEN expect(screen.getByRole('list')).toBeInTheDocument(); @@ -175,7 +175,7 @@ describe('MeilisearchCustomPagination', () => { it('affiche 2 (première page et précédent) + 4 (pages) + 2 (prochain et dernière page) éléments', () => { // WHEN render( - , + , ); // THEN const pagination = screen.getByRole('list'); @@ -186,7 +186,7 @@ describe('MeilisearchCustomPagination', () => { it('affiche 1, 2, 3 et 4 en lien dans les éléments de la liste', () => { // WHEN render( - , + , ); // THEN expect(screen.getByRole('link', { current: false, name: '1' })).toBeInTheDocument(); @@ -199,7 +199,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: '1' }); @@ -215,7 +215,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { name: REVENIR_A_LA_PREMIERE_PAGE }); // WHEN @@ -229,7 +229,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: '1' }); // WHEN @@ -243,7 +243,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: REVENIR_A_LA_PAGE_PRECEDENTE }); // WHEN @@ -257,7 +257,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: '2' }); // WHEN @@ -271,7 +271,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: ALLER_A_LA_PAGE_SUIVANTE }); // WHEN @@ -285,7 +285,7 @@ describe('MeilisearchCustomPagination', () => { // GIVEN const user = userEvent.setup(); render( - , + , ); const lien = screen.getByRole('link', { current: false, name: ALLER_A_LA_PAGE_SUIVANTE }); // WHEN @@ -317,7 +317,7 @@ describe('MeilisearchCustomPagination', () => { it('n affiche pas la pagination', () => { // WHEN render( - , + , ); // THEN expect(screen.queryByRole('list')).not.toBeInTheDocument(); @@ -345,7 +345,7 @@ describe('MeilisearchCustomPagination', () => { it('n affiche pas la pagination', () => { // WHEN render( - , + , ); // THEN expect(screen.queryByRole('list')).not.toBeInTheDocument(); diff --git a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRangeInput.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearcRange.test.tsx similarity index 96% rename from src/client/components/ui/Meilisearch/tests/MeilisearchCustomRangeInput.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearcRange.test.tsx index 6fbd291f52..9110ce6ee2 100644 --- a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRangeInput.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearcRange.test.tsx @@ -11,10 +11,10 @@ import { import { userEvent } from '@testing-library/user-event'; import React from 'react'; -import { MeilisearchCustomRangeInput } from '~/client/components/ui/Meilisearch/MeilisearchCustomRangeInput'; +import { MeilisearchRange } from '~/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange'; import { mockUseRangeInput, -} from '~/client/components/ui/Meilisearch/tests/mockMeilisearchUseFunctions'; +} from '~/client/components/ui/Meilisearch/mockMeilisearchUseFunctions'; // eslint-disable-next-line @typescript-eslint/no-var-requires const spyOnUseRange = jest.spyOn(require('react-instantsearch'), 'useRange'); @@ -23,7 +23,7 @@ let refineMock: jest.Mock; const renderMeilisearchCustomRangeInputComponent = () => { render( - { ); }; -describe('MeilisearchCustomRangeInput', ()=> { +describe('', ()=> { beforeEach(() => { refineMock = jest.fn(); spyOnUseRange.mockImplementation(() => mockUseRangeInput({ diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.module.scss b/src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange.module.scss similarity index 100% rename from src/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.module.scss rename to src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange.module.scss diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.tsx b/src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange.tsx similarity index 89% rename from src/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.tsx rename to src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange.tsx index b30aef28db..2a43f3f7b6 100644 --- a/src/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchRange/MeilisearchRange.tsx @@ -3,12 +3,12 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useRange, UseRangeProps } from 'react-instantsearch'; import { v4 as uuidv4 } from 'uuid'; -import { KeyBoard } from '~/client/components/keyboard/keyboard.enum'; -import { ButtonComponent } from '~/client/components/ui/Button/ButtonComponent'; -import { Icon } from '~/client/components/ui/Icon/Icon'; -import styles from '~/client/components/ui/Meilisearch/MeilisearchCustomRangeInput.module.scss'; +import { KeyBoard } from '../../../keyboard/keyboard.enum'; +import { ButtonComponent } from '../../Button/ButtonComponent'; +import { Icon } from '../../Icon/Icon'; +import styles from './MeilisearchRange.module.scss'; -interface MeilisearchCustomRangeInputProps extends Pick, 'className'> { +interface MeilisearchRangeProps extends Pick, 'className'> { label: string placeholder: string unite: string @@ -17,7 +17,7 @@ interface MeilisearchCustomRangeInputProps extends Pick; const renderMeilisearchCustomRangeInputForModalComponent = () => { render( - { + beforeEach(() => { + mockScrollIntoView(); + }); + + it('je vois le select avec son label', () => { + render(); + + const select = screen.getByRole('combobox', { name: 'test' }); + expect(select).toBeVisible(); + }); + + it('le label des options débute par une majuscule', async () => { + const user = userEvent.setup(); + spyed.mockImplementation(() => mockUseRefinementList({ + items: [ + generateRefinementListItem({ label: 'audit', value: 'auditeur' }), + generateRefinementListItem({ label: 'dev', value: 'developpeur' }), + ], + refine: jest.fn(), + })); + + render(); + + await user.click(screen.getByRole('combobox', { name: 'test' })); + + expect(screen.getByRole('option', { name: 'Audit' })).toBeVisible(); + expect(screen.getByRole('option', { name: 'Dev' })).toBeVisible(); + }); + + it('lorsque l‘utilisateur selectionne une option, refine est appelé avec l‘option selectionnée', async () => { + const user = userEvent.setup(); + const refine = jest.fn(); + spyed.mockImplementation(() => mockUseRefinementList({ + items: [ + generateRefinementListItem({ label: 'audit', value: 'auditeur' }), + generateRefinementListItem({ label: 'dev', value: 'developpeur' }), + ], + refine, + })); + + render(); + + await user.click(screen.getByRole('combobox', { name: 'test' })); + await user.click(screen.getByRole('option', { name: 'Audit' })); + + expect(refine).toHaveBeenCalledTimes(1); + expect(refine).toHaveBeenCalledWith('auditeur'); + }); + + it('lorsque l‘utilisateur a séléctionné une option, l‘option est séléctionnée', () => { + const refine = jest.fn(); + spyed.mockImplementation(() => mockUseRefinementList({ + items: [ + generateRefinementListItem({ isRefined: true, label: 'audit', value: 'auditeur' }), + generateRefinementListItem({ isRefined: true, label: 'dev', value: 'developpeur' }), + generateRefinementListItem({ isRefined: false, label: 'cuisinier', value: 'cuisinier' }), + ], + refine, + })); + + render(); + + expect(screen.getByRole('option', { hidden: true, name: 'Audit' })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByRole('option', { hidden: true, name: 'Dev' })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByRole('option', { hidden: true, name: 'Cuisinier' })).toHaveAttribute('aria-selected', 'false'); + }); +}); diff --git a/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx new file mode 100644 index 0000000000..6d52ef32fb --- /dev/null +++ b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx @@ -0,0 +1,37 @@ +import React, { useCallback, useMemo } from 'react'; +import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'; + +import { getCapitalizedItems } from '~/client/components/ui/Meilisearch/getCapitalizedItems'; + +import { OptionSelect, Select } from '../../Form/Select/Select'; + +// TODO (BRUJ 08/07/2024): A supprimer le Omit lors du passage des options en composition +type MeilisearchSelectMultipleProps = Omit, 'optionList'> + +export function MeilisearchSelectMultiple(props: UseRefinementListProps & MeilisearchSelectMultipleProps) { + const { refine, items } = useRefinementList(props); + const { label, className } = props; + + // TODO (BRUJ 08/07/2024): A supprimer lors du passage des options en composition + const optionsList: Array = useMemo(() => { + return items.map((item) => ({ + libellé: getCapitalizedItems(item.label), + valeur: item.value, + })); + }, [items]); + + const valuesSelected = useMemo(() => { + return items.filter((item) => item.isRefined) + .map((item) => item.value); + }, [items]); + + const onOptionSelected = useCallback((option: HTMLElement) => { + const value = option.getAttribute('data-value'); + if (value) refine(value); + }, [refine]); + + return ( +