// @vitest-environment jsdom import {afterEach, describe, expect, it, vi} from 'vitest'; import {cleanup, fireEvent, screen, waitFor, within} from '@testing-library/react'; import PublicProfilePage from './PublicProfilePage.jsx'; import {fetchRoutes, json} from '../test/fetchRoutes.js'; import {renderWithProviders} from '../test/renderWithProviders.jsx'; vi.mock('react-i18next', () => import('../test/i18n-mock.js')); const library = { totalGames: 233, totalPlaytimeMinutes: 60000, byStatus: {BACKLOG: 142, PLAYING: 6, FINISHED: 51, COMPLETED: 9, DROPPED: 25}, playing: [{appid: 220, name: 'Half-Life 2', playtimeMinutes: 300, status: 'PLAYING', rating: null}], finished: [{appid: 620, name: 'Portal 2', playtimeMinutes: 900, status: 'COMPLETED', rating: 5}], }; // `me` only for a test with a session: without one the page asks nothing about the viewer. function mockBackends({name = 'Lobo', profileStatus = 200, libraryStatus = 200, style, answer = library, me, equipped = {}, rewardsStatus = 200, extra = {}} = {}) { fetchRoutes({ [`GET /api/public/profiles/${encodeURIComponent(name)}`]: json(profileStatus, {displayName: 'Lobo', memberSince: '2026-03-02', ticket: 'the-ticket', style}), 'GET /api/public/profiles/library': json(libraryStatus, answer), 'GET /api/public/profiles/rewards': json(rewardsStatus, {equipped}), ...extra, ...(me && { 'GET /api/users/me': json(200, me), 'PUT /api/users/settings/profile-tiles': json(204, {}), 'GET /api/follows': json(200, {following: [], followers: []}), }), }); } const renderPage = (name = 'Lobo') => renderWithProviders(, {path: '/u/:name', entries: [`/u/${name}`]}); describe('PublicProfilePage', () => { afterEach(cleanup); it('shows the coming badge on a game that is not released yet, in both lists', async () => { mockBackends({answer: {...library, playing: [{...library.playing[0], status: 'PLAYING', coming: true, comingOn: '2026-12-04'}], finished: [{...library.finished[0], coming: false, comingOn: null}]}}); renderPage(); await screen.findByText('Portal 2'); expect(screen.getAllByText('LIBRARY.COMING')).toHaveLength(1); expect(screen.getByText('Half-Life 2').closest('li').textContent).toContain('LIBRARY.COMING'); expect(screen.getByText('Portal 2').closest('li').textContent).not.toContain('LIBRARY.COMING'); }); it('wears the premium accent and the badge of the owner', async () => { mockBackends({style: {accent: 'red'}, equipped: {ACCENT: 'ACCENT_GOLD', BADGE: 'BADGE_PILE_SLAYER', BANNER_FRAME: 'FRAME_GOLD'}}); renderPage(); expect(await screen.findByText('POINTS.ITEMS.BADGE_PILE_SLAYER')).toBeTruthy(); const wrapper = screen.getByText('Lobo').closest('[data-accent]'); expect(wrapper.dataset.accent).toBe('gold'); expect(wrapper.querySelector('[data-slot="card"]').className).toContain('ring-amber-400'); expect(fetch.mock.calls[2][1].headers['X-Profile-Ticket']).toBe('the-ticket'); }); it('wears the effects of the owner: background, banner and the cover of a COMPLETED game', async () => { mockBackends({style: {accent: 'red'}, equipped: { PAGE_BACKGROUND: 'BG_STARFIELD', BANNER_BACKGROUND: 'BN_WAVES', COMPLETED_EFFECT: 'FX_NEON_PULSE'}}); const {container} = renderPage(); expect(await screen.findByText('Portal 2')).toBeTruthy(); await waitFor(() => expect(container.querySelector('[data-effect="starfield"]')).not.toBeNull()); expect(screen.getByText('Lobo').closest('[data-slot="card"]').querySelector('[data-effect="waves"]')).not.toBeNull(); expect(screen.getByText('Portal 2').closest('li').querySelector('.fx-cover-neon-pulse')).not.toBeNull(); }); it('wears the skin of the owner on the wrapper with its own base layer, or resets to the plain theme', async () => { mockBackends({style: {accent: 'red'}, equipped: {SKIN: 'SKIN_CYBERPUNK'}}); const {unmount} = renderPage(); expect(await screen.findByText('Portal 2')).toBeTruthy(); const wrapper = screen.getByText('Lobo').closest('[data-skin]'); expect(wrapper.dataset.skin).toBe('cyberpunk'); expect(wrapper.querySelector('.skin-bg')).not.toBeNull(); unmount(); cleanup(); mockBackends({style: {accent: 'red'}, equipped: {}}); renderPage(); expect(await screen.findByText('Portal 2')).toBeTruthy(); expect(screen.getByText('Lobo').closest('[data-skin]').dataset.skin).toBe('none'); }); it('still shows the profile when the looks call fails', async () => { mockBackends({style: {accent: 'red'}, rewardsStatus: 500}); renderPage(); expect(await screen.findByText('Portal 2')).toBeTruthy(); expect(screen.getByText('Lobo').closest('[data-accent]').dataset.accent).toBe('red'); }); it('ignores a code the app does not know', async () => { mockBackends({style: {accent: 'red'}, equipped: {ACCENT: 'ACCENT_X', BADGE: 'BADGE_X'}}); renderPage(); expect(await screen.findByText('Portal 2')).toBeTruthy(); expect(screen.getByText('Lobo').closest('[data-accent]').dataset.accent).toBe('red'); }); it('asks Auth for the ticket, then Core for the library with it', async () => { mockBackends({name: 'Lo bo'}); renderPage('Lo bo'); expect(await screen.findByText('Portal 2')).toBeTruthy(); expect(fetch.mock.calls[0][0]).toMatch(/\/api\/public\/profiles\/Lo%20bo$/); expect(fetch.mock.calls[1][1].headers['X-Profile-Ticket']).toBe('the-ticket'); expect(screen.getByText('Lobo')).toBeTruthy(); expect(screen.getByText('Half-Life 2')).toBeTruthy(); expect(screen.getAllByText('233')).toHaveLength(2); expect(screen.queryByText('PROFILE.PERFECT_GAMES')).toBeNull(); }); it('shows perfect games and the average completion when there are achievements', async () => { library.perfectGames = 12; library.averageCompletion = 64.5; try { mockBackends(); renderPage(); expect(await screen.findByText('PROFILE.PERFECT_GAMES')).toBeTruthy(); expect(screen.getByText('12')).toBeTruthy(); expect(screen.getByText('64,5 %')).toBeTruthy(); } finally { delete library.perfectGames; delete library.averageCompletion; } }); it('shows only the stat groups the user shares, without links', async () => { library.stats = {pile: null, trend: null, topGames: null, taste: null, finished: null, habits: {moonTrips: 2.5, longestDay: null, underOneHourGames: 0, underOneHourShare: null}}; mockBackends({style: {tiles: [{id: 'STATS_HABITS', size: 'M'}, {id: 'STATS_PILE', size: 'M'}]}}); renderPage(); expect(await screen.findByText('STATS.HABITS.TITLE')).toBeTruthy(); expect(screen.queryByText('STATS.PILE.TITLE')).toBeNull(); expect(screen.queryByText('STATS.TOP_GAMES.TITLE')).toBeNull(); delete library.stats; }); it('applies the owner\'s accent and corners to the page only, with banner and background', async () => { mockBackends({style: {accent: 'red', corners: 'round', bannerAppid: 620, backgroundAppid: 220, tiles: [{id: 'PLAYING', size: 'M'}]}}); const {container} = renderPage(); await screen.findByText('Half-Life 2'); const page = container.querySelector('[data-accent="red"]'); expect(page.dataset.corners).toBe('round'); expect(document.documentElement.dataset.accent).not.toBe('red'); expect([...container.querySelectorAll('img')].map((img) => img.getAttribute('src'))) .toEqual(expect.arrayContaining([expect.stringContaining('/620/'), expect.stringContaining('/220/')])); }); it('shows the sections in the owner\'s order and leaves out hidden ones', async () => { mockBackends({ style: {tiles: [{id: 'FINISHED', size: 'M'}, {id: 'PLAYING', size: 'S'}, {id: 'SUMMARY', size: 'M'}]}, answer: {...library, byStatus: null, finishedGames: 60}, }); const {container} = renderPage(); await screen.findByText('Portal 2'); expect([...container.querySelectorAll('[data-tile]')].map((el) => el.dataset.tile)) .toEqual(['FINISHED', 'PLAYING', 'SUMMARY']); expect(screen.getByText('60')).toBeTruthy(); expect(screen.queryByText('BACKLOG.STATUS.BACKLOG')).toBeNull(); }); it('leaves out the status bar of a profile without games', async () => { mockBackends({answer: {...library, totalGames: 0, byStatus: {BACKLOG: 0, PLAYING: 0}, playing: [], finished: []}}); const {container} = renderPage(); await screen.findByText('PROFILE.GAMES'); expect([...container.querySelectorAll('[data-tile]')].map((el) => el.dataset.tile)).toEqual(['SUMMARY']); }); it('shows every section for an Auth without a style', async () => { mockBackends(); const {container} = renderPage(); await screen.findByText('Portal 2'); expect([...container.querySelectorAll('[data-tile]')].map((el) => el.dataset.tile)) .toEqual(['SUMMARY', 'STATUS', 'PLAYING', 'FINISHED']); expect(container.querySelector('[data-accent="violet"]')).toBeTruthy(); }); it('shows the small playing tile with half the games and at half width', async () => { const games = Array.from({length: 8}, (_, i) => ({appid: 100 + i, name: `Game ${i}`, playtimeMinutes: 60, status: 'PLAYING'})); mockBackends({style: {tiles: [{id: 'PLAYING', size: 'S'}]}, answer: {...library, playing: games}}); const {container} = renderPage(); await screen.findByText('Game 0'); expect(screen.queryByText('Game 4')).toBeNull(); expect(container.querySelector('[data-tile="PLAYING"]').className).toContain('md:col-span-6'); }); const libraryLoads = () => fetch.mock.calls.filter(([url]) => url.includes('/api/public/profiles/library')).length; const putBody = () => JSON.parse(fetch.mock.calls.find(([, init]) => init?.method === 'PUT')[1].body); it('offers "Customize" to the owner and hides a tile without a new load', async () => { localStorage.setItem('tbp_session', '1'); try { mockBackends({me: {displayName: 'lobo', role: 'USER'}}); const {container} = renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'})); // The test texts carry no tile name; the first tile is SUMMARY. fireEvent.click(screen.getAllByRole('button', {name: 'TILES.HIDE'})[0]); fireEvent.click(screen.getByRole('button', {name: 'TILES.DONE'})); expect(await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'})).toBeTruthy(); expect(fetch.mock.calls.find(([, init]) => init?.method === 'PUT')[0]).toMatch(/\/api\/users\/settings\/profile-tiles$/); expect(putBody().tiles.map((tile) => tile.id)).toEqual(['STATUS', 'PLAYING', 'FINISHED']); expect([...container.querySelectorAll('[data-tile]')].map((el) => el.dataset.tile)).not.toContain('SUMMARY'); expect(libraryLoads()).toBe(1); } finally { localStorage.clear(); } }); it('loads again after adding a tile, because Core sent no data for it', async () => { localStorage.setItem('tbp_session', '1'); try { mockBackends({me: {displayName: 'Lobo', role: 'USER'}}); renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'})); fireEvent.click(screen.getByRole('button', {name: 'STATS.PILE.TITLE'})); fireEvent.click(screen.getByRole('button', {name: 'TILES.DONE'})); await waitFor(() => expect(libraryLoads()).toBe(2)); expect(putBody().tiles.at(-1)).toEqual({id: 'STATS_PILE', size: 'M'}); } finally { localStorage.clear(); } }); it('offers "Follow" to a logged-in visitor, not to the owner and not to a guest', async () => { localStorage.setItem('tbp_session', '1'); try { mockBackends({me: {displayName: 'Someone', role: 'USER'}}); const visitor = renderPage(); expect(await screen.findByRole('button', {name: 'FOLLOW.FOLLOW'})).toBeTruthy(); visitor.unmount(); mockBackends({me: {displayName: 'Lobo', role: 'USER'}}); renderPage(); await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'}); expect(screen.queryByRole('button', {name: 'FOLLOW.FOLLOW'})).toBeNull(); expect(fetch).not.toHaveBeenCalledWith(expect.stringContaining('/api/follows'), expect.anything()); } finally { localStorage.clear(); } }); it('offers no "Follow" to a guest', async () => { mockBackends(); renderPage(); await screen.findByText('Portal 2'); expect(screen.queryByRole('button', {name: 'FOLLOW.FOLLOW'})).toBeNull(); expect(fetch).not.toHaveBeenCalledWith(expect.stringContaining('/api/follows'), expect.anything()); }); it('shows no "Customize" to a visitor', async () => { localStorage.setItem('tbp_session', '1'); try { mockBackends({me: {displayName: 'Someone', role: 'USER'}}); renderPage(); await screen.findByText('Portal 2'); await waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/users/me'), expect.anything())); expect(screen.queryByRole('button', {name: 'TILES.CUSTOMIZE'})).toBeNull(); } finally { localStorage.clear(); } }); it('keeps three numbers in a row on a phone', async () => { mockBackends(); const {container} = renderPage(); await screen.findByText('PROFILE.GAMES'); const summary = container.querySelector('[data-tile="SUMMARY"] [data-slot="card"]'); expect(summary.className.split(' ')).toContain('grid-cols-3'); }); it('says the profile is unknown or private on a 404', async () => { mockBackends({profileStatus: 404}); renderPage(); expect(await screen.findByText('PROFILE.NOT_FOUND')).toBeTruthy(); expect(fetch).toHaveBeenCalledTimes(1); }); it('shows an error when Core fails', async () => { mockBackends({libraryStatus: 500}); renderPage(); await waitFor(() => expect(screen.getByText('PROFILE.ERROR_LOAD')).toBeTruthy()); }); it('opensTheGamesBehindAStatusCountWithTheTicket', async () => { mockBackends({extra: {'GET /api/public/profiles/library/stats/games': json(200, { content: [{appid: 1, name: 'Backlogged', status: 'BACKLOG', playtimeMinutes: 0}], page: 0, size: 40, totalElements: 1, totalPages: 1})}}); const {container} = renderPage(); fireEvent.click(await screen.findByRole('button', {name: /BACKLOG\.STATUS\.BACKLOG/})); expect(await screen.findByText('Backlogged')).toBeTruthy(); const call = fetch.mock.calls.find(([url]) => url.includes('/stats/games')); expect(call[0]).toContain('key=STATUS&arg=BACKLOG'); expect(call[1].headers['X-Profile-Ticket']).toBe('the-ticket'); expect(container.querySelector('a[href^="/library"]')).toBeNull(); }); it('rendersNoButtonForAGroupTheOwnerDoesNotShare', async () => { library.stats = {pile: null, trend: null, topGames: null, taste: null, finished: null, habits: {moonTrips: 2.5, longestDay: null, underOneHourGames: 0, underOneHourShare: null}}; try { mockBackends({style: {tiles: [{id: 'STATS_HABITS', size: 'M'}, {id: 'STATS_PILE', size: 'M'}]}}); renderPage(); await screen.findByText('STATS.HABITS.TITLE'); expect(screen.queryByRole('button', {name: /STATS\.PILE/})).toBeNull(); } finally { delete library.stats; } }); const gamesRoute = {'GET /api/public/profiles/library/stats/games': json(200, { content: [{appid: 1, name: 'Backlogged', status: 'BACKLOG', playtimeMinutes: 0}], page: 0, size: 40, totalElements: 1, totalPages: 1})}; it('drawsTheStatusTileAsADonut', async () => { mockBackends(); const {container} = renderPage(); await screen.findByText('Portal 2'); expect(container.querySelectorAll('[data-tile="STATUS"] path.stroke-card')).toHaveLength(5); }); it('showsTheTotalInTheDonutAsTextWithoutTheSummarySection', async () => { mockBackends({answer: {...library, totalGames: null}}); const {container} = renderPage(); await screen.findByText('Portal 2'); const tile = container.querySelector('[data-tile="STATUS"]'); expect(within(tile).getByText('233')).toBeTruthy(); expect(within(tile).queryByRole('button', {name: /STATS\.OVERVIEW\.GAMES/})).toBeNull(); }); it('opensAllGamesFromTheDonutTotalWhenTheSummaryIsShared', async () => { mockBackends({extra: gamesRoute}); const {container} = renderPage(); await screen.findByText('Portal 2'); fireEvent.click(within(container.querySelector('[data-tile="STATUS"]')).getByRole('button', {name: 'STATS.OVERVIEW.GAMES: 233'})); await screen.findByText('Backlogged'); expect(fetch.mock.calls.find(([url]) => url.includes('/stats/games'))[0]).toContain('key=TOTAL'); }); it('showsTheAverageCompletionAsAMeter', async () => { mockBackends({answer: {...library, averageCompletion: 71.3, perfectGames: 2}}); renderPage(); const meter = await screen.findByRole('meter', {name: 'PROFILE.AVERAGE_COMPLETION'}); expect(meter.getAttribute('aria-valuetext')).toBe('71,3 %'); }); it('showsNoMeterWithoutAnAverageCompletion', async () => { mockBackends(); renderPage(); await screen.findByText('PROFILE.GAMES'); expect(screen.queryByRole('meter')).toBeNull(); }); const topGamesStats = {pile: null, trend: null, taste: null, finished: null, habits: null, topGames: {mostPlayed: [{appid: 620, name: 'Portal 2', imageUrl: null, minutes: 900}], mostLoyal: null}}; it('theOwnerOpensTheDetailPageFromTheList', async () => { localStorage.setItem('tbp_session', '1'); try { mockBackends({me: {displayName: 'lobo', role: 'USER'}, extra: gamesRoute}); renderPage(); fireEvent.click(await screen.findByRole('button', {name: /BACKLOG\.STATUS\.BACKLOG/})); expect((await screen.findByRole('link', {name: /Backlogged/})).getAttribute('href')).toBe('/library/1'); } finally { localStorage.clear(); } }); it('theOwnerTileRowsOpenTheDetailPage', async () => { localStorage.setItem('tbp_session', '1'); library.stats = topGamesStats; try { mockBackends({me: {displayName: 'lobo', role: 'USER'}, style: {tiles: [{id: 'STATS_TOP_GAMES', size: 'M'}]}}); renderPage(); expect((await screen.findByRole('link', {name: /Portal 2/})).getAttribute('href')).toBe('/library/620'); } finally { delete library.stats; localStorage.clear(); } }); it('aVisitorSeesTileRowsWithoutLinks', async () => { library.stats = topGamesStats; try { mockBackends({style: {tiles: [{id: 'STATS_TOP_GAMES', size: 'M'}]}}); const {container} = renderPage(); await screen.findByText('Portal 2'); expect(screen.queryByRole('link', {name: /Portal 2/})).toBeNull(); expect(container.querySelector('a[href*="steampowered"]')).toBeNull(); } finally { delete library.stats; } }); const platformStats = {pile: null, trend: null, taste: null, finished: null, habits: null, topGames: null, platforms: {perPlatform: [{name: 'Steam', games: 10, minutes: 5000}, {name: 'PS5', games: 2, minutes: 600}]}}; const platformTile = {tiles: [{id: 'STATS_PLATFORMS', size: 'M'}]}; const platformGames = {'GET /api/public/profiles/library/stats/games': json(200, { content: [{appid: 7, name: 'Hades', status: 'BACKLOG', playtimeMinutes: 0}], page: 0, size: 40, totalElements: 1, totalPages: 1})}; it('showsTheOwnerChartFormWithoutAPicker', async () => { library.stats = platformStats; try { mockBackends({style: {...platformTile, statCharts: {PLATFORMS: 'DONUT'}}}); const {container} = renderPage(); expect(await screen.findByText('Steam')).toBeTruthy(); expect(container.querySelectorAll('path.stroke-card')).toHaveLength(2); expect(screen.queryByRole('group', {name: 'STATS.CHART.LABEL'})).toBeNull(); } finally { delete library.stats; } }); const tasteStats = {pile: null, trend: null, finished: null, habits: null, topGames: null, platforms: null, taste: {genres: [{name: 'Shooter', games: 2, minutes: 1200}, {name: 'Puzzle', games: 1, minutes: 300}], moods: []}}; it('theOwnerGetsThePickerOnATasteTile', async () => { localStorage.setItem('tbp_session', '1'); library.stats = tasteStats; try { mockBackends({me: {displayName: 'lobo', role: 'USER', settings: {}}, style: {tiles: [{id: 'STATS_TASTE', size: 'M'}]}, extra: {'PUT /api/users/settings/stat-chart': json(204, {})}}); const {container} = renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'STATS.CHART.DONUT'})); await waitFor(() => expect(container.querySelectorAll('path.stroke-card').length).toBeGreaterThan(0)); const bodies = fetch.mock.calls.filter(([, init]) => init?.method === 'PUT').map(([, init]) => JSON.parse(init.body)); expect(bodies).toEqual([{group: 'TASTE', chart: 'DONUT'}]); } finally { delete library.stats; localStorage.clear(); } }); const twoGameStats = {...topGamesStats, topGames: {mostLoyal: null, mostPlayed: [ {appid: 620, name: 'Portal 2', imageUrl: null, minutes: 900}, {appid: 220, name: 'Half-Life 2', imageUrl: null, minutes: 300}]}}; it('topGamesPieLinksForTheOwnerAndIsPlainForAVisitor', async () => { localStorage.setItem('tbp_session', '1'); library.stats = twoGameStats; try { const style = {tiles: [{id: 'STATS_TOP_GAMES', size: 'M'}], statCharts: {TOP_GAMES: 'PIE'}}; mockBackends({me: {displayName: 'lobo', role: 'USER', settings: {statCharts: {TOP_GAMES: 'PIE'}}}, style}); const {unmount} = renderPage(); expect((await screen.findByRole('link', {name: /Portal 2/})).getAttribute('href')).toBe('/library/620'); unmount(); cleanup(); localStorage.clear(); mockBackends({style}); renderPage(); await screen.findByText('Portal 2'); expect(screen.queryByRole('link', {name: /Portal 2/})).toBeNull(); expect(screen.queryByRole('button', {name: /Portal 2/})).toBeNull(); } finally { delete library.stats; localStorage.clear(); } }); it('showsBarsForAFormTheProfileDoesNotKnow', async () => { library.stats = platformStats; try { mockBackends({style: {...platformTile, statCharts: {PLATFORMS: 'RADAR'}}}); const {container} = renderPage(); expect(await screen.findByText('Steam')).toBeTruthy(); expect(container.querySelector('path.stroke-card')).toBeNull(); } finally { delete library.stats; } }); it('aChartLegendRowOfTheOwnerOpensTheDetailPage', async () => { localStorage.setItem('tbp_session', '1'); library.stats = platformStats; try { mockBackends({me: {displayName: 'lobo', role: 'USER'}, style: {...platformTile, statCharts: {PLATFORMS: 'PIE'}}, extra: platformGames}); const {container} = renderPage(); await waitFor(() => expect(container.querySelectorAll('path.stroke-card')).toHaveLength(2)); fireEvent.click(await screen.findByRole('button', {name: /Steam/})); expect((await screen.findByRole('link', {name: /Hades/})).getAttribute('href')).toBe('/library/7'); } finally { delete library.stats; localStorage.clear(); } }); it('aChartLegendRowOfAVisitorShowsAPlainGameRow', async () => { library.stats = platformStats; try { mockBackends({style: {...platformTile, statCharts: {PLATFORMS: 'PIE'}}, extra: platformGames}); const {container} = renderPage(); await waitFor(() => expect(container.querySelectorAll('path.stroke-card')).toHaveLength(2)); fireEvent.click(await screen.findByRole('button', {name: /Steam/})); expect(await screen.findByText('Hades')).toBeTruthy(); expect(screen.queryByRole('link', {name: /Hades/})).toBeNull(); } finally { delete library.stats; } }); it('opensTheGamesBehindPerfectGames', async () => { library.perfectGames = 12; library.averageCompletion = 64.5; try { mockBackends({extra: gamesRoute}); renderPage(); fireEvent.click(await screen.findByRole('button', {name: /PROFILE\.PERFECT_GAMES: 12/})); await screen.findByText('Backlogged'); expect(fetch.mock.calls.find(([url]) => url.includes('/stats/games'))[0]).toContain('key=PERFECT'); } finally { delete library.perfectGames; delete library.averageCompletion; } }); });