diff --git a/README.md b/README.md index ed9280f..b0f55a4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To Be Played is split into three repositories: - **Dashboard** with an edit mode ("Customize"): the user orders, hides and resizes the tiles (small = half width and less content, large = double width and more), and can add the stat groups of "Your stats" as tiles. Stored - in Auth, so it follows the user to the phone; "Reset" goes back to the default. Tiles: most played games, games in progress, "Pile of Shame" with backlog hours, its trend of the last 90 + in Auth, so it follows the user to the phone; "Reset" goes back to the default. Tiles: most played games and games in progress (one call, `GET /api/library/dashboard-games`), "Pile of Shame" with backlog hours, its trend of the last 90 days ("this month: +N added, -N off the pile") and a random suggestion, Steam avatar and a banner from the most played game of the last two weeks, and an achievement card: unlocked total, perfect games, average completion and the 5 games closest to 100 %. The card counts PlayStation trophies too @@ -118,7 +118,7 @@ flowchart LR a hidden trophy shows "Hidden trophy" until it is earned; a set whose list is not stored yet says it loads with the next sync. The sync banner and inbox text say "achievements or trophies". Library tiles show the best set of a game. The sync details list games whose last achievement this sync unlocked, with a button to set them to 100 %. -- **Game detail**: status, priority, 1–5 star rating, notes, achievements, time to beat (story / extras / 100 %), +- **Game detail**: the page shows the stored game at once (`?refresh=false`); when Core says `achievementsStale` a second call refreshes the achievements from Steam and replaces the data. Status, priority, 1–5 star rating, notes, achievements, time to beat (story / extras / 100 %), a link to the game's speedrun.com page when it has verified runs, a hint when Steam shows recent playtime for a game still in the backlog, and one to set the status to 100 % when every achievement is unlocked. @@ -295,7 +295,7 @@ flowchart LR ```mermaid flowchart LR - DetailData["Core GET /api/library/games/{appid}: platforms[] + totalPlaytimeMinutes"] --> Platforms["PlatformsCard: row per platform, total"] + DetailData["Core GET /api/library/games/{appid}?refresh=false, then without it when achievementsStale: platforms[] + totalPlaytimeMinutes"] --> Platforms["PlatformsCard: row per platform, total"] DetailData --> Hero["GameHeroCard: total playtime"] Platforms -->|"row of another edition"| Other["/library/{appid} of that edition"] ``` diff --git a/src/components/GamePointsLine.jsx b/src/components/GamePointsLine.jsx index fcd5a30..46c620e 100644 --- a/src/components/GamePointsLine.jsx +++ b/src/components/GamePointsLine.jsx @@ -12,9 +12,10 @@ const waitingKey = (kind, reason) => (kind === 'FINISHED_TOPUP' ? WAITING_TOPUP. /** * What the game earned: one line per paid row and one per kind that is missing, with the reason from Core. Loading it - * settles the points; a status change after the first load also shows the points toast. + * settles the points; a status change after the first load also shows the points toast. `refreshes` loads it again + * after the Steam refresh of the game page, which may have stored an unlock that settles points. */ -export default function GamePointsLine({game}) { +export default function GamePointsLine({game, refreshes = 0}) { const {t, i18n} = useTranslation(); const check = usePointsCheck(); const [points, setPoints] = useState(null); @@ -30,7 +31,7 @@ export default function GamePointsLine({game}) { return () => { stale = true; }; - }, [check, game.appid, game.status]); + }, [check, game.appid, game.status, refreshes]); if (!points) return null; const earlySinceText = points.earlySince && formatDay(points.earlySince, i18n.language, {dateStyle: 'medium'}); diff --git a/src/hooks/useDashboardData.js b/src/hooks/useDashboardData.js index bd93584..7882c31 100644 --- a/src/hooks/useDashboardData.js +++ b/src/hooks/useDashboardData.js @@ -3,10 +3,8 @@ import {useCurrentUserContext} from '../context/CurrentUserContext.jsx'; import {request} from '../utils/api.js'; import {PLAYING_GAMES, TOP_GAMES, UP_NEXT_GAMES} from '../utils/tiles.js'; -// The games endpoint accepts only the page sizes 20, 40 and 80. -const TOP_GAMES_URL = '/api/library/games?page=0&size=20&sort=playtimeMinutes%2Cdesc'; +const DASHBOARD_GAMES_URL = `/api/library/dashboard-games?top=${TOP_GAMES.L}&playing=${PLAYING_GAMES.L}`; const UP_NEXT_URL = `/api/library/backlog?limit=${UP_NEXT_GAMES.L}&released=true`; -const PLAYING_URL = '/api/library/games?page=0&size=20&sort=playtimeMinutes%2Cdesc&status=PLAYING'; // A card whose request fails or has no JSON body stays empty. async function loadInto(path, set, pick = (data) => data) { @@ -30,8 +28,10 @@ export default function useDashboardData() { const [error, setError] = useState(null); const fetchSteamCount = useCallback(() => loadInto('/api/library/games-count', setSteamData), []); - const fetchTopGames = useCallback(() => loadInto(TOP_GAMES_URL, setTopGames, (page) => (page.content ?? []).slice(0, TOP_GAMES.L)), []); - const fetchPlaying = useCallback(() => loadInto(PLAYING_URL, setPlaying, (page) => (page.content ?? []).slice(0, PLAYING_GAMES.L)), []); + const fetchGames = useCallback(() => loadInto(DASHBOARD_GAMES_URL, (data) => { + setTopGames(data.top ?? []); + setPlaying(data.playing ?? []); + }), []); const fetchUpNext = useCallback(() => loadInto(UP_NEXT_URL, setUpNext, (data) => data.games ?? []), []); const fetchActivity = useCallback(() => loadInto('/api/library/activity', setActivity), []); const fetchAchievements = useCallback(() => loadInto('/api/library/achievements/summary', setAchievements), []); @@ -50,8 +50,7 @@ export default function useDashboardData() { const reloadLibrary = () => { fetchSteamCount(); - fetchTopGames(); - fetchPlaying(); + fetchGames(); fetchUpNext(); fetchSummary(); fetchActivity(); @@ -65,7 +64,7 @@ export default function useDashboardData() { const withHistory = fetchSteamCount().then(async (counts) => { if (counts == null || counts.canSync) await Promise.all([fetchActivity(), fetchAchievements()]); }); - const [{ok, status}] = await Promise.all([reloadUser(), withHistory, fetchTopGames(), fetchPlaying(), + const [{ok, status}] = await Promise.all([reloadUser(), withHistory, fetchGames(), fetchUpNext(), fetchSummary()]); if (!ok) { if (status === 0) setError('INFO.ERROR.NETWORK_FAILURE'); @@ -75,7 +74,7 @@ export default function useDashboardData() { }; fetchUserData(); - }, [reloadUser, fetchSteamCount, fetchTopGames, fetchPlaying, fetchUpNext, fetchSummary, fetchActivity, fetchAchievements]); + }, [reloadUser, fetchSteamCount, fetchGames, fetchUpNext, fetchSummary, fetchActivity, fetchAchievements]); return { steamData, topGames, playing, upNext, summary, summaryFailed, activity, achievements, isLoading, error, diff --git a/src/hooks/useGame.js b/src/hooks/useGame.js index 9c039fa..3c810fa 100644 --- a/src/hooks/useGame.js +++ b/src/hooks/useGame.js @@ -2,11 +2,17 @@ import {useCallback, useEffect, useState} from 'react'; import {request} from '../utils/api.js'; import useAbortPrevious from './useAbortPrevious.js'; +// What the Steam refresh changes. The rest of the refreshed answer was read before a save made during the wait. +const ACHIEVEMENT_FIELDS = ['hasAchievements', 'statsVisible', 'achievementsTotal', 'achievementsUnlocked', + 'achievementsRemaining', 'completionPercent', 'achievements', 'achievementSets', 'platforms', 'achievementsStale']; + export default function useGame(appid) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [reloads, setReloads] = useState(0); + // Counts the finished Steam refreshes, so a reader of the stored achievements can look again. + const [refreshes, setRefreshes] = useState(0); // Another game starts empty, already in this render. A reload of the same game keeps the old data, so the edit // cards keep what is typed. @@ -21,19 +27,33 @@ export default function useGame(appid) { const signal = nextSignal(); setIsLoading(true); setError(null); + const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games/${appid}`; (async () => { - const {ok, status, data: json} = await request( - `${import.meta.env.VITE_CORE_API_URL}/api/library/games/${appid}`, {signal}); + const {ok, status, data: json} = await request(`${url}?refresh=false`, {signal}); if (signal.aborted) return; setIsLoading(false); if (status === 401) return; if (status === 404) setError('GAME_DETAIL.NOT_FOUND'); else if (!ok || !json) setError(status === 0 ? 'INFO.ERROR.NETWORK_FAILURE' : 'GAME_DETAIL.ERROR_LOAD'); else setData(json); + if (!ok || !json?.achievementsStale) return; + // A failed refresh keeps the stored data, no error. + const refreshed = await request(url, {signal}); + if (signal.aborted) return; + if (refreshed.ok && refreshed.data) { + const fresh = Object.fromEntries(ACHIEVEMENT_FIELDS.map((field) => [field, refreshed.data[field]])); + // The finish rule may have set the status during the refresh; keep a status the user changed since. + setData((d) => { + if (!d) return refreshed.data; + const unchanged = d.status === json.status; + return {...d, ...fresh, ...(unchanged && {status: refreshed.data.status, finishedHint: refreshed.data.finishedHint})}; + }); + } + setRefreshes((n) => n + 1); })(); }, [nextSignal, appid, reloads]); const reload = useCallback(() => setReloads((n) => n + 1), []); - return {data, setData, isLoading, error, reload}; + return {data, setData, isLoading, error, reload, refreshes}; } diff --git a/src/pages/Dashboard.test.jsx b/src/pages/Dashboard.test.jsx index a880696..ed6caa0 100644 --- a/src/pages/Dashboard.test.jsx +++ b/src/pages/Dashboard.test.jsx @@ -15,7 +15,6 @@ vi.mock('../hooks/useForceSync.js', () => ({default: () => ({trigger: vi.fn(), b const avatar = 'https://avatars.steamstatic.com/abc_full.jpg'; const games = Array.from({length: 12}, (_, i) => ({appid: 100 + i, name: `Game ${i}`, playtimeMinutes: 1000 - i})); const empty = {content: [], total: 0, byStatus: {}}; -const mostPlayed = (url) => url.includes('sort=playtimeMinutes') && !url.includes('status='); const quietRoutes = { 'GET /api/library/summary': json(200, empty), 'GET /api/hints': json(200, {results: []}), @@ -27,7 +26,7 @@ const quietRoutes = { const dashboardRoutes = (settings) => ({ ...quietRoutes, 'GET /api/users/me': json(200, {email: 'a@b.de', displayName: 'Lobo', steamId: '76561198000000000', settings}), - 'GET /api/library/games': (url) => json(200, mostPlayed(url) ? {content: games, total: 12} : empty), + 'GET /api/library/dashboard-games': json(200, {top: games, playing: []}), 'GET /api/library/stats': json(200, {trend: null, habits: {moonTrips: 1, longestDay: null, underOneHourGames: 0, underOneHourShare: null}}), 'GET /api/library/steam-profile': json(200, {personaName: 'Lobo', avatarUrl: avatar}), 'GET /api/library/activity': json(200, {totalMinutes: 90, days: [{day: '2026-09-23', minutes: 0}, {day: '2026-09-24', minutes: 90}], recentGames: [ @@ -91,13 +90,23 @@ describe('Dashboard', () => { fetchRoutes({...routes, 'GET /api/users/me': () => meAnswer}); const container = renderDashboard().container; - await waitFor(() => expect(asked('/api/library/games')).toBe(true)); + await waitFor(() => expect(asked('/api/library/dashboard-games')).toBe(true)); expect(asked('/api/library/summary')).toBe(true); answerMe(me); await waitFor(() => expect(container.querySelector(`img[src="${avatar}"]`)).not.toBeNull()); }); + it('asks both game tiles in one call', async () => { + const container = mockDashboard(); + await waitFor(() => expect(container.querySelector(`img[src="${avatar}"]`)).not.toBeNull()); + + const gameCalls = fetch.mock.calls.map(([url]) => url).filter((url) => url.includes('dashboard-games')); + expect(gameCalls).toHaveLength(1); + expect(gameCalls[0]).toContain('top=10&playing=8'); + expect(asked('/api/library/games?')).toBe(false); + }); + it('shows today\'s tiles without a saved layout', async () => { const container = mockDashboard(); @@ -231,9 +240,7 @@ describe('Dashboard', () => { 'GET /api/users/me': json(200, {email: 'a@b.de', displayName: 'Lobo'}), 'GET /api/library/games-count': json(200, {count: 1, hasSteamId: true}), 'GET /api/library/backlog': json(200, {games: []}), - 'GET /api/library/games': (url) => (mostPlayed(url) - ? {ok: true, status: 200, json: async () => { throw new SyntaxError('Unexpected token <'); }} - : json(200, empty)), + 'GET /api/library/dashboard-games': {ok: true, status: 200, json: async () => { throw new SyntaxError('Unexpected token <'); }}, }); renderDashboard(); diff --git a/src/pages/Dashboard.visitSync.test.jsx b/src/pages/Dashboard.visitSync.test.jsx index 5f0add2..568af4c 100644 --- a/src/pages/Dashboard.visitSync.test.jsx +++ b/src/pages/Dashboard.visitSync.test.jsx @@ -32,7 +32,7 @@ describe('Dashboard sync on a visit', () => { 'GET /api/news': json(200, {results: []}), 'GET /api/library/sync-changes': () => syncChanges, 'GET /api/users/me': json(200, {email: 'a@b.de', displayName: 'Lobo', steamId: '76561198000000000'}), - 'GET /api/library/games': json(200, empty), + 'GET /api/library/dashboard-games': json(200, {top: [], playing: []}), 'GET /api/library/stats': json(200, {trend: null}), 'GET /api/library/steam-profile': json(200, {personaName: 'Lobo', avatarUrl: null}), 'GET /api/library/activity': json(200, {totalMinutes: 0, days: [], recentGames: []}), @@ -49,7 +49,7 @@ describe('Dashboard sync on a visit', () => { syncStatus = {lastSyncStatus: 'RUNNING', lastSyncedAt: '2026-10-03T10:00:00Z'}; await wait(3000); expect(screen.getByText('LIBRARY.SYNCING')).toBeTruthy(); - const gamesBefore = calls('/api/library/games'); + const gamesBefore = calls('/api/library/dashboard-games'); const changesBefore = calls('/api/library/sync-changes'); syncStatus = {lastSyncStatus: 'OK', lastSyncedAt: '2026-10-04T08:00:00Z'}; @@ -60,6 +60,6 @@ describe('Dashboard sync on a visit', () => { expect(screen.queryByText('LIBRARY.SYNCING')).toBeNull(); expect(await screen.findByText('SYNC_CHANGES.ADDED:1')).toBeTruthy(); expect(calls('/api/library/sync-changes')).toBeGreaterThan(changesBefore); - expect(calls('/api/library/games')).toBeGreaterThan(gamesBefore); + expect(calls('/api/library/dashboard-games')).toBeGreaterThan(gamesBefore); }); }); diff --git a/src/pages/GameDetail.jsx b/src/pages/GameDetail.jsx index 2d2c70f..da7f5f0 100644 --- a/src/pages/GameDetail.jsx +++ b/src/pages/GameDetail.jsx @@ -33,7 +33,7 @@ export default function GameDetail() { const {appid} = useParams(); const navigate = useNavigate(); - const {data, setData, isLoading, error, reload} = useGame(appid); + const {data, setData, isLoading, error, reload, refreshes} = useGame(appid); const [correcting, setCorrecting] = useState(false); const [merging, setMerging] = useState(false); const [linking, setLinking] = useState(false); @@ -96,7 +96,7 @@ export default function GameDetail() {

)} - + {/* The finished question also answers "are you playing it?". */} {!(data.finishedHint && ['BACKLOG', 'PLAYING'].includes(data.status)) && ( diff --git a/src/pages/GameDetail.points.test.jsx b/src/pages/GameDetail.points.test.jsx new file mode 100644 index 0000000..5d92ada --- /dev/null +++ b/src/pages/GameDetail.points.test.jsx @@ -0,0 +1,43 @@ +// @vitest-environment jsdom +import {afterEach, beforeEach, expect, it, vi} from 'vitest'; +import {cleanup, screen, waitFor} from '@testing-library/react'; +import GameDetail from './GameDetail.jsx'; +import {fetchRoutes, json} from '../test/fetchRoutes.js'; +import {renderWithProviders} from '../test/renderWithProviders.jsx'; + +vi.mock('react-i18next', () => import('../test/i18n-mock.js')); +vi.mock('sonner', () => ({toast: Object.assign(vi.fn(), {success: vi.fn(), error: vi.fn()})})); + +const game = { + appid: 220, name: 'Half-Life 2', playtimeMinutes: 100, playtime2Weeks: 0, source: 'STEAM', status: 'FINISHED', + rating: null, notes: null, priority: null, hasAchievements: true, statsVisible: true, achievements: [], +}; + +beforeEach(() => localStorage.setItem('tbp_session', '1')); +afterEach(() => { + cleanup(); + localStorage.clear(); +}); + +it('checksThePointsAgainAfterTheAchievementRefresh', async () => { + let finishRefresh; + const answers = [json(200, {...game, achievementsStale: true, achievementsUnlocked: 3}), + new Promise((resolve) => { finishRefresh = resolve; })]; + fetchRoutes({ + 'GET /api/users/me': json(200, {settings: {}}), + 'GET /api/stats/owners': json(200, {listed: [], listedTotal: 0, privateCount: 0, includesYou: false, page: 0, totalPages: 1}), + 'GET /api/rewards/wallet': json(200, {balance: 0, catalog: [], owned: [], equipped: {}, history: []}), + 'GET /api/rewards/games/220': json(200, {balance: 0, missing: {FINISHED: 'NO_PROOF'}, paid: []}), + 'GET /api/library/games/220': () => answers.shift(), + }); + const pointsCalls = () => fetch.mock.calls.filter(([url]) => url.includes('/api/rewards/games/220')).length; + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + await screen.findByText('Half-Life 2'); + await waitFor(() => expect(pointsCalls()).toBe(1)); + + // The refresh stored the ending achievement: Core would settle the finish now (the old single call did). + finishRefresh(json(200, {...game, achievementsStale: false, achievementsUnlocked: 5})); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(pointsCalls()).toBe(2); +}); diff --git a/src/pages/GameDetail.reload.test.jsx b/src/pages/GameDetail.reload.test.jsx index 84bb696..aebd9b4 100644 --- a/src/pages/GameDetail.reload.test.jsx +++ b/src/pages/GameDetail.reload.test.jsx @@ -17,15 +17,17 @@ const game = { rating: null, notes: null, priority: null, hasAchievements: false, statsVisible: false, achievements: [], }; -const gameLoads = () => fetch.mock.calls.filter(([url]) => url.endsWith('/api/library/games/220')).length; +const gameUrls = () => fetch.mock.calls.map(([url]) => url).filter((url) => new URL(url, 'http://x').pathname.endsWith('/api/library/games/220')); +const gameLoads = () => gameUrls().length; +const gameRoutes = (gameAnswer) => fetchRoutes({ + 'GET /api/users/me': json(200, {role: 'AUDITOR', activeRole: 'AUDITOR'}), + 'GET /api/stats/owners': json(200, {listed: [], listedTotal: 0, privateCount: 0, includesYou: false, page: 0, totalPages: 1}), + 'GET /api/library/games/220': gameAnswer, +}); beforeEach(() => { localStorage.setItem('tbp_session', '1'); - fetchRoutes({ - 'GET /api/users/me': json(200, {role: 'AUDITOR', activeRole: 'AUDITOR'}), - 'GET /api/stats/owners': json(200, {listed: [], listedTotal: 0, privateCount: 0, includesYou: false, page: 0, totalPages: 1}), - 'GET /api/library/games/220': json(200, game), - }); + gameRoutes(json(200, game)); }); afterEach(() => { @@ -42,3 +44,74 @@ it('keeps unsaved notes while the game loads again after a correction', async () await waitFor(() => expect(gameLoads()).toBe(2)); await waitFor(() => expect(screen.getByLabelText('BACKLOG.NOTES_LABEL').value).toBe('my draft')); }); + +it('shows the stored game, then the refreshed one when the achievements are stale', async () => { + const answers = [json(200, {...game, achievementsStale: true}), + json(200, {...game, hasAchievements: true, statsVisible: true, achievementsTotal: 1, achievementsUnlocked: 1, + achievements: [{apiName: 'A', displayName: 'Refreshed achievement', unlocked: true, kind: 'STEAM'}], + achievementSets: [{id: 'steam', kind: 'STEAM', total: 1, unlocked: 1, perfect: true, visible: true, listed: true}], + achievementsStale: false})]; + gameRoutes(() => answers.shift()); + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + expect(await screen.findByText('Refreshed achievement')).toBeTruthy(); + expect(gameUrls()).toHaveLength(2); + expect(gameUrls()[0]).toContain('refresh=false'); + expect(gameUrls()[1]).not.toContain('refresh'); +}); + +it('keeps the stored game when the refreshing call fails', async () => { + const answers = [json(200, {...game, achievementsStale: true}), json(500, {})]; + gameRoutes(() => answers.shift()); + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + expect(await screen.findByText('Half-Life 2')).toBeTruthy(); + await waitFor(() => expect(gameUrls()).toHaveLength(2)); + expect(screen.getByText('Half-Life 2')).toBeTruthy(); + expect(screen.queryByText('GAME_DETAIL.ERROR_LOAD')).toBeNull(); +}); + +it('asks once when the achievements are fresh', async () => { + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + expect(await screen.findByText('Half-Life 2')).toBeTruthy(); + expect(gameUrls()).toHaveLength(1); + expect(gameUrls()[0]).toContain('refresh=false'); +}); + +// The refreshing call answers the status read before a save made in its wait; only its achievements are taken. +it('keepsAStatusSavedWhileTheAchievementsRefresh', async () => { + let finishRefresh; + const answers = [json(200, {...game, achievementsStale: true}), + new Promise((resolve) => { finishRefresh = resolve; })]; + fetchRoutes({ + 'GET /api/users/me': json(200, {role: 'AUDITOR', activeRole: 'AUDITOR'}), + 'GET /api/stats/owners': json(200, {listed: [], listedTotal: 0, privateCount: 0, includesYou: false, page: 0, totalPages: 1}), + 'GET /api/library/games/220': () => answers.shift(), + 'PUT /api/library/games/220/status': (url, init) => json(200, {...game, ...JSON.parse(init.body)}), + }); + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_PLAYING'})); + await waitFor(() => expect(screen.queryByRole('button', {name: 'GAME_DETAIL.SET_PLAYING'})).toBeNull()); + + // Core read the game (BACKLOG) before the save, then waited for Steam. + finishRefresh(json(200, {...game, achievementsStale: false})); + await waitFor(() => expect(gameLoads()).toBe(2)); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(screen.queryByRole('button', {name: 'GAME_DETAIL.SET_PLAYING'})).toBeNull(); +}); + +// The finish rule (ending achievement unlocked) sets the status during the refresh. +it('showsTheStatusTheRefreshSetWhenTheUserChangedNothing', async () => { + let finishRefresh; + const answers = [json(200, {...game, achievementsStale: true}), + new Promise((resolve) => { finishRefresh = resolve; })]; + gameRoutes(() => answers.shift()); + renderWithProviders(, {path: '/library/:appid', entries: ['/library/220']}); + + expect(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_PLAYING'})).toBeTruthy(); + finishRefresh(json(200, {...game, status: 'FINISHED', achievementsStale: false})); + await waitFor(() => expect(gameLoads()).toBe(2)); + await waitFor(() => expect(screen.queryByRole('button', {name: 'GAME_DETAIL.SET_PLAYING'})).toBeNull()); +}); diff --git a/src/pages/languageSwitch.test.jsx b/src/pages/languageSwitch.test.jsx index 23d23a9..1c88b4f 100644 --- a/src/pages/languageSwitch.test.jsx +++ b/src/pages/languageSwitch.test.jsx @@ -18,7 +18,7 @@ vi.mock('react-i18next', () => import('../test/i18n-mock.js')); // The last column: what the page loads besides GET /api/users/me of the provider. const pages = [ ['Dashboard', '/', '/', Dashboard, 'INFO.ERROR.LOAD_PROFILE', - ['/api/library/games', '/api/library/games-count', '/api/library/backlog', '/api/library/summary', + ['/api/library/dashboard-games', '/api/library/games-count', '/api/library/backlog', '/api/library/summary', '/api/library/activity', '/api/library/achievements/summary']], ['GameDetail', '/games/:appid', '/games/220', GameDetail, 'GAME_DETAIL.ERROR_LOAD', ['/api/library/games/220']], ['GameLibrary', '/library', '/library', GameLibrary, 'LIBRARY.ERROR_LOAD',