feat(progression): page /progression + section Dashboard Premium — patterns, exercices long terme, indice de préparation (Sprint 3.6c)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a752029c19
commit
a60c298605
18 changed files with 1005 additions and 7 deletions
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* 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 } 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'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
function renderWithRouter(ui: React.ReactNode) {
|
||||
return render(<MemoryRouter>{ui}</MemoryRouter>)
|
||||
}
|
||||
|
||||
describe('MonProfilPreparation — gating plan', () => {
|
||||
it('plan free → ne rend rien', () => {
|
||||
const { container } = renderWithRouter(<MonProfilPreparation plan="free" />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
it('plan standard → ne rend rien', () => {
|
||||
const { container } = renderWithRouter(<MonProfilPreparation plan="standard" />)
|
||||
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<typeof usePatterns>)
|
||||
|
||||
renderWithRouter(<MonProfilPreparation plan="premium" />)
|
||||
|
||||
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<typeof usePatterns>)
|
||||
|
||||
renderWithRouter(<MonProfilPreparation plan="premium" />)
|
||||
|
||||
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<typeof usePatterns>)
|
||||
|
||||
renderWithRouter(<MonProfilPreparation plan="premium" />)
|
||||
|
||||
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<typeof usePatterns>)
|
||||
|
||||
renderWithRouter(<MonProfilPreparation plan="premium" />)
|
||||
|
||||
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<typeof usePatterns>)
|
||||
|
||||
renderWithRouter(<MonProfilPreparation plan="premium" />)
|
||||
|
||||
expect(screen.getByText(/aucune erreur récurrente/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('85')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue