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,25 +1,39 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
||||
import {MemoryRouter, Route, Routes, useLocation} from 'react-router-dom';
|
||||
import RequireSession from '../components/RequireSession.jsx';
|
||||
import {cleanup, fireEvent, screen, waitFor} from '@testing-library/react';
|
||||
import Dashboard from './Dashboard.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';
|
||||
|
||||
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||
vi.mock('../hooks/useLibrarySync.js', () => ({default: () => ({status: 'OK', lastSyncedAt: null, isRunning: false})}));
|
||||
vi.mock('../hooks/useForceSync.js', () => ({default: () => ({trigger: vi.fn(), busy: false})}));
|
||||
|
||||
const current = {location: null};
|
||||
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: []}),
|
||||
};
|
||||
|
||||
function Spy() {
|
||||
current.location = useLocation();
|
||||
return null;
|
||||
}
|
||||
// Every request of the page with a Steam ID; the test of the 401 answers all but the user with 401.
|
||||
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/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: [
|
||||
{appid: 1086940, name: "Baldur's Gate 3", playtimeMinutes: 900, playtime2Weeks: 90, status: 'PLAYING'}]}),
|
||||
'GET /api/library/games-count': json(200, {count: 1, hasSteamId: true}),
|
||||
'GET /api/library/achievements/summary': json(200, {unlocked: 24, startedGames: 4, perfectGames: 2,
|
||||
averageCompletion: 71.3, pendingGames: 0, hiddenGames: 0, closest: []}),
|
||||
'PUT /api/users/settings/dashboard-tiles': json(204, {}),
|
||||
});
|
||||
|
||||
const ok = (body) => ({ok: true, status: 200, json: async () => body});
|
||||
const unauthorized = () => ({ok: false, status: 401, json: async () => ({})});
|
||||
const renderDashboard = () => renderWithProviders(<Dashboard/>, {entries: ['/dashboard']});
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.setItem('tbp_session', '1');
|
||||
@@ -32,45 +46,24 @@ afterEach(() => {
|
||||
|
||||
describe('Dashboard', () => {
|
||||
it('opens the login page when the core API answers 401', async () => {
|
||||
globalThis.fetch = vi.fn(async (url) =>
|
||||
url.includes('/api/users/me') ? ok({email: 'a@b.de', steamId: '76561198000000000'}) : unauthorized());
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}><PreferencesProvider><CurrentUserProvider>
|
||||
<Routes>
|
||||
<Route element={<RequireSession/>}><Route path="/dashboard" element={<><Dashboard/><Spy/></>}/></Route>
|
||||
<Route path="/login" element={<Spy/>}/>
|
||||
</Routes>
|
||||
</CurrentUserProvider></PreferencesProvider></MemoryRouter>,
|
||||
);
|
||||
fetchRoutes({
|
||||
...Object.fromEntries(Object.keys(dashboardRoutes()).map((key) => [key, json(401, {})])),
|
||||
'GET /api/users/me': json(200, {email: 'a@b.de', steamId: '76561198000000000'}),
|
||||
'POST /api/auth/refresh': json(401, {}),
|
||||
});
|
||||
const {router} = renderWithProviders(<Dashboard/>,
|
||||
{path: '/dashboard', entries: ['/dashboard'], guarded: true, routes: {'/login': <p>login</p>}});
|
||||
|
||||
await waitFor(() => expect(fetch.mock.calls.length).toBeGreaterThanOrEqual(5));
|
||||
|
||||
await waitFor(() => expect(current.location.pathname).toBe('/login'));
|
||||
expect(current.location.state).toEqual({sessionExpired: true, from: '/dashboard'});
|
||||
await waitFor(() => expect(router.state.location.pathname).toBe('/login'));
|
||||
expect(router.state.location.state).toEqual({sessionExpired: true, from: '/dashboard'});
|
||||
expect(screen.queryByText('LIBRARY.EMPTY')).toBeNull();
|
||||
});
|
||||
|
||||
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 mockDashboard = (settings) => {
|
||||
globalThis.fetch = vi.fn(async (url, init) => {
|
||||
if (init?.method === 'PUT') return {ok: true, status: 204, json: async () => ({})};
|
||||
if (url.includes('/api/users/me')) return ok({email: 'a@b.de', displayName: 'Lobo', steamId: '76561198000000000', settings});
|
||||
if (url.includes('sort=playtimeMinutes') && !url.includes('status=')) return ok({content: games, total: 12});
|
||||
if (url.includes('/api/library/stats')) return ok({trend: null, habits: {moonTrips: 1, longestDay: null, underOneHourGames: 0, underOneHourShare: null}});
|
||||
if (url.includes('/api/library/steam-profile')) return ok({personaName: 'Lobo', avatarUrl: avatar});
|
||||
if (url.includes('/api/library/activity')) return ok({totalMinutes: 90, days: [{day: '2026-09-23', minutes: 0}, {day: '2026-09-24', minutes: 90}], recentGames: [
|
||||
{appid: 1086940, name: "Baldur's Gate 3", playtimeMinutes: 900, playtime2Weeks: 90, status: 'PLAYING'}]});
|
||||
if (url.includes('/api/library/games-count')) return ok({count: 1, hasSteamId: true});
|
||||
if (url.includes('/api/library/achievements/summary')) return ok({unlocked: 24, startedGames: 4, perfectGames: 2,
|
||||
averageCompletion: 71.3, pendingGames: 0, hiddenGames: 0, closest: []});
|
||||
return ok({content: [], total: 0, byStatus: {}});
|
||||
});
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}><PreferencesProvider><CurrentUserProvider>
|
||||
<Dashboard/>
|
||||
</CurrentUserProvider></PreferencesProvider></MemoryRouter>,
|
||||
).container;
|
||||
fetchRoutes(dashboardRoutes(settings));
|
||||
return renderDashboard().container;
|
||||
};
|
||||
|
||||
it('shows the Steam avatar and the most played game of the last two weeks as banner', async () => {
|
||||
@@ -154,19 +147,16 @@ describe('Dashboard', () => {
|
||||
});
|
||||
it('shows the page when a card answers 200 without a JSON body', async () => {
|
||||
// No Steam ID, so no activity and achievements are asked.
|
||||
globalThis.fetch = vi.fn(async (url) => {
|
||||
if (url.includes('/api/users/me')) return ok({email: 'a@b.de', displayName: 'Lobo'});
|
||||
if (url.includes('/api/library/games-count')) return ok({count: 1, hasSteamId: true});
|
||||
if (url.includes('sort=playtimeMinutes') && !url.includes('status=')) {
|
||||
return {ok: true, status: 200, json: async () => { throw new SyntaxError('Unexpected token <'); }};
|
||||
}
|
||||
return ok({content: [], total: 0, byStatus: {}});
|
||||
fetchRoutes({
|
||||
...quietRoutes,
|
||||
'GET /api/library/stats': json(200, empty),
|
||||
'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/games': (url) => (mostPlayed(url)
|
||||
? {ok: true, status: 200, json: async () => { throw new SyntaxError('Unexpected token <'); }}
|
||||
: json(200, empty)),
|
||||
});
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}><PreferencesProvider><CurrentUserProvider>
|
||||
<Dashboard/>
|
||||
</CurrentUserProvider></PreferencesProvider></MemoryRouter>,
|
||||
);
|
||||
renderDashboard();
|
||||
|
||||
expect(await screen.findByText(/DASHBOARD.HELLO/)).toBeTruthy();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user