diff --git a/src/client/services/cookies/cookies.service.fixture.ts b/src/client/services/cookies/cookies.service.fixture.ts index 777b5d7f42..77dd3e48b8 100644 --- a/src/client/services/cookies/cookies.service.fixture.ts +++ b/src/client/services/cookies/cookies.service.fixture.ts @@ -7,6 +7,7 @@ export function aCookiesService(override?: Partial): CookiesServ allowService: jest.fn(), isServiceAllowed: jest.fn(() => true), openPanel: jest.fn(), + triggerServices: jest.fn(), ...override, }; } diff --git a/src/client/services/cookies/cookies.service.ts b/src/client/services/cookies/cookies.service.ts index a45adcc696..e4f8fefc6b 100644 --- a/src/client/services/cookies/cookies.service.ts +++ b/src/client/services/cookies/cookies.service.ts @@ -13,4 +13,6 @@ export interface CookiesService { allowService(nom: string): void; openPanel(): void; + + triggerServices(): void; } diff --git a/src/client/services/cookies/null/null.cookies.service.ts b/src/client/services/cookies/null/null.cookies.service.ts index cf6c849b24..f6cb5361fe 100644 --- a/src/client/services/cookies/null/null.cookies.service.ts +++ b/src/client/services/cookies/null/null.cookies.service.ts @@ -20,4 +20,8 @@ export class NullCookiesService implements CookiesService { openPanel(): void { return; } + + triggerServices() { + return; + } } diff --git a/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.test.ts b/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.test.ts index f7cef01794..76c3b1e8f2 100644 --- a/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.test.ts +++ b/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.test.ts @@ -159,4 +159,15 @@ describe('TarteAuCitronCookiesService', () => { expect(tarteaucitron.userInterface.openPanel).toHaveBeenCalledTimes(1); }); }); + + describe('triggerServices', () => { + it('appelle triggerJobsAfterAjaxCall', () => { + const tarteaucitron = aTarteAuCitron({ triggerJobsAfterAjaxCall: jest.fn() }); + const cookiesService = new TarteAuCitronCookiesService(tarteaucitron); + + cookiesService.triggerServices(); + + expect(tarteaucitron.triggerJobsAfterAjaxCall).toHaveBeenCalled(); + }); + }); }); diff --git a/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.ts b/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.ts index 8bdaf17a84..35b997d9b0 100644 --- a/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.ts +++ b/src/client/services/cookies/tarteaucitron/tarteAuCitron.cookies.service.ts @@ -16,7 +16,8 @@ export type TarteAuCitron = { respond: (bouton: HTMLButtonElement, value: boolean) => void, openPanel: () => void, } - state: Record; + state: Record, + triggerJobsAfterAjaxCall: () => void, } export class TarteAuCitronCookiesService implements CookiesService { @@ -84,4 +85,8 @@ export class TarteAuCitronCookiesService implements CookiesService { openPanel(): void { return this.tarteaucitron.userInterface.openPanel(); } + + triggerServices() { + this.tarteaucitron.triggerJobsAfterAjaxCall(); + } } diff --git a/src/client/services/cookies/tarteaucitron/tarteAuCitron.fixture.ts b/src/client/services/cookies/tarteaucitron/tarteAuCitron.fixture.ts index 505003f421..16a6c27108 100644 --- a/src/client/services/cookies/tarteaucitron/tarteAuCitron.fixture.ts +++ b/src/client/services/cookies/tarteaucitron/tarteAuCitron.fixture.ts @@ -6,6 +6,7 @@ export function aTarteAuCitron(override?: Partial): TarteAuCitron job: undefined, services: {}, state: {}, + triggerJobsAfterAjaxCall: jest.fn(), user: {}, userInterface: { openPanel: jest.fn(), diff --git a/src/pages/_app.page.test.tsx b/src/pages/_app.page.test.tsx index b13ba611de..605ee7d21e 100644 --- a/src/pages/_app.page.test.tsx +++ b/src/pages/_app.page.test.tsx @@ -8,9 +8,17 @@ import React from 'react'; import { createMockRouter, mockUseRouter } from '~/client/components/useRouter.mock'; import { mockUUID } from '~/client/components/window.mock'; +import { Dependencies } from '~/client/dependencies.container'; +import { aCookiesService } from '~/client/services/cookies/cookies.service.fixture'; import App from './_app.page'; + +const mockDependencies: Partial = { + cookiesService: aCookiesService(), +}; +jest.mock('~/client/dependencies.container', () => (() => mockDependencies)); + describe('', () => { beforeAll(() => { mockUUID(); @@ -31,4 +39,39 @@ describe('', () => { const cible = screen.getByText('Cible'); await waitFor(() => expect(cible).toHaveFocus()); }); + describe('Trigger services tiers', () => { + it('ne trigger pas les cookies à l’arrivée sur la page', () => { + // NOTE (GAFI 26-11-2024): Les services sont automatiquement triggered au chargement de tarteaucitron + const router = createMockRouter() as Router; + mockUseRouter({}); + mockDependencies.cookiesService = aCookiesService({ triggerServices: jest.fn() }); + const Component = Object.assign( + function Component() { + return null; + }, + { getLayout: (page: React.ReactElement) => <>{page} }, + ); + + render(); + + expect(mockDependencies.cookiesService.triggerServices).not.toHaveBeenCalled(); + }); + it('trigger les cookies au changement de page', () => { + mockDependencies.cookiesService = aCookiesService({ triggerServices: jest.fn() }); + const router = createMockRouter() as Router; + mockUseRouter({ asPath: '/page-1' }); + const Component = Object.assign( + function Component() { + return null; + }, + { getLayout: (page: React.ReactElement) => <>{page} }, + ); + const { rerender } = render(); + + mockUseRouter({ asPath: '/page-2' }); + rerender(); + + expect(mockDependencies.cookiesService.triggerServices).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/pages/_app.page.tsx b/src/pages/_app.page.tsx index 2ae7d02674..273e9ef01e 100644 --- a/src/pages/_app.page.tsx +++ b/src/pages/_app.page.tsx @@ -4,7 +4,7 @@ import { NextPage } from 'next'; import { AppProps } from 'next/app'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import React, { ReactElement, ReactNode, useEffect, useMemo } from 'react'; +import React, { ReactElement, ReactNode, useEffect, useMemo, useRef } from 'react'; import ErrorServer from '~/client/components/layouts/Error/ErrorServer'; import { Layout } from '~/client/components/layouts/Layout'; @@ -12,6 +12,7 @@ import { DependenciesProvider } from '~/client/context/dependenciesContainer.con import dependenciesContainer from '~/client/dependencies.container'; import usePageHistory from '~/client/hooks/usePageHistory'; import useSessionId from '~/client/hooks/useSessionId'; +import { CookiesService } from '~/client/services/cookies/cookies.service'; export type NextPageWithLayout

= NextPage & { getLayout?: (page: ReactElement) => ReactNode; @@ -21,25 +22,34 @@ type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; } +function useTriggerServicesOnNavigation(cookiesService: CookiesService) { + const router = useRouter(); + + const previousPath = useRef(router.asPath); + useEffect(function triggerAnalyticsServices() { + if (previousPath.current === router.asPath) { + return; + } + cookiesService.triggerServices(); + previousPath.current = router.asPath; + }, [cookiesService, router.asPath]); +} + export default function App({ Component, pageProps }: AppPropsWithLayout) { const sessionId = useSessionId(); - /* isClientSide permet de générer l'essentiel de la page uniquement côté client - Il est nécessaire pour le moment car la codebase contient toujours des appels à des méthodes comme useBreakpoint - qui causent des hydration mismatch = différence de rendu entre serveur et premier chargement du JS côté client - La suppression de isClientSide permettra un rendu SSR / SSG complet */ - const dependenciesContainerInstance = useMemo(() => dependenciesContainer(sessionId), [sessionId]); const router = useRouter(); usePageHistory(); + useTriggerServicesOnNavigation(dependenciesContainerInstance.cookiesService); - useEffect(() => { + useEffect(function focusAnchor() { const [/* full path */, targetId] = router.asPath.match(/^[^#]*#(.+)$/) ?? []; if (targetId) { document.getElementById(targetId)?.focus(); } - }, [router.asPath, sessionId]); + }, [router.asPath]); const getLayout = Component.getLayout ?? defaultLayout; return ( @@ -50,17 +60,13 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) { content="width=device-width, height=device-height, initial-scale=1, viewport-fit=cover, minimum-scale=1.0" /> - { - dependenciesContainerInstance && ( - - {getLayout( - pageProps.error - ? - : , - )} - - ) - } + + {getLayout( + pageProps.error + ? + : , + )} + ); }