Skip to content

Commit eab560c

Browse files
committed
feat(analytics): instrumente le parcours simulateur
Émet les events Matomo ouverture/soumission/résultat/réinitialisation depuis la page simulateur.
1 parent cd75b37 commit eab560c

2 files changed

Lines changed: 117 additions & 2 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, expect, it, vi, beforeEach } from "vitest";
2+
import { render, screen, fireEvent, within } from "@testing-library/react";
3+
import { MemoryRouter } from "react-router-dom";
4+
5+
// jsdom n'implémente pas scrollIntoView (appelé par la page après évaluation).
6+
Element.prototype.scrollIntoView = vi.fn();
7+
8+
const trackEvent = vi.fn();
9+
vi.mock("@shared/analytics", async () => {
10+
const actual = await vi.importActual<typeof import("@shared/analytics")>("@shared/analytics");
11+
return {
12+
...actual,
13+
useMatomo: () => ({ trackEvent, trackPageView: vi.fn(), enableHeatmaps: vi.fn() }),
14+
};
15+
});
16+
17+
import { SimulateursIndexPage } from "./SimulateursIndexPage";
18+
import { MATOMO_EVENTS } from "@shared/analytics";
19+
20+
function selectType(value: string) {
21+
fireEvent.change(screen.getByLabelText(/Type d'établissement/i), { target: { value } });
22+
}
23+
24+
function getForm(): HTMLElement {
25+
const form = screen.getByRole("button", { name: /valider/i }).closest("form");
26+
if (!form) throw new Error("formulaire introuvable");
27+
return form;
28+
}
29+
30+
// Remplit chaque select avec sa première option valide ; deux passes pour capter
31+
// le select conditionnel "statut" révélé après le choix de la zone.
32+
function fillAndSubmit(form: HTMLElement) {
33+
for (let pass = 0; pass < 2; pass++) {
34+
for (const select of within(form).queryAllByRole("combobox")) {
35+
const option = within(select)
36+
.getAllByRole("option")
37+
.find((opt) => !(opt as HTMLOptionElement).disabled);
38+
if (option)
39+
fireEvent.change(select, { target: { value: (option as HTMLOptionElement).value } });
40+
}
41+
}
42+
fireEvent.submit(form);
43+
}
44+
45+
describe("SimulateursIndexPage — tracking Matomo", () => {
46+
beforeEach(() => trackEvent.mockClear());
47+
48+
it("émet simulateur_ouvert au choix d'un type", () => {
49+
render(
50+
<MemoryRouter>
51+
<SimulateursIndexPage />
52+
</MemoryRouter>,
53+
);
54+
selectType("abattoir");
55+
expect(trackEvent).toHaveBeenCalledWith(MATOMO_EVENTS.SIMULATEUR_OUVERT, { name: "abattoir" });
56+
});
57+
58+
it("émet simulation_lancee puis resultat_affiche à une soumission valide", () => {
59+
render(
60+
<MemoryRouter>
61+
<SimulateursIndexPage />
62+
</MemoryRouter>,
63+
);
64+
selectType("abattoir");
65+
trackEvent.mockClear();
66+
fillAndSubmit(getForm());
67+
68+
const events = trackEvent.mock.calls.map((call) => call[0] as string);
69+
const lancee = events.indexOf(MATOMO_EVENTS.SIMULATION_LANCEE);
70+
const affiche = events.indexOf(MATOMO_EVENTS.RESULTAT_AFFICHE);
71+
expect(lancee).toBeGreaterThanOrEqual(0);
72+
expect(affiche).toBeGreaterThan(lancee);
73+
});
74+
75+
it("émet reinitialisation au clic sur le bouton Réinitialiser", () => {
76+
render(
77+
<MemoryRouter>
78+
<SimulateursIndexPage />
79+
</MemoryRouter>,
80+
);
81+
selectType("abattoir");
82+
trackEvent.mockClear();
83+
fireEvent.click(screen.getByRole("button", { name: /réinitialiser/i }));
84+
expect(trackEvent).toHaveBeenCalledWith(MATOMO_EVENTS.REINITIALISATION, { name: "abattoir" });
85+
});
86+
87+
it("n'émet pas reinitialisation sur une simple saisie de champ", () => {
88+
render(
89+
<MemoryRouter>
90+
<SimulateursIndexPage />
91+
</MemoryRouter>,
92+
);
93+
selectType("abattoir");
94+
trackEvent.mockClear();
95+
const select = within(getForm()).getAllByRole("combobox")[0];
96+
const option = within(select)
97+
.getAllByRole("option")
98+
.find((opt) => !(opt as HTMLOptionElement).disabled);
99+
fireEvent.change(select, { target: { value: (option as HTMLOptionElement).value } });
100+
expect(trackEvent).not.toHaveBeenCalledWith(MATOMO_EVENTS.REINITIALISATION, expect.anything());
101+
});
102+
});

src/features/simulateurs/pages/SimulateursIndexPage.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@engine";
1515
import { Notice } from "@shared/components/Notice";
1616
import { PageTitle } from "@shared/components/PageTitle";
17+
import { useMatomo, MATOMO_EVENTS } from "@shared/analytics";
1718
import { AbattoirsForm } from "../abattoirs/components/AbattoirsForm";
1819
import { AbattoirsResult } from "../abattoirs/components/AbattoirsResult";
1920
import { EtablissementsForm } from "../etablissements/components/EtablissementsForm";
@@ -28,6 +29,7 @@ export function SimulateursIndexPage() {
2829
null,
2930
);
3031
const resultRef = useRef<HTMLDivElement>(null);
32+
const { trackEvent } = useMatomo();
3133

3234
// Scroll vers le panneau de résultats à chaque nouvelle évaluation.
3335
useEffect(() => {
@@ -37,21 +39,32 @@ export function SimulateursIndexPage() {
3739
}, [abattoirsResult, etablissementsResult]);
3840

3941
function handleAbattoirsSubmit(inputs: AbattoirsInputs) {
42+
trackEvent(MATOMO_EVENTS.SIMULATION_LANCEE, { name: "abattoir" });
4043
setAbattoirsResult(evaluateAbattoir(inputs));
44+
trackEvent(MATOMO_EVENTS.RESULTAT_AFFICHE, { name: "abattoir" });
4145
}
4246

4347
function handleEtablissementsSubmit(inputs: EtablissementsInputs) {
48+
trackEvent(MATOMO_EVENTS.SIMULATION_LANCEE, { name: "autre" });
4449
setEtablissementsResult(evaluateEtablissements(inputs));
50+
trackEvent(MATOMO_EVENTS.RESULTAT_AFFICHE, { name: "autre" });
4551
}
4652

4753
function resetResults() {
4854
setAbattoirsResult(null);
4955
setEtablissementsResult(null);
5056
}
5157

58+
// Réinitialisation explicite (bouton du formulaire), distincte des resets implicites (saisie).
59+
function handleReset() {
60+
resetResults();
61+
if (type !== "") trackEvent(MATOMO_EVENTS.REINITIALISATION, { name: type });
62+
}
63+
5264
function handleTypeChange(value: TypeEtablissement) {
5365
setType(value);
5466
resetResults();
67+
if (value !== "") trackEvent(MATOMO_EVENTS.SIMULATEUR_OUVERT, { name: value });
5568
}
5669

5770
return (
@@ -93,7 +106,7 @@ export function SimulateursIndexPage() {
93106
<div className="fr-mt-8w">
94107
<AbattoirsForm
95108
onSubmit={handleAbattoirsSubmit}
96-
onReset={resetResults}
109+
onReset={handleReset}
97110
onChange={resetResults}
98111
/>
99112
</div>
@@ -103,7 +116,7 @@ export function SimulateursIndexPage() {
103116
<div className="fr-mt-8w">
104117
<EtablissementsForm
105118
onSubmit={handleEtablissementsSubmit}
106-
onReset={resetResults}
119+
onReset={handleReset}
107120
onChange={resetResults}
108121
/>
109122
</div>

0 commit comments

Comments
 (0)