Show map rankings for player personal records
This commit is contained in:
@@ -75,6 +75,38 @@ it('resolves tied bests by earliest completion, receipt time for unknown endings
|
||||
}
|
||||
});
|
||||
|
||||
it('ranks each map best against distinct players using leaderboard ties, qualification, and game boundaries', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const handle = db();
|
||||
seed(handle, 1, { round: 20, kills: 200, score: 2000 });
|
||||
seed(handle, 2, { guid: '202', round: 30, kills: 100, score: 3000 });
|
||||
seed(handle, 3, { guid: '202', round: 30, kills: 150, score: 2000 });
|
||||
seed(handle, 4, { guid: '303', round: 30, kills: 300, score: 2000 });
|
||||
seed(handle, 5, { guid: '404', round: 10, kills: 200, score: 1000 });
|
||||
seed(handle, 6, { map: 'moon', round: 5, kills: 10, score: 10 });
|
||||
seed(handle, 7, { game: 't6', round: 6, kills: 6, score: 6 });
|
||||
seed(handle, 8, { game: 't6', guid: '202', round: 7, kills: 7, score: 7 });
|
||||
seed(handle, 9, { guid: 'active', status: 'active', round: 99, kills: 9999, score: 9999 });
|
||||
seed(handle, 10, { guid: 'interrupted', status: 'interrupted', round: 99, kills: 9999, score: 9999 });
|
||||
seed(handle, 11, { guid: 'unrecorded', hasStats: 0, round: 99, kills: 9999, score: 9999 });
|
||||
seed(handle, 12, { guid: 'unqualified', round: 1, kills: 9999, score: 9999 });
|
||||
handle.prepare('UPDATE leaderboard_matches SET start_players=2 WHERE id IN (1,6,7)').run();
|
||||
handle.prepare('UPDATE leaderboard_matches SET start_players=4 WHERE id IN (2,3,4,5,8)').run();
|
||||
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
|
||||
const { getPlayerLeaderboardEntry } = await import('../../src/lib/server/fleet/leaderboard.js');
|
||||
const profile = getPlayerProfile('101')!;
|
||||
expect(profile.maps.find(map => map.key === 't5:theater')?.ranks).toEqual({ highestRound: 3, bestKills: 2, bestScore: 2 });
|
||||
expect(profile.maps.find(map => map.key === 't5:moon')?.ranks).toEqual({ highestRound: 1, bestKills: 1, bestScore: 1 });
|
||||
expect(profile.maps.find(map => map.key === 't6:theater')?.ranks).toEqual({ highestRound: 2, bestKills: 2, bestScore: 2 });
|
||||
for (const map of profile.maps) for (const metric of ['highestRound', 'bestKills', 'bestScore'] as const) {
|
||||
expect(map.ranks[metric]).toBe(getPlayerLeaderboardEntry('101', { game: map.game, map: map.key, metric })?.rank);
|
||||
}
|
||||
expect(getPlayerProfile('101', 0, { game: 't6', map: 't6:theater', players: 2 })?.maps).toEqual(profile.maps);
|
||||
seed(handle, 13, { guid: 'new-leader', round: 40, kills: 400, score: 4000 });
|
||||
expect(getPlayerProfile('101')?.maps.find(map => map.key === 't5:theater')?.ranks)
|
||||
.toEqual({ highestRound: 4, bestKills: 3, bestScore: 3 });
|
||||
});
|
||||
|
||||
it('never uses unfinished, unsupported, nonqualifying or unrecorded stats as evidence', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const handle = db();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { render } from 'svelte/server';
|
||||
import { writable } from 'svelte/store';
|
||||
import PlayerProfilePage from '../../src/routes/leaderboard/players/[guid]/+page.svelte';
|
||||
import PlayerMapCards from '../../src/lib/ui/components/PlayerMapCards.svelte';
|
||||
import type { PlayerMapRecord, PlayerProfile, RecordEvidence } from '../../src/lib/leaderboard.js';
|
||||
|
||||
vi.mock('$app/stores', () => ({ page: writable({
|
||||
url: new URL('http://localhost/leaderboard/players/101?players=2'), data: { user: null }
|
||||
}) }));
|
||||
vi.mock('$app/navigation', () => ({ goto: vi.fn(), invalidateAll: 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: true, firstObservedRound: 0, value: 22 };
|
||||
const stats = { highestRound: 22, bestKills: 507, bestScore: 5290, kills: 722, roundsSurvived: 41,
|
||||
headshots: 10, revives: 2, downs: 3, gamesPlayed: 9, gamesCompleted: 8 };
|
||||
const map: PlayerMapRecord = { ...stats, game: 't5', key: 't5:theater', name: 'Kino Der Toten',
|
||||
lastSeen: evidence.endedAt!, ranks: { highestRound: 7, bestKills: 12, bestScore: 3 },
|
||||
records: { highestRound: evidence, bestKills: { ...evidence, matchId: 72, value: 507 },
|
||||
bestScore: { ...evidence, matchId: 73, value: 5290 } } };
|
||||
|
||||
function expectRankedRecords(body: string) {
|
||||
for (const [label, rank] of [['Best round', 7], ['Best kills', 12], ['Best score', 3]]) {
|
||||
expect(body).toContain(`aria-label="Kino Der Toten ${label} rank ${rank}"`);
|
||||
expect(body).toContain(`Rank #${rank}`);
|
||||
}
|
||||
for (const id of [71, 72, 73]) expect(body).toContain(`/matches/${id}?returnTo=`);
|
||||
for (const value of ['22', '507', '5,290']) expect(body).toContain(`>${value}</a>`);
|
||||
}
|
||||
|
||||
it('shows each record rank and preserves match links in mobile map cards', () => {
|
||||
expectRankedRecords(render(PlayerMapCards, { props: { maps: [map], returnTo: '/leaderboard/players/101?players=2' } }).body);
|
||||
});
|
||||
|
||||
it('shows the same ranks in the desktop profile with lifetime map scope explained', () => {
|
||||
const profile: PlayerProfile = { guid: '101', name: 'Cherry', firstSeen: evidence.startedAt, lastSeen: evidence.endedAt!,
|
||||
stats, records: map.records, maps: [map], matches: [], matchesTotal: 0, limit: 25, offset: 0 };
|
||||
const body = render(PlayerProfilePage, { props: { data: { profile, returnTo: '/leaderboard',
|
||||
scope: { game: '', map: '', players: '2', metric: 'kills', entry: null } } } }).body;
|
||||
const table = body.slice(body.indexOf('<table'), body.indexOf('</table>'));
|
||||
expectRankedRecords(table);
|
||||
expect(body).toContain('Lifetime personal bests across all squad sizes, ranked against other players on each map.');
|
||||
expect(body).toContain('Tied records share a rank.');
|
||||
});
|
||||
Reference in New Issue
Block a user