From 19cd8e3356cc372b3f71c5e3f992d0e391020440 Mon Sep 17 00:00:00 2001 From: dienamo Date: Mon, 22 Jul 2024 14:39:23 +0200 Subject: [PATCH 01/29] :feat intialisation des tests formulaire candidature structure --- .../CandidatureConseiller.test.jsx | 2 +- .../CandidatureStructure.jsx | 12 ++++++ .../CandidatureStructure.test.jsx | 38 +++++++++++++++++++ src/views/candidature-structure/Sommaire.jsx | 35 +++++++++++++++++ .../test-utils.js | 0 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/views/candidature-structure/CandidatureStructure.jsx create mode 100644 src/views/candidature-structure/CandidatureStructure.test.jsx create mode 100644 src/views/candidature-structure/Sommaire.jsx rename {src/views/candidature-conseiller => test}/test-utils.js (100%) diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 1adeecf..e9c9c74 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -1,7 +1,7 @@ import { render, screen, within, fireEvent } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import CandidatureConseiller from './CandidatureConseiller'; -import { textMatcher } from './test-utils'; +import { textMatcher } from '../../../test/test-utils'; describe('candidature conseiller', () => { describe('étant un candidat', () => { diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx new file mode 100644 index 0000000..5a778a6 --- /dev/null +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Sommaire from './Sommaire'; + +export default function CandidatureStructure() { + return ( +
+

Je souhaite engager un conseiller numérique

+

Les champs avec * sont obligatoires.

+ +
+ ); +} diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx new file mode 100644 index 0000000..89b3274 --- /dev/null +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -0,0 +1,38 @@ +import { render, screen, within } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import CandidatureStructure from './CandidatureStructure'; +import { textMatcher } from '../../../test/test-utils'; +describe('candidature structure', () => { + describe('étant une structure', () => { + it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { + // WHEN + render(); + + // THEN + const titre = screen.getByRole('heading', { + level: 1, + name: textMatcher('Je souhaite engager un conseiller numérique'), + }); + expect(titre).toBeInTheDocument(); + + const champsObligatoires = screen.getByText(textMatcher('Les champs avec * sont obligatoires.'), { selector: 'p' }); + expect(champsObligatoires).toBeInTheDocument(); + + const navigation = screen.getByRole('navigation', { name: 'Sommaire' }); + const menu = within(navigation).getByRole('list'); + const menuItems = within(menu).getAllByRole('listitem'); + + const informationsDeStructure = within(menuItems[0]).getByRole('link', { name: 'Vos informations de structure' }); + expect(informationsDeStructure).toHaveAttribute('href', '#informations-de-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' }); + 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'); + }); + }); +}); diff --git a/src/views/candidature-structure/Sommaire.jsx b/src/views/candidature-structure/Sommaire.jsx new file mode 100644 index 0000000..203af76 --- /dev/null +++ b/src/views/candidature-structure/Sommaire.jsx @@ -0,0 +1,35 @@ +export default function Sommaire() { + const partiesSommaire = [ + { + ancre: '#informations-de-structure', + libelle: 'Vos informations de structure' + }, + { + ancre: '#informations-de-contact', + libelle: 'Vos informations de contact' + }, + { + ancre: '#votre-besoin-en-conseiller-numerique', + libelle: 'Votre besoin en conseiller numérique' + }, + { + ancre: '#votre-motivation', + libelle: 'Votre motivation' + } + ]; + return ( + + ); +} diff --git a/src/views/candidature-conseiller/test-utils.js b/test/test-utils.js similarity index 100% rename from src/views/candidature-conseiller/test-utils.js rename to test/test-utils.js From 7b26124dc7d03582c069d8d4f1d5ff928a41d6a2 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Thu, 25 Jul 2024 11:07:04 +0200 Subject: [PATCH 02/29] test formulaire partie 'information contact' + 'votre besoin CN' --- src/App.js | 2 + .../BesoinEnConseillerNumerique.jsx | 36 +++++++++ .../CandidatureStructure.jsx | 25 ++++-- .../CandidatureStructure.test.jsx | 80 ++++++++++++++++++- .../InformationsDeContact.jsx | 39 +++++++++ .../PageCandidatureStructure.jsx | 12 +++ 6 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/views/candidature-structure/BesoinEnConseillerNumerique.jsx create mode 100644 src/views/candidature-structure/InformationsDeContact.jsx create mode 100644 src/views/candidature-structure/PageCandidatureStructure.jsx diff --git a/src/App.js b/src/App.js index 3dd4ccd..f358527 100644 --- a/src/App.js +++ b/src/App.js @@ -37,6 +37,7 @@ function App() { const CoordinationTerritoriale = lazy(() => import('./views/coordination-territoriale')); const CarteCoordinateur = lazy(() => import('./views/coordination-territoriale/CarteCoordinateur')); const PageCandidatureConseiller = lazy(() => import('./views/candidature-conseiller/PageCandidatureConseiller')); + const PageCandidatureStructure = lazy(() => import('./views/candidature-structure/PageCandidatureStructure')); return (
@@ -45,6 +46,7 @@ function App() { }/> }/> + }/> }/> }/> }/> diff --git a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx new file mode 100644 index 0000000..de4899a --- /dev/null +++ b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx @@ -0,0 +1,36 @@ +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({ setDateAccueilConseillerNumerique }) { + return ( +
+ Votre besoin en conseiller(s) numérique(s) +
+ + Combien de conseillers numériques souhaitez-vous accueillir ?* + +
+

Avez-vous déjà identifié un candidat pour le poste de conseiller numérique ?*

+

Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique

+ + Oui + + + Non + +
+

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

+ setDateAccueilConseillerNumerique(event.target.value)}> + Choisir une date + +
+ ); +} + +BesoinEnConseillerNumerique.propTypes = { + setDateAccueilConseillerNumerique: PropTypes.func, +}; diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 5a778a6..d0e00af 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,12 +1,27 @@ -import React from 'react'; +import React, { useState } from 'react'; import Sommaire from './Sommaire'; +import InformationsDeContact from './InformationsDeContact'; +import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; export default function CandidatureStructure() { + const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); + return ( -
-

Je souhaite engager un conseiller numérique

-

Les champs avec * sont obligatoires.

- +
+
+
+ +
+
+

Je souhaite engager un conseiller numérique

+

Les champs avec * sont obligatoires.

+
+ {/* TODO : Vos informations de structure */} + + + +
+
); } diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 89b3274..b1ac3d6 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -2,9 +2,10 @@ import { render, screen, within } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import CandidatureStructure from './CandidatureStructure'; import { textMatcher } from '../../../test/test-utils'; + describe('candidature structure', () => { describe('étant une structure', () => { - it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { + it.todo('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { // WHEN render(); @@ -35,4 +36,81 @@ describe('candidature structure', () => { expect(votreMotivation).toHaveAttribute('href', '#votre-motivation'); }); }); + it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { + // GIVEN + render(); + + // WHEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); + + const prenom = within(etapeInformationsDeContact).getByLabelText('Prénom *'); + expect(prenom).toHaveAttribute('type', 'text'); + expect(prenom).toBeRequired(); + + const nom = within(etapeInformationsDeContact).getByLabelText('Nom *'); + expect(nom).toHaveAttribute('type', 'text'); + expect(nom).toBeRequired(); + + const fonction = within(etapeInformationsDeContact).getByLabelText('Fonction *'); + expect(fonction).toHaveAttribute('type', 'text'); + expect(fonction).toBeRequired(); + + const email = within(etapeInformationsDeContact).getByLabelText('Adresse e-mail *'); + expect(email).toHaveAttribute('type', 'email'); + expect(email).toBeRequired(); + + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); + expect(telephone).toHaveAttribute('type', 'tel'); + expect(telephone).toBeRequired(); + + // THEN + + + }); + it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { + + //GIVEN + render(); + + // WHEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeBesoinConseillerNumerique = within(formulaire).getByRole('group', { name: 'Votre besoin en conseiller(s) numérique(s)' }); + expect(etapeBesoinConseillerNumerique).toHaveAttribute('id', 'votre-besoin-en-conseiller-numerique'); + + const combienConseillerNumerique = within(etapeBesoinConseillerNumerique).getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ?*'); + expect(combienConseillerNumerique).toHaveAttribute('type', 'number'); + expect(combienConseillerNumerique).toBeRequired(); + + + const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText(textMatcher('Avez-vous déjà identifié un candidat ' + + 'pour le poste de conseiller numérique ?*'), { selector: 'p' }); + expect(identificationCandidat).toBeInTheDocument(); + + const sousTitreIdentificationCandidat = + within(etapeBesoinConseillerNumerique).getByText(textMatcher('Si oui, merci d’inviter ce candidat ' + + 'à s’inscrire sur la plateforme Conseiller numérique'), { selector: 'p' }); + expect(sousTitreIdentificationCandidat).toBeInTheDocument(); + + const oui = screen.getByRole('radio', { name: 'Oui' }); + expect(oui).toBeRequired(); + expect(oui).toHaveAttribute('name', 'identificationCandidat'); + const non = screen.getByRole('radio', { name: 'Non' }); + expect(non).toBeRequired(); + expect(non).toHaveAttribute('name', 'identificationCandidat'); + + const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText(textMatcher('À partir de quand êtes vous prêt ' + + 'à accueillir votre conseiller numerique ?*'), { selector: 'p' }); + expect(dateAccueilConseillerNumerique).toBeInTheDocument(); + const date = within(etapeBesoinConseillerNumerique).getByLabelText('Choisir une date'); + expect(date).toHaveAttribute('type', 'date'); + expect(date).toBeRequired(); + + // THEN + + }); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + }); diff --git a/src/views/candidature-structure/InformationsDeContact.jsx b/src/views/candidature-structure/InformationsDeContact.jsx new file mode 100644 index 0000000..532b5be --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Input from '../../components/commun/Input'; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de contact +
+ + Prénom * + + + Nom * + + + Fonction * + + + Adresse e-mail * + + + Téléphone * + +
+ ); +} diff --git a/src/views/candidature-structure/PageCandidatureStructure.jsx b/src/views/candidature-structure/PageCandidatureStructure.jsx new file mode 100644 index 0000000..a5ab078 --- /dev/null +++ b/src/views/candidature-structure/PageCandidatureStructure.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Header from '../../components/Header'; +import CandidatureStructure from './CandidatureStructure'; + +export default function PageCandidatureStructure() { + return ( + <> +
+ + + ); +} From a0df62d641bdcb267438fba4193d2c31ec83b5b9 Mon Sep 17 00:00:00 2001 From: Alezco Date: Mon, 29 Jul 2024 17:25:26 +0200 Subject: [PATCH 03/29] Retours de code review --- .../Sommaire.jsx | 31 ++++--------- .../CandidatureConseiller.jsx | 4 +- .../CandidatureConseiller.test.jsx | 18 ++++---- .../candidature-conseiller/Disponibilite.jsx | 2 +- .../InformationsDeContact.jsx | 2 +- .../candidature-conseiller/Motivation.jsx | 2 +- .../SituationEtExperience.jsx | 2 +- .../SommaireConseiller.jsx | 27 +++++++++++ .../CandidatureStructure.jsx | 13 +++++- .../CandidatureStructure.test.jsx | 46 ++++++++++--------- .../{Sommaire.jsx => SommaireStructure.jsx} | 22 +++------ 11 files changed, 92 insertions(+), 77 deletions(-) rename src/{views/candidature-conseiller => components}/Sommaire.jsx (53%) create mode 100644 src/views/candidature-conseiller/SommaireConseiller.jsx rename src/views/candidature-structure/{Sommaire.jsx => SommaireStructure.jsx} (52%) diff --git a/src/views/candidature-conseiller/Sommaire.jsx b/src/components/Sommaire.jsx similarity index 53% rename from src/views/candidature-conseiller/Sommaire.jsx rename to src/components/Sommaire.jsx index d96296a..ad455c2 100644 --- a/src/views/candidature-conseiller/Sommaire.jsx +++ b/src/components/Sommaire.jsx @@ -1,27 +1,8 @@ import React, { useState } from 'react'; +import PropTypes from 'prop-types'; -export default function Sommaire() { - const ancreInformationsDeContact = '#informationsDeContact'; - const [dernierElementClique, setDernierElementClique] = useState(ancreInformationsDeContact); - - const partiesSommaire = [ - { - ancre: ancreInformationsDeContact, - libelle: 'Vos informations de contact' - }, - { - ancre: '#situationEtExperience', - libelle: 'Votre situation et expérience' - }, - { - ancre: '#votreDisponibilite', - libelle: 'Votre disponibilité' - }, - { - ancre: '#votreMotivation', - libelle: 'Votre motivation' - }, - ]; +export default function Sommaire({ parties }) { + const [dernierElementClique, setDernierElementClique] = useState(parties[0].ancre); const getAriaCurrent = ancre => { return ancre === dernierElementClique ? 'page' : false; @@ -30,7 +11,7 @@ export default function Sommaire() { return ( ); } + +Sommaire.propTypes = { + parties: PropTypes.array, +}; diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index 7491ab8..e821f44 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import Sommaire from './Sommaire'; +import SommaireConseiller from './SommaireConseiller'; import InformationsDeContact from './InformationsDeContact'; import SituationEtExperience from './SituationEtExperience'; import Disponibilite from './Disponibilite'; @@ -34,7 +34,7 @@ export default function CandidatureConseiller() {
- +

Je veux devenir conseiller numérique

diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index e9c9c74..212ba04 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -21,16 +21,16 @@ describe('candidature conseiller', () => { const menuItems = within(menu).getAllByRole('listitem'); const informationsDeContact = within(menuItems[0]).getByRole('link', { name: 'Vos informations de contact' }); - expect(informationsDeContact).toHaveAttribute('href', '#informationsDeContact'); + expect(informationsDeContact).toHaveAttribute('href', '#informations-de-contact'); const situationEtExperience = within(menuItems[1]).getByRole('link', { name: 'Votre situation et expérience' }); - expect(situationEtExperience).toHaveAttribute('href', '#situationEtExperience'); + expect(situationEtExperience).toHaveAttribute('href', '#situation-et-experience'); const votreDisponibilite = within(menuItems[2]).getByRole('link', { name: 'Votre disponibilité' }); - expect(votreDisponibilite).toHaveAttribute('href', '#votreDisponibilite'); + expect(votreDisponibilite).toHaveAttribute('href', '#votre-disponibilite'); const votreMotivation = within(menuItems[3]).getByRole('link', { name: 'Votre motivation' }); - expect(votreMotivation).toHaveAttribute('href', '#votreMotivation'); + expect(votreMotivation).toHaveAttribute('href', '#votre-motivation'); }); }); @@ -41,7 +41,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); const prenom = within(etapeInformationsDeContact).getByLabelText('Prénom *'); expect(prenom).toHaveAttribute('type', 'text'); @@ -70,7 +70,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const situationEtExperience = within(formulaire).getByRole('group', { name: 'Votre situation et expérience' }); - expect(situationEtExperience).toHaveAttribute('id', 'situationEtExperience'); + expect(situationEtExperience).toHaveAttribute('id', 'situation-et-experience'); const situation = within(situationEtExperience).getByText(textMatcher('Êtes-vous actuellement dans l’une des situations suivantes ? *'), { selector: 'p' }); expect(situation).toBeInTheDocument(); @@ -131,7 +131,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const votreDisponibilite = within(formulaire).getByRole('group', { name: 'Votre disponibilité' }); - expect(votreDisponibilite).toHaveAttribute('id', 'votreDisponibilite'); + expect(votreDisponibilite).toHaveAttribute('id', 'votre-disponibilite'); const questionDisponibilite = within(votreDisponibilite).getByText( textMatcher('À quel moment êtes-vous prêt(e) à démarrer votre mission et la formation de conseiller numérique ? *'), @@ -197,7 +197,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const votreMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); - expect(votreMotivation).toHaveAttribute('id', 'votreMotivation'); + expect(votreMotivation).toHaveAttribute('id', 'votre-motivation'); const aideMotivation = within(votreMotivation).getByText( textMatcher('En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique ' + @@ -234,7 +234,7 @@ describe('candidature conseiller', () => { expect(descriptionResume).toBeInTheDocument(); }); - it('quand je modifie la date de disponibilité, elle s’affiche dans l’encart "En résumé" est affiché', () => { + it('quand je modifie la date de disponibilité, alors elle s’affiche dans l’encart "En résumé"', () => { // GIVEN render(); const dateDisponibilite = '2023-12-12'; diff --git a/src/views/candidature-conseiller/Disponibilite.jsx b/src/views/candidature-conseiller/Disponibilite.jsx index 2cda5a6..9cbe6fb 100644 --- a/src/views/candidature-conseiller/Disponibilite.jsx +++ b/src/views/candidature-conseiller/Disponibilite.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; export default function Disponibilite({ setDateDisponibilite }) { return ( -
+
Votre disponibilité

diff --git a/src/views/candidature-conseiller/InformationsDeContact.jsx b/src/views/candidature-conseiller/InformationsDeContact.jsx index 8ab07ad..427b4e0 100644 --- a/src/views/candidature-conseiller/InformationsDeContact.jsx +++ b/src/views/candidature-conseiller/InformationsDeContact.jsx @@ -4,7 +4,7 @@ import AddressChooser from './AddressChooser'; export default function InformationsDeContact() { return ( -

+
Vos informations de contact
+
Votre motivation

En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique et{' '} diff --git a/src/views/candidature-conseiller/SituationEtExperience.jsx b/src/views/candidature-conseiller/SituationEtExperience.jsx index bd214bd..4fa0759 100644 --- a/src/views/candidature-conseiller/SituationEtExperience.jsx +++ b/src/views/candidature-conseiller/SituationEtExperience.jsx @@ -19,7 +19,7 @@ export default function SituationEtExperience({ situationChecks, setSituationChe }; return ( -

+
Votre situation et expérience

diff --git a/src/views/candidature-conseiller/SommaireConseiller.jsx b/src/views/candidature-conseiller/SommaireConseiller.jsx new file mode 100644 index 0000000..870bb35 --- /dev/null +++ b/src/views/candidature-conseiller/SommaireConseiller.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import Sommaire from '../../components/Sommaire'; + +export default function SommaireConseiller() { + const parties = [ + { + ancre: '#informations-de-contact', + libelle: 'Vos informations de contact' + }, + { + ancre: '#situation-et-experience', + libelle: 'Votre situation et expérience' + }, + { + ancre: '#votre-disponibilite', + libelle: 'Votre disponibilité' + }, + { + ancre: '#votre-motivation', + libelle: 'Votre motivation' + }, + ]; + + return ( + + ); +} diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index d0e00af..8da9cb7 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,8 +1,17 @@ import React, { useState } from 'react'; -import Sommaire from './Sommaire'; +import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import '@gouvfr/dsfr/dist/component/form/form.min.css'; +import '@gouvfr/dsfr/dist/component/input/input.min.css'; +import '@gouvfr/dsfr/dist/component/checkbox/checkbox.min.css'; +import '@gouvfr/dsfr/dist/component/radio/radio.min.css'; +import '@gouvfr/dsfr/dist/component/badge/badge.min.css'; +import '@gouvfr/dsfr/dist/component/notice/notice.min.css'; +import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; +import '../candidature-conseiller/CandidatureConseiller.css'; + export default function CandidatureStructure() { const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); @@ -10,7 +19,7 @@ export default function CandidatureStructure() {

- +

Je souhaite engager un conseiller numérique

diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index b1ac3d6..46d8216 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -31,17 +31,19 @@ describe('candidature structure', () => { const votreBesoinEnConseillerNumerique = within(menuItems[2]).getByRole('link', { name: 'Votre besoin en conseiller numérique' }); 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'); }); }); + it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { - // GIVEN + // WHEN render(); - // WHEN + // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); @@ -65,17 +67,13 @@ describe('candidature structure', () => { const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); expect(telephone).toHaveAttribute('type', 'tel'); expect(telephone).toBeRequired(); - - // THEN - - }); - it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { - //GIVEN + it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { + // WHEN render(); - // WHEN + // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); const etapeBesoinConseillerNumerique = within(formulaire).getByRole('group', { name: 'Votre besoin en conseiller(s) numérique(s)' }); expect(etapeBesoinConseillerNumerique).toHaveAttribute('id', 'votre-besoin-en-conseiller-numerique'); @@ -84,33 +82,37 @@ describe('candidature structure', () => { expect(combienConseillerNumerique).toHaveAttribute('type', 'number'); expect(combienConseillerNumerique).toBeRequired(); - - const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText(textMatcher('Avez-vous déjà identifié un candidat ' + - 'pour le poste de conseiller numérique ?*'), { selector: 'p' }); + const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText( + textMatcher('Avez-vous déjà identifié un candidat pour le poste de conseiller numérique ?*'), + { selector: 'p' } + ); expect(identificationCandidat).toBeInTheDocument(); - + const sousTitreIdentificationCandidat = - within(etapeBesoinConseillerNumerique).getByText(textMatcher('Si oui, merci d’inviter ce candidat ' + - 'à s’inscrire sur la plateforme Conseiller numérique'), { selector: 'p' }); + within(etapeBesoinConseillerNumerique).getByText( + textMatcher('Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique'), + { selector: 'p' } + ); expect(sousTitreIdentificationCandidat).toBeInTheDocument(); const oui = screen.getByRole('radio', { name: 'Oui' }); expect(oui).toBeRequired(); expect(oui).toHaveAttribute('name', 'identificationCandidat'); + const non = screen.getByRole('radio', { name: 'Non' }); expect(non).toBeRequired(); expect(non).toHaveAttribute('name', 'identificationCandidat'); - const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText(textMatcher('À partir de quand êtes vous prêt ' + - 'à accueillir votre conseiller numerique ?*'), { selector: 'p' }); + const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText( + textMatcher('À partir de quand êtes vous prêt à accueillir votre conseiller numerique ?*'), + { selector: 'p' } + ); expect(dateAccueilConseillerNumerique).toBeInTheDocument(); + const date = within(etapeBesoinConseillerNumerique).getByLabelText('Choisir une date'); expect(date).toHaveAttribute('type', 'date'); expect(date).toBeRequired(); - - // THEN - }); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); diff --git a/src/views/candidature-structure/Sommaire.jsx b/src/views/candidature-structure/SommaireStructure.jsx similarity index 52% rename from src/views/candidature-structure/Sommaire.jsx rename to src/views/candidature-structure/SommaireStructure.jsx index 203af76..510fa21 100644 --- a/src/views/candidature-structure/Sommaire.jsx +++ b/src/views/candidature-structure/SommaireStructure.jsx @@ -1,5 +1,8 @@ -export default function Sommaire() { - const partiesSommaire = [ +import React from 'react'; +import Sommaire from '../../components/Sommaire'; + +export default function SommaireStructure() { + const parties = [ { ancre: '#informations-de-structure', libelle: 'Vos informations de structure' @@ -17,19 +20,8 @@ export default function Sommaire() { libelle: 'Votre motivation' } ]; + return ( - + ); } From 3bab5dde2d09ad4a16172c77927ab233bb522c01 Mon Sep 17 00:00:00 2001 From: Alezco Date: Mon, 29 Jul 2024 18:03:08 +0200 Subject: [PATCH 04/29] Ajout d'un scroll vers la bonne section du formulaire --- src/components/Sommaire.jsx | 5 ++++- src/hooks/useScrollToSection.js | 13 +++++++++++++ .../CandidatureConseiller.jsx | 3 +++ .../CandidatureConseiller.test.jsx | 4 ++++ .../candidature-structure/CandidatureStructure.jsx | 3 +++ .../CandidatureStructure.test.jsx | 6 +++++- 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useScrollToSection.js diff --git a/src/components/Sommaire.jsx b/src/components/Sommaire.jsx index ad455c2..de59f8c 100644 --- a/src/components/Sommaire.jsx +++ b/src/components/Sommaire.jsx @@ -1,8 +1,11 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { useLocation } from 'react-router-dom'; export default function Sommaire({ parties }) { - const [dernierElementClique, setDernierElementClique] = useState(parties[0].ancre); + const { hash } = useLocation(); + + const [dernierElementClique, setDernierElementClique] = useState(hash || parties[0].ancre); const getAriaCurrent = ancre => { return ancre === dernierElementClique ? 'page' : false; diff --git a/src/hooks/useScrollToSection.js b/src/hooks/useScrollToSection.js new file mode 100644 index 0000000..588fca5 --- /dev/null +++ b/src/hooks/useScrollToSection.js @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const useScrollToSection = () => { + const { hash } = useLocation(); + + useEffect(() => { + const sectionId = hash.split('#')[1]; + if (sectionId) { + document.getElementById(sectionId).scrollIntoView(); + } + }, []); +}; diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index e821f44..228ceac 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -6,6 +6,7 @@ import Disponibilite from './Disponibilite'; import Motivation from './Motivation'; import EnResume from './EnResume'; import { situations } from './situations'; +import { useScrollToSection } from '../../hooks/useScrollToSection'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -23,6 +24,8 @@ export default function CandidatureConseiller() { new Array(situations.length).fill(false) ); + useScrollToSection(); + const valider = () => { setIsSituationValid(situationChecks.some(checked => checked)); if (!isSituationValid) { diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 212ba04..55eb768 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -3,6 +3,10 @@ import { describe, expect, it, vi } from 'vitest'; import CandidatureConseiller from './CandidatureConseiller'; import { textMatcher } from '../../../test/test-utils'; +vi.mock('react-router-dom', () => ({ + useLocation: () => ({ hash: '' }), +})); + describe('candidature conseiller', () => { describe('étant un candidat', () => { it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 8da9cb7..e0f5b1d 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import { useScrollToSection } from '../../hooks/useScrollToSection'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -15,6 +16,8 @@ import '../candidature-conseiller/CandidatureConseiller.css'; export default function CandidatureStructure() { const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); + useScrollToSection(); + return (
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 46d8216..e0e398f 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -1,8 +1,12 @@ import { render, screen, within } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import CandidatureStructure from './CandidatureStructure'; import { textMatcher } from '../../../test/test-utils'; +vi.mock('react-router-dom', () => ({ + useLocation: () => ({ hash: '' }), +})); + describe('candidature structure', () => { describe('étant une structure', () => { it.todo('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { From 336cde658669fb0022578becc82ba0d975d4a66e Mon Sep 17 00:00:00 2001 From: dienamo Date: Tue, 30 Jul 2024 10:27:44 +0200 Subject: [PATCH 05/29] :feat ajout premier bloc formulaire informations contact structure --- .../CandidatureStructure.test.jsx | 51 ++++++++++++++++-- .../candidature-structure/CompanyFinder.jsx | 20 +++++++ .../InformationsDeContact.tsx | 53 +++++++++++++++++++ .../candidature-structure/useSiretApi.js | 41 ++++++++++++++ 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/views/candidature-structure/CompanyFinder.jsx create mode 100644 src/views/candidature-structure/InformationsDeContact.tsx create mode 100644 src/views/candidature-structure/useSiretApi.js diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index e0e398f..1052718 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -14,10 +14,7 @@ describe('candidature structure', () => { render(); // THEN - const titre = screen.getByRole('heading', { - level: 1, - name: textMatcher('Je souhaite engager un conseiller numérique'), - }); + const titre = screen.getByRole('heading', { level: 1, name: textMatcher('Je souhaite engager un conseiller numérique') }); expect(titre).toBeInTheDocument(); const champsObligatoires = screen.getByText(textMatcher('Les champs avec * sont obligatoires.'), { selector: 'p' }); @@ -119,4 +116,50 @@ describe('candidature structure', () => { }); it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + + const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); + expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + expect(siretOuRidet).toBeRequired(); + + const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + expect(denomination).toHaveAttribute('type', 'text'); + expect(denomination).toBeRequired(); + + const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + expect(adresse).toHaveAttribute('type', 'text'); + expect(adresse).toBeRequired(); + + const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + expect(questionTypeDeStructure).toBeInTheDocument(); + + const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(_uneCommune).toBeRequired(); + expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + + const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(_unDepartement).toBeRequired(); + expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + + const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(_uneRegion).toBeRequired(); + expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + + const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(_unEtablissemntPublic).toBeRequired(); + expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + + const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(_uneCollectivite).toBeRequired(); + expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + + const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(_unGIP).toBeRequired(); + expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + + // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + // expect(_uneStructurePrivee).toBeRequired(); + // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); }); + diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx new file mode 100644 index 0000000..3caf947 --- /dev/null +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import Input from '../../components/commun/Input'; +import { useSiretApi } from './useSiretApi'; +import { debounce } from '../candidature-conseiller/debounce'; + +export default function CompanyFinder() { + const { search, entreprise } = useSiretApi(); + + return ( + <> + search(event.target.value))} + placeholder="N° SIRET / RIDET" + /> + {entreprise} + + ); +} diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeContact.tsx new file mode 100644 index 0000000..a5ba64c --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import Input from "../../components/commun/Input"; +import CompanyFinder from "./CompanyFinder"; +import BoutonRadio from "../../components/commun/BoutonRadio"; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de structure +
+ + + Dénomination * + + + Adresse * + +

+ Votre structure est * +

+
+ + Une commune + + + Un département + + + Une région + + + Un établissement public de coopération intercommunale + + + Une collectivité à statut particulier + + + Un GIP + + + Une structure privée (association, entreprise de l'ESS, fondations) + +
+
+ ); +} diff --git a/src/views/candidature-structure/useSiretApi.js b/src/views/candidature-structure/useSiretApi.js new file mode 100644 index 0000000..7e059d1 --- /dev/null +++ b/src/views/candidature-structure/useSiretApi.js @@ -0,0 +1,41 @@ +import { useState } from 'react'; + +const getUrlEntrepriseApiV3 = (sirenOuSiret, type) => { + let params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + let url = 'https://entreprise.api.gouv.fr/v3/'; + let service = ''; + switch (type) { + case 'siret': + service = 'insee/sirene/etablissements/' + sirenOuSiret; + break; + case 'siren': + service = 'insee/sirene/unites_legales/' + sirenOuSiret + '/siege_social/'; + break; + default: + break; + } + return url + service + params; +}; + +export const useSiretApi = () => { + const [entreprise, setEntreprise] = useState(null); + const [error, setError] = useState(null); + + const search = async (sirenOuSiret, type) => { + try { + const url = getUrlEntrepriseApiV3(sirenOuSiret, type); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + const result = await response.json(); + setEntreprise(result); + setError(null); + } catch (err) { + setError(err.message); + setEntreprise(null); + } + }; + + return { search, entreprise, error }; +}; \ No newline at end of file From 46f7a3ec15534fc820fdc1c9135b154a867a441a Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 11:05:05 +0200 Subject: [PATCH 06/29] Refacto des informations de structure --- .../CandidatureStructure.jsx | 3 +- .../CandidatureStructure.test.jsx | 98 ++++++++++--------- ...ontact.tsx => InformationsDeStructure.jsx} | 16 +-- 3 files changed, 62 insertions(+), 55 deletions(-) rename src/views/candidature-structure/{InformationsDeContact.tsx => InformationsDeStructure.jsx} (81%) diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index e0f5b1d..1dd2e4b 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; +import InformationsDeStructure from './InformationsDeStructure'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; import { useScrollToSection } from '../../hooks/useScrollToSection'; @@ -28,7 +29,7 @@ export default function CandidatureStructure() {

Je souhaite engager un conseiller numérique

Les champs avec * sont obligatoires.

- {/* TODO : Vos informations de structure */} + diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 1052718..5c6ca32 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -38,7 +38,58 @@ describe('candidature structure', () => { }); }); - it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); + + const siretOuRidet = within(etapeInformationsDeStructure).getByPlaceholderText('N° SIRET / RIDET'); + expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + expect(siretOuRidet).toBeRequired(); + + const denomination = within(etapeInformationsDeStructure).getByLabelText('Dénomination *'); + expect(denomination).toHaveAttribute('type', 'text'); + expect(denomination).toBeRequired(); + + const adresse = within(etapeInformationsDeStructure).getByLabelText('Adresse *'); + expect(adresse).toHaveAttribute('type', 'text'); + expect(adresse).toBeRequired(); + + const questionTypeDeStructure = within(etapeInformationsDeStructure).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + expect(questionTypeDeStructure).toBeInTheDocument(); + + const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(_uneCommune).toBeRequired(); + expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + + const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(_unDepartement).toBeRequired(); + expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + + const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(_uneRegion).toBeRequired(); + expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + + const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(_unEtablissemntPublic).toBeRequired(); + expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + + const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(_uneCollectivite).toBeRequired(); + expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + + const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(_unGIP).toBeRequired(); + expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + + // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + // expect(_uneStructurePrivee).toBeRequired(); + // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { // WHEN @@ -116,50 +167,5 @@ describe('candidature structure', () => { }); it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); - const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); - - const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); - expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); - expect(siretOuRidet).toBeRequired(); - - const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); - expect(denomination).toHaveAttribute('type', 'text'); - expect(denomination).toBeRequired(); - - const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); - expect(adresse).toHaveAttribute('type', 'text'); - expect(adresse).toBeRequired(); - - const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); - expect(questionTypeDeStructure).toBeInTheDocument(); - - const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); - expect(_uneCommune).toBeRequired(); - expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); - - const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); - expect(_unDepartement).toBeRequired(); - expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); - - const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); - expect(_uneRegion).toBeRequired(); - expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); - - const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); - expect(_unEtablissemntPublic).toBeRequired(); - expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); - - const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); - expect(_uneCollectivite).toBeRequired(); - expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); - - const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); - expect(_unGIP).toBeRequired(); - expect(_unGIP).toHaveAttribute('name', 'typeStructure'); - - // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); - // expect(_uneStructurePrivee).toBeRequired(); - // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); }); diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeStructure.jsx similarity index 81% rename from src/views/candidature-structure/InformationsDeContact.tsx rename to src/views/candidature-structure/InformationsDeStructure.jsx index a5ba64c..5df3641 100644 --- a/src/views/candidature-structure/InformationsDeContact.tsx +++ b/src/views/candidature-structure/InformationsDeStructure.jsx @@ -1,23 +1,23 @@ -import React from "react"; -import Input from "../../components/commun/Input"; -import CompanyFinder from "./CompanyFinder"; -import BoutonRadio from "../../components/commun/BoutonRadio"; +import React from 'react'; +import Input from '../../components/commun/Input'; +import CompanyFinder from './CompanyFinder'; +import BoutonRadio from '../../components/commun/BoutonRadio'; export default function InformationsDeContact() { return (
Vos informations de structure
- Dénomination * - Adresse * @@ -45,7 +45,7 @@ export default function InformationsDeContact() { Un GIP - Une structure privée (association, entreprise de l'ESS, fondations) + Une structure privée (association, entreprise de l’ESS, fondations)
From 4e7e7259707b8c33c603a5241811b22c88619bea Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Tue, 30 Jul 2024 11:27:59 +0200 Subject: [PATCH 07/29] partie Votre motivation + test --- .../CandidatureStructure.jsx | 4 ++++ .../CandidatureStructure.test.jsx | 22 ++++++++++++++++++- .../candidature-structure/Motivation.jsx | 17 ++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/views/candidature-structure/Motivation.jsx diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index e0f5b1d..6b8b491 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -2,7 +2,9 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import Motivation from './Motivation'; import { useScrollToSection } from '../../hooks/useScrollToSection'; +import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -31,6 +33,8 @@ export default function CandidatureStructure() { {/* TODO : Vos informations de structure */} + + {/* TODO : Engagement */}
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index e0e398f..2826702 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -118,5 +118,25 @@ describe('candidature structure', () => { expect(date).toBeRequired(); }); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); + expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); + + const sousTitreVotreMotvation = + within(etapeMotivation).getByText('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é.', + { selector: 'p' } + ); + expect(sousTitreVotreMotvation).toBeInTheDocument(); + + const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); + expect(votreMessage).toHaveAttribute('id', 'votreMessage'); + expect(votreMessage).toBeRequired(); + }); + it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); }); diff --git a/src/views/candidature-structure/Motivation.jsx b/src/views/candidature-structure/Motivation.jsx new file mode 100644 index 0000000..dda714a --- /dev/null +++ b/src/views/candidature-structure/Motivation.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; + +export default function Motivation() { + return ( +
+ Votre 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 * + +
+ ); +} From c9955e1a59252061a0585887a5367675ee76a8e7 Mon Sep 17 00:00:00 2001 From: dienamo Date: Tue, 30 Jul 2024 10:27:44 +0200 Subject: [PATCH 08/29] :feat ajout premier bloc formulaire informations contact structure --- .../CandidatureStructure.test.jsx | 68 ++++++++++++++++--- .../candidature-structure/CompanyFinder.jsx | 20 ++++++ .../InformationsDeContact.tsx | 53 +++++++++++++++ .../candidature-structure/useSiretApi.js | 41 +++++++++++ 4 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 src/views/candidature-structure/CompanyFinder.jsx create mode 100644 src/views/candidature-structure/InformationsDeContact.tsx create mode 100644 src/views/candidature-structure/useSiretApi.js diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 2826702..2e4998d 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -14,10 +14,7 @@ describe('candidature structure', () => { render(); // THEN - const titre = screen.getByRole('heading', { - level: 1, - name: textMatcher('Je souhaite engager un conseiller numérique'), - }); + const titre = screen.getByRole('heading', { level: 1, name: textMatcher('Je souhaite engager un conseiller numérique') }); expect(titre).toBeInTheDocument(); const champsObligatoires = screen.getByText(textMatcher('Les champs avec * sont obligatoires.'), { selector: 'p' }); @@ -41,7 +38,58 @@ describe('candidature structure', () => { }); }); - it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + + const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); + expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + expect(siretOuRidet).toBeRequired(); + + const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + expect(denomination).toHaveAttribute('type', 'text'); + expect(denomination).toBeRequired(); + + const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + expect(adresse).toHaveAttribute('type', 'text'); + expect(adresse).toBeRequired(); + + const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + expect(questionTypeDeStructure).toBeInTheDocument(); + + const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(_uneCommune).toBeRequired(); + expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + + const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(_unDepartement).toBeRequired(); + expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + + const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(_uneRegion).toBeRequired(); + expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + + const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(_unEtablissemntPublic).toBeRequired(); + expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + + const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(_uneCollectivite).toBeRequired(); + expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + + const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(_unGIP).toBeRequired(); + expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + + // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + // expect(_uneStructurePrivee).toBeRequired(); + // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { // WHEN @@ -126,17 +174,19 @@ describe('candidature structure', () => { const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); const etapeMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); - + const sousTitreVotreMotvation = within(etapeMotivation).getByText('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é.', - { selector: 'p' } + 'votre besoin en recrutement. Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.', + { selector: 'p' } ); expect(sousTitreVotreMotvation).toBeInTheDocument(); - + const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); expect(votreMessage).toHaveAttribute('id', 'votreMessage'); expect(votreMessage).toBeRequired(); }); it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); + diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx new file mode 100644 index 0000000..3caf947 --- /dev/null +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import Input from '../../components/commun/Input'; +import { useSiretApi } from './useSiretApi'; +import { debounce } from '../candidature-conseiller/debounce'; + +export default function CompanyFinder() { + const { search, entreprise } = useSiretApi(); + + return ( + <> + search(event.target.value))} + placeholder="N° SIRET / RIDET" + /> + {entreprise} + + ); +} diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeContact.tsx new file mode 100644 index 0000000..a5ba64c --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import Input from "../../components/commun/Input"; +import CompanyFinder from "./CompanyFinder"; +import BoutonRadio from "../../components/commun/BoutonRadio"; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de structure +
+ + + Dénomination * + + + Adresse * + +

+ Votre structure est * +

+
+ + Une commune + + + Un département + + + Une région + + + Un établissement public de coopération intercommunale + + + Une collectivité à statut particulier + + + Un GIP + + + Une structure privée (association, entreprise de l'ESS, fondations) + +
+
+ ); +} diff --git a/src/views/candidature-structure/useSiretApi.js b/src/views/candidature-structure/useSiretApi.js new file mode 100644 index 0000000..7e059d1 --- /dev/null +++ b/src/views/candidature-structure/useSiretApi.js @@ -0,0 +1,41 @@ +import { useState } from 'react'; + +const getUrlEntrepriseApiV3 = (sirenOuSiret, type) => { + let params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + let url = 'https://entreprise.api.gouv.fr/v3/'; + let service = ''; + switch (type) { + case 'siret': + service = 'insee/sirene/etablissements/' + sirenOuSiret; + break; + case 'siren': + service = 'insee/sirene/unites_legales/' + sirenOuSiret + '/siege_social/'; + break; + default: + break; + } + return url + service + params; +}; + +export const useSiretApi = () => { + const [entreprise, setEntreprise] = useState(null); + const [error, setError] = useState(null); + + const search = async (sirenOuSiret, type) => { + try { + const url = getUrlEntrepriseApiV3(sirenOuSiret, type); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + const result = await response.json(); + setEntreprise(result); + setError(null); + } catch (err) { + setError(err.message); + setEntreprise(null); + } + }; + + return { search, entreprise, error }; +}; \ No newline at end of file From 9c59059ed872c4282bb59102473e0f62dabbaa41 Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 11:05:05 +0200 Subject: [PATCH 09/29] Refacto des informations de structure --- .../CandidatureStructure.jsx | 3 ++- .../CandidatureStructure.test.jsx | 2 ++ ...DeContact.tsx => InformationsDeStructure.jsx} | 16 ++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) rename src/views/candidature-structure/{InformationsDeContact.tsx => InformationsDeStructure.jsx} (81%) diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 6b8b491..9de4de0 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; +import InformationsDeStructure from './InformationsDeStructure'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; import Motivation from './Motivation'; import { useScrollToSection } from '../../hooks/useScrollToSection'; @@ -30,7 +31,7 @@ export default function CandidatureStructure() {

Je souhaite engager un conseiller numérique

Les champs avec * sont obligatoires.

- {/* TODO : Vos informations de structure */} + diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 2e4998d..0936f31 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -60,6 +60,8 @@ describe('candidature structure', () => { expect(adresse).toBeRequired(); const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); expect(questionTypeDeStructure).toBeInTheDocument(); const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeStructure.jsx similarity index 81% rename from src/views/candidature-structure/InformationsDeContact.tsx rename to src/views/candidature-structure/InformationsDeStructure.jsx index a5ba64c..5df3641 100644 --- a/src/views/candidature-structure/InformationsDeContact.tsx +++ b/src/views/candidature-structure/InformationsDeStructure.jsx @@ -1,23 +1,23 @@ -import React from "react"; -import Input from "../../components/commun/Input"; -import CompanyFinder from "./CompanyFinder"; -import BoutonRadio from "../../components/commun/BoutonRadio"; +import React from 'react'; +import Input from '../../components/commun/Input'; +import CompanyFinder from './CompanyFinder'; +import BoutonRadio from '../../components/commun/BoutonRadio'; export default function InformationsDeContact() { return (
Vos informations de structure
- Dénomination * - Adresse * @@ -45,7 +45,7 @@ export default function InformationsDeContact() { Un GIP - Une structure privée (association, entreprise de l'ESS, fondations) + Une structure privée (association, entreprise de l’ESS, fondations)
From 38d30e0edbc34d4a93549daada55ba0f535f3759 Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 17:38:55 +0200 Subject: [PATCH 10/29] Retours de code review --- package.json | 1 + .../CandidatureStructure.test.jsx | 65 ++++++++++--------- .../candidature-structure/useSiretApi.js | 21 +++--- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 796e25d..d739b55 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint src --max-warnings=0 --cache --cache-location node_modules/.cache/eslint", "start": "vite preview", "test": "vitest --run", + "test:watch": "vitest --watch", "test:coverage": "npm test -- --coverage" }, "dependencies": { diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 0936f31..5ca93a0 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -44,53 +44,52 @@ describe('candidature structure', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); - const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); - expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); - expect(siretOuRidet).toBeRequired(); + // const siretOuRidet = within(etapeInformationsDeStructure).getByPlaceholderText('N° SIRET / RIDET'); + // expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + // expect(siretOuRidet).toBeRequired(); - const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + const denomination = within(etapeInformationsDeStructure).getByLabelText('Dénomination *'); expect(denomination).toHaveAttribute('type', 'text'); expect(denomination).toBeRequired(); - const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + const adresse = within(etapeInformationsDeStructure).getByLabelText('Adresse *'); expect(adresse).toHaveAttribute('type', 'text'); expect(adresse).toBeRequired(); - const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); - const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + const questionTypeDeStructure = within(etapeInformationsDeStructure).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); expect(questionTypeDeStructure).toBeInTheDocument(); - const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); - expect(_uneCommune).toBeRequired(); - expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + const uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(uneCommune).toBeRequired(); + expect(uneCommune).toHaveAttribute('name', 'typeStructure'); - const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); - expect(_unDepartement).toBeRequired(); - expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + const unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(unDepartement).toBeRequired(); + expect(unDepartement).toHaveAttribute('name', 'typeStructure'); - const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); - expect(_uneRegion).toBeRequired(); - expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + const uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(uneRegion).toBeRequired(); + expect(uneRegion).toHaveAttribute('name', 'typeStructure'); - const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); - expect(_unEtablissemntPublic).toBeRequired(); - expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + const unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(unEtablissemntPublic).toBeRequired(); + expect(unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); - const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); - expect(_uneCollectivite).toBeRequired(); - expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + const uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(uneCollectivite).toBeRequired(); + expect(uneCollectivite).toHaveAttribute('name', 'typeStructure'); - const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); - expect(_unGIP).toBeRequired(); - expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + const unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(unGIP).toBeRequired(); + expect(unGIP).toHaveAttribute('name', 'typeStructure'); - // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); - // expect(_uneStructurePrivee).toBeRequired(); - // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + const uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + expect(uneStructurePrivee).toBeRequired(); + expect(uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { @@ -120,6 +119,7 @@ describe('candidature structure', () => { const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); expect(telephone).toHaveAttribute('type', 'tel'); + expect(telephone).toHaveAttribute('pattern', '0[1-9]{9}'); expect(telephone).toBeRequired(); }); @@ -178,8 +178,9 @@ describe('candidature structure', () => { expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); const sousTitreVotreMotvation = - within(etapeMotivation).getByText('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é.', + within(etapeMotivation).getByText( + '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é.', { selector: 'p' } ); expect(sousTitreVotreMotvation).toBeInTheDocument(); diff --git a/src/views/candidature-structure/useSiretApi.js b/src/views/candidature-structure/useSiretApi.js index 7e059d1..0a9ffa2 100644 --- a/src/views/candidature-structure/useSiretApi.js +++ b/src/views/candidature-structure/useSiretApi.js @@ -1,20 +1,15 @@ import { useState } from 'react'; const getUrlEntrepriseApiV3 = (sirenOuSiret, type) => { - let params = '?context=cnum&object=checkSiret&recipient=13002603200016'; - let url = 'https://entreprise.api.gouv.fr/v3/'; + const params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + const url = 'https://entreprise.api.gouv.fr/v3/'; let service = ''; - switch (type) { - case 'siret': - service = 'insee/sirene/etablissements/' + sirenOuSiret; - break; - case 'siren': - service = 'insee/sirene/unites_legales/' + sirenOuSiret + '/siege_social/'; - break; - default: - break; + if (type === 'siret') { + service = `insee/sirene/etablissements/'${sirenOuSiret}`; + } else if (type === 'siren') { + service = `insee/sirene/unites_legales/${sirenOuSiret}/siege_social/`; } - return url + service + params; + return `${url}${service}${params}`; }; export const useSiretApi = () => { @@ -38,4 +33,4 @@ export const useSiretApi = () => { }; return { search, entreprise, error }; -}; \ No newline at end of file +}; From 7664c55fe5b872484b33bc42beb8d87ce1ad4ff6 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Wed, 31 Jul 2024 16:53:20 +0200 Subject: [PATCH 11/29] partie engagement + test --- .../CandidatureStructure.jsx | 4 +-- .../CandidatureStructure.test.jsx | 27 +++++++++++++++++-- .../candidature-structure/Engagement.jsx | 23 ++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/views/candidature-structure/Engagement.jsx diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 9de4de0..3e975b8 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -4,8 +4,8 @@ import InformationsDeContact from './InformationsDeContact'; import InformationsDeStructure from './InformationsDeStructure'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; import Motivation from './Motivation'; +import Engagement from './Engagement'; import { useScrollToSection } from '../../hooks/useScrollToSection'; -import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -35,7 +35,7 @@ export default function CandidatureStructure() { - {/* TODO : Engagement */} +
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index a75ad40..bfda0d7 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -186,7 +186,30 @@ describe('candidature structure', () => { expect(votreMessage).toHaveAttribute('id', 'votreMessage'); expect(votreMessage).toBeRequired(); }); - it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); + + it('quand j’affiche le formulaire alors l’encart des engagements est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'),{ selector: 'p' }); + expect(titreEngagement).toBeInTheDocument(); + const engagements = screen.getByTestId('votre-engagement'); + + const listDetail = within(engagements).getAllByRole('listitem'); + within(listDetail[0]).getByText('Assurer que le conseiller réalise des activités de 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 France Services 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).'); + + const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); + expect(confirmationEngagement).toBeInTheDocument(); + + }); + it.todo('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché'); }); - diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx new file mode 100644 index 0000000..fd409dd --- /dev/null +++ b/src/views/candidature-structure/Engagement.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Notice from '../../components/commun/Notice'; +import Checkbox from '../../components/commun/Checkbox'; + +export default function Engagement() { + return ( + +

En tant que structure accueillante, vous vous engagez à

+
    +
  • Assurer que le conseiller réalise des activités de montée en compétences du public (ateliers numériques, initiations au numérique), gratuites.
  • +
  • Qu’il consacre une partie de son temps aux rencontres locales et nationales organisées pour la communauté et la formation continue, etc.
  • +
  • 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.
  • +
  • Mettre à sa disposition les moyens et équipements pour réaliser sa mission (ordinateur, téléphone portable, voiture si nécessaire).
  • +
+ + Je confirme avoir lu et pris connaissance des conditions d’engagement.* + +
+ ); +} From 246b4373d75752bb21cf098fc9998c441b262ff0 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Wed, 31 Jul 2024 17:07:23 +0200 Subject: [PATCH 12/29] partie submit + test + rectif lint --- .../CandidatureStructure.jsx | 3 +++ .../CandidatureStructure.test.jsx | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 3e975b8..ec2bbef 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -36,6 +36,9 @@ export default function CandidatureStructure() { + diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index bfda0d7..f030343 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -198,18 +198,28 @@ describe('candidature structure', () => { const engagements = screen.getByTestId('votre-engagement'); const listDetail = within(engagements).getAllByRole('listitem'); - within(listDetail[0]).getByText('Assurer que le conseiller réalise des activités de 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[0]).getByText('Assurer que le conseiller réalise des activités de ' + + '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 France Services 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).'); + 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).'); const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); expect(confirmationEngagement).toBeInTheDocument(); }); - it.todo('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché'); + it('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché', () => { + // WHEN + render(); + + //THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + within(formulaire).getByRole('button', { name: 'Envoyer votre candidature' }); + }); }); From 6b34c80cba11cb33665e0e54389b09f7990e37db Mon Sep 17 00:00:00 2001 From: dienamo Date: Mon, 22 Jul 2024 14:39:23 +0200 Subject: [PATCH 13/29] :feat intialisation des tests formulaire candidature structure --- .../CandidatureConseiller.test.jsx | 2 +- .../CandidatureStructure.jsx | 12 ++++++ .../CandidatureStructure.test.jsx | 38 +++++++++++++++++++ src/views/candidature-structure/Sommaire.jsx | 35 +++++++++++++++++ .../test-utils.js | 0 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/views/candidature-structure/CandidatureStructure.jsx create mode 100644 src/views/candidature-structure/CandidatureStructure.test.jsx create mode 100644 src/views/candidature-structure/Sommaire.jsx rename {src/views/candidature-conseiller => test}/test-utils.js (100%) diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 1adeecf..e9c9c74 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -1,7 +1,7 @@ import { render, screen, within, fireEvent } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import CandidatureConseiller from './CandidatureConseiller'; -import { textMatcher } from './test-utils'; +import { textMatcher } from '../../../test/test-utils'; describe('candidature conseiller', () => { describe('étant un candidat', () => { diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx new file mode 100644 index 0000000..5a778a6 --- /dev/null +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Sommaire from './Sommaire'; + +export default function CandidatureStructure() { + return ( +
+

Je souhaite engager un conseiller numérique

+

Les champs avec * sont obligatoires.

+ +
+ ); +} diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx new file mode 100644 index 0000000..89b3274 --- /dev/null +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -0,0 +1,38 @@ +import { render, screen, within } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import CandidatureStructure from './CandidatureStructure'; +import { textMatcher } from '../../../test/test-utils'; +describe('candidature structure', () => { + describe('étant une structure', () => { + it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { + // WHEN + render(); + + // THEN + const titre = screen.getByRole('heading', { + level: 1, + name: textMatcher('Je souhaite engager un conseiller numérique'), + }); + expect(titre).toBeInTheDocument(); + + const champsObligatoires = screen.getByText(textMatcher('Les champs avec * sont obligatoires.'), { selector: 'p' }); + expect(champsObligatoires).toBeInTheDocument(); + + const navigation = screen.getByRole('navigation', { name: 'Sommaire' }); + const menu = within(navigation).getByRole('list'); + const menuItems = within(menu).getAllByRole('listitem'); + + const informationsDeStructure = within(menuItems[0]).getByRole('link', { name: 'Vos informations de structure' }); + expect(informationsDeStructure).toHaveAttribute('href', '#informations-de-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' }); + 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'); + }); + }); +}); diff --git a/src/views/candidature-structure/Sommaire.jsx b/src/views/candidature-structure/Sommaire.jsx new file mode 100644 index 0000000..203af76 --- /dev/null +++ b/src/views/candidature-structure/Sommaire.jsx @@ -0,0 +1,35 @@ +export default function Sommaire() { + const partiesSommaire = [ + { + ancre: '#informations-de-structure', + libelle: 'Vos informations de structure' + }, + { + ancre: '#informations-de-contact', + libelle: 'Vos informations de contact' + }, + { + ancre: '#votre-besoin-en-conseiller-numerique', + libelle: 'Votre besoin en conseiller numérique' + }, + { + ancre: '#votre-motivation', + libelle: 'Votre motivation' + } + ]; + return ( +
+ ); +} diff --git a/src/views/candidature-conseiller/test-utils.js b/test/test-utils.js similarity index 100% rename from src/views/candidature-conseiller/test-utils.js rename to test/test-utils.js From 7f3a56202a6372c2856a73bf1f300f56ef70fa65 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Thu, 25 Jul 2024 11:07:04 +0200 Subject: [PATCH 14/29] test formulaire partie 'information contact' + 'votre besoin CN' --- src/App.js | 2 + .../BesoinEnConseillerNumerique.jsx | 36 +++++++++ .../CandidatureStructure.jsx | 25 ++++-- .../CandidatureStructure.test.jsx | 80 ++++++++++++++++++- .../InformationsDeContact.jsx | 39 +++++++++ .../PageCandidatureStructure.jsx | 12 +++ 6 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/views/candidature-structure/BesoinEnConseillerNumerique.jsx create mode 100644 src/views/candidature-structure/InformationsDeContact.jsx create mode 100644 src/views/candidature-structure/PageCandidatureStructure.jsx diff --git a/src/App.js b/src/App.js index 3dd4ccd..f358527 100644 --- a/src/App.js +++ b/src/App.js @@ -37,6 +37,7 @@ function App() { const CoordinationTerritoriale = lazy(() => import('./views/coordination-territoriale')); const CarteCoordinateur = lazy(() => import('./views/coordination-territoriale/CarteCoordinateur')); const PageCandidatureConseiller = lazy(() => import('./views/candidature-conseiller/PageCandidatureConseiller')); + const PageCandidatureStructure = lazy(() => import('./views/candidature-structure/PageCandidatureStructure')); return (
@@ -45,6 +46,7 @@ function App() { }/> }/> + }/> }/> }/> }/> diff --git a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx new file mode 100644 index 0000000..de4899a --- /dev/null +++ b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx @@ -0,0 +1,36 @@ +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({ setDateAccueilConseillerNumerique }) { + return ( +
+ Votre besoin en conseiller(s) numérique(s) +
+ + Combien de conseillers numériques souhaitez-vous accueillir ?* + +
+

Avez-vous déjà identifié un candidat pour le poste de conseiller numérique ?*

+

Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique

+ + Oui + + + Non + +
+

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

+ setDateAccueilConseillerNumerique(event.target.value)}> + Choisir une date + +
+ ); +} + +BesoinEnConseillerNumerique.propTypes = { + setDateAccueilConseillerNumerique: PropTypes.func, +}; diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 5a778a6..d0e00af 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,12 +1,27 @@ -import React from 'react'; +import React, { useState } from 'react'; import Sommaire from './Sommaire'; +import InformationsDeContact from './InformationsDeContact'; +import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; export default function CandidatureStructure() { + const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); + return ( -
-

Je souhaite engager un conseiller numérique

-

Les champs avec * sont obligatoires.

- +
+
+
+ +
+
+

Je souhaite engager un conseiller numérique

+

Les champs avec * sont obligatoires.

+
+ {/* TODO : Vos informations de structure */} + + + +
+
); } diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 89b3274..b1ac3d6 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -2,9 +2,10 @@ import { render, screen, within } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import CandidatureStructure from './CandidatureStructure'; import { textMatcher } from '../../../test/test-utils'; + describe('candidature structure', () => { describe('étant une structure', () => { - it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { + it.todo('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { // WHEN render(); @@ -35,4 +36,81 @@ describe('candidature structure', () => { expect(votreMotivation).toHaveAttribute('href', '#votre-motivation'); }); }); + it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { + // GIVEN + render(); + + // WHEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); + + const prenom = within(etapeInformationsDeContact).getByLabelText('Prénom *'); + expect(prenom).toHaveAttribute('type', 'text'); + expect(prenom).toBeRequired(); + + const nom = within(etapeInformationsDeContact).getByLabelText('Nom *'); + expect(nom).toHaveAttribute('type', 'text'); + expect(nom).toBeRequired(); + + const fonction = within(etapeInformationsDeContact).getByLabelText('Fonction *'); + expect(fonction).toHaveAttribute('type', 'text'); + expect(fonction).toBeRequired(); + + const email = within(etapeInformationsDeContact).getByLabelText('Adresse e-mail *'); + expect(email).toHaveAttribute('type', 'email'); + expect(email).toBeRequired(); + + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); + expect(telephone).toHaveAttribute('type', 'tel'); + expect(telephone).toBeRequired(); + + // THEN + + + }); + it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { + + //GIVEN + render(); + + // WHEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeBesoinConseillerNumerique = within(formulaire).getByRole('group', { name: 'Votre besoin en conseiller(s) numérique(s)' }); + expect(etapeBesoinConseillerNumerique).toHaveAttribute('id', 'votre-besoin-en-conseiller-numerique'); + + const combienConseillerNumerique = within(etapeBesoinConseillerNumerique).getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ?*'); + expect(combienConseillerNumerique).toHaveAttribute('type', 'number'); + expect(combienConseillerNumerique).toBeRequired(); + + + const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText(textMatcher('Avez-vous déjà identifié un candidat ' + + 'pour le poste de conseiller numérique ?*'), { selector: 'p' }); + expect(identificationCandidat).toBeInTheDocument(); + + const sousTitreIdentificationCandidat = + within(etapeBesoinConseillerNumerique).getByText(textMatcher('Si oui, merci d’inviter ce candidat ' + + 'à s’inscrire sur la plateforme Conseiller numérique'), { selector: 'p' }); + expect(sousTitreIdentificationCandidat).toBeInTheDocument(); + + const oui = screen.getByRole('radio', { name: 'Oui' }); + expect(oui).toBeRequired(); + expect(oui).toHaveAttribute('name', 'identificationCandidat'); + const non = screen.getByRole('radio', { name: 'Non' }); + expect(non).toBeRequired(); + expect(non).toHaveAttribute('name', 'identificationCandidat'); + + const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText(textMatcher('À partir de quand êtes vous prêt ' + + 'à accueillir votre conseiller numerique ?*'), { selector: 'p' }); + expect(dateAccueilConseillerNumerique).toBeInTheDocument(); + const date = within(etapeBesoinConseillerNumerique).getByLabelText('Choisir une date'); + expect(date).toHaveAttribute('type', 'date'); + expect(date).toBeRequired(); + + // THEN + + }); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + }); diff --git a/src/views/candidature-structure/InformationsDeContact.jsx b/src/views/candidature-structure/InformationsDeContact.jsx new file mode 100644 index 0000000..532b5be --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Input from '../../components/commun/Input'; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de contact +
+ + Prénom * + + + Nom * + + + Fonction * + + + Adresse e-mail * + + + Téléphone * + +
+ ); +} diff --git a/src/views/candidature-structure/PageCandidatureStructure.jsx b/src/views/candidature-structure/PageCandidatureStructure.jsx new file mode 100644 index 0000000..a5ab078 --- /dev/null +++ b/src/views/candidature-structure/PageCandidatureStructure.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Header from '../../components/Header'; +import CandidatureStructure from './CandidatureStructure'; + +export default function PageCandidatureStructure() { + return ( + <> +
+ + + ); +} From ef5738e97f04af2ea105a80ad178d07522f7ff1d Mon Sep 17 00:00:00 2001 From: Alezco Date: Mon, 29 Jul 2024 17:25:26 +0200 Subject: [PATCH 15/29] Retours de code review --- .../Sommaire.jsx | 31 ++++--------- .../CandidatureConseiller.jsx | 4 +- .../CandidatureConseiller.test.jsx | 18 ++++---- .../candidature-conseiller/Disponibilite.jsx | 2 +- .../InformationsDeContact.jsx | 2 +- .../candidature-conseiller/Motivation.jsx | 2 +- .../SituationEtExperience.jsx | 2 +- .../SommaireConseiller.jsx | 27 +++++++++++ .../CandidatureStructure.jsx | 13 +++++- .../CandidatureStructure.test.jsx | 46 ++++++++++--------- .../{Sommaire.jsx => SommaireStructure.jsx} | 22 +++------ 11 files changed, 92 insertions(+), 77 deletions(-) rename src/{views/candidature-conseiller => components}/Sommaire.jsx (53%) create mode 100644 src/views/candidature-conseiller/SommaireConseiller.jsx rename src/views/candidature-structure/{Sommaire.jsx => SommaireStructure.jsx} (52%) diff --git a/src/views/candidature-conseiller/Sommaire.jsx b/src/components/Sommaire.jsx similarity index 53% rename from src/views/candidature-conseiller/Sommaire.jsx rename to src/components/Sommaire.jsx index d96296a..ad455c2 100644 --- a/src/views/candidature-conseiller/Sommaire.jsx +++ b/src/components/Sommaire.jsx @@ -1,27 +1,8 @@ import React, { useState } from 'react'; +import PropTypes from 'prop-types'; -export default function Sommaire() { - const ancreInformationsDeContact = '#informationsDeContact'; - const [dernierElementClique, setDernierElementClique] = useState(ancreInformationsDeContact); - - const partiesSommaire = [ - { - ancre: ancreInformationsDeContact, - libelle: 'Vos informations de contact' - }, - { - ancre: '#situationEtExperience', - libelle: 'Votre situation et expérience' - }, - { - ancre: '#votreDisponibilite', - libelle: 'Votre disponibilité' - }, - { - ancre: '#votreMotivation', - libelle: 'Votre motivation' - }, - ]; +export default function Sommaire({ parties }) { + const [dernierElementClique, setDernierElementClique] = useState(parties[0].ancre); const getAriaCurrent = ancre => { return ancre === dernierElementClique ? 'page' : false; @@ -30,7 +11,7 @@ export default function Sommaire() { return ( ); } + +Sommaire.propTypes = { + parties: PropTypes.array, +}; diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index 7491ab8..e821f44 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import Sommaire from './Sommaire'; +import SommaireConseiller from './SommaireConseiller'; import InformationsDeContact from './InformationsDeContact'; import SituationEtExperience from './SituationEtExperience'; import Disponibilite from './Disponibilite'; @@ -34,7 +34,7 @@ export default function CandidatureConseiller() {
- +

Je veux devenir conseiller numérique

diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index e9c9c74..212ba04 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -21,16 +21,16 @@ describe('candidature conseiller', () => { const menuItems = within(menu).getAllByRole('listitem'); const informationsDeContact = within(menuItems[0]).getByRole('link', { name: 'Vos informations de contact' }); - expect(informationsDeContact).toHaveAttribute('href', '#informationsDeContact'); + expect(informationsDeContact).toHaveAttribute('href', '#informations-de-contact'); const situationEtExperience = within(menuItems[1]).getByRole('link', { name: 'Votre situation et expérience' }); - expect(situationEtExperience).toHaveAttribute('href', '#situationEtExperience'); + expect(situationEtExperience).toHaveAttribute('href', '#situation-et-experience'); const votreDisponibilite = within(menuItems[2]).getByRole('link', { name: 'Votre disponibilité' }); - expect(votreDisponibilite).toHaveAttribute('href', '#votreDisponibilite'); + expect(votreDisponibilite).toHaveAttribute('href', '#votre-disponibilite'); const votreMotivation = within(menuItems[3]).getByRole('link', { name: 'Votre motivation' }); - expect(votreMotivation).toHaveAttribute('href', '#votreMotivation'); + expect(votreMotivation).toHaveAttribute('href', '#votre-motivation'); }); }); @@ -41,7 +41,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); const prenom = within(etapeInformationsDeContact).getByLabelText('Prénom *'); expect(prenom).toHaveAttribute('type', 'text'); @@ -70,7 +70,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const situationEtExperience = within(formulaire).getByRole('group', { name: 'Votre situation et expérience' }); - expect(situationEtExperience).toHaveAttribute('id', 'situationEtExperience'); + expect(situationEtExperience).toHaveAttribute('id', 'situation-et-experience'); const situation = within(situationEtExperience).getByText(textMatcher('Êtes-vous actuellement dans l’une des situations suivantes ? *'), { selector: 'p' }); expect(situation).toBeInTheDocument(); @@ -131,7 +131,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const votreDisponibilite = within(formulaire).getByRole('group', { name: 'Votre disponibilité' }); - expect(votreDisponibilite).toHaveAttribute('id', 'votreDisponibilite'); + expect(votreDisponibilite).toHaveAttribute('id', 'votre-disponibilite'); const questionDisponibilite = within(votreDisponibilite).getByText( textMatcher('À quel moment êtes-vous prêt(e) à démarrer votre mission et la formation de conseiller numérique ? *'), @@ -197,7 +197,7 @@ describe('candidature conseiller', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature conseiller' }); const votreMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); - expect(votreMotivation).toHaveAttribute('id', 'votreMotivation'); + expect(votreMotivation).toHaveAttribute('id', 'votre-motivation'); const aideMotivation = within(votreMotivation).getByText( textMatcher('En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique ' + @@ -234,7 +234,7 @@ describe('candidature conseiller', () => { expect(descriptionResume).toBeInTheDocument(); }); - it('quand je modifie la date de disponibilité, elle s’affiche dans l’encart "En résumé" est affiché', () => { + it('quand je modifie la date de disponibilité, alors elle s’affiche dans l’encart "En résumé"', () => { // GIVEN render(); const dateDisponibilite = '2023-12-12'; diff --git a/src/views/candidature-conseiller/Disponibilite.jsx b/src/views/candidature-conseiller/Disponibilite.jsx index 2cda5a6..9cbe6fb 100644 --- a/src/views/candidature-conseiller/Disponibilite.jsx +++ b/src/views/candidature-conseiller/Disponibilite.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; export default function Disponibilite({ setDateDisponibilite }) { return ( -
+
Votre disponibilité

diff --git a/src/views/candidature-conseiller/InformationsDeContact.jsx b/src/views/candidature-conseiller/InformationsDeContact.jsx index 8ab07ad..427b4e0 100644 --- a/src/views/candidature-conseiller/InformationsDeContact.jsx +++ b/src/views/candidature-conseiller/InformationsDeContact.jsx @@ -4,7 +4,7 @@ import AddressChooser from './AddressChooser'; export default function InformationsDeContact() { return ( -

+
Vos informations de contact
+
Votre motivation

En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique et{' '} diff --git a/src/views/candidature-conseiller/SituationEtExperience.jsx b/src/views/candidature-conseiller/SituationEtExperience.jsx index bd214bd..4fa0759 100644 --- a/src/views/candidature-conseiller/SituationEtExperience.jsx +++ b/src/views/candidature-conseiller/SituationEtExperience.jsx @@ -19,7 +19,7 @@ export default function SituationEtExperience({ situationChecks, setSituationChe }; return ( -

+
Votre situation et expérience

diff --git a/src/views/candidature-conseiller/SommaireConseiller.jsx b/src/views/candidature-conseiller/SommaireConseiller.jsx new file mode 100644 index 0000000..870bb35 --- /dev/null +++ b/src/views/candidature-conseiller/SommaireConseiller.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import Sommaire from '../../components/Sommaire'; + +export default function SommaireConseiller() { + const parties = [ + { + ancre: '#informations-de-contact', + libelle: 'Vos informations de contact' + }, + { + ancre: '#situation-et-experience', + libelle: 'Votre situation et expérience' + }, + { + ancre: '#votre-disponibilite', + libelle: 'Votre disponibilité' + }, + { + ancre: '#votre-motivation', + libelle: 'Votre motivation' + }, + ]; + + return ( + + ); +} diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index d0e00af..8da9cb7 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,8 +1,17 @@ import React, { useState } from 'react'; -import Sommaire from './Sommaire'; +import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import '@gouvfr/dsfr/dist/component/form/form.min.css'; +import '@gouvfr/dsfr/dist/component/input/input.min.css'; +import '@gouvfr/dsfr/dist/component/checkbox/checkbox.min.css'; +import '@gouvfr/dsfr/dist/component/radio/radio.min.css'; +import '@gouvfr/dsfr/dist/component/badge/badge.min.css'; +import '@gouvfr/dsfr/dist/component/notice/notice.min.css'; +import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; +import '../candidature-conseiller/CandidatureConseiller.css'; + export default function CandidatureStructure() { const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); @@ -10,7 +19,7 @@ export default function CandidatureStructure() {

- +

Je souhaite engager un conseiller numérique

diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index b1ac3d6..46d8216 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -31,17 +31,19 @@ describe('candidature structure', () => { const votreBesoinEnConseillerNumerique = within(menuItems[2]).getByRole('link', { name: 'Votre besoin en conseiller numérique' }); 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'); }); }); + it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { - // GIVEN + // WHEN render(); - // WHEN + // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de contact' }); expect(etapeInformationsDeContact).toHaveAttribute('id', 'informations-de-contact'); @@ -65,17 +67,13 @@ describe('candidature structure', () => { const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); expect(telephone).toHaveAttribute('type', 'tel'); expect(telephone).toBeRequired(); - - // THEN - - }); - it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { - //GIVEN + it('quand j’affiche le formulaire alors l’étape "Votre besoin en conseiller(s) numérique(s)" est affiché', () => { + // WHEN render(); - // WHEN + // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); const etapeBesoinConseillerNumerique = within(formulaire).getByRole('group', { name: 'Votre besoin en conseiller(s) numérique(s)' }); expect(etapeBesoinConseillerNumerique).toHaveAttribute('id', 'votre-besoin-en-conseiller-numerique'); @@ -84,33 +82,37 @@ describe('candidature structure', () => { expect(combienConseillerNumerique).toHaveAttribute('type', 'number'); expect(combienConseillerNumerique).toBeRequired(); - - const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText(textMatcher('Avez-vous déjà identifié un candidat ' + - 'pour le poste de conseiller numérique ?*'), { selector: 'p' }); + const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText( + textMatcher('Avez-vous déjà identifié un candidat pour le poste de conseiller numérique ?*'), + { selector: 'p' } + ); expect(identificationCandidat).toBeInTheDocument(); - + const sousTitreIdentificationCandidat = - within(etapeBesoinConseillerNumerique).getByText(textMatcher('Si oui, merci d’inviter ce candidat ' + - 'à s’inscrire sur la plateforme Conseiller numérique'), { selector: 'p' }); + within(etapeBesoinConseillerNumerique).getByText( + textMatcher('Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique'), + { selector: 'p' } + ); expect(sousTitreIdentificationCandidat).toBeInTheDocument(); const oui = screen.getByRole('radio', { name: 'Oui' }); expect(oui).toBeRequired(); expect(oui).toHaveAttribute('name', 'identificationCandidat'); + const non = screen.getByRole('radio', { name: 'Non' }); expect(non).toBeRequired(); expect(non).toHaveAttribute('name', 'identificationCandidat'); - const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText(textMatcher('À partir de quand êtes vous prêt ' + - 'à accueillir votre conseiller numerique ?*'), { selector: 'p' }); + const dateAccueilConseillerNumerique = within(etapeBesoinConseillerNumerique).getByText( + textMatcher('À partir de quand êtes vous prêt à accueillir votre conseiller numerique ?*'), + { selector: 'p' } + ); expect(dateAccueilConseillerNumerique).toBeInTheDocument(); + const date = within(etapeBesoinConseillerNumerique).getByLabelText('Choisir une date'); expect(date).toHaveAttribute('type', 'date'); expect(date).toBeRequired(); - - // THEN - }); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); diff --git a/src/views/candidature-structure/Sommaire.jsx b/src/views/candidature-structure/SommaireStructure.jsx similarity index 52% rename from src/views/candidature-structure/Sommaire.jsx rename to src/views/candidature-structure/SommaireStructure.jsx index 203af76..510fa21 100644 --- a/src/views/candidature-structure/Sommaire.jsx +++ b/src/views/candidature-structure/SommaireStructure.jsx @@ -1,5 +1,8 @@ -export default function Sommaire() { - const partiesSommaire = [ +import React from 'react'; +import Sommaire from '../../components/Sommaire'; + +export default function SommaireStructure() { + const parties = [ { ancre: '#informations-de-structure', libelle: 'Vos informations de structure' @@ -17,19 +20,8 @@ export default function Sommaire() { libelle: 'Votre motivation' } ]; + return ( - + ); } From 897c9328a0016eec0715d930481a7d16d759c247 Mon Sep 17 00:00:00 2001 From: Alezco Date: Mon, 29 Jul 2024 18:03:08 +0200 Subject: [PATCH 16/29] Ajout d'un scroll vers la bonne section du formulaire --- src/components/Sommaire.jsx | 5 ++++- src/hooks/useScrollToSection.js | 13 +++++++++++++ .../CandidatureConseiller.jsx | 3 +++ .../CandidatureConseiller.test.jsx | 4 ++++ .../candidature-structure/CandidatureStructure.jsx | 3 +++ .../CandidatureStructure.test.jsx | 6 +++++- 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useScrollToSection.js diff --git a/src/components/Sommaire.jsx b/src/components/Sommaire.jsx index ad455c2..de59f8c 100644 --- a/src/components/Sommaire.jsx +++ b/src/components/Sommaire.jsx @@ -1,8 +1,11 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { useLocation } from 'react-router-dom'; export default function Sommaire({ parties }) { - const [dernierElementClique, setDernierElementClique] = useState(parties[0].ancre); + const { hash } = useLocation(); + + const [dernierElementClique, setDernierElementClique] = useState(hash || parties[0].ancre); const getAriaCurrent = ancre => { return ancre === dernierElementClique ? 'page' : false; diff --git a/src/hooks/useScrollToSection.js b/src/hooks/useScrollToSection.js new file mode 100644 index 0000000..588fca5 --- /dev/null +++ b/src/hooks/useScrollToSection.js @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const useScrollToSection = () => { + const { hash } = useLocation(); + + useEffect(() => { + const sectionId = hash.split('#')[1]; + if (sectionId) { + document.getElementById(sectionId).scrollIntoView(); + } + }, []); +}; diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index e821f44..228ceac 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -6,6 +6,7 @@ import Disponibilite from './Disponibilite'; import Motivation from './Motivation'; import EnResume from './EnResume'; import { situations } from './situations'; +import { useScrollToSection } from '../../hooks/useScrollToSection'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -23,6 +24,8 @@ export default function CandidatureConseiller() { new Array(situations.length).fill(false) ); + useScrollToSection(); + const valider = () => { setIsSituationValid(situationChecks.some(checked => checked)); if (!isSituationValid) { diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 212ba04..55eb768 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -3,6 +3,10 @@ import { describe, expect, it, vi } from 'vitest'; import CandidatureConseiller from './CandidatureConseiller'; import { textMatcher } from '../../../test/test-utils'; +vi.mock('react-router-dom', () => ({ + useLocation: () => ({ hash: '' }), +})); + describe('candidature conseiller', () => { describe('étant un candidat', () => { it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 8da9cb7..e0f5b1d 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import { useScrollToSection } from '../../hooks/useScrollToSection'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -15,6 +16,8 @@ import '../candidature-conseiller/CandidatureConseiller.css'; export default function CandidatureStructure() { const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); + useScrollToSection(); + return (
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 46d8216..e0e398f 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -1,8 +1,12 @@ import { render, screen, within } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import CandidatureStructure from './CandidatureStructure'; import { textMatcher } from '../../../test/test-utils'; +vi.mock('react-router-dom', () => ({ + useLocation: () => ({ hash: '' }), +})); + describe('candidature structure', () => { describe('étant une structure', () => { it.todo('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { From d64b5629176329e6943bd59ab5e0b000d8f8ff5b Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Tue, 30 Jul 2024 11:27:59 +0200 Subject: [PATCH 17/29] partie Votre motivation + test --- .../CandidatureStructure.jsx | 4 ++++ .../CandidatureStructure.test.jsx | 22 ++++++++++++++++++- .../candidature-structure/Motivation.jsx | 17 ++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/views/candidature-structure/Motivation.jsx diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index e0f5b1d..6b8b491 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -2,7 +2,9 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; +import Motivation from './Motivation'; import { useScrollToSection } from '../../hooks/useScrollToSection'; +import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -31,6 +33,8 @@ export default function CandidatureStructure() { {/* TODO : Vos informations de structure */} + + {/* TODO : Engagement */}
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index e0e398f..2826702 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -118,5 +118,25 @@ describe('candidature structure', () => { expect(date).toBeRequired(); }); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); + expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); + + const sousTitreVotreMotvation = + within(etapeMotivation).getByText('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é.', + { selector: 'p' } + ); + expect(sousTitreVotreMotvation).toBeInTheDocument(); + + const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); + expect(votreMessage).toHaveAttribute('id', 'votreMessage'); + expect(votreMessage).toBeRequired(); + }); + it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); }); diff --git a/src/views/candidature-structure/Motivation.jsx b/src/views/candidature-structure/Motivation.jsx new file mode 100644 index 0000000..dda714a --- /dev/null +++ b/src/views/candidature-structure/Motivation.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; + +export default function Motivation() { + return ( +
+ Votre 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 * + +
+ ); +} From 17cd1c2579bf4d46200ba8b04762ce793ac3207c Mon Sep 17 00:00:00 2001 From: dienamo Date: Tue, 30 Jul 2024 10:27:44 +0200 Subject: [PATCH 18/29] :feat ajout premier bloc formulaire informations contact structure --- .../CandidatureStructure.test.jsx | 80 +++++++++++++------ .../candidature-structure/CompanyFinder.jsx | 20 +++++ .../InformationsDeContact.tsx | 53 ++++++++++++ .../candidature-structure/useSiretApi.js | 41 ++++++++++ 4 files changed, 168 insertions(+), 26 deletions(-) create mode 100644 src/views/candidature-structure/CompanyFinder.jsx create mode 100644 src/views/candidature-structure/InformationsDeContact.tsx create mode 100644 src/views/candidature-structure/useSiretApi.js diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 2826702..7ec7edf 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -14,10 +14,7 @@ describe('candidature structure', () => { render(); // THEN - const titre = screen.getByRole('heading', { - level: 1, - name: textMatcher('Je souhaite engager un conseiller numérique'), - }); + const titre = screen.getByRole('heading', { level: 1, name: textMatcher('Je souhaite engager un conseiller numérique') }); expect(titre).toBeInTheDocument(); const champsObligatoires = screen.getByText(textMatcher('Les champs avec * sont obligatoires.'), { selector: 'p' }); @@ -41,7 +38,58 @@ describe('candidature structure', () => { }); }); - it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + + const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); + expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + expect(siretOuRidet).toBeRequired(); + + const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + expect(denomination).toHaveAttribute('type', 'text'); + expect(denomination).toBeRequired(); + + const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + expect(adresse).toHaveAttribute('type', 'text'); + expect(adresse).toBeRequired(); + + const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + expect(questionTypeDeStructure).toBeInTheDocument(); + + const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(_uneCommune).toBeRequired(); + expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + + const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(_unDepartement).toBeRequired(); + expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + + const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(_uneRegion).toBeRequired(); + expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + + const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(_unEtablissemntPublic).toBeRequired(); + expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + + const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(_uneCollectivite).toBeRequired(); + expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + + const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(_unGIP).toBeRequired(); + expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + + // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + // expect(_uneStructurePrivee).toBeRequired(); + // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { // WHEN @@ -118,25 +166,5 @@ describe('candidature structure', () => { expect(date).toBeRequired(); }); - it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { - // WHEN - render(); - - // THEN - const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const etapeMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); - expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); - - const sousTitreVotreMotvation = - within(etapeMotivation).getByText('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é.', - { selector: 'p' } - ); - expect(sousTitreVotreMotvation).toBeInTheDocument(); - - const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); - expect(votreMessage).toHaveAttribute('id', 'votreMessage'); - expect(votreMessage).toBeRequired(); - }); - it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); diff --git a/src/views/candidature-structure/CompanyFinder.jsx b/src/views/candidature-structure/CompanyFinder.jsx new file mode 100644 index 0000000..3caf947 --- /dev/null +++ b/src/views/candidature-structure/CompanyFinder.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import Input from '../../components/commun/Input'; +import { useSiretApi } from './useSiretApi'; +import { debounce } from '../candidature-conseiller/debounce'; + +export default function CompanyFinder() { + const { search, entreprise } = useSiretApi(); + + return ( + <> + search(event.target.value))} + placeholder="N° SIRET / RIDET" + /> + {entreprise} + + ); +} diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeContact.tsx new file mode 100644 index 0000000..a5ba64c --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import Input from "../../components/commun/Input"; +import CompanyFinder from "./CompanyFinder"; +import BoutonRadio from "../../components/commun/BoutonRadio"; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de structure +
+ + + Dénomination * + + + Adresse * + +

+ Votre structure est * +

+
+ + Une commune + + + Un département + + + Une région + + + Un établissement public de coopération intercommunale + + + Une collectivité à statut particulier + + + Un GIP + + + Une structure privée (association, entreprise de l'ESS, fondations) + +
+
+ ); +} diff --git a/src/views/candidature-structure/useSiretApi.js b/src/views/candidature-structure/useSiretApi.js new file mode 100644 index 0000000..7e059d1 --- /dev/null +++ b/src/views/candidature-structure/useSiretApi.js @@ -0,0 +1,41 @@ +import { useState } from 'react'; + +const getUrlEntrepriseApiV3 = (sirenOuSiret, type) => { + let params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + let url = 'https://entreprise.api.gouv.fr/v3/'; + let service = ''; + switch (type) { + case 'siret': + service = 'insee/sirene/etablissements/' + sirenOuSiret; + break; + case 'siren': + service = 'insee/sirene/unites_legales/' + sirenOuSiret + '/siege_social/'; + break; + default: + break; + } + return url + service + params; +}; + +export const useSiretApi = () => { + const [entreprise, setEntreprise] = useState(null); + const [error, setError] = useState(null); + + const search = async (sirenOuSiret, type) => { + try { + const url = getUrlEntrepriseApiV3(sirenOuSiret, type); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + const result = await response.json(); + setEntreprise(result); + setError(null); + } catch (err) { + setError(err.message); + setEntreprise(null); + } + }; + + return { search, entreprise, error }; +}; \ No newline at end of file From dddc38b56a59b39f2ebc7ef1bb57bb292504a8fc Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 11:05:05 +0200 Subject: [PATCH 19/29] Refacto des informations de structure --- .../CandidatureStructure.jsx | 3 +- .../CandidatureStructure.test.jsx | 53 +------------------ ...ontact.tsx => InformationsDeStructure.jsx} | 16 +++--- 3 files changed, 11 insertions(+), 61 deletions(-) rename src/views/candidature-structure/{InformationsDeContact.tsx => InformationsDeStructure.jsx} (81%) diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 6b8b491..9de4de0 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; +import InformationsDeStructure from './InformationsDeStructure'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; import Motivation from './Motivation'; import { useScrollToSection } from '../../hooks/useScrollToSection'; @@ -30,7 +31,7 @@ export default function CandidatureStructure() {

Je souhaite engager un conseiller numérique

Les champs avec * sont obligatoires.

- {/* TODO : Vos informations de structure */} + diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 7ec7edf..1010e3e 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -38,58 +38,7 @@ describe('candidature structure', () => { }); }); - it('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché', () => { - // WHEN - render(); - - // THEN - const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); - - const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); - expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); - expect(siretOuRidet).toBeRequired(); - - const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); - expect(denomination).toHaveAttribute('type', 'text'); - expect(denomination).toBeRequired(); - - const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); - expect(adresse).toHaveAttribute('type', 'text'); - expect(adresse).toBeRequired(); - - const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); - expect(questionTypeDeStructure).toBeInTheDocument(); - - const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); - expect(_uneCommune).toBeRequired(); - expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); - - const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); - expect(_unDepartement).toBeRequired(); - expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); - - const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); - expect(_uneRegion).toBeRequired(); - expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); - - const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); - expect(_unEtablissemntPublic).toBeRequired(); - expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); - - const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); - expect(_uneCollectivite).toBeRequired(); - expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); - - const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); - expect(_unGIP).toBeRequired(); - expect(_unGIP).toHaveAttribute('name', 'typeStructure'); - - // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); - // expect(_uneStructurePrivee).toBeRequired(); - // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); - }); + it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { // WHEN diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeStructure.jsx similarity index 81% rename from src/views/candidature-structure/InformationsDeContact.tsx rename to src/views/candidature-structure/InformationsDeStructure.jsx index a5ba64c..5df3641 100644 --- a/src/views/candidature-structure/InformationsDeContact.tsx +++ b/src/views/candidature-structure/InformationsDeStructure.jsx @@ -1,23 +1,23 @@ -import React from "react"; -import Input from "../../components/commun/Input"; -import CompanyFinder from "./CompanyFinder"; -import BoutonRadio from "../../components/commun/BoutonRadio"; +import React from 'react'; +import Input from '../../components/commun/Input'; +import CompanyFinder from './CompanyFinder'; +import BoutonRadio from '../../components/commun/BoutonRadio'; export default function InformationsDeContact() { return (
Vos informations de structure
- Dénomination * - Adresse * @@ -45,7 +45,7 @@ export default function InformationsDeContact() { Un GIP - Une structure privée (association, entreprise de l'ESS, fondations) + Une structure privée (association, entreprise de l’ESS, fondations)
From 9198df2f8f3f6c207415fea1d345fea4e931b985 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Wed, 31 Jul 2024 16:53:20 +0200 Subject: [PATCH 20/29] partie engagement + test --- .../CandidatureStructure.jsx | 4 +- .../CandidatureStructure.test.jsx | 46 +++++++++++++++++++ .../candidature-structure/Engagement.jsx | 23 ++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/views/candidature-structure/Engagement.jsx diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 9de4de0..3e975b8 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -4,8 +4,8 @@ import InformationsDeContact from './InformationsDeContact'; import InformationsDeStructure from './InformationsDeStructure'; import BesoinEnConseillerNumerique from './BesoinEnConseillerNumerique'; import Motivation from './Motivation'; +import Engagement from './Engagement'; import { useScrollToSection } from '../../hooks/useScrollToSection'; -import ZoneDeTexte from '../../components/commun/ZoneDeTexte'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -35,7 +35,7 @@ export default function CandidatureStructure() { - {/* TODO : Engagement */} +
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 1010e3e..3ee532c 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -116,4 +116,50 @@ describe('candidature structure', () => { }); it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeMotivation = within(formulaire).getByRole('group', { name: 'Votre motivation' }); + expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); + + const sousTitreVotreMotvation = + within(etapeMotivation).getByText('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é.', + { selector: 'p' } + ); + expect(sousTitreVotreMotvation).toBeInTheDocument(); + + const votreMessage = within(etapeMotivation).getByLabelText('Votre message *'); + expect(votreMessage).toHaveAttribute('id', 'votreMessage'); + expect(votreMessage).toBeRequired(); + }); + + it('quand j’affiche le formulaire alors l’encart des engagements est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'),{ selector: 'p' }); + expect(titreEngagement).toBeInTheDocument(); + const engagements = screen.getByTestId('votre-engagement'); + + const listDetail = within(engagements).getAllByRole('listitem'); + within(listDetail[0]).getByText('Assurer que le conseiller réalise des activités de 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 France Services 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).'); + + const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); + expect(confirmationEngagement).toBeInTheDocument(); + + }); + + it.todo('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché'); }); diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx new file mode 100644 index 0000000..fd409dd --- /dev/null +++ b/src/views/candidature-structure/Engagement.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Notice from '../../components/commun/Notice'; +import Checkbox from '../../components/commun/Checkbox'; + +export default function Engagement() { + return ( + +

En tant que structure accueillante, vous vous engagez à

+
    +
  • Assurer que le conseiller réalise des activités de montée en compétences du public (ateliers numériques, initiations au numérique), gratuites.
  • +
  • Qu’il consacre une partie de son temps aux rencontres locales et nationales organisées pour la communauté et la formation continue, etc.
  • +
  • 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.
  • +
  • Mettre à sa disposition les moyens et équipements pour réaliser sa mission (ordinateur, téléphone portable, voiture si nécessaire).
  • +
+ + Je confirme avoir lu et pris connaissance des conditions d’engagement.* + +
+ ); +} From e9ef420cf3edaf2d5d5dad35b823c470e9207337 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Wed, 31 Jul 2024 17:07:23 +0200 Subject: [PATCH 21/29] partie submit + test + rectif lint --- .../CandidatureStructure.jsx | 3 +++ .../CandidatureStructure.test.jsx | 24 +++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 3e975b8..ec2bbef 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -36,6 +36,9 @@ export default function CandidatureStructure() { +
diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 3ee532c..a89f347 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -127,8 +127,8 @@ describe('candidature structure', () => { const sousTitreVotreMotvation = within(etapeMotivation).getByText('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é.', - { selector: 'p' } + 'votre besoin en recrutement. Indiquez les actions prévues, la justification du poste, ainsi que le public ciblé.', + { selector: 'p' } ); expect(sousTitreVotreMotvation).toBeInTheDocument(); @@ -143,23 +143,33 @@ describe('candidature structure', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'),{ selector: 'p' }); + const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'), { selector: 'p' }); expect(titreEngagement).toBeInTheDocument(); const engagements = screen.getByTestId('votre-engagement'); const listDetail = within(engagements).getAllByRole('listitem'); - within(listDetail[0]).getByText('Assurer que le conseiller réalise des activités de 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[0]).getByText('Assurer que le conseiller réalise des activités de ' + + '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 France Services 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).'); + 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).'); const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); expect(confirmationEngagement).toBeInTheDocument(); }); - it.todo('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché'); + it('quand j’affiche le formulaire alors le bouton "Envoyer votre candidature" est affiché', () => { + // WHEN + render(); + + //THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + within(formulaire).getByRole('button', { name: 'Envoyer votre candidature' }); + }); }); From aff916195e0b7fb5162636410f758b288ae7510c Mon Sep 17 00:00:00 2001 From: dienamo Date: Tue, 30 Jul 2024 10:27:44 +0200 Subject: [PATCH 22/29] :feat ajout premier bloc formulaire informations contact structure --- .../CandidatureStructure.test.jsx | 56 ++++++++++++++++++- .../InformationsDeContact.tsx | 53 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/views/candidature-structure/InformationsDeContact.tsx diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index a89f347..b91f743 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -38,7 +38,58 @@ describe('candidature structure', () => { }); }); - it.todo('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché'); + it('quand j’affiche le formulaire alors l’étape "Vos informations de structure" est affiché', () => { + // WHEN + render(); + + // THEN + const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); + const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + + const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); + expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + expect(siretOuRidet).toBeRequired(); + + const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + expect(denomination).toHaveAttribute('type', 'text'); + expect(denomination).toBeRequired(); + + const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + expect(adresse).toHaveAttribute('type', 'text'); + expect(adresse).toBeRequired(); + + const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + expect(questionTypeDeStructure).toBeInTheDocument(); + + const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(_uneCommune).toBeRequired(); + expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + + const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(_unDepartement).toBeRequired(); + expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + + const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(_uneRegion).toBeRequired(); + expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + + const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(_unEtablissemntPublic).toBeRequired(); + expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + + const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(_uneCollectivite).toBeRequired(); + expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + + const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(_unGIP).toBeRequired(); + expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + + // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + // expect(_uneStructurePrivee).toBeRequired(); + // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { // WHEN @@ -172,4 +223,7 @@ describe('candidature structure', () => { const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); within(formulaire).getByRole('button', { name: 'Envoyer votre candidature' }); }); + it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); + it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); + diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeContact.tsx new file mode 100644 index 0000000..a5ba64c --- /dev/null +++ b/src/views/candidature-structure/InformationsDeContact.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import Input from "../../components/commun/Input"; +import CompanyFinder from "./CompanyFinder"; +import BoutonRadio from "../../components/commun/BoutonRadio"; + +export default function InformationsDeContact() { + return ( +
+ Vos informations de structure +
+ + + Dénomination * + + + Adresse * + +

+ Votre structure est * +

+
+ + Une commune + + + Un département + + + Une région + + + Un établissement public de coopération intercommunale + + + Une collectivité à statut particulier + + + Un GIP + + + Une structure privée (association, entreprise de l'ESS, fondations) + +
+
+ ); +} From 527ae038551fb83fcbda05b438c66eb0518c59b8 Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 11:05:05 +0200 Subject: [PATCH 23/29] Refacto des informations de structure --- .../CandidatureStructure.test.jsx | 2 + .../InformationsDeContact.tsx | 53 ------------------- 2 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 src/views/candidature-structure/InformationsDeContact.tsx diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index b91f743..8612660 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -60,6 +60,8 @@ describe('candidature structure', () => { expect(adresse).toBeRequired(); const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); + const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); expect(questionTypeDeStructure).toBeInTheDocument(); const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); diff --git a/src/views/candidature-structure/InformationsDeContact.tsx b/src/views/candidature-structure/InformationsDeContact.tsx deleted file mode 100644 index a5ba64c..0000000 --- a/src/views/candidature-structure/InformationsDeContact.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import Input from "../../components/commun/Input"; -import CompanyFinder from "./CompanyFinder"; -import BoutonRadio from "../../components/commun/BoutonRadio"; - -export default function InformationsDeContact() { - return ( -
- Vos informations de structure -
- - - Dénomination * - - - Adresse * - -

- Votre structure est * -

-
- - Une commune - - - Un département - - - Une région - - - Un établissement public de coopération intercommunale - - - Une collectivité à statut particulier - - - Un GIP - - - Une structure privée (association, entreprise de l'ESS, fondations) - -
-
- ); -} From 049e4f8f453c6107da97ae26218de68dc8340924 Mon Sep 17 00:00:00 2001 From: Alezco Date: Tue, 30 Jul 2024 17:38:55 +0200 Subject: [PATCH 24/29] Retours de code review --- package.json | 1 + .../CandidatureStructure.test.jsx | 65 ++++++++++--------- .../candidature-structure/useSiretApi.js | 21 +++--- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index f3889ac..0dc73bd 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint src --max-warnings=0 --cache --cache-location node_modules/.cache/eslint", "start": "vite preview", "test": "vitest --run", + "test:watch": "vitest --watch", "test:coverage": "npm test -- --coverage" }, "dependencies": { diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 8612660..a5b1686 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -44,53 +44,52 @@ describe('candidature structure', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const etapeInformationsDeContact = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); - expect(etapeInformationsDeContact).toHaveAttribute('id', 'informationsDeContact'); + const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); - const siretOuRidet = within(etapeInformationsDeContact).getByPlaceholderText('N° SIRET / RIDET'); - expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); - expect(siretOuRidet).toBeRequired(); + // const siretOuRidet = within(etapeInformationsDeStructure).getByPlaceholderText('N° SIRET / RIDET'); + // expect(siretOuRidet).toHaveAttribute('id', 'siretEntreprise'); + // expect(siretOuRidet).toBeRequired(); - const denomination = within(etapeInformationsDeContact).getByLabelText('Dénomination *'); + const denomination = within(etapeInformationsDeStructure).getByLabelText('Dénomination *'); expect(denomination).toHaveAttribute('type', 'text'); expect(denomination).toBeRequired(); - const adresse = within(etapeInformationsDeContact).getByLabelText('Adresse *'); + const adresse = within(etapeInformationsDeStructure).getByLabelText('Adresse *'); expect(adresse).toHaveAttribute('type', 'text'); expect(adresse).toBeRequired(); - const questionTypeDeStructure = within(etapeInformationsDeContact).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); - const etapeInformationsDeStructure = within(formulaire).getByRole('group', { name: 'Vos informations de structure' }); + const questionTypeDeStructure = within(etapeInformationsDeStructure).getByText(textMatcher('Votre structure est *'), { selector: 'p' }); expect(etapeInformationsDeStructure).toHaveAttribute('id', 'informations-de-structure'); expect(questionTypeDeStructure).toBeInTheDocument(); - const _uneCommune = screen.getByRole('radio', { name: 'Une commune' }); - expect(_uneCommune).toBeRequired(); - expect(_uneCommune).toHaveAttribute('name', 'typeStructure'); + const uneCommune = screen.getByRole('radio', { name: 'Une commune' }); + expect(uneCommune).toBeRequired(); + expect(uneCommune).toHaveAttribute('name', 'typeStructure'); - const _unDepartement = screen.getByRole('radio', { name: 'Un département' }); - expect(_unDepartement).toBeRequired(); - expect(_unDepartement).toHaveAttribute('name', 'typeStructure'); + const unDepartement = screen.getByRole('radio', { name: 'Un département' }); + expect(unDepartement).toBeRequired(); + expect(unDepartement).toHaveAttribute('name', 'typeStructure'); - const _uneRegion = screen.getByRole('radio', { name: 'Une région' }); - expect(_uneRegion).toBeRequired(); - expect(_uneRegion).toHaveAttribute('name', 'typeStructure'); + const uneRegion = screen.getByRole('radio', { name: 'Une région' }); + expect(uneRegion).toBeRequired(); + expect(uneRegion).toHaveAttribute('name', 'typeStructure'); - const _unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); - expect(_unEtablissemntPublic).toBeRequired(); - expect(_unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); + const unEtablissemntPublic = screen.getByRole('radio', { name: 'Un établissement public de coopération intercommunale' }); + expect(unEtablissemntPublic).toBeRequired(); + expect(unEtablissemntPublic).toHaveAttribute('name', 'typeStructure'); - const _uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); - expect(_uneCollectivite).toBeRequired(); - expect(_uneCollectivite).toHaveAttribute('name', 'typeStructure'); + const uneCollectivite = screen.getByRole('radio', { name: 'Une collectivité à statut particulier' }); + expect(uneCollectivite).toBeRequired(); + expect(uneCollectivite).toHaveAttribute('name', 'typeStructure'); - const _unGIP = screen.getByRole('radio', { name: 'Un GIP' }); - expect(_unGIP).toBeRequired(); - expect(_unGIP).toHaveAttribute('name', 'typeStructure'); + const unGIP = screen.getByRole('radio', { name: 'Un GIP' }); + expect(unGIP).toBeRequired(); + expect(unGIP).toHaveAttribute('name', 'typeStructure'); - // const _uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); - // expect(_uneStructurePrivee).toBeRequired(); - // expect(_uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); + const uneStructurePrivee = screen.getByRole('radio', { name: 'Une structure privée (association, entreprise de l’ESS, fondations)' }); + expect(uneStructurePrivee).toBeRequired(); + expect(uneStructurePrivee).toHaveAttribute('name', 'typeStructure'); }); it('quand j’affiche le formulaire alors l’étape "Vos informations de contact" est affiché', () => { @@ -120,6 +119,7 @@ describe('candidature structure', () => { const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone *'); expect(telephone).toHaveAttribute('type', 'tel'); + expect(telephone).toHaveAttribute('pattern', '0[1-9]{9}'); expect(telephone).toBeRequired(); }); @@ -179,8 +179,9 @@ describe('candidature structure', () => { expect(etapeMotivation).toHaveAttribute('id', 'votre-motivation'); const sousTitreVotreMotvation = - within(etapeMotivation).getByText('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é.', + within(etapeMotivation).getByText( + '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é.', { selector: 'p' } ); expect(sousTitreVotreMotvation).toBeInTheDocument(); diff --git a/src/views/candidature-structure/useSiretApi.js b/src/views/candidature-structure/useSiretApi.js index 7e059d1..0a9ffa2 100644 --- a/src/views/candidature-structure/useSiretApi.js +++ b/src/views/candidature-structure/useSiretApi.js @@ -1,20 +1,15 @@ import { useState } from 'react'; const getUrlEntrepriseApiV3 = (sirenOuSiret, type) => { - let params = '?context=cnum&object=checkSiret&recipient=13002603200016'; - let url = 'https://entreprise.api.gouv.fr/v3/'; + const params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + const url = 'https://entreprise.api.gouv.fr/v3/'; let service = ''; - switch (type) { - case 'siret': - service = 'insee/sirene/etablissements/' + sirenOuSiret; - break; - case 'siren': - service = 'insee/sirene/unites_legales/' + sirenOuSiret + '/siege_social/'; - break; - default: - break; + if (type === 'siret') { + service = `insee/sirene/etablissements/'${sirenOuSiret}`; + } else if (type === 'siren') { + service = `insee/sirene/unites_legales/${sirenOuSiret}/siege_social/`; } - return url + service + params; + return `${url}${service}${params}`; }; export const useSiretApi = () => { @@ -38,4 +33,4 @@ export const useSiretApi = () => { }; return { search, entreprise, error }; -}; \ No newline at end of file +}; From dd0017f4bf205cfd754613d3bde711a71512fd5e Mon Sep 17 00:00:00 2001 From: Alezco Date: Thu, 1 Aug 2024 09:58:31 +0200 Subject: [PATCH 25/29] Correction des tests --- .../candidature-structure/CandidatureStructure.test.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index a5b1686..c4a5198 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -9,7 +9,7 @@ vi.mock('react-router-dom', () => ({ describe('candidature structure', () => { describe('étant une structure', () => { - it.todo('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { + it('quand j’affiche le formulaire alors le titre et le menu s’affichent', () => { // WHEN render(); @@ -168,7 +168,6 @@ describe('candidature structure', () => { expect(date).toBeRequired(); }); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { // WHEN render(); @@ -226,7 +225,5 @@ describe('candidature structure', () => { const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); within(formulaire).getByRole('button', { name: 'Envoyer votre candidature' }); }); - it.todo('quand j’affiche le formulaire alors l’encart des engagements est affiché'); - it.todo('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché'); }); From 9247550599fdfb4942f2a42e8dde1d3c8e9ce34d Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Thu, 1 Aug 2024 15:24:07 +0200 Subject: [PATCH 26/29] quelques rectif required + config min + ponctuation --- src/components/commun/Checkbox.jsx | 5 +++-- src/components/commun/Input.jsx | 7 ++++--- .../candidature-structure/BesoinEnConseillerNumerique.jsx | 4 ++-- .../candidature-structure/CandidatureStructure.test.jsx | 8 +++++--- src/views/candidature-structure/Engagement.jsx | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/commun/Checkbox.jsx b/src/components/commun/Checkbox.jsx index aeff91b..6f0c54d 100644 --- a/src/components/commun/Checkbox.jsx +++ b/src/components/commun/Checkbox.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -export default function Checkbox({ children, id, onCheck, checked }) { +export default function Checkbox({ children, id, onCheck, checked, required }) { return (
- + @@ -19,4 +19,5 @@ Checkbox.propTypes = { id: PropTypes.string, onCheck: PropTypes.func, checked: PropTypes.bool, + required: PropTypes.bool, }; diff --git a/src/components/commun/Input.jsx b/src/components/commun/Input.jsx index d72042d..2b3badd 100644 --- a/src/components/commun/Input.jsx +++ b/src/components/commun/Input.jsx @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -export default function Input({ children, id, isRequired = true, type = 'text', pattern, onChange, list }) { +export default function Input({ children, id, isRequired = true, type = 'text', pattern, onChange, list, min }) { return (
- +
); @@ -19,5 +19,6 @@ Input.propTypes = { type: PropTypes.string, pattern: PropTypes.string, onChange: PropTypes.func, - list: PropTypes.string + list: PropTypes.string, + min: PropTypes.string }; diff --git a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx index de4899a..1bbc849 100644 --- a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx +++ b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx @@ -10,12 +10,12 @@ export default function BesoinEnConseillerNumerique({ setDateAccueilConseillerNu
Votre besoin en conseiller(s) numérique(s)
- + Combien de conseillers numériques souhaitez-vous accueillir ?*

Avez-vous déjà identifié un candidat pour le poste de conseiller numérique ?*

-

Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique

+

Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique.

Oui diff --git a/src/views/candidature-structure/CandidatureStructure.test.jsx b/src/views/candidature-structure/CandidatureStructure.test.jsx index 1ec11e4..3a9a06a 100644 --- a/src/views/candidature-structure/CandidatureStructure.test.jsx +++ b/src/views/candidature-structure/CandidatureStructure.test.jsx @@ -1,4 +1,4 @@ -import { render, screen, within } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import CandidatureStructure from './CandidatureStructure'; import { textMatcher } from '../../../test/test-utils'; @@ -134,6 +134,7 @@ describe('candidature structure', () => { const combienConseillerNumerique = within(etapeBesoinConseillerNumerique).getByLabelText('Combien de conseillers numériques souhaitez-vous accueillir ?*'); expect(combienConseillerNumerique).toHaveAttribute('type', 'number'); + expect(combienConseillerNumerique).toHaveAttribute('min', '1'); expect(combienConseillerNumerique).toBeRequired(); const identificationCandidat = within(etapeBesoinConseillerNumerique).getByText( @@ -144,7 +145,7 @@ describe('candidature structure', () => { const sousTitreIdentificationCandidat = within(etapeBesoinConseillerNumerique).getByText( - textMatcher('Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique'), + textMatcher('Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique.'), { selector: 'p' } ); expect(sousTitreIdentificationCandidat).toBeInTheDocument(); @@ -196,7 +197,7 @@ describe('candidature structure', () => { // THEN const formulaire = screen.getByRole('form', { name: 'Candidature structure' }); - const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'),{ selector: 'p' }); + const titreEngagement = within(formulaire).getByText(textMatcher('En tant que structure accueillante, vous vous engagez à'), { selector: 'p' }); expect(titreEngagement).toBeInTheDocument(); const engagements = screen.getByTestId('votre-engagement'); @@ -214,6 +215,7 @@ describe('candidature structure', () => { const confirmationEngagement = screen.getByLabelText('Je confirme avoir lu et pris connaissance des conditions d’engagement.*'); expect(confirmationEngagement).toBeInTheDocument(); + expect(confirmationEngagement).toBeRequired(); }); diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx index fd409dd..45d572c 100644 --- a/src/views/candidature-structure/Engagement.jsx +++ b/src/views/candidature-structure/Engagement.jsx @@ -15,7 +15,7 @@ export default function Engagement() {
  • Laisser partir le conseiller numérique France Services 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).
  • - + Je confirme avoir lu et pris connaissance des conditions d’engagement.* From 37a7a849cdfecd7d0924e88d55106a62d744f1d7 Mon Sep 17 00:00:00 2001 From: Ornella Ourfi Date: Thu, 1 Aug 2024 16:38:15 +0200 Subject: [PATCH 27/29] retour commentaire , required pour la checkbox --- src/components/commun/Checkbox.jsx | 2 +- src/views/candidature-conseiller/CandidatureConseiller.test.jsx | 1 + src/views/candidature-conseiller/SituationEtExperience.jsx | 2 +- src/views/candidature-structure/Engagement.jsx | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/commun/Checkbox.jsx b/src/components/commun/Checkbox.jsx index 6f0c54d..78c6600 100644 --- a/src/components/commun/Checkbox.jsx +++ b/src/components/commun/Checkbox.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -export default function Checkbox({ children, id, onCheck, checked, required }) { +export default function Checkbox({ children, id, onCheck, checked, required = true }) { return (
    diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 55eb768..07b0b98 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -81,6 +81,7 @@ describe('candidature conseiller', () => { const demandeurEmploi = screen.getByRole('checkbox', { name: 'Demandeur d’emploi' }); expect(demandeurEmploi).toBeInTheDocument(); + expect(demandeurEmploi).not.toBeRequired(); const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' }); expect(enEmploi).toBeInTheDocument(); diff --git a/src/views/candidature-conseiller/SituationEtExperience.jsx b/src/views/candidature-conseiller/SituationEtExperience.jsx index 4fa0759..d3aea37 100644 --- a/src/views/candidature-conseiller/SituationEtExperience.jsx +++ b/src/views/candidature-conseiller/SituationEtExperience.jsx @@ -26,7 +26,7 @@ export default function SituationEtExperience({ situationChecks, setSituationChe Êtes-vous actuellement dans l’une des situations suivantes ? *

    {situations.map(({ id, libelle }, index) => - + {libelle} )} diff --git a/src/views/candidature-structure/Engagement.jsx b/src/views/candidature-structure/Engagement.jsx index 45d572c..fd409dd 100644 --- a/src/views/candidature-structure/Engagement.jsx +++ b/src/views/candidature-structure/Engagement.jsx @@ -15,7 +15,7 @@ export default function Engagement() {
  • Laisser partir le conseiller numérique France Services 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).
  • - + Je confirme avoir lu et pris connaissance des conditions d’engagement.* From ed0d2be4414a28d77222579db16153a9a27894f8 Mon Sep 17 00:00:00 2001 From: dienamo Date: Mon, 5 Aug 2024 11:35:06 +0200 Subject: [PATCH 28/29] :feat encart information structure --- src/hooks/useEntrepriseApi.js | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/hooks/useEntrepriseApi.js diff --git a/src/hooks/useEntrepriseApi.js b/src/hooks/useEntrepriseApi.js new file mode 100644 index 0000000..744e8d8 --- /dev/null +++ b/src/hooks/useEntrepriseApi.js @@ -0,0 +1,77 @@ +import { useState, useCallback } from 'react'; +import { debounce } from '../views/candidature-conseiller/debounce'; + +const API_URL_NC = 'https://data.gouv.nc/api/explore/v2.1/catalog/datasets/entreprises-actives-au-ridet/records'; +const API_URL_FR = 'https://entreprise.api.gouv.fr/v3/'; + +const getUrlBySiret = siret => { + const params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + return `${API_URL_FR}insee/sirene/etablissements/${siret}${params}`; +}; + +async function fetchRidetData(ridet) { + const params = new URLSearchParams({ + where: `rid7='${ridet.padStart(7, '0')}'`, + select: 'denomination,libelle_commune,province,libelle_naf', + limit: '1', + }); + const response = await fetch(`${API_URL_NC}?${params}`); + if (!response.ok) { + throw new Error(`Une erreur est survenue lors du traitement de la requête pour le RIDET ${ridet}`); + } + const data = await response.json(); + return data.results[0] ?? null; +} + +export const useCompanyFinder = token => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [companyData, setCompanyData] = useState(null); + + const search = (async searchValue => { + if (![6, 7, 14].includes(searchValue.length)) { + setError('Veuillez entrer un RIDET (6 ou 7 chiffres) ou un SIRET (14 chiffres) valide.'); + return; + } + setLoading(true); + setError(null); + setCompanyData(null); + + try { + let data; + if (searchValue.length === 6 || searchValue.length === 7) { + data = await fetchRidetData(searchValue); + if (data) { + setCompanyData({ + name: data.denomination, + address: `${data.libelle_commune}, ${data.province}`, + activity: data.libelle_naf + }); + } else { + setError('Aucune entreprise trouvée pour ce RIDET.'); + } + } else { + const url = getUrlBySiret(searchValue); + const response = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + data = await response.json(); + setCompanyData({ + name: data.unite_legale.personne_morale_attributs?.raison_sociale || data.unite_legale.personne_physique_attributs?.nom_complet, + address: `${data.adresse.numero_voie} ${data.adresse.type_voie} ${data.adresse.libelle_voie}, + ${data.adresse.code_postal} ${data.adresse.libelle_commune}`, + activity: data.unite_legale.activite_principale_libelle + }); + } + } catch (err) { + setError('Une erreur est survenue lors de la requête.'); + } finally { + setLoading(false); + } + }); + const debouncedSearch = useCallback(debounce(search, 300), [search]); + + return { search: debouncedSearch, loading, error, companyData }; +}; From e63ed7a6a47800ee0d9fa06b173ebdfc50780be7 Mon Sep 17 00:00:00 2001 From: dienamo Date: Thu, 8 Aug 2024 09:19:16 +0200 Subject: [PATCH 29/29] :fix refacto nommage retours commentaires --- src/hooks/useEntrepriseApi.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/hooks/useEntrepriseApi.js b/src/hooks/useEntrepriseApi.js index 744e8d8..307a295 100644 --- a/src/hooks/useEntrepriseApi.js +++ b/src/hooks/useEntrepriseApi.js @@ -1,11 +1,14 @@ -import { useState, useCallback } from 'react'; -import { debounce } from '../views/candidature-conseiller/debounce'; +import { useState } from 'react'; const API_URL_NC = 'https://data.gouv.nc/api/explore/v2.1/catalog/datasets/entreprises-actives-au-ridet/records'; const API_URL_FR = 'https://entreprise.api.gouv.fr/v3/'; +const TAILLE_SIRET = 14; +const TAILLE_RIDET = [6, 7]; +const TAILLES_POSSIBLES = [...TAILLE_RIDET, TAILLE_SIRET]; + const getUrlBySiret = siret => { - const params = '?context=cnum&object=checkSiret&recipient=13002603200016'; + const params = '?context=cnum&object=checkSiret&recipient=123456789'; return `${API_URL_FR}insee/sirene/etablissements/${siret}${params}`; }; @@ -23,26 +26,26 @@ async function fetchRidetData(ridet) { return data.results[0] ?? null; } -export const useCompanyFinder = token => { +export const useEntrepriseFinder = token => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [companyData, setCompanyData] = useState(null); + const [entrepriseData, setEntrepriseData] = useState(null); const search = (async searchValue => { - if (![6, 7, 14].includes(searchValue.length)) { + if (!TAILLES_POSSIBLES.includes(searchValue.length)) { setError('Veuillez entrer un RIDET (6 ou 7 chiffres) ou un SIRET (14 chiffres) valide.'); return; } setLoading(true); setError(null); - setCompanyData(null); + setEntrepriseData(null); try { let data; - if (searchValue.length === 6 || searchValue.length === 7) { + if (searchValue.length === TAILLE_RIDET[0] || searchValue.length === TAILLE_RIDET[1]) { data = await fetchRidetData(searchValue); if (data) { - setCompanyData({ + setEntrepriseData({ name: data.denomination, address: `${data.libelle_commune}, ${data.province}`, activity: data.libelle_naf @@ -58,7 +61,7 @@ export const useCompanyFinder = token => { } }); data = await response.json(); - setCompanyData({ + setEntrepriseData({ name: data.unite_legale.personne_morale_attributs?.raison_sociale || data.unite_legale.personne_physique_attributs?.nom_complet, address: `${data.adresse.numero_voie} ${data.adresse.type_voie} ${data.adresse.libelle_voie}, ${data.adresse.code_postal} ${data.adresse.libelle_commune}`, @@ -71,7 +74,6 @@ export const useCompanyFinder = token => { setLoading(false); } }); - const debouncedSearch = useCallback(debounce(search, 300), [search]); - return { search: debouncedSearch, loading, error, companyData }; + return { search, loading, error, entrepriseData }; };