Skip to content

Commit 5d4aa42

Browse files
committed
lock user stats
1 parent e128ffd commit 5d4aa42

File tree

14 files changed

+429
-61
lines changed

14 files changed

+429
-61
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest'
2+
import { screen, fireEvent } from '@testing-library/react'
3+
import { render } from '@/test/test-utils'
4+
import CompletePage from './page'
5+
6+
const mockPush = vi.fn()
7+
vi.mock('next/navigation', () => ({
8+
useRouter: () => ({
9+
push: mockPush,
10+
}),
11+
}))
12+
13+
const mockResetSession = vi.fn()
14+
const mockUseEvaluationSession = vi.fn()
15+
vi.mock('@/contexts/EvaluationSessionContext', async (importOriginal) => {
16+
const actual = await importOriginal<typeof import('@/contexts/EvaluationSessionContext')>()
17+
return {
18+
...actual,
19+
useEvaluationSession: () => mockUseEvaluationSession(),
20+
}
21+
})
22+
23+
describe('Complete Page', () => {
24+
beforeEach(() => {
25+
vi.clearAllMocks()
26+
27+
mockUseEvaluationSession.mockReturnValue({
28+
sessionGoal: 5,
29+
resetSession: mockResetSession,
30+
})
31+
})
32+
33+
it('shows session complete message with politician count', () => {
34+
render(<CompletePage />)
35+
36+
expect(screen.getByText('Session Complete!')).toBeInTheDocument()
37+
expect(screen.getByText(/reviewed 5 politicians/)).toBeInTheDocument()
38+
})
39+
40+
it('shows Start Another Round as primary action', () => {
41+
render(<CompletePage />)
42+
43+
expect(screen.getByRole('button', { name: 'Start Another Round' })).toBeInTheDocument()
44+
})
45+
46+
it('shows Return Home as secondary action', () => {
47+
render(<CompletePage />)
48+
49+
expect(screen.getByRole('button', { name: 'Return Home' })).toBeInTheDocument()
50+
})
51+
52+
it('navigates to evaluate page when clicking Start Another Round', () => {
53+
render(<CompletePage />)
54+
55+
fireEvent.click(screen.getByRole('button', { name: 'Start Another Round' }))
56+
57+
expect(mockResetSession).toHaveBeenCalled()
58+
expect(mockPush).toHaveBeenCalledWith('/evaluate')
59+
})
60+
61+
it('navigates to home page when clicking Return Home', () => {
62+
render(<CompletePage />)
63+
64+
fireEvent.click(screen.getByRole('button', { name: 'Return Home' }))
65+
66+
expect(mockResetSession).toHaveBeenCalled()
67+
expect(mockPush).toHaveBeenCalledWith('/')
68+
})
69+
})

poliloom-gui/src/app/evaluate/page.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('Evaluate Page', () => {
4747
render(<EvaluatePage />)
4848
})
4949

50-
expect(screen.getByText('Finding politicians...')).toBeInTheDocument()
50+
expect(screen.getByText('Gathering data...')).toBeInTheDocument()
5151
})
5252

5353
it('shows all caught up message when no politicians and nothing to enrich', async () => {
@@ -92,7 +92,7 @@ describe('Evaluate Page', () => {
9292
render(<EvaluatePage />)
9393
})
9494

95-
expect(screen.getByText('Finding politicians...')).toBeInTheDocument()
95+
expect(screen.getByText('Gathering data...')).toBeInTheDocument()
9696
})
9797

9898
it('shows PoliticianEvaluation component when politician data is available', async () => {

poliloom-gui/src/app/evaluate/page.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import { useEvaluationSession } from '@/contexts/EvaluationSessionContext'
77
import { CenteredCard } from '@/components/ui/CenteredCard'
88
import { Button } from '@/components/ui/Button'
99
import { Spinner } from '@/components/ui/Spinner'
10-
import { useTutorial } from '@/contexts/TutorialContext'
10+
import { useUserProgress } from '@/contexts/UserProgressContext'
1111
import { useUserPreferences } from '@/contexts/UserPreferencesContext'
1212

1313
export default function EvaluatePage() {
1414
const router = useRouter()
1515
const { currentPolitician, loading, isSessionComplete, enrichmentMeta } = useEvaluationSession()
16-
const { completeBasicTutorial, completeAdvancedTutorial } = useTutorial()
16+
const { completeBasicTutorial, completeAdvancedTutorial, statsUnlocked } = useUserProgress()
1717
const { isAdvancedMode } = useUserPreferences()
1818

1919
// Mark tutorials complete once on mount
@@ -28,9 +28,13 @@ export default function EvaluatePage() {
2828
// Navigate to completion page when session goal is reached
2929
useEffect(() => {
3030
if (isSessionComplete) {
31-
router.push('/evaluate/complete')
31+
if (statsUnlocked) {
32+
router.push('/evaluate/complete')
33+
} else {
34+
router.push('/evaluate/unlocked')
35+
}
3236
}
33-
}, [isSessionComplete, router])
37+
}, [isSessionComplete, router, statsUnlocked])
3438

3539
// Determine if we're in a loading state (fetching or waiting for enrichment)
3640
const isWaitingForData =
@@ -51,7 +55,7 @@ export default function EvaluatePage() {
5155
No more politicians to evaluate for your current filters. Try different filters to
5256
continue contributing.
5357
</p>
54-
<Button href="/" size="large">
58+
<Button href="/" size="large" fullWidth>
5559
Start New Session
5660
</Button>
5761
</CenteredCard>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest'
2+
import { screen, fireEvent } from '@testing-library/react'
3+
import { render } from '@/test/test-utils'
4+
import UnlockedPage from './page'
5+
6+
const mockPush = vi.fn()
7+
vi.mock('next/navigation', () => ({
8+
useRouter: () => ({
9+
push: mockPush,
10+
}),
11+
}))
12+
13+
const mockResetSession = vi.fn()
14+
vi.mock('@/contexts/EvaluationSessionContext', async (importOriginal) => {
15+
const actual = await importOriginal<typeof import('@/contexts/EvaluationSessionContext')>()
16+
return {
17+
...actual,
18+
useEvaluationSession: () => ({
19+
resetSession: mockResetSession,
20+
}),
21+
}
22+
})
23+
24+
const mockUnlockStats = vi.fn()
25+
vi.mock('@/contexts/UserProgressContext', async (importOriginal) => {
26+
const actual = await importOriginal<typeof import('@/contexts/UserProgressContext')>()
27+
return {
28+
...actual,
29+
useUserProgress: () => ({
30+
unlockStats: mockUnlockStats,
31+
}),
32+
}
33+
})
34+
35+
describe('Unlocked Page', () => {
36+
beforeEach(() => {
37+
vi.clearAllMocks()
38+
})
39+
40+
it('shows stats unlocked message', () => {
41+
render(<UnlockedPage />)
42+
43+
expect(screen.getByText('Stats Unlocked!')).toBeInTheDocument()
44+
expect(
45+
screen.getByText(/completed your first session and unlocked the community stats page/),
46+
).toBeInTheDocument()
47+
})
48+
49+
it('calls unlockStats on mount', () => {
50+
render(<UnlockedPage />)
51+
52+
expect(mockUnlockStats).toHaveBeenCalled()
53+
})
54+
55+
it('shows View Stats as primary action', () => {
56+
render(<UnlockedPage />)
57+
58+
expect(screen.getByRole('button', { name: 'View Stats' })).toBeInTheDocument()
59+
})
60+
61+
it('shows Start Another Round as secondary action', () => {
62+
render(<UnlockedPage />)
63+
64+
expect(screen.getByRole('button', { name: 'Start Another Round' })).toBeInTheDocument()
65+
})
66+
67+
it('navigates to stats page when clicking View Stats', () => {
68+
render(<UnlockedPage />)
69+
70+
fireEvent.click(screen.getByRole('button', { name: 'View Stats' }))
71+
72+
expect(mockResetSession).toHaveBeenCalled()
73+
expect(mockPush).toHaveBeenCalledWith('/stats')
74+
})
75+
76+
it('navigates to evaluate page when clicking Start Another Round', () => {
77+
render(<UnlockedPage />)
78+
79+
fireEvent.click(screen.getByRole('button', { name: 'Start Another Round' }))
80+
81+
expect(mockResetSession).toHaveBeenCalled()
82+
expect(mockPush).toHaveBeenCalledWith('/evaluate')
83+
})
84+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client'
2+
3+
import { useEffect } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { useEvaluationSession } from '@/contexts/EvaluationSessionContext'
6+
import { useUserProgress } from '@/contexts/UserProgressContext'
7+
import { CenteredCard } from '@/components/ui/CenteredCard'
8+
import { Button } from '@/components/ui/Button'
9+
10+
export default function UnlockedPage() {
11+
const router = useRouter()
12+
const { resetSession } = useEvaluationSession()
13+
const { unlockStats } = useUserProgress()
14+
15+
useEffect(() => {
16+
unlockStats()
17+
}, [unlockStats])
18+
19+
const handleViewStats = () => {
20+
resetSession()
21+
router.push('/stats')
22+
}
23+
24+
const handleStartAnother = () => {
25+
resetSession()
26+
router.push('/evaluate')
27+
}
28+
29+
return (
30+
<CenteredCard emoji="🔓" title="Stats Unlocked!">
31+
<p className="mb-8">
32+
Great work! You&apos;ve completed your first session and unlocked the community stats page.
33+
</p>
34+
<div className="flex flex-col gap-4">
35+
<Button onClick={handleViewStats} size="large" fullWidth>
36+
View Stats
37+
</Button>
38+
<Button onClick={handleStartAnother} variant="secondary" size="large" fullWidth>
39+
Start Another Round
40+
</Button>
41+
</div>
42+
</CenteredCard>
43+
)
44+
}

poliloom-gui/src/app/layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { auth } from '@/auth'
55
import { SessionProvider } from '@/components/SessionProvider'
66
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext'
77
import { EvaluationSessionProvider } from '@/contexts/EvaluationSessionContext'
8-
import { TutorialProvider } from '@/contexts/TutorialContext'
8+
import { UserProgressProvider } from '@/contexts/UserProgressContext'
99
import { FetchInterceptor } from '@/components/FetchInterceptor'
1010
import { MobileGuard } from '@/components/layout/MobileGuard'
1111
import { Header } from '@/components/layout/Header'
@@ -41,10 +41,10 @@ export default async function RootLayout({
4141
<FetchInterceptor />
4242
<UserPreferencesProvider>
4343
<EvaluationSessionProvider>
44-
<TutorialProvider>
44+
<UserProgressProvider>
4545
<Header />
4646
<MobileGuard>{children}</MobileGuard>
47-
</TutorialProvider>
47+
</UserProgressProvider>
4848
</EvaluationSessionProvider>
4949
</UserPreferencesProvider>
5050
</SessionProvider>

poliloom-gui/src/app/page.test.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ vi.mock('next-auth/react', () => ({
3737
signIn: (...args: unknown[]) => mockSignIn(...args),
3838
}))
3939

40-
const mockUseTutorial = vi.fn()
41-
vi.mock('@/contexts/TutorialContext', async (importOriginal) => {
42-
const actual = await importOriginal<typeof import('@/contexts/TutorialContext')>()
40+
const mockUseUserProgress = vi.fn()
41+
vi.mock('@/contexts/UserProgressContext', async (importOriginal) => {
42+
const actual = await importOriginal<typeof import('@/contexts/UserProgressContext')>()
4343
return {
4444
...actual,
45-
useTutorial: () => mockUseTutorial(),
45+
useUserProgress: () => mockUseUserProgress(),
4646
}
4747
})
4848

@@ -60,12 +60,13 @@ describe('Home Page (Filter Selection)', () => {
6060
vi.clearAllMocks()
6161

6262
// Default tutorial state - not completed
63-
mockUseTutorial.mockReturnValue({
63+
mockUseUserProgress.mockReturnValue({
6464
hasCompletedBasicTutorial: false,
6565
hasCompletedAdvancedTutorial: false,
66+
statsUnlocked: false,
6667
completeBasicTutorial: vi.fn(),
6768
completeAdvancedTutorial: vi.fn(),
68-
resetTutorial: vi.fn(),
69+
unlockStats: vi.fn(),
6970
})
7071

7172
// Default user preferences - basic mode
@@ -162,12 +163,13 @@ describe('Home Page (Filter Selection)', () => {
162163
status: 'loading',
163164
})
164165

165-
mockUseTutorial.mockReturnValue({
166+
mockUseUserProgress.mockReturnValue({
166167
hasCompletedBasicTutorial: true,
167168
hasCompletedAdvancedTutorial: false,
169+
statsUnlocked: false,
168170
completeBasicTutorial: vi.fn(),
169171
completeAdvancedTutorial: vi.fn(),
170-
resetTutorial: vi.fn(),
172+
unlockStats: vi.fn(),
171173
})
172174

173175
// Basic mode (default from beforeEach)
@@ -186,12 +188,13 @@ describe('Home Page (Filter Selection)', () => {
186188
status: 'loading',
187189
})
188190

189-
mockUseTutorial.mockReturnValue({
191+
mockUseUserProgress.mockReturnValue({
190192
hasCompletedBasicTutorial: true,
191193
hasCompletedAdvancedTutorial: false,
194+
statsUnlocked: false,
192195
completeBasicTutorial: vi.fn(),
193196
completeAdvancedTutorial: vi.fn(),
194-
resetTutorial: vi.fn(),
197+
unlockStats: vi.fn(),
195198
})
196199

197200
mockUseUserPreferences.mockReturnValue({
@@ -221,12 +224,13 @@ describe('Home Page (Filter Selection)', () => {
221224
status: 'loading',
222225
})
223226

224-
mockUseTutorial.mockReturnValue({
227+
mockUseUserProgress.mockReturnValue({
225228
hasCompletedBasicTutorial: true,
226229
hasCompletedAdvancedTutorial: true,
230+
statsUnlocked: true,
227231
completeBasicTutorial: vi.fn(),
228232
completeAdvancedTutorial: vi.fn(),
229-
resetTutorial: vi.fn(),
233+
unlockStats: vi.fn(),
230234
})
231235

232236
mockUseUserPreferences.mockReturnValue({

poliloom-gui/src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/Button'
55
import { Toggle } from '@/components/ui/Toggle'
66
import { MultiSelect, MultiSelectOption } from '@/components/entity/MultiSelect'
77
import { useUserPreferences } from '@/contexts/UserPreferencesContext'
8-
import { useTutorial } from '@/contexts/TutorialContext'
8+
import { useUserProgress } from '@/contexts/UserProgressContext'
99
import { useMemo } from 'react'
1010
import { PreferenceType, WikidataEntity } from '@/types'
1111

@@ -20,7 +20,7 @@ export default function Home() {
2020
isAdvancedMode,
2121
setAdvancedMode,
2222
} = useUserPreferences()
23-
const { hasCompletedBasicTutorial, hasCompletedAdvancedTutorial } = useTutorial()
23+
const { hasCompletedBasicTutorial, hasCompletedAdvancedTutorial } = useUserProgress()
2424

2525
// Determine where to route the user based on tutorial completion and advanced mode
2626
const { ctaHref, ctaText } = useMemo(() => {

0 commit comments

Comments
 (0)