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..d11e40546 --- /dev/null +++ b/frontend/cypress/e2e/side_window/mission/close_mission_validation.spec.ts @@ -0,0 +1,100 @@ +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.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') + + // 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') + 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..247e7219d --- /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`).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) + }) + }) +}) 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/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/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 [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/ControlUnitSelector.tsx b/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx index a59546f50..f40bd8e89 100644 --- a/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx +++ b/frontend/src/features/missions/MissionForm/ControlUnitSelector.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { FormikTextInput } from '@mtes-mct/monitor-ui' +import { FieldError, FormikTextInput } from '@mtes-mct/monitor-ui' import { useField } from 'formik' import _ from 'lodash' import { type MutableRefObject, useMemo, useRef } from 'react' @@ -14,11 +14,11 @@ import { ReactComponent as DeleteSVG } from '../../../uiMonitor/icons/Delete.svg import type { ControlUnit } from '../../../domain/entities/controlUnit' export function ControlUnitSelector({ controlUnitIndex, controlUnitPath, removeControlUnit, ...props }) { - const [administrationField, , administrationHelpers] = useField( + 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) && ( { 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..b61411a73 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: 'Point de contrôle requis', + 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..0468936a2 100644 --- a/frontend/src/features/missions/MissionForm/Schemas/Theme.ts +++ b/frontend/src/features/missions/MissionForm/Schemas/Theme.ts @@ -4,10 +4,6 @@ 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('')) - .ensure() - .required() - .min(1, 'Sélectionnez au moins une sous thématique'), - theme: Yup.string().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 bc2d14fd8..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() @@ -110,14 +110,16 @@ 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() .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() @@ -127,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/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/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) => ( 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) => (