/** * Tests de la state machine useSimulation. * * Transitions couvertes : * idle → task-selected (selectTask success) * task-selected → correcting (submitText déclenché) * correcting → done (correctEe success) * correcting → task-selected (correctEe error) * * → idle (reset) * guard submitText sans production (aucune mutation) */ import { renderHook, act, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { useSimulation } from '../useSimulation' import { createSimulation } from '@/entities/production/api' import { correctEe } from '@/entities/report/api' import type { Production } from '@/entities/production/types' import type { Report } from '@/entities/report/types' vi.mock('@/entities/production/api') vi.mock('@/entities/report/api') const mockCreateSimulation = vi.mocked(createSimulation) const mockCorrectEe = vi.mocked(correctEe) const mockProduction: Production = { id: 'sim-1', tache: 'EE_T1', mode: 'entrainement', created_at: '2026-04-19T00:00:00Z', sujet: null, } const mockReport: Report = { simulation_id: 'sim-1', score: 80, nclc: 9, feedback_court: 'Bon travail.', criteres: [], erreurs: [], modele: '', idees: [], exercices: [], } function createWrapper() { const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } }, }) return function Wrapper({ children }: { children: React.ReactNode }) { return React.createElement(QueryClientProvider, { client: queryClient }, children) } } beforeEach(() => { vi.clearAllMocks() }) describe('useSimulation — état initial', () => { it('step = idle, production null, report null', () => { const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) expect(result.current.step).toBe('idle') expect(result.current.production).toBeNull() expect(result.current.report).toBeNull() }) }) describe('useSimulation — selectTask', () => { it('step passe à task-selected et production est hydratée après succès', async () => { mockCreateSimulation.mockResolvedValue(mockProduction) const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => { result.current.selectTask({ tache: 'EE_T1', mode: 'entrainement' }) }) await waitFor(() => expect(result.current.step).toBe('task-selected')) expect(result.current.production).toEqual(mockProduction) }) it('isCreating = true pendant la mutation createSimulation', async () => { let resolveCreate!: (p: Production) => void mockCreateSimulation.mockImplementation(() => new Promise(r => { resolveCreate = r })) const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => { result.current.selectTask({ tache: 'EE_T2', mode: 'entrainement' }) }) await waitFor(() => expect(result.current.isCreating).toBe(true)) act(() => resolveCreate(mockProduction)) await waitFor(() => expect(result.current.isCreating).toBe(false)) }) }) describe('useSimulation — submitText', () => { it('step correcting pendant la correction, puis done après succès', async () => { mockCreateSimulation.mockResolvedValue(mockProduction) let resolveCorrect!: (r: Report) => void mockCorrectEe.mockImplementation(() => new Promise(r => { resolveCorrect = r })) const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => result.current.selectTask({ tache: 'EE_T1', mode: 'entrainement' })) await waitFor(() => expect(result.current.step).toBe('task-selected')) act(() => result.current.submitText('Mon texte de production.')) await waitFor(() => expect(result.current.step).toBe('correcting')) act(() => resolveCorrect(mockReport)) await waitFor(() => expect(result.current.step).toBe('done')) expect(result.current.report).toEqual(mockReport) }) it('step revient à task-selected si correctEe échoue', async () => { mockCreateSimulation.mockResolvedValue(mockProduction) mockCorrectEe.mockRejectedValue({ code: 'SIMULATION_NOT_FOUND', message: 'Not found' }) const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => result.current.selectTask({ tache: 'EE_T1', mode: 'entrainement' })) await waitFor(() => expect(result.current.step).toBe('task-selected')) act(() => result.current.submitText('Mon texte.') ) await waitFor(() => expect(result.current.step).toBe('task-selected')) expect(result.current.report).toBeNull() }) it('submitText sans production ne déclenche aucune mutation', async () => { const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => result.current.submitText('texte quelconque')) expect(mockCorrectEe).not.toHaveBeenCalled() expect(result.current.step).toBe('idle') }) }) describe('useSimulation — reset', () => { it('reset depuis task-selected remet step à idle et production à null', async () => { mockCreateSimulation.mockResolvedValue(mockProduction) const { result } = renderHook(() => useSimulation(), { wrapper: createWrapper() }) act(() => result.current.selectTask({ tache: 'EE_T1', mode: 'entrainement' })) await waitFor(() => expect(result.current.step).toBe('task-selected')) act(() => result.current.reset()) expect(result.current.step).toBe('idle') expect(result.current.production).toBeNull() }) })