Skip to content

Commit 72056b5

Browse files
Merge pull request #105 from CSSLab/copilot/fix-88
Allow users to view game history on other users' profiles
2 parents c011f35 + 39922d3 commit 72056b5

4 files changed

Lines changed: 365 additions & 30 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import React from 'react'
2+
import { render, screen, waitFor, act } from '@testing-library/react'
3+
import userEvent from '@testing-library/user-event'
4+
import { GameList } from 'src/components/Profile/GameList'
5+
import { AuthContext } from 'src/contexts'
6+
import * as api from 'src/api'
7+
8+
// Mock the API functions
9+
jest.mock('src/api', () => ({
10+
getAnalysisGameList: jest.fn(),
11+
getLichessGames: jest.fn(),
12+
}))
13+
14+
// Mock custom analysis utility
15+
jest.mock('src/lib/customAnalysis', () => ({
16+
getCustomAnalysesAsWebGames: jest.fn(() => []),
17+
}))
18+
19+
// Mock framer-motion to avoid animation issues in tests
20+
jest.mock('framer-motion', () => ({
21+
motion: {
22+
div: ({
23+
children,
24+
layoutId,
25+
...props
26+
}: React.PropsWithChildren<{ layoutId?: string }>) => (
27+
<div {...props}>{children}</div>
28+
),
29+
},
30+
}))
31+
32+
const mockGetAnalysisGameList = api.getAnalysisGameList as jest.MockedFunction<
33+
typeof api.getAnalysisGameList
34+
>
35+
36+
const mockGetLichessGames = api.getLichessGames as jest.MockedFunction<
37+
typeof api.getLichessGames
38+
>
39+
40+
// Mock user context
41+
const mockUser = {
42+
clientId: 'client123',
43+
displayName: 'Test User',
44+
lichessId: 'testuser123',
45+
id: 'user123',
46+
}
47+
48+
const AuthWrapper = ({ children }: { children: React.ReactNode }) => (
49+
<AuthContext.Provider
50+
value={{
51+
user: mockUser,
52+
connectLichess: jest.fn(),
53+
logout: jest.fn(),
54+
}}
55+
>
56+
{children}
57+
</AuthContext.Provider>
58+
)
59+
60+
describe('GameList', () => {
61+
beforeEach(() => {
62+
jest.clearAllMocks()
63+
// Mock different responses based on game type
64+
mockGetAnalysisGameList.mockImplementation((gameType) => {
65+
if (gameType === 'hand') {
66+
return Promise.resolve({
67+
games: [
68+
{
69+
game_id: 'game1',
70+
maia_name: 'maia_kdd_1500',
71+
result: '1-0',
72+
player_color: 'white',
73+
},
74+
],
75+
total_games: 1,
76+
total_pages: 1,
77+
})
78+
} else if (gameType === 'brain') {
79+
return Promise.resolve({
80+
games: [],
81+
total_games: 0,
82+
total_pages: 0,
83+
})
84+
}
85+
// Default for 'play' and other types
86+
return Promise.resolve({
87+
games: [
88+
{
89+
game_id: 'game1',
90+
maia_name: 'maia_kdd_1500',
91+
result: '1-0',
92+
player_color: 'white',
93+
},
94+
],
95+
total_games: 1,
96+
total_pages: 1,
97+
})
98+
})
99+
})
100+
101+
it('renders with default props (all tabs shown for current user)', async () => {
102+
await act(async () => {
103+
render(
104+
<AuthWrapper>
105+
<GameList />
106+
</AuthWrapper>,
107+
)
108+
})
109+
110+
expect(screen.getByText('Your Games')).toBeInTheDocument()
111+
expect(screen.getByText('Play')).toBeInTheDocument()
112+
expect(screen.getByText('H&B')).toBeInTheDocument()
113+
expect(screen.getByText('Custom')).toBeInTheDocument()
114+
expect(screen.getByText('Lichess')).toBeInTheDocument()
115+
})
116+
117+
it('renders with limited tabs for other users', async () => {
118+
await act(async () => {
119+
render(
120+
<AuthWrapper>
121+
<GameList
122+
lichessId="otheruser"
123+
userName="OtherUser"
124+
showCustom={false}
125+
showLichess={false}
126+
/>
127+
</AuthWrapper>,
128+
)
129+
})
130+
131+
expect(screen.getByText("OtherUser's Games")).toBeInTheDocument()
132+
expect(screen.getByText('Play')).toBeInTheDocument()
133+
expect(screen.getByText('H&B')).toBeInTheDocument()
134+
expect(screen.queryByText('Custom')).not.toBeInTheDocument()
135+
expect(screen.queryByText('Lichess')).not.toBeInTheDocument()
136+
})
137+
138+
it('fetches games with lichessId when provided', async () => {
139+
await act(async () => {
140+
render(
141+
<AuthWrapper>
142+
<GameList
143+
lichessId="otheruser"
144+
userName="OtherUser"
145+
showCustom={false}
146+
showLichess={false}
147+
/>
148+
</AuthWrapper>,
149+
)
150+
})
151+
152+
await waitFor(() => {
153+
expect(mockGetAnalysisGameList).toHaveBeenCalledWith(
154+
'play',
155+
1,
156+
'otheruser',
157+
)
158+
})
159+
})
160+
161+
it('displays correct game labels for other users', async () => {
162+
await act(async () => {
163+
render(
164+
<AuthWrapper>
165+
<GameList
166+
lichessId="otheruser"
167+
userName="OtherUser"
168+
showCustom={false}
169+
showLichess={false}
170+
/>
171+
</AuthWrapper>,
172+
)
173+
})
174+
175+
await waitFor(() => {
176+
expect(screen.getByText('OtherUser vs. Maia 1500')).toBeInTheDocument()
177+
})
178+
})
179+
180+
it('displays correct game labels for current user', async () => {
181+
await act(async () => {
182+
render(
183+
<AuthWrapper>
184+
<GameList />
185+
</AuthWrapper>,
186+
)
187+
})
188+
189+
await waitFor(() => {
190+
expect(screen.getByText('You vs. Maia 1500')).toBeInTheDocument()
191+
})
192+
})
193+
194+
it('switches between H&B subsections', async () => {
195+
const user = userEvent.setup()
196+
197+
await act(async () => {
198+
render(
199+
<AuthWrapper>
200+
<GameList />
201+
</AuthWrapper>,
202+
)
203+
})
204+
205+
// Click on H&B tab
206+
await act(async () => {
207+
await user.click(screen.getByText('H&B'))
208+
})
209+
210+
// Wait for the hand games to load and check subsection labels
211+
await waitFor(() => {
212+
expect(screen.getByText('Hand (1)')).toBeInTheDocument()
213+
expect(screen.getByText('Brain (0)')).toBeInTheDocument()
214+
})
215+
216+
// Click on Brain subsection
217+
await act(async () => {
218+
await user.click(screen.getByText('Brain (0)'))
219+
})
220+
221+
// Verify API call for brain games
222+
await waitFor(() => {
223+
expect(mockGetAnalysisGameList).toHaveBeenCalledWith(
224+
'brain',
225+
1,
226+
undefined,
227+
)
228+
})
229+
})
230+
231+
it('adjusts grid columns based on available tabs', async () => {
232+
const { rerender } = render(
233+
<AuthWrapper>
234+
<GameList />
235+
</AuthWrapper>,
236+
)
237+
238+
// With all 4 tabs
239+
expect(document.querySelector('.grid-cols-4')).toBeInTheDocument()
240+
241+
// With only 2 tabs
242+
await act(async () => {
243+
rerender(
244+
<AuthWrapper>
245+
<GameList showCustom={false} showLichess={false} />
246+
</AuthWrapper>,
247+
)
248+
})
249+
250+
expect(document.querySelector('.grid-cols-2')).toBeInTheDocument()
251+
})
252+
})

src/api/analysis/analysis.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,22 @@ export const getAnalysisList = async (): Promise<
7676
return data
7777
}
7878

79-
export const getAnalysisGameList = async (type = 'play', page = 1) => {
80-
const res = await fetch(buildUrl(`analysis/user/list/${type}/${page}`))
79+
export const getAnalysisGameList = async (
80+
type = 'play',
81+
page = 1,
82+
lichessId?: string,
83+
) => {
84+
const url = buildUrl(`analysis/user/list/${type}/${page}`)
85+
const searchParams = new URLSearchParams()
86+
87+
if (lichessId) {
88+
searchParams.append('lichess_id', lichessId)
89+
}
90+
91+
const fullUrl = searchParams.toString()
92+
? `${url}?${searchParams.toString()}`
93+
: url
94+
const res = await fetch(fullUrl)
8195

8296
if (res.status === 401) {
8397
throw new Error('Unauthorized')

0 commit comments

Comments
 (0)