From 532d2df8e22df15fbc6cd8c4f4f1b49fa507f8a6 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Wed, 11 Oct 2023 11:54:07 +0200 Subject: [PATCH 1/4] [Mission] fix closeSchema --- frontend/src/domain/entities/missions.ts | 2 +- .../Themes/ThemeSelector/SubThemesSelector.tsx | 6 ++++-- .../ActionForm/Themes/ThemeSelector/index.tsx | 6 ++++-- .../missions/MissionForm/MissionForm.tsx | 5 ++--- .../missions/MissionForm/Schemas/Control.ts | 11 +++++++++-- .../missions/MissionForm/Schemas/Surveillance.ts | 10 +++++++++- .../missions/MissionForm/Schemas/Theme.ts | 4 ++-- .../missions/MissionForm/Schemas/index.ts | 4 +++- .../formikUseCases/updateActionThemes.ts | 2 +- .../src/features/missions/Missions.helpers.ts | 16 ++++++++++++++-- .../src/features/missions/MultiZonePicker.tsx | 2 +- 11 files changed, 50 insertions(+), 18 deletions(-) diff --git a/frontend/src/domain/entities/missions.ts b/frontend/src/domain/entities/missions.ts index 5111a27a9..fd843e313 100644 --- a/frontend/src/domain/entities/missions.ts +++ b/frontend/src/domain/entities/missions.ts @@ -284,7 +284,7 @@ export type EnvActionCommonProperties = { export type EnvActionTheme = { protectedSpecies?: string[] - subThemes?: string[] + subThemes: string[] theme: string } export type NewEnvActionControl = EnvActionCommonProperties & { diff --git a/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/SubThemesSelector.tsx b/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/SubThemesSelector.tsx index 80b528fb1..cac538463 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/SubThemesSelector.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/SubThemesSelector.tsx @@ -14,7 +14,9 @@ export function SubThemesSelector({ actionIndex, label, theme, themeIndex }) { const { data: controlThemes, isError, isLoading } = useGetControlThemesQuery() const { newWindowContainerRef } = useNewWindow() const { setFieldValue } = useFormikContext() - const [currentSubThemesField] = useField(`envActions[${actionIndex}].themes[${themeIndex}].subThemes`) + const [currentSubThemesField, currentSubThemesProps] = useField( + `envActions[${actionIndex}].themes[${themeIndex}].subThemes` + ) const availableThemes = useMemo( () => @@ -41,7 +43,7 @@ export function SubThemesSelector({ actionIndex, label, theme, themeIndex }) { baseContainer={newWindowContainerRef.current} data-cy="envaction-subtheme-selector" disabled={!theme} - isErrorMessageHidden + error={currentSubThemesProps.error} isLight label={label} name={`${actionIndex}-${themeIndex}`} diff --git a/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/index.tsx index f5549411e..bf115eaf7 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/Themes/ThemeSelector/index.tsx @@ -13,7 +13,9 @@ import type { Mission } from '../../../../../../domain/entities/missions' export function ThemeSelector({ actionIndex, label, themeIndex }) { const { data: controlThemes, isError, isLoading } = useGetControlThemesQuery() const { newWindowContainerRef } = useNewWindow() - const [currentThemeField] = useField(`envActions[${actionIndex}].themes[${themeIndex}].theme`) + const [currentThemeField, currentThemeProps] = useField( + `envActions[${actionIndex}].themes[${themeIndex}].theme` + ) const { setFieldValue, values } = useFormikContext() const availableThemes = useMemo( @@ -37,7 +39,7 @@ export function ThemeSelector({ actionIndex, label, themeIndex }) { key={`${actionIndex}-${themeIndex}`} baseContainer={newWindowContainerRef.current} data-cy="envaction-theme-selector" - isErrorMessageHidden + error={currentThemeProps.error} isLight label={label} name={`${actionIndex}-${themeIndex}`} diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index 9c428cd76..19b3e550b 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -96,10 +96,9 @@ export function MissionForm({ id, isAlreadyClosed, isNewMission, selectedMission validateForm().then(errors => { if (_.isEmpty(errors)) { handleSubmit() - - return + } else { + setShouldValidateOnChange(true) } - setShouldValidateOnChange(true) }) } diff --git a/frontend/src/features/missions/MissionForm/Schemas/Control.ts b/frontend/src/features/missions/MissionForm/Schemas/Control.ts index 0ab8d651d..3fa414ebd 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/Control.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/Control.ts @@ -1,3 +1,4 @@ +import _ from 'lodash' import * as Yup from 'yup' import { ClosedInfractionSchema, NewInfractionSchema } from './Infraction' @@ -8,6 +9,12 @@ import { REACT_APP_CYPRESS_TEST } from '../../../../env' const shouldUseAlternateValidationInTestEnvironment = process.env.NODE_ENV === 'development' || REACT_APP_CYPRESS_TEST +const ControlPointSchema = Yup.object().test({ + message: 'Veuillez définir un point de contrôle', + name: 'has-geom', + test: val => val && !_.isEmpty(val?.coordinates) +}) + export const getNewEnvActionControlSchema = (ctx: any): Yup.SchemaOf => Yup.object() .shape({ @@ -59,10 +66,10 @@ export const getClosedEnvActionControlSchema = (ctx: any): Yup.SchemaOf { if (!actionTargetType || actionTargetType === TargetTypeEnum.VEHICLE) { return schema.nullable().required('Requis') diff --git a/frontend/src/features/missions/MissionForm/Schemas/Surveillance.ts b/frontend/src/features/missions/MissionForm/Schemas/Surveillance.ts index 46bb47e3f..415f2576e 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/Surveillance.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/Surveillance.ts @@ -1,3 +1,4 @@ +import _ from 'lodash' import * as Yup from 'yup' import { ThemeSchema } from './Theme' @@ -6,6 +7,12 @@ import { REACT_APP_CYPRESS_TEST } from '../../../../env' const shouldUseAlternateValidationInTestEnvironment = process.env.NODE_ENV === 'development' || REACT_APP_CYPRESS_TEST +const SurveillanceZoneSchema = Yup.object().test({ + message: 'Veuillez définir une zone de surveillance', + name: 'has-geom', + test: val => val && !_.isEmpty(val?.coordinates) +}) + export const getNewEnvActionSurveillanceSchema = (ctx: any): Yup.SchemaOf => Yup.object() .shape({ @@ -92,7 +99,8 @@ export const getClosedEnvActionSurveillanceSchema = (ctx: any): Yup.SchemaOf shouldUseAlternateValidationInTestEnvironment ? Yup.object().nullable() - : Yup.array().ensure().min(1, 'Requis'), + : Yup.array().of(SurveillanceZoneSchema).ensure().min(1, 'Veuillez définir une zone de surveillance'), + then: () => Yup.object().nullable() }), diff --git a/frontend/src/features/missions/MissionForm/Schemas/Theme.ts b/frontend/src/features/missions/MissionForm/Schemas/Theme.ts index 5c2f5be52..82338abdb 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/Theme.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/Theme.ts @@ -5,9 +5,9 @@ import type { EnvActionTheme } from '../../../../domain/entities/missions' export const ThemeSchema: Yup.SchemaOf = Yup.object().shape({ protectedSpecies: Yup.array().of(Yup.string().optional()).nullable().optional(), subThemes: Yup.array() - .of(Yup.string().required().default('')) + .of(Yup.string().required()) .ensure() .required() .min(1, 'Sélectionnez au moins une sous thématique'), - theme: Yup.string().required('Sélectionnez un thême') + theme: Yup.string().nullable().required('Sélectionnez un thème') }) diff --git a/frontend/src/features/missions/MissionForm/Schemas/index.ts b/frontend/src/features/missions/MissionForm/Schemas/index.ts index bc2d14fd8..8eb76a520 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/index.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/index.ts @@ -110,7 +110,9 @@ const NewMissionSchema: Yup.SchemaOf = Yup.object() envActions: Yup.array() .of(NewEnvActionSchema as any) .nullable(), - geom: shouldUseAlternateValidationInTestEnvironment ? Yup.object().nullable() : MissionZoneSchema, + geom: shouldUseAlternateValidationInTestEnvironment + ? Yup.object().nullable() + : Yup.array().of(MissionZoneSchema).ensure().min(1, 'Veuillez définir une zone de mission'), isClosed: Yup.boolean().oneOf([false]).required(), missionTypes: MissionTypesSchema, openBy: Yup.string() diff --git a/frontend/src/features/missions/MissionForm/formikUseCases/updateActionThemes.ts b/frontend/src/features/missions/MissionForm/formikUseCases/updateActionThemes.ts index fafd228ed..4dd6d0171 100644 --- a/frontend/src/features/missions/MissionForm/formikUseCases/updateActionThemes.ts +++ b/frontend/src/features/missions/MissionForm/formikUseCases/updateActionThemes.ts @@ -9,7 +9,7 @@ export const updateTheme = const currentThemeValue = get(mission, themesPath) as EnvActionTheme[] const newValue = [...currentThemeValue] - newValue.splice(themeIndex, 1, { theme: value }) + newValue.splice(themeIndex, 1, { protectedSpecies: [], subThemes: [], theme: value }) setFieldValue(themesPath, newValue) } diff --git a/frontend/src/features/missions/Missions.helpers.ts b/frontend/src/features/missions/Missions.helpers.ts index ef912ceaa..7c499d7e2 100644 --- a/frontend/src/features/missions/Missions.helpers.ts +++ b/frontend/src/features/missions/Missions.helpers.ts @@ -37,7 +37,13 @@ export const actionFactory = ({ id: uuidv4(), infractions: [], observations: '', - themes: [], + themes: [ + { + protectedSpecies: undefined, + subThemes: [], + theme: '' + } + ], ...action } case ActionTypeEnum.NOTE: @@ -56,7 +62,13 @@ export const actionFactory = ({ durationMatchesMission: true, id: uuidv4(), observations: '', - themes: [], + themes: [ + { + protectedSpecies: undefined, + subThemes: [], + theme: '' + } + ], ...action } } diff --git a/frontend/src/features/missions/MultiZonePicker.tsx b/frontend/src/features/missions/MultiZonePicker.tsx index 6e8011263..e2fe6a96f 100644 --- a/frontend/src/features/missions/MultiZonePicker.tsx +++ b/frontend/src/features/missions/MultiZonePicker.tsx @@ -98,7 +98,7 @@ export function MultiZonePicker({ {addButtonLabel} - {!!meta.error && Veuillez définir une zone} + {!!meta.error && {meta.error}} <> {polygons.map((polygonCoordinates, index) => ( From 137f858f8e0852eccc533a5b22e8d9115793708c Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Wed, 11 Oct 2023 15:46:27 +0200 Subject: [PATCH 2/4] [Tests] create test for close mission validation --- frontend/config/cypress.config.ts | 10 +- .../reporting/create_reporting.spec.ts} | 2 +- .../mission/close_mission_validation.spec.ts | 92 ++++++++++ .../mission/create_mission.spec.ts} | 0 .../mission/mission_actions.spec.ts | 163 ++++++++++++++++++ .../mission/mission_dates_validation.spec.ts} | 157 +---------------- .../mission_list/missions.spec.ts} | 0 .../mission_list/missions_navigation.spec.ts} | 0 .../reporting/reportings.spec.ts} | 0 .../ActionForm/SurveillanceForm.tsx | 1 + .../MissionForm/ControlUnitSelector.tsx | 11 +- .../MissionForm/GeneralInformationsForm.tsx | 32 ++-- .../missions/MissionForm/Schemas/Control.ts | 4 +- .../missions/MissionForm/Schemas/Theme.ts | 8 +- .../missions/MissionForm/Schemas/index.ts | 10 +- .../features/missions/MultiPointPicker.tsx | 2 +- 16 files changed, 293 insertions(+), 199 deletions(-) rename frontend/cypress/e2e/{04_map_create_reporting.spec.ts => main_window/reporting/create_reporting.spec.ts} (94%) create mode 100644 frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts rename frontend/cypress/e2e/{01_side_window_mission.spec.ts => side_window/mission/create_mission.spec.ts} (100%) create mode 100644 frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts rename frontend/cypress/e2e/{02_side_window_mission_actions.spec.ts => side_window/mission/mission_dates_validation.spec.ts} (51%) rename frontend/cypress/e2e/{00_side_window_missions.spec.ts => side_window/mission_list/missions.spec.ts} (100%) rename frontend/cypress/e2e/{03_side_window_missions_navigation.spec.ts => side_window/mission_list/missions_navigation.spec.ts} (100%) rename frontend/cypress/e2e/{05_side_window_reportings.spec.ts => side_window/reporting/reportings.spec.ts} (100%) diff --git a/frontend/config/cypress.config.ts b/frontend/config/cypress.config.ts index da2a09361..131dfd9c3 100644 --- a/frontend/config/cypress.config.ts +++ b/frontend/config/cypress.config.ts @@ -19,15 +19,7 @@ export default defineConfig({ initCypressMousePositionPlugin(on) initPlugin(on, config) }, - specPattern: [ - 'cypress/e2e/00_side_window_missions.spec.ts', - 'cypress/e2e/01_side_window_mission.spec.ts', - 'cypress/e2e/02_side_window_mission_actions.spec.ts', - 'cypress/e2e/03_side_window_missions_navigation.spec.ts', - 'cypress/e2e/05_side_window_reportings.spec.ts', - // 'cypress/e2e/04_create_reporting.spec.ts', - 'cypress/e2e/**/*.spec.ts' - ] + specPattern: ['cypress/e2e/**/*.spec.ts'] }, env: { 'cypress-plugin-snapshots': { diff --git a/frontend/cypress/e2e/04_map_create_reporting.spec.ts b/frontend/cypress/e2e/main_window/reporting/create_reporting.spec.ts similarity index 94% rename from frontend/cypress/e2e/04_map_create_reporting.spec.ts rename to frontend/cypress/e2e/main_window/reporting/create_reporting.spec.ts index 2951ab7bf..719975c63 100644 --- a/frontend/cypress/e2e/04_map_create_reporting.spec.ts +++ b/frontend/cypress/e2e/main_window/reporting/create_reporting.spec.ts @@ -1,4 +1,4 @@ -import { FAKE_API_PUT_RESPONSE, FAKE_MAPBOX_RESPONSE } from './constants' +import { FAKE_API_PUT_RESPONSE, FAKE_MAPBOX_RESPONSE } from '../../constants' context('Reporting', () => { beforeEach(() => { diff --git a/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts b/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts new file mode 100644 index 000000000..ae80b5d38 --- /dev/null +++ b/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts @@ -0,0 +1,92 @@ +context('Mission', () => { + beforeEach(() => { + cy.viewport(1280, 1024) + cy.visit(`/side_window`) + }) + + it('A new mission with control and surveillance can be closed with all required values', () => { + // Given + cy.get('*[data-cy="add-mission"]').click() + cy.clickButton(' Enregistrer et clôturer') + cy.wait(100) + + cy.get('*[data-cy="mission-errors"]').should('exist') + cy.contains('Date de fin requise').should('exist') + cy.contains('Type de mission').should('exist') + cy.contains('Administration requise').should('exist') + cy.contains('Unité requise').should('exist') + cy.contains("Trigramme d'ouverture requis").should('exist') + cy.contains('Trigramme de clôture requis').should('exist') + + // we fill all the required inputs + cy.fill('Début de mission (UTC)', [2023, 10, 11, 7, 35]) + cy.fill('Fin de mission (UTC)', [2023, 10, 12, 7, 35]) + cy.fill('Type de mission', ['Air']) + cy.get('*[data-cy="add-control-unit"]').click() + cy.get('.rs-picker-search-bar-input').type('Cross{enter}') + cy.fill('Ouvert par', 'PCF').scrollIntoView() + cy.fill('Clôturé par', 'PCF').scrollIntoView() + + cy.get('*[data-cy="mission-errors"]').should('not.exist') + + // we add a control + cy.clickButton('Ajouter') + cy.clickButton('Ajouter des contrôles') + cy.clickButton(' Enregistrer et clôturer') + cy.wait(100) + + cy.get('*[data-cy="mission-errors"]').should('exist') + cy.contains('Thème requis').should('exist') + cy.contains('Sous-thématique requise').should('exist') + cy.contains('Date requise').should('exist') + // can't test this one because there is map interaction + // cy.contains('Point de contrôle requis').should('exist') + + // we fill all the required inputs + cy.fill('Thématique de contrôle', 'Police des espèces protégées') + + // TODO understand why `cy.fill` doesn't work here + cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains("Perturbation d'animaux").click({ force: true }) + cy.get('*[data-cy="envaction-subtheme-selector"]').click('topLeft', { force: true }) + cy.fill('Date et heure du contrôle (UTC)', [2023, 10, 11, 12, 12]) + + cy.get('*[data-cy="point-picker-coordinates"]').should('not.exist') + cy.fill('Nombre total de contrôles', '2') + cy.fill('Type de cible', 'Personne morale') + + // we add an infraction + cy.clickButton('+ Ajouter un contrôle avec infraction') + cy.fill("Type d'infraction", 'Avec PV') + cy.fill('Mise en demeure', 'Oui') + cy.fill('NATINF', ["1508 - Execution d'un travail dissimule"]) + + cy.get('*[data-cy="mission-errors"]').should('not.exist') + + // we add a surveillance + cy.clickButton('Ajouter') + cy.clickButton('Ajouter une surveillance') + cy.clickButton(' Enregistrer et clôturer') + cy.wait(100) + + cy.get('*[data-cy="mission-errors"]').should('exist') + + cy.fill('Thématique de surveillance', 'Rejets illicites') + // TODO understand why `cy.fill` doesn't work here + cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Jet de déchet').click({ force: true }) + cy.get('*[data-cy="envaction-subtheme-selector"]').click('topLeft', { force: true }) + + cy.getDataCy('surveillance-zone-matches-mission').should('have.class', 'rs-checkbox-checked') + cy.get('*[data-cy="mission-errors"]').should('not.exist') + + // Then + cy.intercept('PUT', '/bff/v1/missions').as('createAndCloseMission') + cy.clickButton('Enregistrer et clôturer') + cy.wait(100) + + cy.wait('@createAndCloseMission').then(({ response }) => { + expect(response && response.statusCode).equal(200) + }) + }) +}) diff --git a/frontend/cypress/e2e/01_side_window_mission.spec.ts b/frontend/cypress/e2e/side_window/mission/create_mission.spec.ts similarity index 100% rename from frontend/cypress/e2e/01_side_window_mission.spec.ts rename to frontend/cypress/e2e/side_window/mission/create_mission.spec.ts diff --git a/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts new file mode 100644 index 000000000..34ba079c7 --- /dev/null +++ b/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts @@ -0,0 +1,163 @@ +/// + +context('Mission actions', () => { + beforeEach(() => { + cy.viewport(1280, 1024) + cy.visit(`/side_window`) + }) + + it('An infraction Should be duplicated', () => { + // Given + cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) + cy.get('*[data-cy="action-card"]').eq(1).click() + cy.get('*[data-cy="control-form-number-controls"]').type('{backspace}2') + cy.get('*[data-cy="infraction-form"]').should('not.exist') + + // When + cy.get('*[data-cy="duplicate-infraction"]').click({ force: true }) + cy.get('*[data-cy="infraction-form-registrationNumber"]').should('have.value', 'BALTIK') + cy.get('*[data-cy="infraction-form-validate"]').click({ force: true }) + cy.get('*[data-cy="duplicate-infraction"]').eq(1).should('be.disabled') + + cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') + cy.get('form').submit() + + // Then + cy.wait('@updateMission').then(({ request, response }) => { + expect(response && response.statusCode).equal(200) + const { infractions } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') + expect(infractions.length).equal(2) + const duplicatedInfraction = infractions[1] + + expect(duplicatedInfraction.controlledPersonIdentity).equal('John Doe') + expect(duplicatedInfraction.formalNotice).equal('PENDING') + expect(duplicatedInfraction.infractionType).equal('WITH_REPORT') + expect(duplicatedInfraction.natinf.length).equal(2) + expect(duplicatedInfraction.observations).equal("Pas d'observations") + expect(duplicatedInfraction.registrationNumber).equal('BALTIK') + expect(duplicatedInfraction.relevantCourt).equal('LOCAL_COURT') + expect(duplicatedInfraction.toProcess).equal(false) + expect(duplicatedInfraction.vesselSize).equal('FROM_24_TO_46m') + expect(duplicatedInfraction.vesselType).equal('COMMERCIAL') + expect(duplicatedInfraction.id).not.equal('c52c6f20-e495-4b29-b3df-d7edfb67fdd7') + }) + }) + + it('allow only one theme and may be multiple subthemes in control actions', () => { + // Given + cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) + cy.get('*[data-cy="action-card"]').eq(1).click() + cy.get('*[data-cy="envaction-theme-element"]').should('have.length', 1) + cy.get('*[data-cy="envaction-theme-selector"]').contains('Police des mouillages') + cy.get('*[data-cy="envaction-theme-element"]').contains('Mouillage individuel') + cy.get('*[data-cy="envaction-theme-element"]').contains('ZMEL') + cy.get('*[data-cy="envaction-protected-species-selector"]').should('not.exist') + // When + cy.get('*[data-cy="envaction-theme-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Police des espèces protégées').click() + + cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Perturbation').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Atteinte aux habitats').click({ force: true }) + cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) + + cy.get('*[data-cy="envaction-protected-species-selector"]').should('exist') + cy.get('*[data-cy="envaction-protected-species-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Habitat').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Oiseaux').click({ force: true }) + cy.get('*[data-cy="envaction-protected-species-selector"]').click({ force: true }) + + cy.get('*[data-cy="envaction-add-theme"]').should('not.exist') + + cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') + cy.get('form').submit() + + // Then + cy.wait('@updateMission').then(({ request, response }) => { + expect(response && response.statusCode).equal(200) + + const { themes } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') + expect(themes.length).equal(1) + expect(themes[0].theme).equal('Police des espèces protégées et de leurs habitats (faune et flore)') + expect(themes[0].subThemes.length).equal(2) + expect(themes[0].subThemes[0]).equal("Perturbation d'animaux") + expect(themes[0].subThemes[1]).equal("Atteinte aux habitats d'espèces protégées") + expect(themes[0].protectedSpecies.length).equal(2) + expect(themes[0].protectedSpecies[0]).equal('HABITAT') + expect(themes[0].protectedSpecies[1]).equal('BIRDS') + }) + }) + + it('save observations in control Actions', () => { + // Given + cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) + cy.get('*[data-cy="action-card"]').eq(1).click() + cy.get('[id="envActions[1].observations"]').contains('RAS') + + // When + cy.get('[id="envActions[1].observations"]').type('{backspace}{backspace}Une observation importante', { + force: true + }) + + cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') + cy.get('form').submit() + + // Then + cy.wait('@updateMission').then(({ request, response }) => { + expect(response && response.statusCode).equal(200) + + const { observations } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') + expect(observations).equal('RUne observation importante') + }) + }) + + it('allow multiple themes and may be multiple subthemes in surveillance actions', () => { + // Given + cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) + cy.get('*[data-cy="action-card"]').eq(0).click() + cy.get('*[data-cy="envaction-theme-element"]').should('have.length', 2) + cy.get('*[data-cy="envaction-theme-selector"]') + .eq(0) + .contains('Police des espèces protégées et de leurs habitats (faune et flore)') + cy.get('*[data-cy="envaction-theme-element"]').contains('Destruction, capture, arrachage') + cy.get('*[data-cy="envaction-protected-species-selector"]').should('exist') + cy.get('*[data-cy="envaction-theme-element"]').contains('Flore') + cy.get('*[data-cy="envaction-theme-element"]').contains('Oiseaux') + + // When + cy.get('*[data-cy="envaction-theme-selector"]').eq(0).click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').eq(0).contains('Police des réserves naturelles').click() + + cy.get('*[data-cy="envaction-add-theme"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-selector"]').eq(2).click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').eq(2).contains('Rejets illicites').click() + + cy.get('*[data-cy="envaction-subtheme-selector"]').eq(2).click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').eq(2).contains('Jet de déchet').click({ force: true }) + + cy.get('*[data-cy="envaction-protected-species-selector"]').should('have.length', 0) + + cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') + cy.get('form').submit() + + // Then + cy.wait('@updateMission').then(({ response }) => { + expect(response && response.statusCode).equal(200) + + const { themes } = response && response.body.envActions.find(a => a.id === 'b8007c8a-5135-4bc3-816f-c69c7b75d807') + expect(themes.length).equal(3) + expect(themes[0].theme).equal('Police des réserves naturelles') + expect(themes[0].subThemes.length).equal(0) + expect(themes[0].protectedSpecies.length).equal(0) + expect(themes[1].theme).equal('Police des mouillages') + expect(themes[1].subThemes.length).equal(2) + expect(themes[1].subThemes[0]).equal('Mouillage individuel') + expect(themes[1].subThemes[1]).equal('ZMEL') + expect(themes[1].protectedSpecies.length).equal(0) + expect(themes[2].theme).equal('Rejets illicites') + expect(themes[2].subThemes.length).equal(1) + expect(themes[2].subThemes[0]).equal('Jet de déchet') + expect(themes[2].protectedSpecies.length).equal(0) + }) + }) +}) diff --git a/frontend/cypress/e2e/02_side_window_mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission/mission_dates_validation.spec.ts similarity index 51% rename from frontend/cypress/e2e/02_side_window_mission_actions.spec.ts rename to frontend/cypress/e2e/side_window/mission/mission_dates_validation.spec.ts index 281df6f64..c0c6d4356 100644 --- a/frontend/cypress/e2e/02_side_window_mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission/mission_dates_validation.spec.ts @@ -1,167 +1,12 @@ /// -context('Mission actions', () => { +context('Mission dates', () => { beforeEach(() => { cy.viewport(1280, 1024) cy.visit(`/side_window`).wait(1000) }) - it('An infraction Should be duplicated', () => { - // Given - cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) - cy.get('*[data-cy="action-card"]').eq(1).click() - cy.get('*[data-cy="control-form-number-controls"]').type('{backspace}2') - cy.get('*[data-cy="infraction-form"]').should('not.exist') - - // When - cy.get('*[data-cy="duplicate-infraction"]').click({ force: true }) - cy.get('*[data-cy="infraction-form-registrationNumber"]').should('have.value', 'BALTIK') - cy.get('*[data-cy="infraction-form-validate"]').click({ force: true }) - cy.get('*[data-cy="duplicate-infraction"]').eq(1).should('be.disabled') - - cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') - cy.get('form').submit() - - // Then - cy.wait('@updateMission').then(({ request, response }) => { - expect(response && response.statusCode).equal(200) - const { infractions } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') - expect(infractions.length).equal(2) - const duplicatedInfraction = infractions[1] - - expect(duplicatedInfraction.controlledPersonIdentity).equal('John Doe') - expect(duplicatedInfraction.formalNotice).equal('PENDING') - expect(duplicatedInfraction.infractionType).equal('WITH_REPORT') - expect(duplicatedInfraction.natinf.length).equal(2) - expect(duplicatedInfraction.observations).equal("Pas d'observations") - expect(duplicatedInfraction.registrationNumber).equal('BALTIK') - expect(duplicatedInfraction.relevantCourt).equal('LOCAL_COURT') - expect(duplicatedInfraction.toProcess).equal(false) - expect(duplicatedInfraction.vesselSize).equal('FROM_24_TO_46m') - expect(duplicatedInfraction.vesselType).equal('COMMERCIAL') - expect(duplicatedInfraction.id).not.equal('c52c6f20-e495-4b29-b3df-d7edfb67fdd7') - }) - }) - - it('allow only one theme and may be multiple subthemes in control actions', () => { - // Given - cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) - cy.get('*[data-cy="action-card"]').eq(1).click() - cy.get('*[data-cy="envaction-theme-element"]').should('have.length', 1) - cy.get('*[data-cy="envaction-theme-selector"]').contains('Police des mouillages') - cy.get('*[data-cy="envaction-theme-element"]').contains('Mouillage individuel') - cy.get('*[data-cy="envaction-theme-element"]').contains('ZMEL') - cy.get('*[data-cy="envaction-protected-species-selector"]').should('not.exist') - // When - cy.get('*[data-cy="envaction-theme-selector"]').click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').contains('Police des espèces protégées').click() - - cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').contains('Perturbation').click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').contains('Atteinte aux habitats').click({ force: true }) - cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) - - cy.get('*[data-cy="envaction-protected-species-selector"]').should('exist') - cy.get('*[data-cy="envaction-protected-species-selector"]').click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').contains('Habitat').click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').contains('Oiseaux').click({ force: true }) - cy.get('*[data-cy="envaction-protected-species-selector"]').click({ force: true }) - - cy.get('*[data-cy="envaction-add-theme"]').should('not.exist') - - cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') - cy.get('form').submit() - - // Then - cy.wait('@updateMission').then(({ request, response }) => { - expect(response && response.statusCode).equal(200) - - const { themes } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') - expect(themes.length).equal(1) - expect(themes[0].theme).equal('Police des espèces protégées et de leurs habitats (faune et flore)') - expect(themes[0].subThemes.length).equal(2) - expect(themes[0].subThemes[0]).equal("Perturbation d'animaux") - expect(themes[0].subThemes[1]).equal("Atteinte aux habitats d'espèces protégées") - expect(themes[0].protectedSpecies.length).equal(2) - expect(themes[0].protectedSpecies[0]).equal('HABITAT') - expect(themes[0].protectedSpecies[1]).equal('BIRDS') - }) - }) - - it('save observations in control Actions', () => { - // Given - cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) - cy.get('*[data-cy="action-card"]').eq(1).click() - cy.get('[id="envActions[1].observations"]').contains('RAS') - - // When - cy.get('[id="envActions[1].observations"]').type('{backspace}{backspace}Une observation importante', { - force: true - }) - - cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') - cy.get('form').submit() - - // Then - cy.wait('@updateMission').then(({ request, response }) => { - expect(response && response.statusCode).equal(200) - - const { observations } = request.body.envActions.find(a => a.id === 'c52c6f20-e495-4b29-b3df-d7edfb67fdd7') - expect(observations).equal('RUne observation importante') - }) - }) - - it('allow multiple themes and may be multiple subthemes in surveillance actions', () => { - // Given - cy.get('*[data-cy="edit-mission-34"]').click({ force: true }) - cy.get('*[data-cy="action-card"]').eq(0).click() - cy.get('*[data-cy="envaction-theme-element"]').should('have.length', 2) - cy.get('*[data-cy="envaction-theme-selector"]') - .eq(0) - .contains('Police des espèces protégées et de leurs habitats (faune et flore)') - cy.get('*[data-cy="envaction-theme-element"]').contains('Destruction, capture, arrachage') - cy.get('*[data-cy="envaction-protected-species-selector"]').should('exist') - cy.get('*[data-cy="envaction-theme-element"]').contains('Flore') - cy.get('*[data-cy="envaction-theme-element"]').contains('Oiseaux') - - // When - cy.get('*[data-cy="envaction-theme-selector"]').eq(0).click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').eq(0).contains('Police des réserves naturelles').click() - - cy.get('*[data-cy="envaction-add-theme"]').click({ force: true }) - cy.get('*[data-cy="envaction-theme-selector"]').eq(2).click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').eq(2).contains('Rejets illicites').click() - - cy.get('*[data-cy="envaction-subtheme-selector"]').eq(2).click({ force: true }) - cy.get('*[data-cy="envaction-theme-element"]').eq(2).contains('Jet de déchet').click({ force: true }) - - cy.get('*[data-cy="envaction-protected-species-selector"]').should('have.length', 0) - - cy.intercept('PUT', `/bff/v1/missions/34`).as('updateMission') - cy.get('form').submit() - - // Then - cy.wait('@updateMission').then(({ response }) => { - expect(response && response.statusCode).equal(200) - - const { themes } = response && response.body.envActions.find(a => a.id === 'b8007c8a-5135-4bc3-816f-c69c7b75d807') - expect(themes.length).equal(3) - expect(themes[0].theme).equal('Police des réserves naturelles') - expect(themes[0].subThemes.length).equal(0) - expect(themes[0].protectedSpecies.length).equal(0) - expect(themes[1].theme).equal('Police des mouillages') - expect(themes[1].subThemes.length).equal(2) - expect(themes[1].subThemes[0]).equal('Mouillage individuel') - expect(themes[1].subThemes[1]).equal('ZMEL') - expect(themes[1].protectedSpecies.length).equal(0) - expect(themes[2].theme).equal('Rejets illicites') - expect(themes[2].subThemes.length).equal(1) - expect(themes[2].subThemes[0]).equal('Jet de déchet') - expect(themes[2].protectedSpecies.length).equal(0) - }) - }) - it('A mission should be created and closed with surveillances and valid dates', () => { // Given cy.wait(200) diff --git a/frontend/cypress/e2e/00_side_window_missions.spec.ts b/frontend/cypress/e2e/side_window/mission_list/missions.spec.ts similarity index 100% rename from frontend/cypress/e2e/00_side_window_missions.spec.ts rename to frontend/cypress/e2e/side_window/mission_list/missions.spec.ts diff --git a/frontend/cypress/e2e/03_side_window_missions_navigation.spec.ts b/frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts similarity index 100% rename from frontend/cypress/e2e/03_side_window_missions_navigation.spec.ts rename to frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts diff --git a/frontend/cypress/e2e/05_side_window_reportings.spec.ts b/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts similarity index 100% rename from frontend/cypress/e2e/05_side_window_reportings.spec.ts rename to frontend/cypress/e2e/side_window/reporting/reportings.spec.ts diff --git a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm.tsx index 57cfdacff..6c2bb706c 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm.tsx @@ -125,6 +125,7 @@ export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionI name={`envActions[${currentActionIndex}].geom`} /> ( + const [administrationField, administrationMeta, administrationHelpers] = useField( `controlUnits.${controlUnitIndex}.administration` ) const [unitField, , unitHelpers] = useField(`controlUnits.${controlUnitIndex}.id`) - const [, , unitNameHelpers] = useField(`controlUnits.${controlUnitIndex}.name`) + const [, unitNameMeta, unitNameHelpers] = useField(`controlUnits.${controlUnitIndex}.name`) const [resourcesField, , resourcesHelpers] = useField( `controlUnits.${controlUnitIndex}.resources` ) @@ -118,6 +118,7 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC )} + {administrationMeta.error && {administrationMeta.error}} @@ -136,6 +137,7 @@ export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeC key={unitField.value} /> + {unitNameMeta.error && {unitNameMeta.error}} @@ -176,8 +178,7 @@ const RessourceUnitWrapper = styled.div` const FormGroupFixed = styled.div` display: flex; - flex-direction: row; - height: 52px; + flex-direction: column; width: 100%; :not(:last-child) { diff --git a/frontend/src/features/missions/MissionForm/GeneralInformationsForm.tsx b/frontend/src/features/missions/MissionForm/GeneralInformationsForm.tsx index 28ee19daa..8db1247cb 100644 --- a/frontend/src/features/missions/MissionForm/GeneralInformationsForm.tsx +++ b/frontend/src/features/missions/MissionForm/GeneralInformationsForm.tsx @@ -86,20 +86,24 @@ export function GeneralInformationsForm() { {errors.endDateTimeUtc && {errors.endDateTimeUtc}} - - - {(missionSourceField.value === MissionSourceEnum.MONITORFISH || - missionSourceField.value === MissionSourceEnum.POSEIDON_CNSP) && ( - - )} - +
+ + + + {(missionSourceField.value === MissionSourceEnum.MONITORFISH || + missionSourceField.value === MissionSourceEnum.POSEIDON_CNSP) && ( + + )} + + {errors.missionTypes && {errors.missionTypes}} +
{(missionSourceField.value === MissionSourceEnum.MONITORFISH || missionSourceField.value === MissionSourceEnum.POSEIDON_CNSP) && ( val && !_.isEmpty(val?.coordinates) }) @@ -66,7 +66,7 @@ export const getClosedEnvActionControlSchema = (ctx: any): Yup.SchemaOf = Yup.object().shape({ protectedSpecies: Yup.array().of(Yup.string().optional()).nullable().optional(), - subThemes: Yup.array() - .of(Yup.string().required()) - .ensure() - .required() - .min(1, 'Sélectionnez au moins une sous thématique'), - theme: Yup.string().nullable().required('Sélectionnez un thème') + subThemes: Yup.array().of(Yup.string().required()).ensure().required().min(1, 'Sous-thématique requise'), + theme: Yup.string().nullable().required('Thème requis') }) diff --git a/frontend/src/features/missions/MissionForm/Schemas/index.ts b/frontend/src/features/missions/MissionForm/Schemas/index.ts index 8eb76a520..bea338d65 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/index.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/index.ts @@ -19,7 +19,7 @@ const shouldUseAlternateValidationInTestEnvironment = process.env.NODE_ENV === ' const MissionTypesSchema = Yup.array() .of(Yup.mixed().oneOf(Object.values(MissionTypeEnum)).required()) .ensure() - .min(1, 'Requis') + .min(1, 'Type de mission requis') const ControlResourceSchema: Yup.SchemaOf = Yup.object() .shape({ @@ -30,7 +30,7 @@ const ControlResourceSchema: Yup.SchemaOf = Yup const ControlUnitSchema: Yup.SchemaOf = Yup.object() .shape({ - administration: Yup.string().required(), + administration: Yup.string().required('Administration requise'), contact: Yup.string() .nullable() .test({ @@ -45,7 +45,7 @@ const ControlUnitSchema: Yup.SchemaOf = Yup.object() } }), id: Yup.number().required(), - name: Yup.string().required(), + name: Yup.string().required('Unité requise'), resources: Yup.array().ensure().of(ControlResourceSchema).required() }) .defined() @@ -119,7 +119,7 @@ const NewMissionSchema: Yup.SchemaOf = Yup.object() .min(3, 'le Trigramme doit comporter 3 lettres') .max(3, 'le Trigramme doit comporter 3 lettres') .nullable() - .required('Trigramme requis'), + .required("Trigramme d'ouverture requis"), startDateTimeUtc: Yup.date().required('Date de début requise') }) .required() @@ -129,7 +129,7 @@ const ClosedMissionSchema = NewMissionSchema.shape({ .min(3, 'Minimum 3 lettres pour le Trigramme') .max(3, 'Maximum 3 lettres pour le Trigramme') .nullable() - .required('Trigramme requis'), + .required('Trigramme de clôture requis'), controlUnits: Yup.array().of(ClosedControlUnitSchema).ensure().defined().min(1), envActions: Yup.array() .of(ClosedEnvActionSchema as any) diff --git a/frontend/src/features/missions/MultiPointPicker.tsx b/frontend/src/features/missions/MultiPointPicker.tsx index 223e5393c..604eb7acc 100644 --- a/frontend/src/features/missions/MultiPointPicker.tsx +++ b/frontend/src/features/missions/MultiPointPicker.tsx @@ -90,7 +90,7 @@ export function MultiPointPicker({ addButtonLabel, label = undefined, name }: Mu > {addButtonLabel} - {!!meta.error && Veuillez définir un point de contrôle} + {!!meta.error && {meta.error}} <> {points.map((coordinates, index) => ( From d9e78a83fdde65fa784c30f9d849ce36c65e5b48 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Wed, 11 Oct 2023 15:54:23 +0200 Subject: [PATCH 3/4] [Test] update close mission test --- .../mission/close_mission_validation.spec.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts b/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts index ae80b5d38..d11e40546 100644 --- a/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts +++ b/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts @@ -44,14 +44,12 @@ context('Mission', () => { // we fill all the required inputs cy.fill('Thématique de contrôle', 'Police des espèces protégées') - // TODO understand why `cy.fill` doesn't work here cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) cy.get('*[data-cy="envaction-theme-element"]').contains("Perturbation d'animaux").click({ force: true }) cy.get('*[data-cy="envaction-subtheme-selector"]').click('topLeft', { force: true }) cy.fill('Date et heure du contrôle (UTC)', [2023, 10, 11, 12, 12]) - cy.get('*[data-cy="point-picker-coordinates"]').should('not.exist') cy.fill('Nombre total de contrôles', '2') cy.fill('Type de cible', 'Personne morale') @@ -80,6 +78,16 @@ context('Mission', () => { cy.getDataCy('surveillance-zone-matches-mission').should('have.class', 'rs-checkbox-checked') cy.get('*[data-cy="mission-errors"]').should('not.exist') + // delete theme to test error + cy.fill('Thématique de surveillance', '') + cy.get('*[data-cy="mission-errors"]').should('exist') + + cy.fill('Thématique de surveillance', 'Rejets illicites') + // TODO understand why `cy.fill` doesn't work here + cy.get('*[data-cy="envaction-subtheme-selector"]').click({ force: true }) + cy.get('*[data-cy="envaction-theme-element"]').contains('Jet de déchet').click({ force: true }) + cy.get('*[data-cy="envaction-subtheme-selector"]').click('topLeft', { force: true }) + // Then cy.intercept('PUT', '/bff/v1/missions').as('createAndCloseMission') cy.clickButton('Enregistrer et clôturer') From 2db03b4b97f309e2e2ba28283a96afc5d665fc60 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Thu, 12 Oct 2023 11:30:35 +0200 Subject: [PATCH 4/4] [Test] update e2e tests after rebase --- .../cypress/e2e/side_window/mission/mission_actions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts index 34ba079c7..247e7219d 100644 --- a/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission/mission_actions.spec.ts @@ -3,7 +3,7 @@ context('Mission actions', () => { beforeEach(() => { cy.viewport(1280, 1024) - cy.visit(`/side_window`) + cy.visit(`/side_window`).wait(1000) }) it('An infraction Should be duplicated', () => {