|
| 1 | +import { describe, it, expect, vi, beforeEach } from 'vitest' |
| 2 | +import { renderHook, act, waitFor } from '@testing-library/react' |
| 3 | +import { EvaluationSessionProvider, useEvaluationSession } from './EvaluationSessionContext' |
| 4 | +import { mockPolitician } from '@/test/mock-data' |
| 5 | +import { Politician } from '@/types' |
| 6 | + |
| 7 | +// Mock useAuthSession |
| 8 | +vi.mock('@/hooks/useAuthSession', () => ({ |
| 9 | + useAuthSession: () => ({ |
| 10 | + session: { accessToken: 'mock-token' }, |
| 11 | + status: 'authenticated', |
| 12 | + isAuthenticated: true, |
| 13 | + }), |
| 14 | +})) |
| 15 | + |
| 16 | +// Mock UserPreferencesContext with STABLE references to avoid infinite loops |
| 17 | +// The context has a useEffect that clears politicians when filters change, |
| 18 | +// and useMemo depends on filters - so a new array each render causes loops |
| 19 | +const stableFilters: never[] = [] |
| 20 | +vi.mock('./UserPreferencesContext', () => ({ |
| 21 | + useUserPreferences: () => ({ |
| 22 | + filters: stableFilters, |
| 23 | + initialized: true, |
| 24 | + }), |
| 25 | +})) |
| 26 | + |
| 27 | +// Create a second mock politician for testing advancement |
| 28 | +const mockPolitician2: Politician = { |
| 29 | + ...mockPolitician, |
| 30 | + id: 'pol-2', |
| 31 | + name: 'Second Politician', |
| 32 | + wikidata_id: 'Q123456', |
| 33 | +} |
| 34 | + |
| 35 | +describe('EvaluationSessionContext', () => { |
| 36 | + beforeEach(() => { |
| 37 | + vi.clearAllMocks() |
| 38 | + |
| 39 | + // Setup mock fetch - always returns same politicians (stable response) |
| 40 | + // has_enrichable_politicians: false prevents auto-polling |
| 41 | + global.fetch = vi.fn().mockImplementation((url: string) => { |
| 42 | + if (url.includes('/api/evaluations/politicians')) { |
| 43 | + return Promise.resolve({ |
| 44 | + ok: true, |
| 45 | + json: async () => ({ |
| 46 | + politicians: [mockPolitician, mockPolitician2], |
| 47 | + meta: { has_enrichable_politicians: false, total_matching_filters: 10 }, |
| 48 | + }), |
| 49 | + }) |
| 50 | + } |
| 51 | + if (url.includes('/api/evaluations')) { |
| 52 | + return Promise.resolve({ |
| 53 | + ok: true, |
| 54 | + json: async () => ({ success: true, message: 'OK', errors: [] }), |
| 55 | + }) |
| 56 | + } |
| 57 | + return Promise.resolve({ ok: false }) |
| 58 | + }) as unknown as typeof fetch |
| 59 | + }) |
| 60 | + |
| 61 | + it('advances to next politician when session is reset', async () => { |
| 62 | + const wrapper = ({ children }: { children: React.ReactNode }) => ( |
| 63 | + <EvaluationSessionProvider>{children}</EvaluationSessionProvider> |
| 64 | + ) |
| 65 | + |
| 66 | + const { result } = renderHook(() => useEvaluationSession(), { wrapper }) |
| 67 | + |
| 68 | + // Wait for initial load |
| 69 | + await waitFor(() => { |
| 70 | + expect(result.current.currentPolitician).not.toBeNull() |
| 71 | + }) |
| 72 | + |
| 73 | + // Verify we start with first politician and have next pre-fetched |
| 74 | + expect(result.current.currentPolitician?.id).toBe('pol-1') |
| 75 | + expect(result.current.nextPolitician?.id).toBe('pol-2') |
| 76 | + expect(result.current.completedCount).toBe(0) |
| 77 | + |
| 78 | + // Reset the session (simulating "Start new round" on unmount) |
| 79 | + act(() => { |
| 80 | + result.current.resetSession() |
| 81 | + }) |
| 82 | + |
| 83 | + // After reset, next politician should be promoted to current |
| 84 | + expect(result.current.currentPolitician?.id).toBe('pol-2') |
| 85 | + expect(result.current.completedCount).toBe(0) |
| 86 | + }) |
| 87 | +}) |
0 commit comments