diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1eeb8a586a..cd2da11013 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,6 +49,7 @@ "react-select": "5.8.3", "react-toastify": "9.1.3", "redux": "5.0.1", + "redux-mock-store": "1.5.5", "redux-persist": "6.0.0", "redux-thunk": "3.1.0", "rsuite": "5.54.0", @@ -13173,6 +13174,11 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -16285,6 +16291,17 @@ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "license": "MIT" }, + "node_modules/redux-mock-store": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.5.tgz", + "integrity": "sha512-YxX+ofKUTQkZE4HbhYG4kKGr7oCTJfB0GLy7bSeqx86GLpGirrbUWstMnqXkqHNaQpcnbMGbof2dYs5KsPE6Zg==", + "dependencies": { + "lodash.isplainobject": "^4.0.6" + }, + "peerDependencies": { + "redux": "*" + } + }, "node_modules/redux-persist": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 614cad8477..e53b097bb2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -74,6 +74,7 @@ "react-select": "5.8.3", "react-toastify": "9.1.3", "redux": "5.0.1", + "redux-mock-store": "1.5.5", "redux-persist": "6.0.0", "redux-thunk": "3.1.0", "rsuite": "5.54.0", @@ -138,9 +139,9 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "lint-staged": "14.0.1", + "madge": "8.0.0", "ora": "8.1.1", "postgres": "3.4.5", - "madge": "8.0.0", "prettier": "3.4.1", "puppeteer": "22.15.0", "strip-json-comments": "5.0.1", diff --git a/frontend/src/features/Reporting/components/ReportingCard/__tests__/ReportingCard.test.tsx b/frontend/src/features/Reporting/components/ReportingCard/__tests__/ReportingCard.test.tsx index 4a395eab5f..faebf959ba 100644 --- a/frontend/src/features/Reporting/components/ReportingCard/__tests__/ReportingCard.test.tsx +++ b/frontend/src/features/Reporting/components/ReportingCard/__tests__/ReportingCard.test.tsx @@ -1,31 +1,34 @@ import { Seafront } from '@constants/seafront' +import { PendingAlertValueType } from '@features/Alert/types' import { ReportingCard } from '@features/Reporting/components/ReportingCard' import { ReportingType } from '@features/Reporting/types' -import { afterAll, describe, expect, it } from '@jest/globals' +import { afterAll, describe, expect, it, jest } from '@jest/globals' import { THEME, ThemeProvider } from '@mtes-mct/monitor-ui' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { noop } from 'lodash' +import { Provider } from 'react-redux' +import configureStore from 'redux-mock-store' import { VesselIdentifier } from '../../../../../domain/entities/vessel/types' -import { PendingAlertValueType } from '../../../../Alert/types' import type { PendingAlertReporting } from '@features/Reporting/types' -// @ts-ignore -jest.mock('../../../useCases/archiveReporting', () => () => ({ archiveReporting: jest.fn() })) -// @ts-ignore +jest.mock('../../../useCases/archiveReporting', () => ({ archiveReporting: jest.fn() })) jest.mock('../../../../../hooks/useMainAppDispatch', () => ({ useMainAppDispatch: () => {} })) +jest.mock('../../../useCases/deleteReporting', () => ({ deleteReporting: jest.fn() })) describe('ReportingCard()', () => { afterAll(() => { // Reset module registry to clear the mock - // @ts-ignore jest.resetModules() }) it('should display all other dates', async () => { // Given + const mockStore = configureStore() + const store = mockStore({}) + const reporting: PendingAlertReporting = { creationDate: '2023-10-30T09:10:00Z', externalReferenceNumber: '', @@ -57,17 +60,19 @@ describe('ReportingCard()', () => { } render( - - - + + + + + ) const linkElement = screen.getByText(/Voir les dates des autres alertes/i) diff --git a/frontend/src/features/Reporting/components/ReportingCard/index.tsx b/frontend/src/features/Reporting/components/ReportingCard/index.tsx index fa211d93c5..fa01ae574e 100644 --- a/frontend/src/features/Reporting/components/ReportingCard/index.tsx +++ b/frontend/src/features/Reporting/components/ReportingCard/index.tsx @@ -1,5 +1,6 @@ import { ConfirmationModal } from '@components/ConfirmationModal' import { getAlertNameFromType } from '@features/Alert/components/SideWindowAlerts/AlertListAndReportingList/utils' +import { PendingAlertValueType } from '@features/Alert/types' import { deleteReporting } from '@features/Reporting/useCases/deleteReporting' import { reportingIsAnInfractionSuspicion } from '@features/Reporting/utils' import { useMainAppDispatch } from '@hooks/useMainAppDispatch' @@ -35,6 +36,9 @@ export function ReportingCard({ const reportingName = Object.values(ReportingTypeCharacteristics).find( reportingType => reportingType.code === reporting.type )?.name + const canBeArchived = !( + reporting.type === ReportingType.ALERT && reporting.value.type === PendingAlertValueType.MISSING_FAR_48_HOURS_ALERT + ) const alertDateTime = getDateTime( reporting.type === ReportingType.ALERT ? reporting.validationDate : reporting.creationDate, true @@ -54,7 +58,7 @@ export function ReportingCard({ }, [reporting, reportingName]) const archive = () => { - dispatch(archiveReporting(reporting.id, reporting.type)) + dispatch(archiveReporting(reporting)) } const askForDeletionConfirmation = () => { @@ -166,7 +170,11 @@ export function ReportingCard({ Icon={Icon.Archive} iconSize={20} onClick={archive} - title="Archiver ce signalement" + title={ + canBeArchived + ? 'Archiver ce signalement' + : `Ce signalement sera archivé sous la forme de 2 alertes "Absence de message FAR en 24h"` + } /> jest.fn()) +// @ts-ignore +jest.mock('../deleteReporting', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + // @ts-ignore + deleteReporting: jest.fn() +})) + +describe('archiveReporting()', () => { + const INITIAL_STATE = { + vessel: { + selectedVesselIdentity: { + externalReferenceNumber: '', + flagState: '', + internalReferenceNumber: '', + vesselId: 1234568, + vesselIdentifier: VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + vesselName: '' + } + } + } + + afterAll(() => { + // Reset module registry to clear the mock + // @ts-ignore + jest.resetModules() + }) + + it('Should delete reporting When alert is MISSING_FAR_48_HOURS_ALERT', async () => { + // When + mockedDispatch(archiveReporting(fortyHeightHourAlertReporting), INITIAL_STATE) + + // Then + expect(deleteReporting).toHaveBeenCalled() + }) + + it('Should not delete reporting When the alert is not an MISSING_FAR_48_HOURS_ALERT', async () => { + // Given + const otherAlertReporting = { + ...fortyHeightHourAlertReporting, + value: { + ...fortyHeightHourAlertReporting.value, + type: PendingAlertValueType.MISSING_FAR_ALERT + } + } + + // When + mockedDispatch(archiveReporting(otherAlertReporting), INITIAL_STATE) + + // Then + expect(deleteReporting).toHaveBeenCalledTimes(0) + }) +}) diff --git a/frontend/src/features/Reporting/useCases/archiveReporting.ts b/frontend/src/features/Reporting/useCases/archiveReporting.ts index 8968f2df71..3bdf246029 100644 --- a/frontend/src/features/Reporting/useCases/archiveReporting.ts +++ b/frontend/src/features/Reporting/useCases/archiveReporting.ts @@ -1,3 +1,6 @@ +import { PendingAlertValueType } from '@features/Alert/types' +import { ReportingType } from '@features/Reporting/types' +import { deleteReporting } from '@features/Reporting/useCases/deleteReporting' import { renderVesselFeatures } from '@features/Vessel/useCases/renderVesselFeatures' import { DisplayedErrorKey } from '@libs/DisplayedError/constants' @@ -6,20 +9,29 @@ import { displayOrLogError } from '../../../domain/use_cases/error/displayOrLogE import { removeVesselReporting } from '../../Vessel/slice' import { reportingApi } from '../reportingApi' -import type { ReportingType } from '@features/Reporting/types' +import type { Reporting } from '@features/Reporting/types' import type { MainAppThunk } from '@store' export const archiveReporting = - (id: number, type: ReportingType): MainAppThunk> => + (reporting: Reporting.Reporting): MainAppThunk> => async (dispatch, getState) => { const { selectedVesselIdentity } = getState().vessel try { - await dispatch(reportingApi.endpoints.archiveReporting.initiate(id)).unwrap() + if ( + reporting.type === ReportingType.ALERT && + reporting.value.type === PendingAlertValueType.MISSING_FAR_48_HOURS_ALERT + ) { + await dispatch(deleteReporting(reporting.id, reporting.type)) + + return + } + + await dispatch(reportingApi.endpoints.archiveReporting.initiate(reporting.id)).unwrap() dispatch( removeVesselReporting({ - reportingType: type, + reportingType: reporting.type, vesselFeatureId: Vessel.getVesselFeatureId(selectedVesselIdentity) }) ) @@ -29,7 +41,7 @@ export const archiveReporting = dispatch( displayOrLogError( error as Error, - () => archiveReporting(id, type), + () => archiveReporting(reporting), true, DisplayedErrorKey.VESSEL_SIDEBAR_ERROR ) diff --git a/frontend/src/store/__tests__/utils.ts b/frontend/src/store/__tests__/utils.ts new file mode 100644 index 0000000000..785c7455db --- /dev/null +++ b/frontend/src/store/__tests__/utils.ts @@ -0,0 +1,22 @@ +import { jest } from '@jest/globals' + +/** + * To be used to capture all dispatched actions. + * + * we could have more middleware functions being called within the + * use-case middleware, and we should be able to capture all of these events. + */ +export const mockedDispatch = (action, initialState) => { + const store = { + dispatch: jest.fn(fn => { + if (typeof fn === 'function') { + fn(store.dispatch, store.getState) + } + }), + getState: jest.fn(() => initialState) + } + + action(store.dispatch, store.getState) + + return store +}