Skip to content

Commit 0e44ba6

Browse files
Implement favorites functionality for games with custom naming
Co-authored-by: kevinjosethomas <46242684+kevinjosethomas@users.noreply.github.com>
1 parent 91788bc commit 0e44ba6

7 files changed

Lines changed: 623 additions & 67 deletions

File tree

__tests__/components/GameList.test.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ jest.mock('src/lib/customAnalysis', () => ({
1616
getCustomAnalysesAsWebGames: jest.fn(() => []),
1717
}))
1818

19+
// Mock favorites utility
20+
jest.mock('src/lib/favorites', () => ({
21+
getFavoritesAsWebGames: jest.fn(() => []),
22+
addFavoriteGame: jest.fn(),
23+
removeFavoriteGame: jest.fn(),
24+
isFavoriteGame: jest.fn(() => false),
25+
}))
26+
27+
// Mock FavoriteModal component
28+
jest.mock('src/components/Common/FavoriteModal', () => ({
29+
FavoriteModal: () => null,
30+
}))
31+
1932
// Mock framer-motion to avoid animation issues in tests
2033
jest.mock('framer-motion', () => ({
2134
motion: {
@@ -136,6 +149,8 @@ describe('GameList', () => {
136149
})
137150

138151
it('fetches games with lichessId when provided', async () => {
152+
const user = userEvent.setup()
153+
139154
await act(async () => {
140155
render(
141156
<AuthWrapper>
@@ -149,6 +164,11 @@ describe('GameList', () => {
149164
)
150165
})
151166

167+
// Click on Play tab to trigger API call
168+
await act(async () => {
169+
await user.click(screen.getByText('Play'))
170+
})
171+
152172
await waitFor(() => {
153173
expect(mockGetAnalysisGameList).toHaveBeenCalledWith(
154174
'play',
@@ -159,6 +179,8 @@ describe('GameList', () => {
159179
})
160180

161181
it('displays correct game labels for other users', async () => {
182+
const user = userEvent.setup()
183+
162184
await act(async () => {
163185
render(
164186
<AuthWrapper>
@@ -172,12 +194,19 @@ describe('GameList', () => {
172194
)
173195
})
174196

197+
// Click on Play tab to see games
198+
await act(async () => {
199+
await user.click(screen.getByText('Play'))
200+
})
201+
175202
await waitFor(() => {
176203
expect(screen.getByText('OtherUser vs. Maia 1500')).toBeInTheDocument()
177204
})
178205
})
179206

180207
it('displays correct game labels for current user', async () => {
208+
const user = userEvent.setup()
209+
181210
await act(async () => {
182211
render(
183212
<AuthWrapper>
@@ -186,6 +215,11 @@ describe('GameList', () => {
186215
)
187216
})
188217

218+
// Click on Play tab to see games
219+
await act(async () => {
220+
await user.click(screen.getByText('Play'))
221+
})
222+
189223
await waitFor(() => {
190224
expect(screen.getByText('You vs. Maia 1500')).toBeInTheDocument()
191225
})
@@ -235,10 +269,10 @@ describe('GameList', () => {
235269
</AuthWrapper>,
236270
)
237271

238-
// With all 4 tabs
239-
expect(document.querySelector('.grid-cols-4')).toBeInTheDocument()
272+
// With all 5 tabs (favorites, play, hb, custom, lichess)
273+
expect(document.querySelector('.grid-cols-5')).toBeInTheDocument()
240274

241-
// With only 2 tabs
275+
// With only 3 tabs (favorites, play, hb)
242276
await act(async () => {
243277
rerender(
244278
<AuthWrapper>
@@ -247,6 +281,6 @@ describe('GameList', () => {
247281
)
248282
})
249283

250-
expect(document.querySelector('.grid-cols-2')).toBeInTheDocument()
284+
expect(document.querySelector('.grid-cols-3')).toBeInTheDocument()
251285
})
252286
})

__tests__/lib/favorites.test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {
2+
addFavoriteGame,
3+
removeFavoriteGame,
4+
updateFavoriteName,
5+
getFavoriteGames,
6+
isFavoriteGame,
7+
getFavoriteGame,
8+
getFavoritesAsWebGames,
9+
} from 'src/lib/favorites'
10+
import { AnalysisWebGame } from 'src/types'
11+
12+
// Mock localStorage
13+
const localStorageMock = (() => {
14+
let store: { [key: string]: string } = {}
15+
16+
return {
17+
getItem: (key: string) => store[key] || null,
18+
setItem: (key: string, value: string) => {
19+
store[key] = value.toString()
20+
},
21+
removeItem: (key: string) => {
22+
delete store[key]
23+
},
24+
clear: () => {
25+
store = {}
26+
},
27+
}
28+
})()
29+
30+
Object.defineProperty(window, 'localStorage', {
31+
value: localStorageMock,
32+
})
33+
34+
describe('favorites', () => {
35+
beforeEach(() => {
36+
localStorageMock.clear()
37+
})
38+
39+
const mockGame: AnalysisWebGame = {
40+
id: 'test-game-1',
41+
type: 'play',
42+
label: 'You vs. Maia 1600',
43+
result: '1-0',
44+
}
45+
46+
describe('addFavoriteGame', () => {
47+
it('should add a game to favorites with default name', () => {
48+
const favorite = addFavoriteGame(mockGame)
49+
50+
expect(favorite.id).toBe(mockGame.id)
51+
expect(favorite.customName).toBe(mockGame.label)
52+
expect(favorite.originalLabel).toBe(mockGame.label)
53+
expect(isFavoriteGame(mockGame.id)).toBe(true)
54+
})
55+
56+
it('should add a game to favorites with custom name', () => {
57+
const customName = 'My Best Game'
58+
const favorite = addFavoriteGame(mockGame, customName)
59+
60+
expect(favorite.customName).toBe(customName)
61+
expect(favorite.originalLabel).toBe(mockGame.label)
62+
})
63+
64+
it('should update existing favorite when added again', () => {
65+
addFavoriteGame(mockGame, 'First Name')
66+
addFavoriteGame(mockGame, 'Updated Name')
67+
68+
const favorites = getFavoriteGames()
69+
expect(favorites).toHaveLength(1)
70+
expect(favorites[0].customName).toBe('Updated Name')
71+
})
72+
})
73+
74+
describe('removeFavoriteGame', () => {
75+
it('should remove a game from favorites', () => {
76+
addFavoriteGame(mockGame)
77+
expect(isFavoriteGame(mockGame.id)).toBe(true)
78+
79+
removeFavoriteGame(mockGame.id)
80+
expect(isFavoriteGame(mockGame.id)).toBe(false)
81+
})
82+
})
83+
84+
describe('updateFavoriteName', () => {
85+
it('should update favorite name', () => {
86+
addFavoriteGame(mockGame, 'Original Name')
87+
updateFavoriteName(mockGame.id, 'New Name')
88+
89+
const favorite = getFavoriteGame(mockGame.id)
90+
expect(favorite?.customName).toBe('New Name')
91+
})
92+
93+
it('should do nothing if favorite does not exist', () => {
94+
const initialFavorites = getFavoriteGames()
95+
updateFavoriteName('non-existent', 'New Name')
96+
97+
expect(getFavoriteGames()).toEqual(initialFavorites)
98+
})
99+
})
100+
101+
describe('getFavoritesAsWebGames', () => {
102+
it('should convert favorites to web games', () => {
103+
const customName = 'Custom Game Name'
104+
addFavoriteGame(mockGame, customName)
105+
106+
const webGames = getFavoritesAsWebGames()
107+
expect(webGames).toHaveLength(1)
108+
expect(webGames[0].label).toBe(customName)
109+
expect(webGames[0].id).toBe(mockGame.id)
110+
})
111+
})
112+
113+
describe('storage limits', () => {
114+
it('should limit favorites to 100 entries', () => {
115+
// Add 101 favorites
116+
for (let i = 0; i < 101; i++) {
117+
const game: AnalysisWebGame = {
118+
id: `game-${i}`,
119+
type: 'play',
120+
label: `Game ${i}`,
121+
result: '1-0',
122+
}
123+
addFavoriteGame(game)
124+
}
125+
126+
const favorites = getFavoriteGames()
127+
expect(favorites).toHaveLength(100)
128+
// Latest should be at the top
129+
expect(favorites[0].id).toBe('game-100')
130+
})
131+
})
132+
})

0 commit comments

Comments
 (0)