- {pendingLabel}{' '}
- Rafraîchissez la page dans quelques instants.
+ {pendingLabel}
)
diff --git a/src/features/simulations/hooks/__tests__/useAutosave.test.ts b/src/features/simulations/hooks/__tests__/useAutosave.test.ts
index 7d0e214..d7dd16f 100644
--- a/src/features/simulations/hooks/__tests__/useAutosave.test.ts
+++ b/src/features/simulations/hooks/__tests__/useAutosave.test.ts
@@ -112,6 +112,48 @@ describe('useAutosave', () => {
expect(mocked).not.toHaveBeenCalled()
})
+ it('FTD-23 : enabled true→false annule le debounce en cours', async () => {
+ const { rerender } = renderHook(
+ ({ contenu, enabled }: { contenu: string; enabled: boolean }) =>
+ useAutosave('sim-1', contenu, enabled),
+ { initialProps: { contenu: '', enabled: true } },
+ )
+
+ rerender({ contenu: 'hello world', enabled: true })
+
+ // Avance partiellement le debounce.
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(15_000)
+ })
+ expect(mocked).not.toHaveBeenCalled()
+
+ // Passage à enabled=false (simule step='done' après correction).
+ rerender({ contenu: 'hello world', enabled: false })
+
+ // Fin du debounce — ne doit PAS déclencher d'appel.
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(30_000)
+ })
+ expect(mocked).not.toHaveBeenCalled()
+ })
+
+ it('FTD-23 : enabled=false + beforeunload = aucun appel', async () => {
+ const { rerender } = renderHook(
+ ({ contenu, enabled }: { contenu: string; enabled: boolean }) =>
+ useAutosave('sim-1', contenu, enabled),
+ { initialProps: { contenu: 'texte', enabled: true } },
+ )
+
+ rerender({ contenu: 'texte', enabled: false })
+
+ await act(async () => {
+ window.dispatchEvent(new Event('beforeunload'))
+ await Promise.resolve()
+ })
+
+ expect(mocked).not.toHaveBeenCalled()
+ })
+
it("dédoublonnage : pas de second appel si le contenu n'a pas changé", async () => {
const { rerender } = renderHook(
({ contenu }: { contenu: string }) => useAutosave('sim-1', contenu, true),
diff --git a/src/features/simulations/hooks/__tests__/useRapport.test.tsx b/src/features/simulations/hooks/__tests__/useRapport.test.tsx
new file mode 100644
index 0000000..c7212ad
--- /dev/null
+++ b/src/features/simulations/hooks/__tests__/useRapport.test.tsx
@@ -0,0 +1,207 @@
+/**
+ * Tests du hook useRapport — FTD-24.
+ *
+ * Valide :
+ * - Démarrage polling quand exercices_status ou modele_status === 'pending'
+ * - Arrêt polling quand les deux statuts sortent de 'pending' (ready)
+ * - Arrêt polling quand les deux statuts sont 'error'
+ * - hasTimedOut=true après 2 min de polling continu
+ * - refetch() remet hasTimedOut=false et relance le polling
+ *
+ * Note : fake timers + waitFor ne font pas bon ménage. On avance les timers
+ * manuellement via `vi.advanceTimersByTimeAsync` sous `act()`, ce qui déclenche
+ * les refetchs TanStack Query et les re-renders synchronement dans le test.
+ */
+
+import React from 'react'
+import { act, renderHook } from '@testing-library/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+vi.mock('@/entities/report/api', () => ({
+ getReport: vi.fn(),
+}))
+
+import { getReport } from '@/entities/report/api'
+import type { Report } from '@/entities/report/types'
+import { useRapport } from '../useRapport'
+
+const mockedGetReport = vi.mocked(getReport)
+
+function makeReport(overrides: Partial