Public Access
feat(stats): add platform statistics and landing blocks
Add PlatformStats component to stats page. Extract YearBars from FinishedStats for reuse on landing. Add three blocks to landing: games per platform, top genres, finished per year. Update translations.
This commit is contained in:
1 parent
b129f6a6e4
commit
22633a9e45
12 files changed
+143
-18
No files matched your search
@@ -30,7 +30,9 @@ To Be Played is split into three repositories:
|
|||||||
repeats, and "Start playing" in one click.
|
repeats, and "Start playing" in one click.
|
||||||
- **Your stats** (`/stats`): totals and statuses; the pile in hours, in weeks at your pace of the last 90 days and in
|
- **Your stats** (`/stats`): totals and statuses; the pile in hours, in weeks at your pace of the last 90 days and in
|
||||||
years of full-time work, and its oldest unplayed game; most played and most loyal games; top genres and moods;
|
years of full-time work, and its oldest unplayed game; most played and most loyal games; top genres and moods;
|
||||||
trips to the moon, the longest gaming day, games started but under one hour; finished games per year.
|
trips to the moon, the longest gaming day, games started but under one hour; finished games per year; games per platform.
|
||||||
|
- **Landing page**: next to the community numbers and the top games, three blocks: games per platform, top genres and
|
||||||
|
finished games per year of all users.
|
||||||
- **Library**: status filters, "short games" filter (up to 10 h), search, sorting by playtime, recently seen, newest
|
- **Library**: status filters, "short games" filter (up to 10 h), search, sorting by playtime, recently seen, newest
|
||||||
in library, rating or priority, paging. Each row shows the unlocked achievements.
|
in library, rating or priority, paging. Each row shows the unlocked achievements.
|
||||||
- **Add a game by name** for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit,
|
- **Add a game by name** for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit,
|
||||||
|
|||||||
+8
-1
@@ -271,7 +271,10 @@
|
|||||||
"BIGGEST_LIBRARY": "Größte Bibliothek",
|
"BIGGEST_LIBRARY": "Größte Bibliothek",
|
||||||
"BIGGEST_BACKLOG": "Größter Backlog",
|
"BIGGEST_BACKLOG": "Größter Backlog",
|
||||||
"TOP_PLAYED": "Meistgespielte Spiele",
|
"TOP_PLAYED": "Meistgespielte Spiele",
|
||||||
"TOP_OWNED": "Meistbesessene Spiele"
|
"TOP_OWNED": "Meistbesessene Spiele",
|
||||||
|
"PLATFORMS": "Spiele pro Plattform",
|
||||||
|
"TOP_GENRES": "Top-Genres",
|
||||||
|
"FINISHED_PER_YEAR": "Abgeschlossen pro Jahr"
|
||||||
},
|
},
|
||||||
"MAIN": {
|
"MAIN": {
|
||||||
"WELCOME": "Dein Game-Backlog unter Kontrolle",
|
"WELCOME": "Dein Game-Backlog unter Kontrolle",
|
||||||
@@ -476,6 +479,10 @@
|
|||||||
"BEFORE_one": "{{count}} weiteres Spiel wurde vor Beginn des Verlaufs durchgespielt.",
|
"BEFORE_one": "{{count}} weiteres Spiel wurde vor Beginn des Verlaufs durchgespielt.",
|
||||||
"BEFORE_other": "{{count}} weitere Spiele wurden vor Beginn des Verlaufs durchgespielt."
|
"BEFORE_other": "{{count}} weitere Spiele wurden vor Beginn des Verlaufs durchgespielt."
|
||||||
},
|
},
|
||||||
|
"PLATFORMS": {
|
||||||
|
"TITLE": "Spiele pro Plattform",
|
||||||
|
"EMPTY": "Noch keine Spiele."
|
||||||
|
},
|
||||||
"GENRE": {
|
"GENRE": {
|
||||||
"ADVENTURE": "Adventure",
|
"ADVENTURE": "Adventure",
|
||||||
"ARCADE": "Arcade",
|
"ARCADE": "Arcade",
|
||||||
|
|||||||
+8
-1
@@ -271,7 +271,10 @@
|
|||||||
"BIGGEST_LIBRARY": "Biggest library",
|
"BIGGEST_LIBRARY": "Biggest library",
|
||||||
"BIGGEST_BACKLOG": "Biggest backlog",
|
"BIGGEST_BACKLOG": "Biggest backlog",
|
||||||
"TOP_PLAYED": "Most played games",
|
"TOP_PLAYED": "Most played games",
|
||||||
"TOP_OWNED": "Most owned games"
|
"TOP_OWNED": "Most owned games",
|
||||||
|
"PLATFORMS": "Games per platform",
|
||||||
|
"TOP_GENRES": "Top genres",
|
||||||
|
"FINISHED_PER_YEAR": "Finished per year"
|
||||||
},
|
},
|
||||||
"MAIN": {
|
"MAIN": {
|
||||||
"WELCOME": "Control your game backlog",
|
"WELCOME": "Control your game backlog",
|
||||||
@@ -476,6 +479,10 @@
|
|||||||
"BEFORE_one": "{{count}} more game was finished before the history started.",
|
"BEFORE_one": "{{count}} more game was finished before the history started.",
|
||||||
"BEFORE_other": "{{count}} more games were finished before the history started."
|
"BEFORE_other": "{{count}} more games were finished before the history started."
|
||||||
},
|
},
|
||||||
|
"PLATFORMS": {
|
||||||
|
"TITLE": "Games per platform",
|
||||||
|
"EMPTY": "No games yet."
|
||||||
|
},
|
||||||
"GENRE": {
|
"GENRE": {
|
||||||
"ADVENTURE": "Adventure",
|
"ADVENTURE": "Adventure",
|
||||||
"ARCADE": "Arcade",
|
"ARCADE": "Arcade",
|
||||||
|
|||||||
@@ -3,26 +3,32 @@ import {Flag} from 'lucide-react';
|
|||||||
|
|
||||||
import StatsSection from './StatsSection.jsx';
|
import StatsSection from './StatsSection.jsx';
|
||||||
|
|
||||||
|
export function YearBars({perYear}) {
|
||||||
|
const max = Math.max(1, ...perYear.map((y) => y.games));
|
||||||
|
return (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{perYear.map((y) => (
|
||||||
|
<li key={y.year} className="flex items-center gap-3 text-sm">
|
||||||
|
<span className="w-12 tabular-nums">{y.year}</span>
|
||||||
|
<div className="h-2 flex-1 rounded-full bg-muted">
|
||||||
|
<div className="h-full rounded-full bg-primary"
|
||||||
|
style={{width: `${(y.games / max) * 100}%`}}/>
|
||||||
|
</div>
|
||||||
|
<span className="w-8 text-right tabular-nums">{y.games}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function FinishedStats({finished}) {
|
export default function FinishedStats({finished}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const max = Math.max(1, ...finished.perYear.map((y) => y.games));
|
|
||||||
return (
|
return (
|
||||||
<StatsSection title={t('STATS.FINISHED.TITLE')} icon={Flag}>
|
<StatsSection title={t('STATS.FINISHED.TITLE')} icon={Flag}>
|
||||||
{finished.perYear.length === 0 ? (
|
{finished.perYear.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">{t('STATS.FINISHED.EMPTY')}</p>
|
<p className="text-sm text-muted-foreground">{t('STATS.FINISHED.EMPTY')}</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="space-y-2">
|
<YearBars perYear={finished.perYear}/>
|
||||||
{finished.perYear.map((y) => (
|
|
||||||
<li key={y.year} className="flex items-center gap-3 text-sm">
|
|
||||||
<span className="w-12 tabular-nums">{y.year}</span>
|
|
||||||
<div className="h-2 flex-1 rounded-full bg-muted">
|
|
||||||
<div className="h-full rounded-full bg-primary"
|
|
||||||
style={{width: `${(y.games / max) * 100}%`}}/>
|
|
||||||
</div>
|
|
||||||
<span className="w-8 text-right tabular-nums">{y.games}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
)}
|
||||||
{finished.finishedBeforeTracking > 0 && (
|
{finished.finishedBeforeTracking > 0 && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {useTranslation} from 'react-i18next';
|
||||||
|
import {MonitorSmartphone} from 'lucide-react';
|
||||||
|
|
||||||
|
import StatsSection from './StatsSection.jsx';
|
||||||
|
import {TagBars} from './TasteStats.jsx';
|
||||||
|
|
||||||
|
export default function PlatformStats({platforms}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
return (
|
||||||
|
<StatsSection title={t('STATS.PLATFORMS.TITLE')} icon={MonitorSmartphone}>
|
||||||
|
{platforms.perPlatform.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">{t('STATS.PLATFORMS.EMPTY')}</p>
|
||||||
|
) : (
|
||||||
|
<TagBars tags={platforms.perPlatform}/>
|
||||||
|
)}
|
||||||
|
</StatsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import StatsSection from './StatsSection.jsx';
|
|||||||
import {formatPlaytime} from '../../utils/games.js';
|
import {formatPlaytime} from '../../utils/games.js';
|
||||||
import {genreKey} from '../../utils/stats.js';
|
import {genreKey} from '../../utils/stats.js';
|
||||||
|
|
||||||
function TagBars({tags, label}) {
|
export function TagBars({tags, label = (name) => name}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const max = Math.max(1, ...tags.map((tag) => tag.minutes));
|
const max = Math.max(1, ...tags.map((tag) => tag.minutes));
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import TopGamesStats from './TopGamesStats.jsx';
|
|||||||
import TasteStats from './TasteStats.jsx';
|
import TasteStats from './TasteStats.jsx';
|
||||||
import HabitsStats from './HabitsStats.jsx';
|
import HabitsStats from './HabitsStats.jsx';
|
||||||
import FinishedStats from './FinishedStats.jsx';
|
import FinishedStats from './FinishedStats.jsx';
|
||||||
|
import PlatformStats from './PlatformStats.jsx';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One stat group as a tile; null while its data is not there. A function, not a component: a tile grid leaves out a
|
* One stat group as a tile; null while its data is not there. A function, not a component: a tile grid leaves out a
|
||||||
@@ -22,6 +23,7 @@ export default function statGroupTile(id, stats, linked = true) {
|
|||||||
case 'STATS_TASTE': return stats?.taste ? <TasteStats taste={stats.taste}/> : null;
|
case 'STATS_TASTE': return stats?.taste ? <TasteStats taste={stats.taste}/> : null;
|
||||||
case 'STATS_HABITS': return stats?.habits ? <HabitsStats habits={stats.habits}/> : null;
|
case 'STATS_HABITS': return stats?.habits ? <HabitsStats habits={stats.habits}/> : null;
|
||||||
case 'STATS_FINISHED': return stats?.finished ? <FinishedStats finished={stats.finished}/> : null;
|
case 'STATS_FINISHED': return stats?.finished ? <FinishedStats finished={stats.finished}/> : null;
|
||||||
|
case 'STATS_PLATFORMS': return stats?.platforms ? <PlatformStats platforms={stats.platforms}/> : null;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,10 @@ import {Skeleton} from '@/components/ui/skeleton';
|
|||||||
import GameCover from '../components/GameCover.jsx';
|
import GameCover from '../components/GameCover.jsx';
|
||||||
import Logo from '../components/Logo.jsx';
|
import Logo from '../components/Logo.jsx';
|
||||||
import StatCard from '../components/StatCard.jsx';
|
import StatCard from '../components/StatCard.jsx';
|
||||||
|
import {YearBars} from '../components/stats/FinishedStats.jsx';
|
||||||
|
import {TagBars} from '../components/stats/TasteStats.jsx';
|
||||||
import useSession from '../hooks/useSession.js';
|
import useSession from '../hooks/useSession.js';
|
||||||
|
import {genreKey} from '../utils/stats.js';
|
||||||
|
|
||||||
import usePublicStats from '../hooks/usePublicStats.js';
|
import usePublicStats from '../hooks/usePublicStats.js';
|
||||||
|
|
||||||
@@ -36,6 +39,15 @@ function RankedGames({title, games, formatValue}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BarsCard({title, children}) {
|
||||||
|
return (
|
||||||
|
<Card className="gap-0 py-0">
|
||||||
|
<h3 className="flex items-center gap-2 border-b px-5 py-4 font-semibold">{title}</h3>
|
||||||
|
<div className="px-5 py-4">{children}</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
const {t, i18n} = useTranslation();
|
const {t, i18n} = useTranslation();
|
||||||
const {data, isLoading} = usePublicStats();
|
const {data, isLoading} = usePublicStats();
|
||||||
@@ -102,6 +114,25 @@ export default function LandingPage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
|
{core?.gamesPerPlatform?.length > 0 && (
|
||||||
|
<BarsCard title={t('LANDING.PLATFORMS')}>
|
||||||
|
<TagBars tags={core.gamesPerPlatform}/>
|
||||||
|
</BarsCard>
|
||||||
|
)}
|
||||||
|
{core?.topGenres?.length > 0 && (
|
||||||
|
<BarsCard title={t('LANDING.TOP_GENRES')}>
|
||||||
|
<TagBars tags={core.topGenres}
|
||||||
|
label={(name) => t(`STATS.GENRE.${genreKey(name)}`, {defaultValue: name})}/>
|
||||||
|
</BarsCard>
|
||||||
|
)}
|
||||||
|
{core?.finishedPerYear?.length > 0 && (
|
||||||
|
<BarsCard title={t('LANDING.FINISHED_PER_YEAR')}>
|
||||||
|
<YearBars perYear={core.finishedPerYear}/>
|
||||||
|
</BarsCard>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
import {cleanup, render, screen} from '@testing-library/react';
|
||||||
|
import {MemoryRouter} from 'react-router-dom';
|
||||||
|
import LandingPage from './LandingPage.jsx';
|
||||||
|
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||||
|
vi.mock('../hooks/useSession.js', () => ({default: () => ({active: false})}));
|
||||||
|
|
||||||
|
// Auth and Core answer the same path; one object serves both shapes.
|
||||||
|
const answer = (core) => fetchRoutes({'GET /api/stats/public': json(200, {topPlayedGames: [], topOwnedGames: [], ...core})});
|
||||||
|
|
||||||
|
const renderPage = () => render(<MemoryRouter><LandingPage/></MemoryRouter>);
|
||||||
|
|
||||||
|
describe('LandingPage', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('shows games per platform, top genres and finished per year', async () => {
|
||||||
|
answer({
|
||||||
|
gamesPerPlatform: [{name: 'PS5', games: 3, minutes: 600}],
|
||||||
|
topGenres: [{name: 'Shooter', games: 2, minutes: 1200}],
|
||||||
|
finishedPerYear: [{year: 2025, games: 4}],
|
||||||
|
});
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
expect(await screen.findByText('LANDING.PLATFORMS')).toBeTruthy();
|
||||||
|
expect(screen.getByText('PS5')).toBeTruthy();
|
||||||
|
expect(screen.getByText('LANDING.TOP_GENRES')).toBeTruthy();
|
||||||
|
expect(screen.getByText('STATS.GENRE.SHOOTER')).toBeTruthy();
|
||||||
|
expect(screen.getByText('LANDING.FINISHED_PER_YEAR')).toBeTruthy();
|
||||||
|
expect(screen.getByText('2025')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the three blocks while their lists are empty', async () => {
|
||||||
|
answer({gamesPerPlatform: [], topGenres: [], finishedPerYear: []});
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await screen.findByText('LANDING.STATS_TITLE');
|
||||||
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledTimes(2));
|
||||||
|
for (const title of ['PLATFORMS', 'TOP_GENRES', 'FINISHED_PER_YEAR']) {
|
||||||
|
expect(screen.queryByText(`LANDING.${title}`)).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,6 +11,7 @@ import TopGamesStats from '../components/stats/TopGamesStats.jsx';
|
|||||||
import TasteStats from '../components/stats/TasteStats.jsx';
|
import TasteStats from '../components/stats/TasteStats.jsx';
|
||||||
import HabitsStats from '../components/stats/HabitsStats.jsx';
|
import HabitsStats from '../components/stats/HabitsStats.jsx';
|
||||||
import FinishedStats from '../components/stats/FinishedStats.jsx';
|
import FinishedStats from '../components/stats/FinishedStats.jsx';
|
||||||
|
import PlatformStats from '../components/stats/PlatformStats.jsx';
|
||||||
import useUserStats from '../hooks/useUserStats.js';
|
import useUserStats from '../hooks/useUserStats.js';
|
||||||
|
|
||||||
const ALL_GROUPS = [];
|
const ALL_GROUPS = [];
|
||||||
@@ -47,6 +48,7 @@ export default function StatsPage() {
|
|||||||
<TrendStats trend={stats.trend}/>
|
<TrendStats trend={stats.trend}/>
|
||||||
<TopGamesStats topGames={stats.topGames}/>
|
<TopGamesStats topGames={stats.topGames}/>
|
||||||
<TasteStats taste={stats.taste}/>
|
<TasteStats taste={stats.taste}/>
|
||||||
|
<PlatformStats platforms={stats.platforms}/>
|
||||||
<HabitsStats habits={stats.habits}/>
|
<HabitsStats habits={stats.habits}/>
|
||||||
<FinishedStats finished={stats.finished}/>
|
<FinishedStats finished={stats.finished}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const stats = (extra = {}) => ({
|
|||||||
taste: {genres: [{name: 'Shooter', games: 2, minutes: 1200}], moods: [{name: 'CHILL', games: 1, minutes: 900}]},
|
taste: {genres: [{name: 'Shooter', games: 2, minutes: 1200}], moods: [{name: 'CHILL', games: 1, minutes: 900}]},
|
||||||
habits: {moonTrips: 0.5, longestDay: {day: '2026-07-01', minutes: 200}, underOneHourGames: 1, underOneHourShare: 0.125},
|
habits: {moonTrips: 0.5, longestDay: {day: '2026-07-01', minutes: 200}, underOneHourGames: 1, underOneHourShare: 0.125},
|
||||||
finished: {perYear: [{year: 2026, games: 2}], finishedBeforeTracking: 1},
|
finished: {perYear: [{year: 2026, games: 2}], finishedBeforeTracking: 1},
|
||||||
|
platforms: {perPlatform: [{name: 'Steam', games: 10, minutes: 5000}, {name: 'PS5', games: 2, minutes: 600}]},
|
||||||
trend: {days: [{day: '2026-09-26', backlogGames: 6, neverPlayed: 4, backlogMinutes: 5000},
|
trend: {days: [{day: '2026-09-26', backlogGames: 6, neverPlayed: 4, backlogMinutes: 5000},
|
||||||
{day: '2026-09-27', backlogGames: 5, neverPlayed: 4, backlogMinutes: 4200}], thisMonth: {added: 1, done: 2}},
|
{day: '2026-09-27', backlogGames: 5, neverPlayed: 4, backlogMinutes: 4200}], thisMonth: {added: 1, done: 2}},
|
||||||
...extra,
|
...extra,
|
||||||
@@ -45,11 +46,12 @@ describe('StatsPage', () => {
|
|||||||
expect(await screen.findByText('STATS.OVERVIEW.TITLE')).toBeTruthy();
|
expect(await screen.findByText('STATS.OVERVIEW.TITLE')).toBeTruthy();
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetch).toHaveBeenCalledOnce();
|
||||||
expect(fetch.mock.calls[0][0]).toMatch(/\/api\/library\/stats$/);
|
expect(fetch.mock.calls[0][0]).toMatch(/\/api\/library\/stats$/);
|
||||||
for (const title of ['PILE', 'TREND', 'TOP_GAMES', 'TASTE', 'HABITS', 'FINISHED']) {
|
for (const title of ['PILE', 'TREND', 'TOP_GAMES', 'TASTE', 'HABITS', 'FINISHED', 'PLATFORMS']) {
|
||||||
expect(screen.getByText(`STATS.${title}.TITLE`)).toBeTruthy();
|
expect(screen.getByText(`STATS.${title}.TITLE`)).toBeTruthy();
|
||||||
}
|
}
|
||||||
expect(screen.getAllByText('Portal 2')).toHaveLength(2);
|
expect(screen.getAllByText('Portal 2')).toHaveLength(2);
|
||||||
expect(screen.getByText('Half-Life 2')).toBeTruthy();
|
expect(screen.getByText('Half-Life 2')).toBeTruthy();
|
||||||
|
expect(screen.getByText('PS5')).toBeTruthy();
|
||||||
expect(screen.getByText('STATS.PILE.PACE')).toBeTruthy();
|
expect(screen.getByText('STATS.PILE.PACE')).toBeTruthy();
|
||||||
expect(screen.getByText('STATS.FINISHED.BEFORE:1')).toBeTruthy();
|
expect(screen.getByText('STATS.FINISHED.BEFORE:1')).toBeTruthy();
|
||||||
expect(screen.getByTestId('pile-trend')).toBeTruthy();
|
expect(screen.getByTestId('pile-trend')).toBeTruthy();
|
||||||
@@ -64,6 +66,7 @@ describe('StatsPage', () => {
|
|||||||
taste: {genres: [], moods: []},
|
taste: {genres: [], moods: []},
|
||||||
habits: {moonTrips: 0, longestDay: null, underOneHourGames: 0, underOneHourShare: null},
|
habits: {moonTrips: 0, longestDay: null, underOneHourGames: 0, underOneHourShare: null},
|
||||||
finished: {perYear: [], finishedBeforeTracking: 0},
|
finished: {perYear: [], finishedBeforeTracking: 0},
|
||||||
|
platforms: {perPlatform: []},
|
||||||
trend: {days: [empty.trend.days[1]], thisMonth: {added: 0, done: 0}},
|
trend: {days: [empty.trend.days[1]], thisMonth: {added: 0, done: 0}},
|
||||||
}));
|
}));
|
||||||
renderPage();
|
renderPage();
|
||||||
@@ -72,6 +75,7 @@ describe('StatsPage', () => {
|
|||||||
expect(screen.getByText('STATS.TOP_GAMES.EMPTY')).toBeTruthy();
|
expect(screen.getByText('STATS.TOP_GAMES.EMPTY')).toBeTruthy();
|
||||||
expect(screen.getByText('STATS.TASTE.EMPTY')).toBeTruthy();
|
expect(screen.getByText('STATS.TASTE.EMPTY')).toBeTruthy();
|
||||||
expect(screen.getByText('STATS.FINISHED.EMPTY')).toBeTruthy();
|
expect(screen.getByText('STATS.FINISHED.EMPTY')).toBeTruthy();
|
||||||
|
expect(screen.getByText('STATS.PLATFORMS.EMPTY')).toBeTruthy();
|
||||||
expect(screen.getByText('STATS.TREND.STARTS')).toBeTruthy();
|
expect(screen.getByText('STATS.TREND.STARTS')).toBeTruthy();
|
||||||
expect(screen.queryByText('STATS.HABITS.LONGEST_DAY')).toBeNull();
|
expect(screen.queryByText('STATS.HABITS.LONGEST_DAY')).toBeNull();
|
||||||
expect(screen.queryByTestId('pile-trend')).toBeNull();
|
expect(screen.queryByTestId('pile-trend')).toBeNull();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const STAT_TILES = {
|
|||||||
STATS_TASTE: {M: 6, L: 12},
|
STATS_TASTE: {M: 6, L: 12},
|
||||||
STATS_HABITS: {M: 6, L: 12},
|
STATS_HABITS: {M: 6, L: 12},
|
||||||
STATS_FINISHED: {M: 6, L: 12},
|
STATS_FINISHED: {M: 6, L: 12},
|
||||||
|
STATS_PLATFORMS: {M: 6, L: 12},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DASHBOARD_TILES = {
|
export const DASHBOARD_TILES = {
|
||||||
|
|||||||
Reference in new issue
Block a user