@@ -19,6 +19,7 @@ export default function Input({ children, id, isRequired = true, type = 'text',
name={id}
value={value}
aria-busy={ariaBusy}
+ data-testid={testId}
/>
{isLoading && (
@@ -42,5 +43,6 @@ Input.propTypes = {
readOnly: PropTypes.bool,
isLoading: PropTypes.bool,
ariaBusy: PropTypes.bool,
- value: PropTypes.string
+ value: PropTypes.string,
+ testId: PropTypes.string,
};
diff --git a/src/views/candidature-conseiller/AddressChooser.jsx b/src/views/candidature-conseiller/AddressChooser.jsx
index 8eb39f6e..7d112f84 100644
--- a/src/views/candidature-conseiller/AddressChooser.jsx
+++ b/src/views/candidature-conseiller/AddressChooser.jsx
@@ -1,25 +1,32 @@
-import React from 'react';
+import React, { useState } from 'react';
import Input from '../../components/commun/Input';
import { useGeoApi } from './useGeoApi';
import { debounce } from './debounce';
export default function AddressChooser() {
const { searchByName, villes } = useGeoApi();
+ const [codeCommune, setCodeCommune] = useState('');
return (
<>
searchByName(event.target.value))}
+ onChange={debounce(async event => {
+ searchByName(event.target.value);
+ const codeCommune = await villes.find(({ codesPostaux, nom }) =>
+ (`${codesPostaux[0]} ${nom}`).toUpperCase() === (event.target.value).toUpperCase())?.code;
+ setCodeCommune(codeCommune);
+ })}
>
- Votre lieu d’habitation Saississez le nom ou le code postal de votre commune.
+ Votre lieu d’habitation *{' '}
+ Saississez le nom ou le code postal de votre commune.
+
diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx
index 2c5fdde1..e1ca6e77 100644
--- a/src/views/candidature-conseiller/CandidatureConseiller.jsx
+++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx
@@ -59,7 +59,7 @@ export default function CandidatureConseiller() {
setValidationError(error.message);
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
- navigate('/candidature-validee');
+ navigate('/candidature-validee-conseiller');
}
}
};
diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx
index b99877c0..961eaf57 100644
--- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx
+++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx
@@ -1,8 +1,9 @@
-import { render, screen, within, fireEvent, act } from '@testing-library/react';
+import { render, screen, within, fireEvent, act, renderHook } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import CandidatureConseiller from './CandidatureConseiller';
import { textMatcher, dateDujour } from '../../../test/test-utils';
import * as ReactRouterDom from 'react-router-dom';
+import * as useApiAdmin from './useApiAdmin';
vi.mock('react-router-dom', () => ({
useLocation: () => ({ hash: '' }),
@@ -63,8 +64,11 @@ describe('candidature conseiller', () => {
expect(telephone).toHaveAttribute('type', 'tel');
expect(telephone).toHaveAttribute('pattern', '[+](33|590|596|594|262|269|687)[0-9]{9}');
- const habitation = within(etapeInformationsDeContact).getByLabelText('Votre lieu d’habitation Saississez le nom ou le code postal de votre commune.');
+ const habitation = within(etapeInformationsDeContact).getByLabelText(('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.'));
expect(habitation).toHaveAttribute('type', 'text');
+
+ const habitationCodeCommune = within(etapeInformationsDeContact).getByTestId('lieuHabitationCodeCommune-hidden');
+ expect(habitationCodeCommune).toHaveAttribute('id', 'lieuHabitationCodeCommune');
});
it('quand j’affiche le formulaire alors l’étape "Votre situation et expérience" est affiché', () => {
@@ -272,28 +276,78 @@ describe('candidature conseiller', () => {
it('quand je renseigne un début de nom de ville qui existe alors plusieurs résultats sont affichés', async () => {
// GIVEN
render();
- const geoApiResponse = [
- {
- code: '75001',
- nom: 'Paris',
- },
- {
- code: '82137',
- nom: 'Parisot',
+ const geoApiResponse = {
+ 'type': 'FeatureCollection',
+ 'version': 'draft',
+ 'features': [
+ {
+ 'type': 'Feature',
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [2.347, 48.859]
+ },
+ 'properties': {
+ 'label': 'Paris',
+ 'score': 0.730668760330579,
+ 'id': '75056',
+ 'type': 'municipality',
+ 'name': 'Paris',
+ 'postcode': '75001',
+ 'citycode': '75056',
+ 'x': 652089.7,
+ 'y': 6862305.26,
+ 'population': 2133111,
+ 'city': 'Paris',
+ 'context': '75, Paris, Île-de-France',
+ 'importance': 0.67372,
+ 'municipality': 'Paris'
+ }
+ },
+ {
+ 'type': 'Feature',
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [1.869755, 44.253003]
+ },
+ 'properties': {
+ 'label': 'Parisot',
+ 'score': 0.932836363636364,
+ 'id': '82137',
+ 'banId': '4e195f30-96f0-47c8-82e9-2968b067bccc',
+ 'type': 'municipality',
+ 'name': 'Parisot',
+ 'postcode': '82160',
+ 'citycode': '82137',
+ 'x': 609752.79,
+ 'y': 6351088.82,
+ 'population': 554,
+ 'city': 'Parisot',
+ 'context': '82, Tarn-et-Garonne, Occitanie',
+ 'importance': 0.2612,
+ 'municipality': 'Parisot'
+ }
+ }
+ ],
+ 'attribution': 'BAN',
+ 'licence': 'ETALAB-2.0',
+ 'query': 'paris 75002',
+ 'filters': {
+ 'type': 'municipality'
},
- ];
+ 'limit': 5
+ };
vi.spyOn(global, 'fetch').mockResolvedValueOnce({
json: async () => Promise.resolve(geoApiResponse)
});
// WHEN
- const adresse = screen.getByLabelText('Votre lieu d’habitation Saississez le nom ou le code postal de votre commune.');
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
fireEvent.change(adresse, { target: { value: 'par' } });
// THEN
const paris = await screen.findByRole('option', { name: '75001 Paris', hidden: true });
- const parisot = await screen.findByRole('option', { name: '82137 Parisot', hidden: true });
+ const parisot = await screen.findByRole('option', { name: '82160 Parisot', hidden: true });
expect(paris).toBeInTheDocument();
expect(parisot).toBeInTheDocument();
});
@@ -317,6 +371,8 @@ describe('candidature conseiller', () => {
fireEvent.change(nom, { target: { value: 'Dupont' } });
const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr');
fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } });
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
+ fireEvent.change(adresse, { target: { value: '93100 Montreuil' } });
const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' });
fireEvent.click(enEmploi);
const oui = screen.getByRole('radio', { name: 'Oui' });
@@ -354,6 +410,8 @@ describe('candidature conseiller', () => {
fireEvent.change(nom, { target: { value: 'Dupont' } });
const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr');
fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } });
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
+ fireEvent.change(adresse, { target: { value: '93100 Montreuil' } });
const oui = screen.getByRole('radio', { name: 'Oui' });
fireEvent.click(oui);
const date = screen.getByLabelText('Choisir une date');
@@ -389,6 +447,8 @@ describe('candidature conseiller', () => {
fireEvent.change(nom, { target: { value: 'Dupont' } });
const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr');
fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } });
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
+ fireEvent.change(adresse, { target: { value: '93100 Montreuil' } });
const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' });
fireEvent.click(enEmploi);
const oui = screen.getByRole('radio', { name: 'Oui' });
@@ -434,6 +494,8 @@ describe('candidature conseiller', () => {
const nom = screen.getByLabelText('Nom *');
fireEvent.change(nom, { target: { value: 'Dupont' } });
const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr');
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
+ fireEvent.change(adresse, { target: { value: '93100 Montreuil' } });
fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } });
const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' });
fireEvent.click(enEmploi);
@@ -455,7 +517,147 @@ describe('candidature conseiller', () => {
});
// THEN
- expect(mockNavigate).toHaveBeenCalledWith('/candidature-validee');
+ expect(mockNavigate).toHaveBeenCalledWith('/candidature-validee-conseiller');
+
+ vi.useRealTimers();
+ });
+
+ it('quand je remplis complètementle formulaire avec un numéro téléphone valide, alors je suis redirigé vers la page de candidature validée', async () => {
+ // GIVEN
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date(2023, 11, 12, 13));
+
+ vi.stubGlobal('fetch', vi.fn(
+ () => ({ status: 200, json: async () => Promise.resolve({}) }))
+ );
+
+ const mockNavigate = vi.fn().mockReturnValue(() => { });
+ vi.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(mockNavigate);
+
+ render();
+ const prenom = screen.getByLabelText('Prénom *');
+ fireEvent.change(prenom, { target: { value: 'Jean' } });
+ const nom = screen.getByLabelText('Nom *');
+ fireEvent.change(nom, { target: { value: 'Dupont' } });
+ const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr');
+ fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } });
+ const adresse = screen.getByLabelText('Votre lieu d’habitation * Saississez le nom ou le code postal de votre commune.');
+ fireEvent.change(adresse, { target: { value: '93100 Montreuil' } });
+ const telephone = screen.getByLabelText('Téléphone Format attendu : +33122334455');
+ fireEvent.change(telephone, { target: { value: '+33159590730' } });
+ const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' });
+ fireEvent.click(enEmploi);
+ const oui = screen.getByRole('radio', { name: 'Oui' });
+ fireEvent.click(oui);
+ const date = screen.getByLabelText('Choisir une date');
+ fireEvent.change(date, { target: { value: dateDujour() } });
+ const _5km = screen.getByRole('radio', { name: '5 km' });
+ fireEvent.click(_5km);
+ const descriptionMotivation = screen.getByLabelText('Votre message *');
+ fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } });
+
+ // WHEN
+ const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' });
+
+ // eslint-disable-next-line testing-library/no-unnecessary-act
+ await act(() => {
+ fireEvent.click(envoyer);
+ });
+
+ // THEN
+ expect(mockNavigate).toHaveBeenCalledWith('/candidature-validee-conseiller');
+
+ vi.useRealTimers();
+ });
+
+ it('quand je valide le formulaire alors j’envoie toute les données nescessaires', async () => {
+ // GIVEN
+ const formData = [
+ [
+ 'prenom',
+ 'Jean'
+ ],
+ [
+ 'nom',
+ 'Dupont'
+ ],
+ [
+ 'email',
+ 'jean.dupont@example.com'
+ ],
+ [
+ 'telephone',
+ ''
+ ],
+ [
+ 'lieuHabitation',
+ '93100 Montreuil'
+ ],
+ [
+ 'lieuHabitationCodeCommune',
+ '93048'
+ ],
+ [
+ 'estDemandeurEmploi',
+ 'on'
+ ],
+ [
+ 'aUneExperienceMedNum',
+ 'oui'
+ ],
+ [
+ 'dateDisponibilite',
+ '2023-12-12'
+ ],
+ [
+ 'distanceMax',
+ '5'
+ ],
+ [
+ 'motivation',
+ 'je suis motivé !'
+ ],
+ [
+ 'g-recaptcha-response',
+ '1'
+ ],
+ [
+ 'h-captcha-response',
+ '1'
+ ]
+ ];
+ const { buildConseillerData } = renderHook(() => useApiAdmin.useApiAdmin()).result.current;
+
+ //WHEN
+ const result = await buildConseillerData(formData);
+
+ // THEN
+ expect(result).toBe(JSON.stringify({
+ 'prenom': 'Jean',
+ 'nom': 'Dupont',
+ 'email': 'jean.dupont@example.com',
+ 'telephone': '',
+ 'estDemandeurEmploi': true,
+ 'aUneExperienceMedNum': true,
+ 'dateDisponibilite': '2023-12-12',
+ 'distanceMax': '5',
+ 'motivation': 'je suis motivé !',
+ 'h-captcha-response': '1',
+ 'estEnEmploi': false,
+ 'estEnFormation': false,
+ 'estDiplomeMedNum': false,
+ 'nomCommune': 'Montreuil',
+ 'codePostal': '93100',
+ 'codeCommune': '93048',
+ 'location': {
+ 'type': 'Point',
+ 'coordinates': [2.4491,
+ 48.8637]
+ },
+ 'codeDepartement': '93',
+ 'codeRegion': '11',
+ 'codeCom': null,
+ }));
vi.useRealTimers();
});
diff --git a/src/views/candidature-conseiller/useApiAdmin.js b/src/views/candidature-conseiller/useApiAdmin.js
index 5b5b36f9..a427800e 100644
--- a/src/views/candidature-conseiller/useApiAdmin.js
+++ b/src/views/candidature-conseiller/useApiAdmin.js
@@ -52,21 +52,21 @@ export const useApiAdmin = () => {
conseillerData[key] = conseillerData[key] === 'on' || conseillerData[key] === 'oui';
};
- const getInformationsVille = async codePostal => {
- if (codePostal) {
- return await getVilleParCode(codePostal);
+ const getInformationsVille = async codeCommune => {
+ if (codeCommune) {
+ return await getVilleParCode(codeCommune);
}
};
- const handleInformationsVille = async (formulaireData, codePostal) => {
- const informationsVille = (await getInformationsVille(codePostal))?.[0];
+ const handleInformationsVille = async (formulaireData, codeCommune) => {
+ const informationsVille = (await getInformationsVille(codeCommune));
formulaireData.nomCommune = informationsVille?.nom;
- formulaireData.codePostal = informationsVille?.code;
+ formulaireData.codePostal = informationsVille?.codesPostaux[0];
formulaireData.codeCommune = informationsVille?.code;
formulaireData.location = informationsVille?.centre;
formulaireData.codeDepartement = informationsVille?.codeDepartement;
formulaireData.codeRegion = informationsVille?.codeRegion;
- formulaireData.codeCom = informationsVille?.code;
+ formulaireData.codeCom = informationsVille?.codeDepartement === '00' ? informationsVille?.code.substring(0, 3) : null;
return formulaireData;
};
@@ -77,10 +77,11 @@ export const useApiAdmin = () => {
convertValueToBoolean(conseillerData, 'estEnFormation');
convertValueToBoolean(conseillerData, 'estDiplomeMedNum');
convertValueToBoolean(conseillerData, 'aUneExperienceMedNum');
- const codePostal = conseillerData.lieuHabitation.match(/\d{5}/)?.[0];
- await handleInformationsVille(conseillerData, codePostal);
+ const codeCommune = conseillerData.lieuHabitationCodeCommune;
+ await handleInformationsVille(conseillerData, codeCommune);
delete conseillerData.lieuHabitation;
delete conseillerData['g-recaptcha-response'];
+ delete conseillerData['lieuHabitationCodeCommune'];
return JSON.stringify(conseillerData);
};
@@ -105,30 +106,29 @@ export const useApiAdmin = () => {
delete structureData.denomination;
};
- const handleAdresse = async structureData => {
- const codePostal = structureData.adresse.match(/\d{5}/)?.[0];
- await handleInformationsVille(structureData, codePostal);
+ const handleAdresse = async (structureData, codeCommune) => {
+ await handleInformationsVille(structureData, codeCommune);
delete structureData.adresse;
return structureData;
};
- const buildStructureData = async (formData, geoLocation) => {
+ const buildStructureData = async (formData, geoLocation, codeCommune) => {
const structureData = Object.fromEntries(formData);
structureData.location = geoLocation;
convertValueToBoolean(structureData, 'aIdentifieCandidat');
handleContact(structureData);
handleInformationsStructure(structureData);
- await handleAdresse(structureData);
+ await handleAdresse(structureData, codeCommune);
convertValueToBoolean(structureData, 'confirmationEngagement');
delete structureData['g-recaptcha-response'];
return JSON.stringify(structureData);
};
- const buildCoordinateurData = async formData => {
+ const buildCoordinateurData = async (formData, geoLocation, codeCommune) => {
const coordinateurData = Object.fromEntries(formData);
handleContact(coordinateurData);
handleInformationsStructure(coordinateurData);
- await handleAdresse(coordinateurData);
+ await handleAdresse(coordinateurData, codeCommune);
convertValueToBoolean(coordinateurData, 'aIdentifieCoordinateur');
convertValueToBoolean(coordinateurData, 'confirmationEngagement');
delete coordinateurData['g-recaptcha-response'];
diff --git a/src/views/candidature-conseiller/useGeoApi.js b/src/views/candidature-conseiller/useGeoApi.js
index d1d4aff8..5cfb21c3 100644
--- a/src/views/candidature-conseiller/useGeoApi.js
+++ b/src/views/candidature-conseiller/useGeoApi.js
@@ -1,22 +1,32 @@
import { useState } from 'react';
export const useGeoApi = () => {
- const baseUrl = new URL('https://geo.api.gouv.fr/communes');
- baseUrl.searchParams.set('limit', '10');
- baseUrl.searchParams.set('fields', 'nom,code,codesPostaux,centre,codeDepartement,codeRegion,codeCom');
-
const [villes, setVilles] = useState([]);
+ const distinctVilles = villes => [...new Map(villes.map(item => [item.code, item])).values()];
+
const searchByName = async rechercheUtilisateur => {
- const url = `${baseUrl.toString()}&nom=${rechercheUtilisateur}`;
+ const baseUrlSearch = new URL('https://api-adresse.data.gouv.fr/search');
+ baseUrlSearch.searchParams.set('type', 'municipality');
+ const url = `${baseUrlSearch.toString()}&q=${encodeURIComponent(rechercheUtilisateur)}`;
const villes = await fetch(url);
- const resultat = await villes.json();
- setVilles(resultat);
+ const communes = await villes.json();
+
+ const resultat = communes?.features.map(result => ({
+ 'nom': result.properties.municipality,
+ 'code': result.properties.citycode,
+ 'codesPostaux': [
+ result.properties.postcode
+ ]
+ }));
+ const propositionVilles = await distinctVilles(resultat);
+ setVilles(propositionVilles);
};
- const getVilleParCode = async codePostal => {
- const url = `${baseUrl.toString()}&codePostal=${codePostal}`;
- const ville = await fetch(url);
+ const getVilleParCode = async codeCommune => {
+ const baseUrl = new URL(`https://geo.api.gouv.fr/communes/${codeCommune}`);
+ baseUrl.searchParams.set('fields', 'nom,code,codesPostaux,centre,codeDepartement,codeRegion');
+ const ville = await fetch(baseUrl.toString());
return await ville.json();
};
diff --git a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx
index d85ef9b6..b754f52e 100644
--- a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx
+++ b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx
@@ -24,6 +24,7 @@ import '../candidature-conseiller/CandidatureConseiller.css';
export default function CandidatureCoordinateur() {
const [geoLocation, setGeoLocation] = useState(null);
const [validationError, setValidationError] = useState('');
+ const [codeCommune, setCodeCommune] = useState('');
const navigate = useNavigate();
const { buildCoordinateurData, creerCandidatureCoordinateur } = useApiAdmin();
useScrollToSection();
@@ -36,14 +37,14 @@ export default function CandidatureCoordinateur() {
event.preventDefault();
const formData = new FormData(event.currentTarget);
- const coordinateurData = await buildCoordinateurData(formData);
+ const coordinateurData = await buildCoordinateurData(formData, geoLocation, codeCommune);
const resultatCreation = await creerCandidatureCoordinateur(coordinateurData);
if (resultatCreation.status >= 400) {
const error = await resultatCreation.json();
setValidationError(error.message);
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
- navigate('/candidature-validee');
+ navigate('/candidature-validee-structure');
}
};
@@ -64,7 +65,7 @@ export default function CandidatureCoordinateur() {
}