import { beforeEach, expect, it, vi } from 'vitest'; import { render } from 'svelte/server'; import { writable, type Writable } from 'svelte/store'; import { page } from '$app/stores'; import Leaderboard from '../../src/routes/leaderboard/+page.svelte'; import { createLeaderboardUpdates, leaderboardPageSize } from '../../src/lib/leaderboard-updates.js'; import type { LeaderboardEntry, LeaderboardResponse, RecordEvidence } from '../../src/lib/leaderboard.js'; vi.mock('$app/stores', () => ({ page: writable({ url: new URL('http://localhost/leaderboard') }), navigating: writable(null) })); vi.mock('$app/navigation', () => ({ goto: vi.fn() })); const evidence: RecordEvidence = { matchId: 71, game: 't5', mapKey: 't5:theater', mapName: 'Kino Der Toten', server: 't5_zombie_theater_1', startedAt: '2026-10-06T12:00:00Z', endedAt: '2026-10-06T13:00:00Z', startObserved: true, presentAtStart: false, firstObservedRound: 8, value: 49 }; const entry: LeaderboardEntry = { rank: 27, guid: '12345', name: 'Cherry', highestRound: 49, kills: 1000, roundsSurvived: 60, headshots: 100, revives: 3, downs: 5, gamesPlayed: 6, gamesCompleted: 5, bestKills: 500, bestScore: 4000, lastSeen: evidence.endedAt!, records: { highestRound: evidence, bestKills: evidence, bestScore: evidence }, headshotRate: 0.1, killsPerDown: 200, averageKills: 1000 / 6, averageRounds: 10, completionRate: 5 / 6 }; function report(): LeaderboardResponse { return { entries: [structuredClone(entry)], maps: [{ key: 't5:theater', game: 't5', map: 'theater', name: 'Kino Der Toten' }], total: 60, limit: 25, offset: 25, metric: 'highestRound', summary: { players: 473, matches: 411, kills: 112899, roundsSurvived: 7062, highestRound: 49, firstTrackedAt: evidence.startedAt, updatedAt: evidence.endedAt } }; } function scope(search = '') { (page as unknown as Writable<{ url: URL }>).set({ url: new URL(`http://localhost/leaderboard${search}`) }); } function html(data = report()) { return render(Leaderboard, { props: { data: { leaderboard: data, user: null, setupRequired: false } } }).body; } beforeEach(() => scope()); it('shows prominent totals and restored stat columns, preserving ranks and collapsed identity and evidence', () => { const body = html(); const headers = body.slice(body.indexOf('')); expect(headers.match(/scope="col"/g)).toHaveLength(9); expect(headers).toContain('Highest round'); expect(headers).toContain('Total kills'); expect(headers).toContain('Games played'); for (const label of ['Total rounds', 'Headshots', 'Revives', 'Downs']) expect(headers).toContain(label); expect(body).toMatch(/]*>27<\/span>/); expect(body).toContain('26–26 of 60 players'); const overview = body.slice(body.indexOf('class="overview'), body.indexOf('class="rankings')); expect(overview).toContain('All-time stats for All games & maps'); for (const value of ['49', '112,899', '7,062', '411', '473']) expect(overview).toMatch(new RegExp(`]*>${value}`)); expect(overview).toContain('Fleet record'); expect(overview).not.toContain(']*open/); expect(body).not.toContain('Copy GUID'); expect(body).not.toContain('Every round counts'); expect(body).toContain('Record details'); expect(body).toContain('First observed at round 8'); expect(body).not.toContain('>Search'); }); it('deduplicates the selected metric and preserves scope and pagination on player and match links', () => { scope('?game=t5&map=t5%3Atheater&metric=kills&q=Cherry&offset=25'); const data = report(); data.metric = 'kills'; const body = html(data); const headers = body.slice(body.indexOf('')); expect(headers.match(/scope="col"/g)).toHaveLength(8); expect(body).toContain('offset%3D25'); expect(body).toContain('q%3DCherry'); scope('?game=t5&map=t5%3Atheater&metric=highestRound&q=Cherry&offset=25'); const recordBody = html(); expect(recordBody).toContain('/matches/71?returnTo=%2Fleaderboard'); expect(recordBody).toContain('map%3Dt5%253Atheater'); expect(recordBody).toContain('Selection record'); expect(recordBody).not.toContain('Fleet record'); }); it('keeps qualification and denominator visible for efficiency rankings and missing values distinct', () => { scope('?metric=headshotRate'); const data = report(); data.metric = 'headshotRate'; let body = html(data); expect(body).toContain('At least 5 ended games and 100 kills within the selected game and map'); expect(body).toContain('100 headshots / 1,000 kills · 6 games'); expect(body).toContain('10%'); data.entries[0].headshotRate = null; body = html(data); expect(body).toContain('>—'); }); it('holds changing ranks until explicitly applied, coalescing later polls even after focus leaves', () => { const first = report(); const updates = createLeaderboardUpdates('all', first); const second = structuredClone(first); second.entries[0].rank = 28; expect(updates.receive('all', second, true)).toEqual({ current: first, pending: second }); const third = structuredClone(second); third.entries[0].rank = 30; expect(updates.receive('all', third, false)).toEqual({ current: first, pending: third }); expect(updates.apply()).toEqual(third); expect(updates.receive('all', third, true)?.pending).toBeNull(); }); it('ignores old scopes, clears a reverted pending update, and accepts unattended refreshes', () => { const first = report(); const second = structuredClone(first); second.total = 100; const updates = createLeaderboardUpdates('all', first); updates.receive('all', second, true); expect(updates.receive('all', structuredClone(first), true)?.pending).toBeNull(); expect(updates.receive('all', second, false)?.current).toEqual(second); updates.receive('all', first, true); updates.reset('t5', first); expect(updates.receive('all', second, false)).toBeNull(); expect(updates.apply()).toEqual(first); expect(leaderboardPageSize).toBe(25); });