Public Access
feat(tiles): let users arrange the dashboard and profile tiles
This commit is contained in:
1 parent
b55fcfec92
commit
aa9e83e0cb
24 files changed
+792
-426
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {cleanup, render, screen, waitFor} from '@testing-library/react';
|
||||
import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
||||
import {MemoryRouter, Route, Routes, useLocation} from 'react-router-dom';
|
||||
import Dashboard from './Dashboard.jsx';
|
||||
import {PreferencesProvider} from '../context/PreferencesContext.jsx';
|
||||
@@ -49,9 +49,13 @@ describe('Dashboard', () => {
|
||||
});
|
||||
|
||||
const avatar = 'https://avatars.steamstatic.com/abc_full.jpg';
|
||||
const mockDashboard = () => {
|
||||
globalThis.fetch = vi.fn(async (url) => {
|
||||
if (url.includes('/api/users/me')) return ok({email: 'a@b.de', displayName: 'Lobo', steamId: '76561198000000000'});
|
||||
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'}]});
|
||||
@@ -74,6 +78,55 @@ describe('Dashboard', () => {
|
||||
expect(container.querySelector('img[src$="/1086940/library_hero.jpg"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
const order = (container) => [...container.querySelectorAll('[data-tile]')].map((el) => el.dataset.tile);
|
||||
|
||||
it('shows today\'s tiles without a saved layout', async () => {
|
||||
const container = mockDashboard();
|
||||
|
||||
await screen.findByText('DASHBOARD.ACHIEVEMENTS.TITLE');
|
||||
expect(order(container)).toEqual(['GAME_COUNT', 'MOST_PLAYED', 'PLAYING', 'PILE_OF_SHAME', 'ACTIVITY', 'ACHIEVEMENTS']);
|
||||
expect(screen.getByText('Game 4')).toBeTruthy();
|
||||
expect(screen.queryByText('Game 5')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows a saved layout with its sizes and loads the stats of its stat tiles', async () => {
|
||||
const container = mockDashboard({dashboardTiles: [{id: 'STATS_HABITS', size: 'L'}, {id: 'MOST_PLAYED', size: 'S'}]});
|
||||
|
||||
await screen.findByText('STATS.HABITS.TITLE');
|
||||
expect(order(container)).toEqual(['STATS_HABITS', 'MOST_PLAYED']);
|
||||
expect(screen.getByText('Game 2')).toBeTruthy();
|
||||
expect(screen.queryByText('Game 3')).toBeNull();
|
||||
// No pile tile, so no TREND.
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringMatching(/\/api\/library\/stats\?groups=HABITS$/), expect.anything());
|
||||
});
|
||||
|
||||
it('leaves out a stat tile whose data did not come', async () => {
|
||||
const container = mockDashboard({dashboardTiles: [{id: 'STATS_PILE', size: 'M'}, {id: 'MOST_PLAYED', size: 'M'}]});
|
||||
|
||||
await screen.findByText('Game 0');
|
||||
expect(order(container)).toEqual(['MOST_PLAYED']);
|
||||
});
|
||||
|
||||
it('saves the default layout as "never changed"', async () => {
|
||||
mockDashboard({dashboardTiles: [{id: 'MOST_PLAYED', size: 'S'}]});
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'}));
|
||||
fireEvent.click(screen.getByRole('button', {name: 'TILES.RESET'}));
|
||||
fireEvent.click(screen.getByRole('button', {name: 'TILES.DONE'}));
|
||||
|
||||
await waitFor(() => expect(fetch.mock.calls.some(([, init]) => init?.method === 'PUT')).toBe(true));
|
||||
const [url, init] = fetch.mock.calls.find(([, i]) => i?.method === 'PUT');
|
||||
expect(url).toMatch(/\/api\/users\/settings\/dashboard-tiles$/);
|
||||
expect(JSON.parse(init.body)).toEqual({tiles: null});
|
||||
expect(await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'})).toBeTruthy();
|
||||
});
|
||||
it('puts "Customize" into the welcome card', async () => {
|
||||
mockDashboard();
|
||||
|
||||
const button = await screen.findByRole('button', {name: 'TILES.CUSTOMIZE'});
|
||||
expect(button.closest('[data-slot="card"]')?.textContent ?? 'not in a card').toContain('DASHBOARD.WELCOME_BACK');
|
||||
});
|
||||
|
||||
it('shows the achievement summary', async () => {
|
||||
mockDashboard();
|
||||
|
||||
|
||||
Reference in new issue
Block a user