From 437a6ee2bff194f6b05a920e9bb1ac943a88cef6 Mon Sep 17 00:00:00 2001 From: Ornella <68587983+Ornella452@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:45:09 +0100 Subject: [PATCH 01/11] Fix: retour formulaire (#247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * retour formulaire : Modif wording + ajout limit 2500 caractères + correction trim siret + correctif required siret * modification telephone format 01.. ou +331... * retour reviews code --- src/components/commun/Input.jsx | 6 ++- src/components/commun/ZoneDeTexte.jsx | 5 +- .../CandidatureConseiller.test.jsx | 23 ++++---- .../InformationsDeContact.jsx | 4 +- .../candidature-conseiller/Motivation.jsx | 2 +- .../CandidatureCoordinateur.test.jsx | 42 ++++++++------- .../CompanyFinder.jsx | 3 +- .../candidature-coordinateur/Motivation.jsx | 2 +- .../CandidatureStructure.test.jsx | 52 +++++++++---------- .../candidature-structure/CompanyFinder.jsx | 5 +- .../candidature-structure/Engagement.jsx | 2 +- .../InformationsDeContact.jsx | 7 +-- .../candidature-structure/Motivation.jsx | 2 +- .../SommaireStructure.jsx | 2 +- 14 files changed, 84 insertions(+), 73 deletions(-) diff --git a/src/components/commun/Input.jsx b/src/components/commun/Input.jsx index e1a00094..682c965b 100644 --- a/src/components/commun/Input.jsx +++ b/src/components/commun/Input.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; export default function Input({ children, id, isRequired = true, autoComplete = 'on', testId = '', type = 'text', - pattern, onChange, list, min, disabled, isLoading, ariaBusy, value }) { + pattern, onChange, list, min, disabled, isLoading, ariaBusy, value, maxlength }) { return (
@@ -14,12 +14,13 @@ export default function Input({ children, id, isRequired = true, autoComplete = required={isRequired} autoComplete={autoComplete} pattern={pattern} + maxlength={maxlength} onChange={onChange} list={list} min={min} disabled={disabled} name={id} - value={value?.trim()} + value={value} aria-busy={ariaBusy} data-testid={testId} /> @@ -48,4 +49,5 @@ Input.propTypes = { ariaBusy: PropTypes.bool, value: PropTypes.string, testId: PropTypes.string, + maxlength: PropTypes.string }; diff --git a/src/components/commun/ZoneDeTexte.jsx b/src/components/commun/ZoneDeTexte.jsx index 037e4a67..55ed95e5 100644 --- a/src/components/commun/ZoneDeTexte.jsx +++ b/src/components/commun/ZoneDeTexte.jsx @@ -1,13 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -export default function ZoneDeTexte({ children, id, isRequired = true }) { +export default function ZoneDeTexte({ children, id, isRequired = true, maxlength = "2500" }) { return (
- +
); } @@ -16,4 +16,5 @@ ZoneDeTexte.propTypes = { children: PropTypes.node, id: PropTypes.string, isRequired: PropTypes.bool, + maxlength: PropTypes.string, }; diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index a4bbeae5..3166da8b 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -60,9 +60,9 @@ describe('candidature conseiller', () => { expect(email).toHaveAttribute('type', 'email'); expect(email).toBeRequired(); - const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone Format attendu : +33122334455'); + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone Format attendu : 0122334455 ou +33122334455'); expect(telephone).toHaveAttribute('type', 'tel'); - expect(telephone).toHaveAttribute('pattern', '[+](33|590|596|594|262|269|687)[0-9]{9}'); + expect(telephone).toHaveAttribute('pattern', '([+][0-9]{11,12})|([0-9]{10})'); const habitation = within(etapeInformationsDeContact).getByLabelText(('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.')); expect(habitation).toHaveAttribute('type', 'text'); @@ -225,8 +225,9 @@ describe('candidature conseiller', () => { ); expect(aideMotivation).toBeInTheDocument(); - const descriptionMotivation = within(votreMotivation).getByLabelText('Votre message *'); + const descriptionMotivation = within(votreMotivation).getByLabelText('Votre message * Limité à 2500 caractères'); expect(descriptionMotivation).toHaveAttribute('name', 'motivation'); + expect(descriptionMotivation).toHaveAttribute('maxlength', '2500'); expect(descriptionMotivation).toBeRequired(); }); @@ -381,7 +382,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN @@ -418,7 +419,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN @@ -462,7 +463,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN @@ -511,7 +512,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN @@ -549,7 +550,7 @@ describe('candidature conseiller', () => { fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.'); fireEvent.change(adresse, { target: { value: '93100 Montreuil' } }); - const telephone = screen.getByLabelText('Téléphone Format attendu : +33122334455'); + const telephone = screen.getByLabelText('Téléphone Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33159590730' } }); const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' }); fireEvent.click(enEmploi); @@ -559,7 +560,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN @@ -691,7 +692,7 @@ describe('candidature conseiller', () => { fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.'); fireEvent.change(adresse, { target: { value: '93100 Montreuil' } }); - const telephone = screen.getByLabelText('Téléphone Format attendu : +33122334455'); + const telephone = screen.getByLabelText('Téléphone Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33159590730' } }); const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' }); fireEvent.click(enEmploi); @@ -701,7 +702,7 @@ describe('candidature conseiller', () => { fireEvent.change(date, { target: { value: dateDujour() } }); const _5km = screen.getByRole('radio', { name: '5 km' }); fireEvent.click(_5km); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); // WHEN diff --git a/src/views/candidature-conseiller/InformationsDeContact.jsx b/src/views/candidature-conseiller/InformationsDeContact.jsx index 0e4b6bd7..3f8f6fc1 100644 --- a/src/views/candidature-conseiller/InformationsDeContact.jsx +++ b/src/views/candidature-conseiller/InformationsDeContact.jsx @@ -26,10 +26,10 @@ export default function InformationsDeContact() { - Téléphone Format attendu : +33122334455 + Téléphone Format attendu : 0122334455 ou +33122334455 diff --git a/src/views/candidature-conseiller/Motivation.jsx b/src/views/candidature-conseiller/Motivation.jsx index eec77939..74602da7 100644 --- a/src/views/candidature-conseiller/Motivation.jsx +++ b/src/views/candidature-conseiller/Motivation.jsx @@ -10,7 +10,7 @@ export default function Motivation() { aider les personnes à devenir autonomes dans l’utilisation des outils numériques.

- Votre message * + Votre message * Limité à 2500 caractères ); diff --git a/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx b/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx index 46dc99cb..da324a26 100644 --- a/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx +++ b/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx @@ -49,6 +49,10 @@ describe('candidature coordinateur', () => { const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); + const siretOuRidet = within(etapeInformationsDeStructure).getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + expect(siretOuRidet).toHaveAttribute('id', 'siret'); + expect(siretOuRidet).toBeRequired(); + const denomination = within(etapeInformationsDeStructure).getByLabelText('Dénomination *'); expect(denomination).toHaveAttribute('type', 'text'); expect(denomination).toBeRequired(); @@ -111,13 +115,13 @@ describe('candidature coordinateur', () => { expect(fonction).toHaveAttribute('type', 'text'); expect(fonction).toBeRequired(); - const email = within(etapeInformationsDeContact).getByLabelText('Adresse électronique *'); + const email = within(etapeInformationsDeContact).getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); expect(email).toHaveAttribute('type', 'email'); expect(email).toBeRequired(); - const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); expect(telephone).toHaveAttribute('type', 'tel'); - expect(telephone).toHaveAttribute('pattern', '[+](33|590|596|594|262|269|687)[0-9]{9}'); + expect(telephone).toHaveAttribute('pattern', '([+][0-9]{11,12})|([0-9]{10})'); expect(telephone).toBeRequired(); }); @@ -190,7 +194,7 @@ describe('candidature coordinateur', () => { ); expect(sousTitreVotreMotvation).toBeInTheDocument(); - const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); + const votreMessage = within(etapeMotivation).getByLabelText('Votre message * Limité à 2500 caractères'); expect(votreMessage).toHaveAttribute('id', 'motivation'); expect(votreMessage).toBeRequired(); @@ -262,8 +266,8 @@ describe('candidature coordinateur', () => { }); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -276,9 +280,9 @@ describe('candidature coordinateur', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const identificationCandidat = screen.getByRole('radio', { name: 'Oui' }); fireEvent.click(identificationCandidat); @@ -286,7 +290,7 @@ describe('candidature coordinateur', () => { fireEvent.click(typeMission); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); @@ -321,8 +325,8 @@ describe('candidature coordinateur', () => { vi.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(mockNavigate); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -335,9 +339,9 @@ describe('candidature coordinateur', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const identificationCandidat = screen.getByRole('radio', { name: 'Oui' }); fireEvent.click(identificationCandidat); @@ -345,7 +349,7 @@ describe('candidature coordinateur', () => { fireEvent.click(typeMission); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); @@ -487,8 +491,8 @@ describe('candidature coordinateur', () => { }); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -501,9 +505,9 @@ describe('candidature coordinateur', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const identificationCandidat = screen.getByRole('radio', { name: 'Oui' }); fireEvent.click(identificationCandidat); @@ -511,7 +515,7 @@ describe('candidature coordinateur', () => { fireEvent.click(typeMission); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); diff --git a/src/views/candidature-coordinateur/CompanyFinder.jsx b/src/views/candidature-coordinateur/CompanyFinder.jsx index 8df16d4b..326bf19e 100644 --- a/src/views/candidature-coordinateur/CompanyFinder.jsx +++ b/src/views/candidature-coordinateur/CompanyFinder.jsx @@ -10,9 +10,10 @@ export default function CompanyFinder() { <> search(event.target.value))} placeholder="N° SIRET / RIDET" + pattern="^[0-9]{9}|[0-9]{14}$" + maxlength="14" /> {entreprise} diff --git a/src/views/candidature-coordinateur/Motivation.jsx b/src/views/candidature-coordinateur/Motivation.jsx index 38bc2eae..b1525510 100644 --- a/src/views/candidature-coordinateur/Motivation.jsx +++ b/src/views/candidature-coordinateur/Motivation.jsx @@ -10,7 +10,7 @@ export default function Motivation() { Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.

- Votre message * + Votre message * Limité à 2500 caractères
  • diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 2880f993..79c266af 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -33,7 +33,7 @@ describe('candidature structure', () => { const informationsDeContact = within(menuItems[1]).getByRole('link', { name: 'Vos informations de contact' }); expect(informationsDeContact).toHaveAttribute('href', '#informations-de-contact'); - const votreBesoinEnConseillerNumerique = within(menuItems[2]).getByRole('link', { name: 'Votre besoin en conseiller numérique' }); + const votreBesoinEnConseillerNumerique = within(menuItems[2]).getByRole('link', { name: 'Votre besoin en conseiller(s) numérique(s)' }); expect(votreBesoinEnConseillerNumerique).toHaveAttribute('href', '#votre-besoin-en-conseiller-numerique'); const votreMotivation = within(menuItems[3]).getByRole('link', { name: 'Votre motivation' }); expect(votreMotivation).toHaveAttribute('href', '#votre-motivation'); @@ -48,7 +48,7 @@ describe('candidature structure', () => { const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); - const siretOuRidet = within(etapeInformationsDeStructure).getByLabelText('SIRET / RIDET *'); + const siretOuRidet = within(etapeInformationsDeStructure).getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); expect(siretOuRidet).toHaveAttribute('id', 'siret'); expect(siretOuRidet).toBeRequired(); @@ -115,13 +115,13 @@ describe('candidature structure', () => { expect(fonction).toHaveAttribute('type', 'text'); expect(fonction).toBeRequired(); - const email = within(etapeInformationsDeContact).getByLabelText('Adresse électronique *'); + const email = within(etapeInformationsDeContact).getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); expect(email).toHaveAttribute('type', 'email'); expect(email).toBeRequired(); - const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); expect(telephone).toHaveAttribute('type', 'tel'); - expect(telephone).toHaveAttribute('pattern', '[+](33|590|596|594|262|269|687)[0-9]{9}'); + expect(telephone).toHaveAttribute('pattern', '([+][0-9]{11,12})|([0-9]{10})'); expect(telephone).toBeRequired(); }); @@ -188,7 +188,7 @@ describe('candidature structure', () => { ); expect(sousTitreVotreMotvation).toBeInTheDocument(); - const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); + const votreMessage = within(etapeMotivation).getByLabelText('Votre message * Limité à 2500 caractères'); expect(votreMessage).toHaveAttribute('id', 'motivation'); expect(votreMessage).toBeRequired(); }); @@ -211,7 +211,7 @@ describe('candidature structure', () => { within(listDetail[2]).getByText('Qu’il revête une tenue vestimentaire dédiée fournie par l’Etat,'); within(listDetail[3]).getByText('Tout mettre en oeuvre pour sélectionner le candidat dans un délai maximum d’un mois sur la plateforme,'); within(listDetail[4]).getByText('Signer dans les 15 jours suivants un contrat avec ce candidat,'); - within(listDetail[5]).getByText('Laisser partir le conseiller numérique France Services en formation initiale ou continue,'); + within(listDetail[5]).getByText('Laisser partir le conseiller numérique en formation initiale ou continue,'); within(listDetail[6]).getByText('Mettre à sa disposition les moyens et ' + 'équipements pour réaliser sa mission (ordinateur, téléphone portable, voiture si nécessaire),'); @@ -245,7 +245,7 @@ describe('candidature structure', () => { render(); // WHEN - const siretInput = screen.getByLabelText('SIRET / RIDET *'); + const siretInput = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); fireEvent.change(siretInput, { target: { value: '13002603200016' } }); // THEN @@ -274,7 +274,7 @@ describe('candidature structure', () => { render(); // WHEN - const ridetInput = screen.getByLabelText('SIRET / RIDET *'); + const ridetInput = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); fireEvent.change(ridetInput, { target: { value: '1071539' } }); // THEN @@ -289,7 +289,7 @@ describe('candidature structure', () => { render(); // WHEN - const siretInput = screen.getByLabelText('SIRET / RIDET *'); + const siretInput = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); fireEvent.change(siretInput, { target: { value: '1300260320001' } }); // THEN @@ -314,7 +314,7 @@ describe('candidature structure', () => { render(); // WHEN - const siretInput = screen.getByLabelText('SIRET / RIDET *'); + const siretInput = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); fireEvent.change(siretInput, { target: { value: '13002603200016' } }); // THEN @@ -355,8 +355,8 @@ describe('candidature structure', () => { }); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -369,9 +369,9 @@ describe('candidature structure', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const nombre = screen.getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ? *'); fireEvent.change(nombre, { target: { value: 1 } }); @@ -379,7 +379,7 @@ describe('candidature structure', () => { fireEvent.click(identificationCandidat); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); @@ -414,8 +414,8 @@ describe('candidature structure', () => { vi.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(mockNavigate); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -428,9 +428,9 @@ describe('candidature structure', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const nombre = screen.getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ? *'); fireEvent.change(nombre, { target: { value: 1 } }); @@ -438,7 +438,7 @@ describe('candidature structure', () => { fireEvent.click(identificationCandidat); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); @@ -581,8 +581,8 @@ describe('candidature structure', () => { }); render(); - const siret = screen.getByLabelText('SIRET / RIDET *'); - fireEvent.change(siret, { target: { value: '1234567890123' } }); + const siret = screen.getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + fireEvent.change(siret, { target: { value: '12345678901234' } }); const denomination = screen.getByLabelText('Dénomination *'); fireEvent.change(denomination, { target: { value: 'Entreprise' } }); const adresse = screen.getByLabelText('Adresse *'); @@ -595,9 +595,9 @@ describe('candidature structure', () => { fireEvent.change(nom, { target: { value: 'Dupont' } }); const fonction = screen.getByLabelText('Fonction *'); fireEvent.change(fonction, { target: { value: 'Test' } }); - const email = screen.getByLabelText('Adresse électronique *'); + const email = screen.getByLabelText('Adresse électronique * Format attendu : nom@domaine.fr'); fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); - const telephone = screen.getByLabelText('Téléphone *'); + const telephone = screen.getByLabelText('Téléphone * Format attendu : 0122334455 ou +33122334455'); fireEvent.change(telephone, { target: { value: '+33123456789' } }); const nombre = screen.getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ? *'); fireEvent.change(nombre, { target: { value: 1 } }); @@ -605,7 +605,7 @@ describe('candidature structure', () => { fireEvent.click(identificationCandidat); const date = screen.getByLabelText('Choisir une date'); fireEvent.change(date, { target: { value: dateDujour() } }); - const descriptionMotivation = screen.getByLabelText('Votre message *'); + const descriptionMotivation = screen.getByLabelText('Votre message * Limité à 2500 caractères'); fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' }); fireEvent.click(confirmation); diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx index 8e2c7d91..de29c4b4 100644 --- a/src/views/candidature-structure/CompanyFinder.jsx +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -11,10 +11,11 @@ export default function CompanyFinder({ onSearch }) { return ( handleSearch(event.target.value)} + pattern="^(?:[0-9]{9}|[0-9]{14})$" + maxlength="14" > - SIRET / RIDET * + SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789) ); } diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx index aacaa4b9..94167b96 100644 --- a/src/views/candidature-structure/Engagement.jsx +++ b/src/views/candidature-structure/Engagement.jsx @@ -12,7 +12,7 @@ export default function Engagement() {
  • Qu’il revête une tenue vestimentaire dédiée fournie par l’Etat,
  • Tout mettre en oeuvre pour sélectionner le candidat dans un délai maximum d’un mois sur la plateforme,
  • Signer dans les 15 jours suivants un contrat avec ce candidat,
  • -
  • Laisser partir le conseiller numérique France Services en formation initiale ou continue,
  • +
  • Laisser partir le conseiller numérique en formation initiale ou continue,
  • Mettre à sa disposition les moyens et équipements pour réaliser sa mission (ordinateur, téléphone portable, voiture si nécessaire),
diff --git a/src/views/candidature-structure/InformationsDeContact.jsx b/src/views/candidature-structure/InformationsDeContact.jsx index 74756d7e..d866b8fe 100644 --- a/src/views/candidature-structure/InformationsDeContact.jsx +++ b/src/views/candidature-structure/InformationsDeContact.jsx @@ -29,15 +29,16 @@ export default function InformationsDeContact() { name="email" type="email" > - Adresse électronique * + Adresse électronique * Format attendu : nom@domaine.fr - Téléphone * + Téléphone *{' '} + Format attendu : 0122334455 ou +33122334455 ); diff --git a/src/views/candidature-structure/Motivation.jsx b/src/views/candidature-structure/Motivation.jsx index cd0507a8..0004c646 100644 --- a/src/views/candidature-structure/Motivation.jsx +++ b/src/views/candidature-structure/Motivation.jsx @@ -10,7 +10,7 @@ export default function Motivation() { Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.

- Votre message * + Votre message * Limité à 2500 caractères ); diff --git a/src/views/candidature-structure/SommaireStructure.jsx b/src/views/candidature-structure/SommaireStructure.jsx index 510fa214..ed1934fc 100644 --- a/src/views/candidature-structure/SommaireStructure.jsx +++ b/src/views/candidature-structure/SommaireStructure.jsx @@ -13,7 +13,7 @@ export default function SommaireStructure() { }, { ancre: '#votre-besoin-en-conseiller-numerique', - libelle: 'Votre besoin en conseiller numérique' + libelle: 'Votre besoin en conseiller(s) numérique(s)' }, { ancre: '#votre-motivation', From ab5566e40948cb2adffcaed84e9924fd351cf72f Mon Sep 17 00:00:00 2001 From: Ornella <68587983+Ornella452@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:29:06 +0100 Subject: [PATCH 02/11] migrate hcaptcha vers turnstile (captcha cloudflare) (#248) --- index.html | 2 +- src/components/commun/Captcha.jsx | 15 +++++++++++---- src/components/commun/Input.jsx | 2 +- src/components/commun/ZoneDeTexte.jsx | 2 +- .../candidature-conseiller/AddressChooser.jsx | 2 +- .../CandidatureConseiller.jsx | 7 ++++--- .../CandidatureConseiller.test.jsx | 16 +++++++++------- src/views/candidature-conseiller/useGeoApi.js | 4 ++-- .../CandidatureCoordinateur.jsx | 7 ++++--- .../CandidatureCoordinateur.test.jsx | 14 ++++++++------ .../CandidatureStructure.jsx | 7 ++++--- .../CandidatureStructure.test.jsx | 14 ++++++++------ 12 files changed, 54 insertions(+), 38 deletions(-) diff --git a/index.html b/index.html index fbbab0c0..79e10e15 100644 --- a/index.html +++ b/index.html @@ -43,6 +43,6 @@ })(); - + diff --git a/src/components/commun/Captcha.jsx b/src/components/commun/Captcha.jsx index 390d4806..184962d4 100644 --- a/src/components/commun/Captcha.jsx +++ b/src/components/commun/Captcha.jsx @@ -1,15 +1,18 @@ import React, { useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; -const SITE_KEY = '84e24b30-44ca-488c-9260-ec80c290c166'; +const SITE_KEY = '0x4AAAAAAA0pjEgohPDZsyqu'; -export default function Captcha() { +export default function Captcha({ setWidgetId }) { const captchaRef = useRef(null); useEffect(() => { - if (window.hcaptcha) { - window.hcaptcha.render(captchaRef.current, { + if (window.turnstile) { + window.turnstile.remove(); + const widgetId = window.turnstile.render(captchaRef.current, { sitekey: SITE_KEY, }); + setWidgetId(widgetId); } }, []); @@ -17,3 +20,7 @@ export default function Captcha() {
); } + +Captcha.propTypes = { + setWidgetId: PropTypes.func, +}; diff --git a/src/components/commun/Input.jsx b/src/components/commun/Input.jsx index 682c965b..e972c23d 100644 --- a/src/components/commun/Input.jsx +++ b/src/components/commun/Input.jsx @@ -14,7 +14,7 @@ export default function Input({ children, id, isRequired = true, autoComplete = required={isRequired} autoComplete={autoComplete} pattern={pattern} - maxlength={maxlength} + maxLength={maxlength} onChange={onChange} list={list} min={min} diff --git a/src/components/commun/ZoneDeTexte.jsx b/src/components/commun/ZoneDeTexte.jsx index 55ed95e5..5c6bfc9d 100644 --- a/src/components/commun/ZoneDeTexte.jsx +++ b/src/components/commun/ZoneDeTexte.jsx @@ -7,7 +7,7 @@ export default function ZoneDeTexte({ children, id, isRequired = true, maxlength - +
); } diff --git a/src/views/candidature-conseiller/AddressChooser.jsx b/src/views/candidature-conseiller/AddressChooser.jsx index 194357a0..7f054f14 100644 --- a/src/views/candidature-conseiller/AddressChooser.jsx +++ b/src/views/candidature-conseiller/AddressChooser.jsx @@ -25,7 +25,7 @@ export default function AddressChooser() { - {villes.map(({ codesPostaux, nom }, key) => ( + {villes?.map(({ codesPostaux, nom }, key) => ( diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index d2c4c541..11926d0c 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -25,6 +25,7 @@ export default function CandidatureConseiller() { const [dateDisponibilite, setDateDisponibilite] = useState(''); const [isSituationValid, setIsSituationValid] = useState(true); const [validationError, setValidationError] = useState(''); + const [widgetId, setWidgetId] = useState(null); const { buildConseillerData, creerCandidatureConseiller } = useApiAdmin(); const navigate = useNavigate(); @@ -57,11 +58,11 @@ export default function CandidatureConseiller() { if (resultatCreation?.status >= 400) { const error = await resultatCreation.json(); setValidationError(error.message); - window.hcaptcha.reset(); + window.turnstile.reset(widgetId); window.scrollTo({ top: 0, behavior: 'smooth' }); } else if (!resultatCreation.status) { setValidationError(resultatCreation.message); - window.hcaptcha.reset(); + window.turnstile.reset(widgetId); window.scrollTo({ top: 0, behavior: 'smooth' }); } else { navigate('/candidature-validee-conseiller'); @@ -95,7 +96,7 @@ export default function CandidatureConseiller() {
- +
diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index eb21b5da..f938bc13 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -725,7 +725,7 @@ describe('candidature conseiller', () => { vi.useRealTimers(); }); - it('quand je remplis le formulaire et que je saisi mon lieu d’habitation alors la recherche est lancer à partir de 3 caractères', async () => { + it('quand je remplis le formulaire et que je saisis mon lieu d’habitation alors la recherche est lancée à partir de 3 caractères', async () => { // GIVEN vi.useFakeTimers(); const searchByNameSpy = vi.fn(); @@ -753,4 +753,59 @@ describe('candidature conseiller', () => { vi.useRealTimers(); }); + + it.each([ + { + description: 'un prénom', + selector: 'Prénom *', + message: 'Veuillez renseigner le prénom' + }, + { + description: 'un nom', + selector: 'Nom *', + message: 'Veuillez renseigner le nom' + }, + { + description: 'un email', + selector: 'Adresse électronique * Format attendu : nom@domaine.fr', + message: 'Veuillez renseigner le mail' + }, + { + description: 'une adresse', + selector: 'Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.', + message: 'Veuillez renseigner l’adresse' + }, + { + description: 'une date', + selector: 'Choisir une date', + message: 'Veuillez renseigner la date' + }, + { + description: 'une motivation', + selector: 'Votre message * Limité à 2500 caractères', + message: 'Veuillez renseigner la motivation' + }, + ])('quand je valide le formulaire avec $description vide alors j’ai un message d’erreur ', async ({ selector, message }) => { + // GIVEN + vi.stubGlobal('turnstile', { + reset: vi.fn(), + remove: vi.fn(), + render: vi.fn() + }); + render(); + const champDeFormulaire = screen.getByLabelText(selector); + Object.defineProperty(champDeFormulaire, 'validationMessage', { + value: message, + configurable: true, + }); + + // WHEN + const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' }); + + fireEvent.click(envoyer); + + // THEN + const contenuErreurValidation = await screen.findByText(message, { selector: 'p' }); + expect(contenuErreurValidation).toBeInTheDocument(); + }); }); diff --git a/src/views/candidature-conseiller/Disponibilite.jsx b/src/views/candidature-conseiller/Disponibilite.jsx index f0f268fe..9b905458 100644 --- a/src/views/candidature-conseiller/Disponibilite.jsx +++ b/src/views/candidature-conseiller/Disponibilite.jsx @@ -3,7 +3,7 @@ import BoutonRadio from '../../components/commun/BoutonRadio'; import Datepicker from '../../components/commun/Datepicker'; import PropTypes from 'prop-types'; -export default function Disponibilite({ setDateDisponibilite }) { +export default function Disponibilite({ setDateDisponibilite, errors }) { const dateDuJour = new Date().toISOString().slice(0, 10); return ( @@ -16,7 +16,7 @@ export default function Disponibilite({ setDateDisponibilite }) {

Accompagnement de personnes vers l’autonomie dans leurs usages de technologies, services et médias numériques.

- setDateDisponibilite(event.target.value)} min={dateDuJour}> + setDateDisponibilite(event.target.value)} min={dateDuJour} error={errors.dateDisponibilite}> Choisir une date
@@ -57,4 +57,5 @@ export default function Disponibilite({ setDateDisponibilite }) { Disponibilite.propTypes = { setDateDisponibilite: PropTypes.func, + errors: PropTypes.object }; diff --git a/src/views/candidature-conseiller/InformationsDeContact.jsx b/src/views/candidature-conseiller/InformationsDeContact.jsx index 29a0fcc3..c3e8372d 100644 --- a/src/views/candidature-conseiller/InformationsDeContact.jsx +++ b/src/views/candidature-conseiller/InformationsDeContact.jsx @@ -1,19 +1,22 @@ import React from 'react'; import Input from '../../components/commun/Input'; import AddressChooser from './AddressChooser'; +import PropTypes from 'prop-types'; -export default function InformationsDeContact() { +export default function InformationsDeContact({ errors }) { return (
Vos informations de contact
Prénom * Nom * @@ -21,6 +24,7 @@ export default function InformationsDeContact() { id="email" type="email" pattern=".+@.+\..{2,}" + error={errors.email} > Adresse électronique * Format attendu : nom@domaine.fr @@ -29,10 +33,15 @@ export default function InformationsDeContact() { type="tel" pattern="([+][0-9]{11,12})|([0-9]{10})" isRequired={false} + error={errors.telephone} > Téléphone Format attendu : 0122334455 ou +33122334455 - +
); } + +InformationsDeContact.propTypes = { + errors: PropTypes.object +}; diff --git a/src/views/candidature-conseiller/Motivation.jsx b/src/views/candidature-conseiller/Motivation.jsx index 74602da7..218852e6 100644 --- a/src/views/candidature-conseiller/Motivation.jsx +++ b/src/views/candidature-conseiller/Motivation.jsx @@ -1,7 +1,8 @@ import React from 'react'; import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; +import PropTypes from 'prop-types'; -export default function Motivation() { +export default function Motivation({ errors }) { return (
Votre motivation @@ -9,9 +10,13 @@ export default function Motivation() { En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique et{' '} aider les personnes à devenir autonomes dans l’utilisation des outils numériques.

- + Votre message * Limité à 2500 caractères
); } + +Motivation.propTypes = { + errors: PropTypes.object +}; diff --git a/src/views/candidature-coordinateur/BesoinEnCoordinateur.jsx b/src/views/candidature-coordinateur/BesoinEnCoordinateur.jsx index 3ae629cb..f09eb124 100644 --- a/src/views/candidature-coordinateur/BesoinEnCoordinateur.jsx +++ b/src/views/candidature-coordinateur/BesoinEnCoordinateur.jsx @@ -1,8 +1,9 @@ import React from 'react'; import BoutonRadio from '../../components/commun/BoutonRadio'; import Datepicker from '../../components/commun/Datepicker'; +import PropTypes from 'prop-types'; -export default function BesoinEnCoordinateur() { +export default function BesoinEnCoordinateur({ errors }) { const dateDuJour = new Date().toISOString().slice(0, 10); return ( @@ -28,9 +29,13 @@ export default function BesoinEnCoordinateur() {

À partir de quand êtes vous prêt à accueillir votre coordinateur ? *

- + Choisir une date ); } + +BesoinEnCoordinateur.propTypes = { + errors: PropTypes.object +}; diff --git a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx index e4557430..27f82c74 100644 --- a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx +++ b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import SommaireCoordinateur from './SommaireCoordinateur'; import InformationsDeContact from '../candidature-structure/InformationsDeContact'; import InformationsDeStructure from '../candidature-structure/InformationsDeStructure'; @@ -20,14 +20,17 @@ import '@gouvfr/dsfr/dist/component/notice/notice.min.css'; import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; import '@gouvfr/dsfr/dist/component/alert/alert.min.css'; import '../candidature-conseiller/CandidatureConseiller.css'; +import { checkValidity } from '../../shared/checkValidity'; export default function CandidatureCoordinateur() { const [geoLocation, setGeoLocation] = useState(null); const [validationError, setValidationError] = useState(''); const [widgetId, setWidgetId] = useState(null); const [codeCommune, setCodeCommune] = useState(''); + const [errors, setErrors] = useState({}); const navigate = useNavigate(); const { buildCoordinateurData, creerCandidatureCoordinateur } = useApiAdmin(); + const ref = useRef(null); useScrollToSection(); useEffect(() => { @@ -70,16 +73,24 @@ export default function CandidatureCoordinateur() {
} -
- - - - + checkValidity(ref, setErrors)} + ref={ref}> + + + +
- diff --git a/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx b/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx index 3768b264..05b1c303 100644 --- a/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx +++ b/src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx @@ -539,5 +539,74 @@ describe('candidature coordinateur', () => { vi.useRealTimers(); }); + + it.each([ + { + description: 'un SIRET/RIDET', + selector: 'SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)', + message: 'Veuillez renseigner le SIRET/RIDET' + }, + { + description: 'une dénomination', + selector: 'Dénomination *', + message: 'Veuillez renseigner la dénomination' + }, + { + description: 'un prénom', + selector: 'Prénom *', + message: 'Veuillez renseigner ce prénom' + }, + { + description: 'un nom', + selector: 'Nom *', + message: 'Veuillez renseigner le nom' + }, + { + description: 'une fonction', + selector: 'Fonction *', + message: 'Veuillez renseigner la fonction' + }, + { + description: 'un email', + selector: 'Adresse électronique * Format attendu : nom@domaine.fr', + message: 'Veuillez renseigner le mail' + }, + { + description: 'un téléphone', + selector: 'Téléphone * Format attendu : 0122334455 ou +33122334455', + message: 'Veuillez renseigner le téléphone' + }, + { + description: 'une date', + selector: 'Choisir une date', + message: 'Veuillez renseigner la date' + }, + { + description: 'une motivation', + selector: 'Votre message * Limité à 2500 caractères', + message: 'Veuillez renseigner la motivation' + }, + ])('quand je valide le formulaire avec $description vide alors j’ai un message d’erreur ', async ({ selector, message }) => { + // GIVEN + vi.stubGlobal('turnstile', { + reset: vi.fn(), + remove: vi.fn(), + render: vi.fn() + }); + render(); + const champDeFormulaire = screen.getByLabelText(selector); + Object.defineProperty(champDeFormulaire, 'validationMessage', { + value: message, + configurable: true, + }); + + // WHEN + const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' }); + fireEvent.click(envoyer); + + // THEN + const contenuErreurValidation = await screen.findByText(message, { selector: 'p' }); + expect(contenuErreurValidation).toBeInTheDocument(); + }); }); diff --git a/src/views/candidature-coordinateur/Motivation.jsx b/src/views/candidature-coordinateur/Motivation.jsx index b1525510..b26eb44a 100644 --- a/src/views/candidature-coordinateur/Motivation.jsx +++ b/src/views/candidature-coordinateur/Motivation.jsx @@ -1,7 +1,8 @@ import React from 'react'; import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; +import PropTypes from 'prop-types'; -export default function Motivation() { +export default function Motivation({ errors }) { return (
Votre motivation @@ -9,7 +10,7 @@ export default function Motivation() { En quelques lignes, décrivez le motif de votre besoin en recrutement.{' '} Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.

- + Votre message * Limité à 2500 caractères
    @@ -29,3 +30,7 @@ export default function Motivation() {
); } + +Motivation.propTypes = { + errors: PropTypes.object +}; diff --git a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx index ecbd75e3..a16ff744 100644 --- a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx +++ b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx @@ -3,15 +3,16 @@ import React from 'react'; import BoutonRadio from '../../components/commun/BoutonRadio'; import Datepicker from '../../components/commun/Datepicker'; import Input from '../../components/commun/Input'; +import PropTypes from 'prop-types'; -export default function BesoinEnConseillerNumerique() { +export default function BesoinEnConseillerNumerique({ errors }) { const dateDuJour = new Date().toISOString().slice(0, 10); return (
Votre besoin en conseiller(s) numérique(s)
- + Combien de conseillers numériques souhaitez-vous accueillir ? *
@@ -25,9 +26,13 @@ export default function BesoinEnConseillerNumerique() {

À partir de quand êtes vous prêt à accueillir votre conseiller numerique ? *

- + Choisir une date
); } + +BesoinEnConseillerNumerique.propTypes = { + errors: PropTypes.object +}; diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 4f5f70bb..b95edfa6 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import InformationsDeStructure from './InformationsDeStructure'; @@ -20,14 +20,17 @@ import '@gouvfr/dsfr/dist/component/notice/notice.min.css'; import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; import '@gouvfr/dsfr/dist/component/alert/alert.min.css'; import '../candidature-conseiller/CandidatureConseiller.css'; +import { checkValidity } from '../../shared/checkValidity'; export default function CandidatureStructure() { const [geoLocation, setGeoLocation] = useState(null); const [codeCommune, setCodeCommune] = useState(''); const [widgetId, setWidgetId] = useState(null); const [validationError, setValidationError] = useState(''); + const [errors, setErrors] = useState({}); const navigate = useNavigate(); const { buildStructureData, creerCandidatureStructure } = useApiAdmin(); + const ref = useRef(null); useEffect(() => { document.title = 'Conseiller numérique - Engager un conseiller numérique'; @@ -71,16 +74,25 @@ export default function CandidatureStructure() { } -
- - - - + checkValidity(ref, setErrors)} + ref={ref} + > + + + +
- +
- diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 2fb3ec24..bca7d3b7 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -629,5 +629,79 @@ describe('candidature structure', () => { vi.useRealTimers(); }); + + it.each([ + { + description: 'un SIRET/RIDET', + selector: 'SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)', + message: 'Veuillez renseigner le SIRET/RIDET' + }, + { + description: 'une dénomination', + selector: 'Dénomination *', + message: 'Veuillez renseigner la dénomination' + }, + { + description: 'un prénom', + selector: 'Prénom *', + message: 'Veuillez renseigner ce prénom' + }, + { + description: 'un nom', + selector: 'Nom *', + message: 'Veuillez renseigner le nom' + }, + { + description: 'une fonction', + selector: 'Fonction *', + message: 'Veuillez renseigner la fonction' + }, + { + description: 'un email', + selector: 'Adresse électronique * Format attendu : nom@domaine.fr', + message: 'Veuillez renseigner le mail' + }, + { + description: 'un téléphone', + selector: 'Téléphone * Format attendu : 0122334455 ou +33122334455', + message: 'Veuillez renseigner le téléphone' + }, + { + description: 'un nombre de conseillers souhaités', + selector: 'Combien de conseillers numériques souhaitez-vous accueillir ? *', + message: 'Veuillez renseigner le nombre de conseillers souhaités' + }, + { + description: 'une date', + selector: 'Choisir une date', + message: 'Veuillez renseigner la date' + }, + { + description: 'une motivation', + selector: 'Votre message * Limité à 2500 caractères', + message: 'Veuillez renseigner la motivation' + }, + ])('quand je valide le formulaire avec $description vide alors j’ai un message d’erreur ', async ({ selector, message }) => { + // GIVEN + vi.stubGlobal('turnstile', { + reset: vi.fn(), + remove: vi.fn(), + render: vi.fn() + }); + render(); + const champDeFormulaire = screen.getByLabelText(selector); + Object.defineProperty(champDeFormulaire, 'validationMessage', { + value: message, + configurable: true, + }); + + // WHEN + const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' }); + fireEvent.click(envoyer); + + // THEN + const contenuErreurValidation = await screen.findByText(message, { selector: 'p' }); + expect(contenuErreurValidation).toBeInTheDocument(); + }); }); diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx index de29c4b4..b72cb85f 100644 --- a/src/views/candidature-structure/CompanyFinder.jsx +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -3,7 +3,7 @@ import Input from '../../components/commun/Input'; import { debounce } from '../candidature-conseiller/debounce'; import PropTypes from 'prop-types'; -export default function CompanyFinder({ onSearch }) { +export default function CompanyFinder({ onSearch, errors }) { const handleSearch = debounce(value => { onSearch(value); }); @@ -12,8 +12,9 @@ export default function CompanyFinder({ onSearch }) { handleSearch(event.target.value)} - pattern="^(?:[0-9]{9}|[0-9]{14})$" - maxlength="14" + pattern="^(?:[0-9]{9}|[0-9]{14})$" + maxlength="14" + error={errors.siret} > SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789) @@ -21,5 +22,6 @@ export default function CompanyFinder({ onSearch }) { } CompanyFinder.propTypes = { - onSearch: PropTypes.func + onSearch: PropTypes.func, + errors: PropTypes.object, }; diff --git a/src/views/candidature-structure/InformationsDeContact.jsx b/src/views/candidature-structure/InformationsDeContact.jsx index 0e4725b4..f9657655 100644 --- a/src/views/candidature-structure/InformationsDeContact.jsx +++ b/src/views/candidature-structure/InformationsDeContact.jsx @@ -1,7 +1,8 @@ import React from 'react'; import Input from '../../components/commun/Input'; +import PropTypes from 'prop-types'; -export default function InformationsDeContact() { +export default function InformationsDeContact({ errors }) { return (
Vos informations de contact @@ -9,18 +10,21 @@ export default function InformationsDeContact() { Prénom * Nom * Fonction * @@ -29,6 +33,7 @@ export default function InformationsDeContact() { name="email" type="email" pattern=".+@.+\..{2,}" + error={errors.email} > Adresse électronique * Format attendu : nom@domaine.fr @@ -37,6 +42,7 @@ export default function InformationsDeContact() { name="telephone" type="tel" pattern="([+][0-9]{11,12})|([0-9]{10})" + error={errors.telephone} > Téléphone *{' '} Format attendu : 0122334455 ou +33122334455 @@ -44,3 +50,7 @@ export default function InformationsDeContact() {
); } + +InformationsDeContact.propTypes = { + errors: PropTypes.object, +}; diff --git a/src/views/candidature-structure/InformationsDeStructure.jsx b/src/views/candidature-structure/InformationsDeStructure.jsx index 9fef55c4..1de88a82 100644 --- a/src/views/candidature-structure/InformationsDeStructure.jsx +++ b/src/views/candidature-structure/InformationsDeStructure.jsx @@ -10,7 +10,7 @@ const TAILLE_SIRET = 14; const TAILLE_RIDET = [6, 7]; const TAILLES_POSSIBLES = [...TAILLE_RIDET, TAILLE_SIRET]; -export default function InformationsDeStructure({ setGeoLocation, setCodeCommune }) { +export default function InformationsDeStructure({ setGeoLocation, setCodeCommune, errors }) { const { entreprise, search, @@ -55,13 +55,14 @@ export default function InformationsDeStructure({ setGeoLocation, setCodeCommune > Vos informations de structure
- + setDenomination(event.target.value)} + error={errors.denomination} > Dénomination * @@ -70,10 +71,11 @@ export default function InformationsDeStructure({ setGeoLocation, setCodeCommune id="adresse" value={adresse} onChange={handleAdresseChange} - disabled ={!entreprise?.isRidet} + disabled={!entreprise?.isRidet} list="adresseSuggestions" isLoading={(entreprise && loading && !entreprise?.isRidet) || addressLoading} ariaBusy={(entreprise && loading && !entreprise?.isRidet) || addressLoading} + error={errors.adresse} > Adresse * @@ -121,4 +123,5 @@ InformationsDeStructure.propTypes = { setGeoLocation: PropTypes.func.isRequired, setCodeCommune: PropTypes.func.isRequired, geoLocation: PropTypes.object, + errors: PropTypes.object, }; diff --git a/src/views/candidature-structure/Motivation.jsx b/src/views/candidature-structure/Motivation.jsx index 0004c646..a0f674b4 100644 --- a/src/views/candidature-structure/Motivation.jsx +++ b/src/views/candidature-structure/Motivation.jsx @@ -1,7 +1,8 @@ import React from 'react'; import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; +import PropTypes from 'prop-types'; -export default function Motivation() { +export default function Motivation({ errors }) { return (
Votre motivation @@ -9,9 +10,13 @@ export default function Motivation() { En quelques lignes, décrivez le motif de votre besoin en recrutement.{' '} Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.

- + Votre message * Limité à 2500 caractères
); } + +Motivation.propTypes = { + errors: PropTypes.object +}; From b738d5db8b2273a252810dd4bda9f5a8eadaccd3 Mon Sep 17 00:00:00 2001 From: Benjamin Morali Date: Wed, 27 Nov 2024 12:00:51 +0100 Subject: [PATCH 06/11] Retours formulaire (#253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Retours formulaire * Ajout de valeur par défaut pour les radio button * Meileure validation de la dénomination de structrure * Ajout de l'adresse complète pour les RIDET * Modification du menu * resolve error test * gestion siret & ridet * Ajout d'un état disabled sur le bouton envoyer --------- Co-authored-by: ornella --- src/components/Menu.js | 26 +++- src/components/commun/BoutonRadio.jsx | 7 +- src/components/commun/RadioGroup.jsx | 46 +++++++ .../CandidatureConseiller.jsx | 11 +- .../CandidatureConseiller.test.jsx | 2 + .../candidature-conseiller/Disponibilite.jsx | 61 +++++---- .../SituationEtExperience.jsx | 20 ++- .../candidature-conseiller/useApiAdmin.js | 10 ++ .../BesoinEnCoordinateur.jsx | 38 ++++-- .../CandidatureCoordinateur.jsx | 16 ++- .../CandidatureCoordinateur.test.jsx | 112 +++++++++++++++- .../CompanyFinder.jsx | 4 +- .../BesoinEnConseillerNumerique.jsx | 20 ++- .../CandidatureStructure.jsx | 16 ++- .../CandidatureStructure.test.jsx | 121 +++++++++++++++++- .../candidature-structure/CompanyFinder.jsx | 2 +- .../candidature-structure/Engagement.jsx | 1 - .../InformationsDeStructure.jsx | 72 +++++++---- .../useEntrepriseFinder.js | 2 +- 19 files changed, 477 insertions(+), 110 deletions(-) create mode 100644 src/components/commun/RadioGroup.jsx diff --git a/src/components/Menu.js b/src/components/Menu.js index f4a0c88b..f316ba46 100644 --- a/src/components/Menu.js +++ b/src/components/Menu.js @@ -97,25 +97,37 @@ function Menu() { aria-expanded={activeMenu === 'cnfs'} aria-controls="menu-cnfs" onClick={onClickMenu} - {...(location.pathname.startsWith('/aide-candidat') || location.pathname.startsWith('/aide-structure') ? { 'aria-current': true } : {})}> - Recrutement + { + ...(location.pathname.startsWith('/nouveau-formulaire-conseiller') || + location.pathname.startsWith('/nouveau-formulaire-structure') || + location.pathname.startsWith('/nouveau-formulaire-coordinateur') ? { 'aria-current': true } : {}) + }> + Candidature diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index bca7d3b7..da9f944a 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -49,6 +49,7 @@ describe('candidature structure', () => { expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); const siretOuRidet = within(etapeInformationsDeStructure).getByLabelText('SIRET / RIDET * Format attendu : SIRET (12345678901234) ou RIDET (123456789)'); + expect(siretOuRidet).toHaveAttribute('pattern', '^(?:[0-9]{6,10}|[0-9]{14})$'); expect(siretOuRidet).toHaveAttribute('id', 'siret'); expect(siretOuRidet).toBeRequired(); @@ -209,11 +210,10 @@ describe('candidature structure', () => { 'montée en compétences du public (ateliers numériques, initiations au numérique), gratuites,'); within(listDetail[1]).getByText('Qu’il consacre une partie de son temps aux rencontres locales et ' + 'nationales organisées pour la communauté et la formation continue, etc,'); - within(listDetail[2]).getByText('Qu’il revête une tenue vestimentaire dédiée fournie par l’Etat,'); - within(listDetail[3]).getByText('Tout mettre en oeuvre pour sélectionner le candidat dans un délai maximum d’un mois sur la plateforme,'); - within(listDetail[4]).getByText('Signer dans les 15 jours suivants un contrat avec ce candidat,'); - within(listDetail[5]).getByText('Laisser partir le conseiller numérique en formation initiale ou continue,'); - within(listDetail[6]).getByText('Mettre à sa disposition les moyens et ' + + within(listDetail[2]).getByText('Tout mettre en oeuvre pour sélectionner le candidat dans un délai maximum d’un mois sur la plateforme,'); + within(listDetail[3]).getByText('Signer dans les 15 jours suivants un contrat avec ce candidat,'); + within(listDetail[4]).getByText('Laisser partir le conseiller numérique en formation initiale ou continue,'); + within(listDetail[5]).getByText('Mettre à sa disposition les moyens et ' + 'équipements pour réaliser sa mission (ordinateur, téléphone portable, voiture si nécessaire),'); const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); @@ -459,7 +459,7 @@ describe('candidature structure', () => { vi.useRealTimers(); }); - it('quand je valide le formulaire alors j’envoie toute les données nescessaires', async () => { + it('quand je valide le formulaire alors j’envoie toute les données nescessaires en renseignant un siret', async () => { // GIVEN const formData = [ [ @@ -557,6 +557,7 @@ describe('candidature structure', () => { 'telephone': '+33123456789' }, 'nom': 'AGENCE NATIONALE DE LA COHESION DES TERRITOIRES', + 'ridet': null, 'nomCommune': 'Paris 7e Arrondissement', 'codePostal': '75007', 'codeCommune': '75107', @@ -567,7 +568,115 @@ describe('candidature structure', () => { vi.useRealTimers(); }); + it('quand je valide le formulaire alors j’envoie toute les données nescessaires en renseignant un ridet', async () => { + // GIVEN + const formData = [ + [ + 'siret', + '1234567' + ], + [ + 'denomination', + 'AGENCE NATIONALE DE LA COHESION DES TERRITOIRES' + ], + [ + 'adresse', + '20 AVENUE DE SEGUR, 75007 PARIS' + ], + [ + 'type', + 'COMMUNE' + ], + [ + 'prenom', + 'Jean' + ], + [ + 'nom', + 'Dupont' + ], + [ + 'fonction', + 'Test' + ], + [ + 'email', + 'jean.dupont@example.com' + ], + [ + 'telephone', + '+33123456789' + ], + [ + 'nombreConseillersSouhaites', + '1' + ], + [ + 'aIdentifieCandidat', + 'oui' + ], + [ + 'dateDebutMission', + '2024-12-12' + ], + [ + 'motivation', + 'je suis motivé !' + ], + [ + 'confirmationEngagement', + 'on' + ], + [ + 'g-recaptcha-response', + '1' + ], + [ + 'cf-turnstile-response', + '1' + ] + ]; + + const { buildStructureData } = renderHook(() => useApiAdmin.useApiAdmin()).result.current; + const { getGeoLocationFromAddress } = renderHook(() => useEntrepriseFinder()).result.current; + let geoLocation; + + // WHEN + await act(async () => { + geoLocation = await getGeoLocationFromAddress('20 AVENUE DE SEGUR, 75007 PARIS'); + }); + const result = await buildStructureData(formData, geoLocation, '75107'); + + // THEN + expect(result).toBe(JSON.stringify({ + 'siret': null, + 'type': 'COMMUNE', + 'nombreConseillersSouhaites': '1', + 'aIdentifieCandidat': true, + 'dateDebutMission': '2024-12-12', + 'motivation': 'je suis motivé !', + 'confirmationEngagement': true, + 'cf-turnstile-response': '1', + 'location': { 'type': 'Point', 'coordinates': [2.3115, 48.8548] }, + 'contact': { + 'prenom': 'Jean', + 'nom': 'Dupont', + 'fonction': 'Test', + 'email': 'jean.dupont@example.com', + 'telephone': '+33123456789' + }, + 'nom': 'AGENCE NATIONALE DE LA COHESION DES TERRITOIRES', + 'ridet': '1234567', + 'nomCommune': 'Paris 7e Arrondissement', + 'codePostal': '75007', + 'codeCommune': '75107', + 'codeDepartement': '75', + 'codeRegion': '11', + 'codeCom': null, + })); + vi.useRealTimers(); + }); it('quand je candidate et qu’une erreur serveur survient, alors le message d’erreur s’affiche et le captcha est rénitialisé', async () => { // GIVEN vi.useFakeTimers(); diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx index b72cb85f..d1406929 100644 --- a/src/views/candidature-structure/CompanyFinder.jsx +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -12,7 +12,7 @@ export default function CompanyFinder({ onSearch, errors }) { handleSearch(event.target.value)} - pattern="^(?:[0-9]{9}|[0-9]{14})$" + pattern="^(?:[0-9]{6,10}|[0-9]{14})$" maxlength="14" error={errors.siret} > diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx index 94167b96..3132b588 100644 --- a/src/views/candidature-structure/Engagement.jsx +++ b/src/views/candidature-structure/Engagement.jsx @@ -9,7 +9,6 @@ export default function Engagement() {