/** * Tests — MonProfilPreparation (Sprint 3.6c). * * Couvre le gating plan : absent Free/Standard, visible Premium (ready + not-ready). * Le hook `usePatterns` est mocké pour isoler la présentation. */ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' import { render, screen, cleanup } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' vi.mock('@/features/progression/hooks/usePatterns', () => ({ usePatterns: vi.fn(), })) import { usePatterns } from '@/features/progression/hooks/usePatterns' import { MonProfilPreparation } from '../MonProfilPreparation' beforeEach(() => { // Mock par défaut — usePatterns est appelé inconditionnellement depuis le // composant (Règle des hooks). Les tests Premium surchargent ce mock. vi.mocked(usePatterns).mockReturnValue({ data: undefined, isLoading: false, isError: false, } as unknown as ReturnType) }) afterEach(cleanup) function renderWithRouter(ui: React.ReactNode) { return render({ui}) } describe('MonProfilPreparation — gating plan', () => { it('plan free → ne rend rien', () => { const { container } = renderWithRouter() expect(container).toBeEmptyDOMElement() }) it('plan standard → ne rend rien', () => { const { container } = renderWithRouter() expect(container).toBeEmptyDOMElement() }) }) describe('MonProfilPreparation — plan premium', () => { it('ready: true → affiche score, message, nb patterns, CTA /progression', () => { vi.mocked(usePatterns).mockReturnValue({ data: { ready: true, patterns: [ { code: 'accord_sujet_verbe', critere: 'competence_grammaticale', frequency: 4, description: null, }, { code: 'connecteurs_repetes', critere: 'coherence_cohesion', frequency: 3, description: null, }, { code: 'repetition_lexicale', critere: 'competence_lexicale', frequency: 3, description: null, }, ], exercises: [], preparation_index: { score: 72, message: 'Vous êtes en bonne voie pour NCLC 9+' }, analyzed_productions: 5, last_analysis: '2026-04-22T12:00:00Z', }, isLoading: false, isError: false, } as unknown as ReturnType) renderWithRouter() expect(screen.getByText('72')).toBeInTheDocument() expect(screen.getByText(/NCLC 9/)).toBeInTheDocument() expect(screen.getByText(/3 erreurs récurrentes identifiées/i)).toBeInTheDocument() expect(screen.getByRole('link', { name: /voir mon profil de préparation/i })).toHaveAttribute( 'href', '/progression', ) }) it('ready: false → message compact "Encore X simulations"', () => { vi.mocked(usePatterns).mockReturnValue({ data: { ready: false, minimum: 5, current: 2 }, isLoading: false, isError: false, } as unknown as ReturnType) renderWithRouter() expect(screen.getByText(/encore/i)).toBeInTheDocument() // Le nombre restant (3) est dans un span séparé du mot "simulations" expect(screen.getByText('3')).toBeInTheDocument() expect(screen.getByText(/pour débloquer votre profil/i)).toBeInTheDocument() expect(screen.getByText(/2\/5 simulations corrigées/i)).toBeInTheDocument() }) it('isLoading → placeholder "Chargement"', () => { vi.mocked(usePatterns).mockReturnValue({ data: undefined, isLoading: true, isError: false, } as unknown as ReturnType) renderWithRouter() expect(screen.getByText(/chargement/i)).toBeInTheDocument() }) it('isError → message "temporairement indisponible"', () => { vi.mocked(usePatterns).mockReturnValue({ data: undefined, isLoading: false, isError: true, } as unknown as ReturnType) renderWithRouter() expect(screen.getByText(/temporairement indisponible/i)).toBeInTheDocument() }) it('ready: true avec 0 pattern → message "Aucune erreur récurrente"', () => { vi.mocked(usePatterns).mockReturnValue({ data: { ready: true, patterns: [], exercises: [], preparation_index: { score: 85, message: 'Vous êtes en bonne voie pour NCLC 9+' }, analyzed_productions: 5, last_analysis: '2026-04-22T12:00:00Z', }, isLoading: false, isError: false, } as unknown as ReturnType) renderWithRouter() expect(screen.getByText(/aucune erreur récurrente/i)).toBeInTheDocument() expect(screen.getByText('85')).toBeInTheDocument() }) })