Public Access
feat(stats): add chart form selection and pie, donut, line charts
Tag rows with 0 games are no longer clickable.
This commit is contained in:
1 parent
8b94a25844
commit
e054e13a59
21 files changed
+815
-56
No files matched your search
@@ -118,7 +118,7 @@ const Dashboard = () => {
|
||||
);
|
||||
case 'ACTIVITY': return hasSteamId && activity && <ActivityCard activity={activity}/>;
|
||||
case 'ACHIEVEMENTS': return hasSteamId && achievements && <AchievementsCard summary={achievements}/>;
|
||||
default: return statGroupTile(id, stats.stats);
|
||||
default: return statGroupTile(id, stats.stats, true, userData?.settings?.statCharts);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -96,6 +96,21 @@ describe('Dashboard', () => {
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringMatching(/\/api\/library\/stats\?groups=HABITS,TREND$/), expect.anything());
|
||||
});
|
||||
|
||||
it('showsAStatTileInTheChartFormOfTheUser', async () => {
|
||||
fetchRoutes({
|
||||
...dashboardRoutes({dashboardTiles: [{id: 'STATS_FINISHED', size: 'M'}], statCharts: {FINISHED: 'LINE'}}),
|
||||
'GET /api/library/stats': json(200, {trend: null, finished: {finishedBeforeTracking: 0,
|
||||
perYear: [{year: 2022, games: 3}, {year: 2024, games: 1}]}}),
|
||||
});
|
||||
renderDashboard();
|
||||
|
||||
expect(await screen.findByRole('button', {name: /2022/})).toBeTruthy();
|
||||
expect(screen.getByRole('button', {name: /2024/})).toBeTruthy();
|
||||
expect(screen.getByText('2023: 0')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', {name: /2023/})).toBeNull();
|
||||
expect(screen.queryByRole('group', {name: 'STATS.CHART.LABEL'})).toBeNull();
|
||||
});
|
||||
|
||||
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'}]});
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ export default function PublicProfilePage() {
|
||||
&& <Playing games={library.playing.slice(0, PLAYING_GAMES[size])} compact={size === 'S'}/>;
|
||||
case 'FINISHED':
|
||||
return library.finished?.length > 0 && <Finished games={library.finished.slice(0, FINISHED_GAMES[size])} equipped={equipped}/>;
|
||||
default: return statGroupTile(id, library.stats, owner);
|
||||
default: return statGroupTile(id, library.stats, owner, profile.style?.statCharts);
|
||||
}
|
||||
};
|
||||
// Core sends only the data of the shown tiles (the ticket says which), so a new tile needs a new load.
|
||||
|
||||
@@ -339,6 +339,73 @@ describe('PublicProfilePage', () => {
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
+30
-4
@@ -1,3 +1,4 @@
|
||||
import {useRef} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {RefreshCw} from 'lucide-react';
|
||||
|
||||
@@ -14,12 +15,37 @@ import FinishedStats from '../components/stats/FinishedStats.jsx';
|
||||
import PlatformStats from '../components/stats/PlatformStats.jsx';
|
||||
import {StatDrillProvider} from '../components/stats/StatDrill.jsx';
|
||||
import useUserStats from '../hooks/useUserStats.js';
|
||||
import useSaveResponse from '../hooks/useSaveResponse.js';
|
||||
import {useCurrentUserContext} from '../context/CurrentUserContext.jsx';
|
||||
import {chartOf, saveStatChart, STAT_CHARTS} from '../utils/statCharts.js';
|
||||
|
||||
const ALL_GROUPS = [];
|
||||
|
||||
export default function StatsPage() {
|
||||
const {t} = useTranslation();
|
||||
const {stats, failed, reload} = useUserStats(ALL_GROUPS);
|
||||
const {user, updateUser} = useCurrentUserContext();
|
||||
const runSave = useSaveResponse();
|
||||
const charts = user?.settings?.statCharts;
|
||||
|
||||
// The new form shows at once; a failed save puts only this group back.
|
||||
const setChart = (group, form) => updateUser((u) => {
|
||||
const next = {...u.settings?.statCharts};
|
||||
if (form) next[group] = form;
|
||||
else delete next[group];
|
||||
return {...u, settings: {...u.settings, statCharts: next}};
|
||||
});
|
||||
const latest = useRef({});
|
||||
const change = async (group, form) => {
|
||||
const before = charts?.[group] ?? null;
|
||||
const sent = form === STAT_CHARTS[group][0] ? null : form;
|
||||
const id = (latest.current[group] ?? 0) + 1;
|
||||
latest.current[group] = id;
|
||||
setChart(group, sent);
|
||||
// A failed older save must not undo a newer choice.
|
||||
if (!await runSave(() => saveStatChart(group, sent)) && latest.current[group] === id) setChart(group, before);
|
||||
};
|
||||
const chartProps = (group) => ({chart: chartOf(charts, group), onChart: (form) => change(group, form)});
|
||||
|
||||
return (
|
||||
<StatDrillProvider source="own">
|
||||
@@ -47,12 +73,12 @@ export default function StatsPage() {
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="md:col-span-2"><OverviewStats overview={stats.overview}/></div>
|
||||
<PileStats pile={stats.pile}/>
|
||||
<TrendStats trend={stats.trend}/>
|
||||
<TrendStats trend={stats.trend} {...chartProps('TREND')}/>
|
||||
<TopGamesStats topGames={stats.topGames}/>
|
||||
<TasteStats taste={stats.taste}/>
|
||||
<PlatformStats platforms={stats.platforms}/>
|
||||
<TasteStats taste={stats.taste} {...chartProps('TASTE')}/>
|
||||
<PlatformStats platforms={stats.platforms} {...chartProps('PLATFORMS')}/>
|
||||
<HabitsStats habits={stats.habits}/>
|
||||
<FinishedStats finished={stats.finished}/>
|
||||
<FinishedStats finished={stats.finished} {...chartProps('FINISHED')}/>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
||||
import {MemoryRouter} from 'react-router-dom';
|
||||
import {cleanup, fireEvent, screen, waitFor, within} from '@testing-library/react';
|
||||
import StatsPage from './StatsPage.jsx';
|
||||
import {PreferencesProvider} from '../context/PreferencesContext.jsx';
|
||||
import {renderWithProviders} from '../test/renderWithProviders.jsx';
|
||||
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
||||
|
||||
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||
|
||||
const answerStats = (answer) => fetchRoutes({'GET /api/library/stats': answer});
|
||||
const me = (statCharts) => json(200, {email: 'a@b.de', settings: {statCharts}});
|
||||
const answerStats = (answer, statCharts) => fetchRoutes({
|
||||
'GET /api/library/stats': answer,
|
||||
'GET /api/users/me': me(statCharts),
|
||||
'PUT /api/users/settings/stat-chart': json(204, {}),
|
||||
});
|
||||
|
||||
const stats = (extra = {}) => ({
|
||||
overview: {totalGames: 12, totalPlaytimeMinutes: 6000, playedGames: 8, neverStarted: 4,
|
||||
@@ -27,7 +31,7 @@ const stats = (extra = {}) => ({
|
||||
...extra,
|
||||
});
|
||||
|
||||
const renderPage = () => render(<MemoryRouter><PreferencesProvider><StatsPage/></PreferencesProvider></MemoryRouter>);
|
||||
const renderPage = () => renderWithProviders(<StatsPage/>);
|
||||
|
||||
describe('StatsPage', () => {
|
||||
beforeEach(() => {
|
||||
@@ -44,8 +48,9 @@ describe('StatsPage', () => {
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByText('STATS.OVERVIEW.TITLE')).toBeTruthy();
|
||||
expect(fetch).toHaveBeenCalledOnce();
|
||||
expect(fetch.mock.calls[0][0]).toMatch(/\/api\/library\/stats$/);
|
||||
const statsCalls = fetch.mock.calls.filter(([url]) => url.includes('/api/library/stats'));
|
||||
expect(statsCalls).toHaveLength(1);
|
||||
expect(statsCalls[0][0]).toMatch(/\/api\/library\/stats$/);
|
||||
for (const title of ['PILE', 'TREND', 'TOP_GAMES', 'TASTE', 'HABITS', 'FINISHED', 'PLATFORMS']) {
|
||||
expect(screen.getByText(`STATS.${title}.TITLE`)).toBeTruthy();
|
||||
}
|
||||
@@ -87,12 +92,13 @@ describe('StatsPage', () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', {name: /DASHBOARD.RETRY/}));
|
||||
|
||||
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(2));
|
||||
await waitFor(() => expect(fetch.mock.calls.filter(([url]) => url.includes('/api/library/stats'))).toHaveLength(2));
|
||||
});
|
||||
|
||||
it('opensTheGamesBehindARowWithTheRawName', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/library/stats': json(200, stats()),
|
||||
'GET /api/users/me': me(),
|
||||
'GET /api/library/stats/games': json(200, {content: [{appid: 620, name: 'Portal 2', status: 'BACKLOG', playtimeMinutes: 60}],
|
||||
page: 0, size: 40, totalElements: 1, totalPages: 1}),
|
||||
});
|
||||
@@ -149,4 +155,197 @@ describe('StatsPage', () => {
|
||||
|
||||
expect(screen.queryByRole('button', {name: /STATS.HABITS.MOON/})).toBeNull();
|
||||
});
|
||||
|
||||
describe('chart forms', () => {
|
||||
const section = (title) => screen.getByText(title).closest('[data-slot="card"]');
|
||||
const picker = (title) => within(section(title)).getByRole('group', {name: 'STATS.CHART.LABEL'});
|
||||
const putBodies = () => fetch.mock.calls.filter(([, init]) => init?.method === 'PUT').map(([, init]) => JSON.parse(init.body));
|
||||
const slices = (title) => section(title).querySelectorAll('path.stroke-card');
|
||||
|
||||
it('showsBarsAsTodayAndAPickerPerChartableGroup', async () => {
|
||||
answerStats(json(200, stats()));
|
||||
renderPage();
|
||||
|
||||
await screen.findByText('STATS.PLATFORMS.TITLE');
|
||||
|
||||
expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(0);
|
||||
for (const title of ['PLATFORMS', 'TASTE', 'FINISHED', 'TREND']) {
|
||||
expect(within(picker(`STATS.${title}.TITLE`)).getAllByRole('button').length).toBeGreaterThan(1);
|
||||
}
|
||||
expect(within(section('STATS.TOP_GAMES.TITLE')).queryByRole('group', {name: 'STATS.CHART.LABEL'})).toBeNull();
|
||||
expect(within(picker('STATS.PLATFORMS.TITLE')).getByRole('button', {name: 'STATS.CHART.BARS'}).getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
|
||||
it('savesThePickedFormAndSavesNullForTheDefault', async () => {
|
||||
answerStats(json(200, stats()));
|
||||
renderPage();
|
||||
await screen.findByText('STATS.PLATFORMS.TITLE');
|
||||
|
||||
fireEvent.click(within(picker('STATS.PLATFORMS.TITLE')).getByRole('button', {name: 'STATS.CHART.PIE'}));
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(2));
|
||||
expect(putBodies()).toEqual([{group: 'PLATFORMS', chart: 'PIE'}]);
|
||||
|
||||
fireEvent.click(within(picker('STATS.PLATFORMS.TITLE')).getByRole('button', {name: 'STATS.CHART.BARS'}));
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(0));
|
||||
expect(putBodies().at(-1)).toEqual({group: 'PLATFORMS', chart: null});
|
||||
});
|
||||
|
||||
it('showsTheStoredFormOfTheUser', async () => {
|
||||
answerStats(json(200, stats()), {PLATFORMS: 'DONUT', TREND: 'BARS'});
|
||||
renderPage();
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(2));
|
||||
expect(screen.queryByTestId('pile-trend')).toBeNull();
|
||||
});
|
||||
|
||||
it('putsTheOldFormBackWhenTheSaveFails', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/library/stats': json(200, stats()),
|
||||
'GET /api/users/me': me(),
|
||||
'PUT /api/users/settings/stat-chart': json(500, {}),
|
||||
});
|
||||
renderPage();
|
||||
await screen.findByText('STATS.PLATFORMS.TITLE');
|
||||
|
||||
fireEvent.click(within(picker('STATS.PLATFORMS.TITLE')).getByRole('button', {name: 'STATS.CHART.PIE'}));
|
||||
|
||||
await waitFor(() => expect(putBodies()).toHaveLength(1));
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(0));
|
||||
expect(within(picker('STATS.PLATFORMS.TITLE')).getByRole('button', {name: 'STATS.CHART.BARS'}).getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
|
||||
it('aLegendRowOpensTheGamesWithThePlatform', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/library/stats': json(200, stats()),
|
||||
'GET /api/users/me': me({PLATFORMS: 'PIE'}),
|
||||
'GET /api/library/stats/games': json(200, {content: [], page: 0, size: 40, totalElements: 0, totalPages: 0}),
|
||||
});
|
||||
renderPage();
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(2));
|
||||
fireEvent.click(await within(section('STATS.PLATFORMS.TITLE')).findByRole('button', {name: /PS5/}));
|
||||
|
||||
expect(await screen.findByRole('dialog')).toBeTruthy();
|
||||
await waitFor(() => expect(fetch.mock.calls.at(-1)[0]).toMatch(/stats\/games\?key=PLATFORM&arg=PS5&page=0$/));
|
||||
});
|
||||
|
||||
it('theOtherSliceIsNotAButtonAndHoldsTheRest', async () => {
|
||||
const perPlatform = Array.from({length: 8}, (_, i) => ({name: `P${i}`, games: 8 - i, minutes: 10}));
|
||||
answerStats(json(200, stats({platforms: {perPlatform}})), {PLATFORMS: 'PIE'});
|
||||
renderPage();
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(6));
|
||||
const legend = within(section('STATS.PLATFORMS.TITLE'));
|
||||
expect(legend.getAllByRole('button', {name: /P\d/})).toHaveLength(5);
|
||||
expect(legend.getByText('STATS.CHART.OTHER').closest('button')).toBeNull();
|
||||
expect(legend.getByText('STATS.TASTE.GAMES:6')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('aPlatformRowWithoutGamesIsNoButtonInBarsAndList', async () => {
|
||||
const perPlatform = [{name: 'Steam', games: 3, minutes: 5}, {name: 'Switch', games: 0, minutes: 0}];
|
||||
for (const form of ['BARS', 'LIST']) {
|
||||
answerStats(json(200, stats({platforms: {perPlatform}})), {PLATFORMS: form});
|
||||
renderPage();
|
||||
|
||||
const platforms = within(await screen.findByText('STATS.PLATFORMS.TITLE').then(() => section('STATS.PLATFORMS.TITLE')));
|
||||
expect(platforms.getByRole('button', {name: /Steam/})).toBeTruthy();
|
||||
expect(platforms.getByText('Switch')).toBeTruthy();
|
||||
expect(platforms.queryByRole('button', {name: /Switch/})).toBeNull();
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('aPlatformWithoutGamesGetsNoSlice', async () => {
|
||||
answerStats(json(200, stats({platforms: {perPlatform: [
|
||||
{name: 'Steam', games: 3, minutes: 5}, {name: 'Switch', games: 0, minutes: 0}]}})), {PLATFORMS: 'PIE'});
|
||||
renderPage();
|
||||
|
||||
await waitFor(() => expect(slices('STATS.PLATFORMS.TITLE')).toHaveLength(1));
|
||||
expect(within(section('STATS.PLATFORMS.TITLE')).queryByText('Switch')).toBeNull();
|
||||
});
|
||||
|
||||
it('showsTheEmptyTextForEveryForm', async () => {
|
||||
answerStats(json(200, stats({platforms: {perPlatform: []}})), {PLATFORMS: 'PIE'});
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByText('STATS.PLATFORMS.EMPTY')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('aLineYearOpensItsGamesAndAYearWithoutGamesIsNoButton', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/library/stats': json(200, stats({finished: {finishedBeforeTracking: 0,
|
||||
perYear: [{year: 2022, games: 3}, {year: 2024, games: 1}]}})),
|
||||
'GET /api/users/me': me({FINISHED: 'LINE'}),
|
||||
'GET /api/library/stats/games': json(200, {content: [], page: 0, size: 40, totalElements: 0, totalPages: 0}),
|
||||
});
|
||||
renderPage();
|
||||
|
||||
const finished = within(await screen.findByText('STATS.FINISHED.TITLE').then(() => section('STATS.FINISHED.TITLE')));
|
||||
await waitFor(() => expect(finished.getByRole('button', {name: /2022/})).toBeTruthy());
|
||||
expect(finished.getByRole('button', {name: /2024/})).toBeTruthy();
|
||||
expect(finished.queryByRole('button', {name: /2023/})).toBeNull();
|
||||
expect(finished.getByText('2023: 0')).toBeTruthy();
|
||||
const circles = [...section('STATS.FINISHED.TITLE').querySelectorAll('circle')];
|
||||
expect(circles).toHaveLength(3);
|
||||
expect(circles.filter((c) => c.getAttribute('class').includes('cursor-pointer'))).toHaveLength(2);
|
||||
|
||||
fireEvent.click(finished.getByRole('button', {name: /2022/}));
|
||||
|
||||
await waitFor(() => expect(fetch.mock.calls.at(-1)[0]).toMatch(/stats\/games\?key=FINISHED_YEAR&arg=2022&page=0$/));
|
||||
});
|
||||
|
||||
it('aLineWithOneYearDrawsBars', async () => {
|
||||
answerStats(json(200, stats()), {FINISHED: 'LINE'});
|
||||
renderPage();
|
||||
|
||||
const finished = within(await screen.findByText('STATS.FINISHED.TITLE').then(() => section('STATS.FINISHED.TITLE')));
|
||||
await waitFor(() => expect(finished.getByRole('button', {name: /2026/})).toBeTruthy());
|
||||
expect(section('STATS.FINISHED.TITLE').querySelector('polyline')).toBeNull();
|
||||
});
|
||||
|
||||
it('aDonutOfTheTasteOpensTheGenreWithItsRawNameAndShowsTheOverlapNote', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/library/stats': json(200, stats()),
|
||||
'GET /api/users/me': me({TASTE: 'DONUT'}),
|
||||
'GET /api/library/stats/games': json(200, {content: [], page: 0, size: 40, totalElements: 0, totalPages: 0}),
|
||||
});
|
||||
renderPage();
|
||||
|
||||
const taste = within(await screen.findByText('STATS.TASTE.TITLE').then(() => section('STATS.TASTE.TITLE')));
|
||||
await waitFor(() => expect(taste.getAllByText('STATS.CHART.OVERLAP')).toHaveLength(2));
|
||||
fireEvent.click(taste.getByRole('button', {name: /STATS\.GENRE/}));
|
||||
|
||||
await waitFor(() => expect(fetch.mock.calls.at(-1)[0]).toMatch(/stats\/games\?key=GENRE&arg=Shooter&page=0$/));
|
||||
});
|
||||
|
||||
it('theTrendBarsStayOneClickTargetWithAListOfTheDays', async () => {
|
||||
answerStats(json(200, stats()), {TREND: 'BARS'});
|
||||
renderPage();
|
||||
|
||||
const trend = within(await screen.findByText('STATS.TREND.TITLE').then(() => section('STATS.TREND.TITLE')));
|
||||
await waitFor(() => expect(section('STATS.TREND.TITLE').querySelectorAll('rect')).toHaveLength(2));
|
||||
expect(trend.getAllByRole('button', {name: 'STATS.DRILL.TREND_MOVES'})).toHaveLength(1);
|
||||
expect(section('STATS.TREND.TITLE').querySelectorAll('table tr')).toHaveLength(2);
|
||||
});
|
||||
|
||||
// REVIEW-FINDING: the picker buttons sit inside the h2, so the heading name becomes "title + Chart Bars Pie ..."
|
||||
it('reviewFinding_thePickerIsNotPartOfTheHeadingName', async () => {
|
||||
answerStats(json(200, stats()));
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByRole('heading', {name: 'STATS.PLATFORMS.TITLE'})).toBeTruthy();
|
||||
});
|
||||
|
||||
// REVIEW-FINDING: an empty tag list in pie/donut form draws an empty chart box and the overlap note
|
||||
it('reviewFinding_anEmptyMoodListInThePieShowsNoChartAndNoNote', async () => {
|
||||
answerStats(json(200, stats({taste: {genres: [{name: 'Shooter', games: 2, minutes: 1200}], moods: []}})),
|
||||
{TASTE: 'PIE'});
|
||||
renderPage();
|
||||
|
||||
await waitFor(() => expect(slices('STATS.TASTE.TITLE')).toHaveLength(1));
|
||||
expect(within(section('STATS.TASTE.TITLE')).getAllByText('STATS.CHART.OVERLAP')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user