Public Access
test: data router, one render helper, fetchRoutes in every test
This commit is contained in:
1 parent
d0ea9e900d
commit
2a22789435
37 files changed
+454
-425
No files matched your search
@@ -1,10 +1,9 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {act, cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
||||
import {MemoryRouter, Route, Routes} from 'react-router-dom';
|
||||
import {act, cleanup, fireEvent, screen, waitFor} from '@testing-library/react';
|
||||
import GameDetail from './GameDetail.jsx';
|
||||
import {PreferencesProvider} from '../context/PreferencesContext.jsx';
|
||||
import {CurrentUserProvider} from '../context/CurrentUserContext.jsx';
|
||||
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
||||
import {renderWithProviders} from '../test/renderWithProviders.jsx';
|
||||
import {resetBrokenImageReports} from '../utils/brokenImages.js';
|
||||
import {toast} from 'sonner';
|
||||
|
||||
@@ -16,20 +15,20 @@ const game = {
|
||||
rating: null, notes: null, priority: null, hasAchievements: false, statsVisible: false, achievements: [],
|
||||
};
|
||||
|
||||
const renderDetail = (appid = 220) => render(
|
||||
<MemoryRouter initialEntries={[`/library/${appid}`]}><PreferencesProvider><CurrentUserProvider>
|
||||
<Routes>
|
||||
<Route path="/library/:appid" element={<GameDetail/>}/>
|
||||
<Route path="/library" element={<p>library page</p>}/>
|
||||
</Routes>
|
||||
</CurrentUserProvider></PreferencesProvider></MemoryRouter>,
|
||||
);
|
||||
const renderDetail = (appid = 220) => renderWithProviders(<GameDetail/>,
|
||||
{path: '/library/:appid', entries: [`/library/${appid}`], routes: {'/library': <p>library page</p>}});
|
||||
|
||||
const echo = (url, init) => json(200, JSON.parse(init.body));
|
||||
|
||||
// The user and the game at /library/<shown.appid>; `routes` adds or replaces answers.
|
||||
const answer = (shown, routes = {}, me = {settings: {}}) => fetchRoutes({
|
||||
'GET /api/users/me': json(200, me),
|
||||
[`GET /api/library/games/${shown.appid}`]: json(200, shown),
|
||||
...routes,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.setItem('tbp_session', '1');
|
||||
globalThis.fetch = vi.fn(async (url, init) => init?.method === 'PUT'
|
||||
? {ok: true, status: 200, json: async () => JSON.parse(init.body)}
|
||||
: {ok: true, status: 200, json: async () => game});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -40,8 +39,7 @@ afterEach(() => {
|
||||
|
||||
describe('GameDetail', () => {
|
||||
it('asks "finished?" instead of "playing?" when Core has a finished hint', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...game, playtime2Weeks: 90,
|
||||
source: 'STEAM', finishedHint: {type: 'ENDING', achievement: 'Survivor'}})}));
|
||||
answer({...game, playtime2Weeks: 90, source: 'STEAM', finishedHint: {type: 'ENDING', achievement: 'Survivor'}});
|
||||
renderDetail();
|
||||
|
||||
expect(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_FINISHED'})).toBeTruthy();
|
||||
@@ -49,9 +47,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('hides a Steam game and shows it again', async () => {
|
||||
globalThis.fetch = vi.fn(async (url, init) => init?.method === 'PUT'
|
||||
? {ok: true, status: 200, json: async () => JSON.parse(init.body)}
|
||||
: {ok: true, status: 200, json: async () => ({...game, source: 'STEAM', hidden: false})});
|
||||
answer({...game, source: 'STEAM', hidden: false}, {'PUT /api/library/games/220/hidden': echo});
|
||||
renderDetail();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.HIDE'}));
|
||||
@@ -68,9 +64,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('keeps the game shown when Core refuses', async () => {
|
||||
globalThis.fetch = vi.fn(async (url, init) => init?.method === 'PUT'
|
||||
? {ok: false, status: 409, json: async () => ({code: 'MANUAL_GAME'})}
|
||||
: {ok: true, status: 200, json: async () => ({...game, source: 'STEAM', hidden: false})});
|
||||
answer({...game, source: 'STEAM', hidden: false}, {'PUT /api/library/games/220/hidden': json(409, {code: 'MANUAL_GAME'})});
|
||||
renderDetail();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.HIDE'}));
|
||||
@@ -80,8 +74,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('offers no hide for a game added by hand', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200,
|
||||
json: async () => ({...game, source: 'MANUAL', platform: 'PS5', hidden: false})}));
|
||||
answer({...game, source: 'MANUAL', platform: 'PS5', hidden: false});
|
||||
renderDetail();
|
||||
|
||||
await screen.findByText('GAME_DETAIL.OPEN_IN_STEAM');
|
||||
@@ -91,11 +84,8 @@ describe('GameDetail', () => {
|
||||
it('reports a broken achievement icon once and shows the empty tile', async () => {
|
||||
const achievement = (apiName) => ({apiName, displayName: apiName, unlocked: false, iconUrl: `https://x/${apiName}.jpg`,
|
||||
iconGrayUrl: `https://x/${apiName}_gray.jpg`});
|
||||
globalThis.fetch = vi.fn(async (url, init) => init?.method === 'POST'
|
||||
? {ok: true, status: 204}
|
||||
: {ok: true, status: 200, json: async () => ({...game, hasAchievements: true, statsVisible: true,
|
||||
achievementsTotal: 2, achievementsUnlocked: 0, completionPercent: 0,
|
||||
achievements: [achievement('A'), achievement('B')]})});
|
||||
answer({...game, hasAchievements: true, statsVisible: true, achievementsTotal: 2, achievementsUnlocked: 0,
|
||||
completionPercent: 0, achievements: [achievement('A'), achievement('B')]}, {'POST /api/library/broken-images': json(204)});
|
||||
const {container} = renderDetail();
|
||||
|
||||
await screen.findByText('A');
|
||||
@@ -109,8 +99,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('links the speedrun.com page when Core found one', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200,
|
||||
json: async () => ({...game, speedrunUrl: 'https://www.speedrun.com/hl2'})}));
|
||||
answer({...game, speedrunUrl: 'https://www.speedrun.com/hl2'});
|
||||
renderDetail();
|
||||
|
||||
const link = await screen.findByRole('link', {name: /GAME_DETAIL.OPEN_IN_SPEEDRUN/});
|
||||
@@ -119,7 +108,7 @@ describe('GameDetail', () => {
|
||||
|
||||
it.each([null, 'https://www.speedrun.com.evil.example/hl2', 'https://evil.example/hl2', 42])(
|
||||
'shows no speedrun link for %s', async (speedrunUrl) => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...game, speedrunUrl})}));
|
||||
answer({...game, speedrunUrl});
|
||||
renderDetail();
|
||||
|
||||
await screen.findByText('GAME_DETAIL.OPEN_IN_STEAM');
|
||||
@@ -127,6 +116,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('keeps unsaved notes in the backlog card when the hint sets the game to playing', async () => {
|
||||
answer(game, {'PUT /api/library/games/220/backlog': echo});
|
||||
renderDetail();
|
||||
const notes = await screen.findByLabelText('BACKLOG.NOTES_LABEL');
|
||||
fireEvent.change(notes, {target: {value: 'my draft'}});
|
||||
@@ -138,6 +128,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('uses the game as background and can remove it again', async () => {
|
||||
answer(game, {'PUT /api/users/settings/appearance': echo});
|
||||
renderDetail();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_BACKGROUND'}));
|
||||
@@ -152,6 +143,7 @@ describe('GameDetail', () => {
|
||||
});
|
||||
|
||||
it('uses the game as banner and goes back to the automatic banner', async () => {
|
||||
answer(game, {'PUT /api/users/settings/appearance': echo});
|
||||
renderDetail();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_BANNER'}));
|
||||
@@ -166,6 +158,7 @@ describe('GameDetail', () => {
|
||||
|
||||
it('offers "use as banner" for the picked game while the banner is off, and turns it on', async () => {
|
||||
localStorage.setItem('tbp_appearance', JSON.stringify({bannerAppid: 220, showBanner: false}));
|
||||
answer(game, {'PUT /api/users/settings/appearance': echo});
|
||||
renderDetail();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_BANNER'}));
|
||||
@@ -182,10 +175,10 @@ describe('GameDetail of a game added by hand', () => {
|
||||
playtime2Weeks: 0, imageUrl: 'https://images.igdb.com/z.jpg',
|
||||
};
|
||||
|
||||
const noBody = {ok: true, status: 204, json: async () => { throw new Error('204 has no body'); }};
|
||||
|
||||
beforeEach(() => {
|
||||
globalThis.fetch = vi.fn(async (url, init) => (init?.method === 'PUT' || init?.method === 'DELETE')
|
||||
? {ok: true, status: 204, json: async () => { throw new Error('204 has no body'); }}
|
||||
: {ok: true, status: 200, json: async () => manualGame});
|
||||
answer(manualGame, {'PUT /api/library/games/-1025/manual': noBody, 'DELETE /api/library/games/-1025': noBody});
|
||||
});
|
||||
|
||||
it('shows platform and IGDB picture, and offers no Steam actions', async () => {
|
||||
@@ -227,7 +220,7 @@ describe('GameDetail of a game added by hand', () => {
|
||||
});
|
||||
|
||||
it('keeps Save off for an unchanged game with 75 minutes', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...manualGame, playtimeMinutes: 75})}));
|
||||
answer({...manualGame, playtimeMinutes: 75});
|
||||
renderDetail(-1025);
|
||||
|
||||
await screen.findByLabelText('MANUAL_GAME.HOURS');
|
||||
@@ -236,7 +229,7 @@ describe('GameDetail of a game added by hand', () => {
|
||||
});
|
||||
|
||||
it('shows no manual card for a Steam game', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...game, source: 'STEAM'})}));
|
||||
answer({...game, source: 'STEAM'});
|
||||
renderDetail();
|
||||
|
||||
expect(await screen.findByRole('button', {name: 'GAME_DETAIL.SET_BACKGROUND'})).toBeTruthy();
|
||||
@@ -245,10 +238,10 @@ describe('GameDetail of a game added by hand', () => {
|
||||
|
||||
it('keeps a background set in another tab when it sets the profile banner', async () => {
|
||||
let backgroundAppid = null;
|
||||
globalThis.fetch = vi.fn(async (url, init) => ({ok: true, status: init?.method === 'PUT' ? 204 : 200,
|
||||
json: async () => (url.includes('/api/users/me')
|
||||
? {role: 'USER', settings: {profileStyle: {accent: 'red', corners: 'round', backgroundAppid}}}
|
||||
: {...game, source: 'STEAM'})}));
|
||||
answer({...game, source: 'STEAM'}, {
|
||||
'GET /api/users/me': () => json(200, {role: 'USER', settings: {profileStyle: {accent: 'red', corners: 'round', backgroundAppid}}}),
|
||||
'PUT /api/users/settings/profile-style': json(204),
|
||||
});
|
||||
renderDetail();
|
||||
const menu = await screen.findByRole('button', {name: 'GAME_DETAIL.PROFILE_ART'});
|
||||
|
||||
@@ -264,10 +257,10 @@ describe('GameDetail of a game added by hand', () => {
|
||||
|
||||
it('keeps a banner picked in another tab when it sets this game as banner', async () => {
|
||||
let bannerAppid = 220;
|
||||
globalThis.fetch = vi.fn(async (url, init) => ({ok: true, status: init?.method === 'PUT' ? 204 : 200,
|
||||
json: async () => (url.includes('/api/users/me')
|
||||
? {role: 'USER', settings: {profileStyle: {bannerAppid}}}
|
||||
: {...game, source: 'STEAM'})}));
|
||||
answer({...game, source: 'STEAM'}, {
|
||||
'GET /api/users/me': () => json(200, {role: 'USER', settings: {profileStyle: {bannerAppid}}}),
|
||||
'PUT /api/users/settings/profile-style': json(204),
|
||||
});
|
||||
renderDetail();
|
||||
const menu = await screen.findByRole('button', {name: 'GAME_DETAIL.PROFILE_ART'});
|
||||
|
||||
@@ -282,12 +275,12 @@ describe('GameDetail of a game added by hand', () => {
|
||||
|
||||
it('uses the art as profile banner and removes it on the second click', async () => {
|
||||
let stored = {accent: 'red', corners: 'round'};
|
||||
globalThis.fetch = vi.fn(async (url, init) => {
|
||||
if (init?.method === 'PUT') stored = JSON.parse(init.body);
|
||||
return {ok: true, status: init?.method === 'PUT' ? 204 : 200,
|
||||
json: async () => (url.includes('/api/users/me')
|
||||
? {role: 'USER', settings: {profileStyle: stored}}
|
||||
: {...game, source: 'STEAM'})};
|
||||
answer({...game, source: 'STEAM'}, {
|
||||
'GET /api/users/me': () => json(200, {role: 'USER', settings: {profileStyle: stored}}),
|
||||
'PUT /api/users/settings/profile-style': (url, init) => {
|
||||
stored = JSON.parse(init.body);
|
||||
return json(204);
|
||||
},
|
||||
});
|
||||
const styleBodies = () => fetch.mock.calls.filter(([url, init]) => init?.method === 'PUT' && url.endsWith('/profile-style'))
|
||||
.map(([, init]) => JSON.parse(init.body));
|
||||
@@ -308,10 +301,7 @@ describe('GameDetail of a game added by hand', () => {
|
||||
});
|
||||
|
||||
describe('corrections', () => {
|
||||
const loggedInAs = (me) => {
|
||||
globalThis.fetch = vi.fn(async (url) => ({ok: true, status: 200,
|
||||
json: async () => (url.includes('/api/users/me') ? me : game)}));
|
||||
};
|
||||
const loggedInAs = (me) => answer(game, {}, me);
|
||||
|
||||
it('offers "correct data" to an active auditor', async () => {
|
||||
loggedInAs({role: 'AUDITOR', activeRole: 'AUDITOR'});
|
||||
@@ -359,7 +349,7 @@ describe('GameDetail confetti', () => {
|
||||
});
|
||||
|
||||
const opened = async (entry) => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...game, ...entry})}));
|
||||
answer({...game, ...entry});
|
||||
const {container} = renderDetail();
|
||||
await screen.findByText('Half-Life 2');
|
||||
return container.querySelector('canvas[data-size]');
|
||||
|
||||
Reference in new issue
Block a user