This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { beforeEach, expect, it, vi } from 'vitest';
|
||||
import { render } from 'svelte/server';
|
||||
import type { MetricsData, MapMetrics, ServerMetrics } from '../../src/lib/metrics.js';
|
||||
import type {
|
||||
MetricsData,
|
||||
MapMetrics,
|
||||
ServerMetrics,
|
||||
MatchMetrics
|
||||
} from '../../src/lib/metrics.js';
|
||||
import MetricsPage from '../../src/routes/metrics/+page.svelte';
|
||||
import MetricsSummary from '../../src/lib/ui/components/metrics/MetricsSummary.svelte';
|
||||
import MetricsTables from '../../src/lib/ui/components/metrics/MetricsTables.svelte';
|
||||
@@ -16,9 +21,24 @@ vi.mock('$app/navigation', () => ({
|
||||
goto: vi.fn(),
|
||||
replaceState: vi.fn()
|
||||
}));
|
||||
const matches = (changes: Partial<MatchMetrics> = {}): MatchMetrics => ({
|
||||
started: 271,
|
||||
withRecordedPlayers: 271,
|
||||
averageGroupSize: 2.4,
|
||||
solo: 48,
|
||||
soloShare: (100 * 48) / 271,
|
||||
...changes
|
||||
});
|
||||
const map = (i: number, changes: Partial<MapMetrics> = {}): MapMetrics => ({
|
||||
game: 't5',
|
||||
map: `Map ${String(i).padStart(2, '0')}`,
|
||||
matches: matches({
|
||||
started: i,
|
||||
withRecordedPlayers: i,
|
||||
averageGroupSize: 2,
|
||||
solo: 0,
|
||||
soloShare: 0
|
||||
}),
|
||||
observed: 10,
|
||||
active: 5,
|
||||
idle: 5,
|
||||
@@ -56,6 +76,7 @@ function report(): MetricsData {
|
||||
scopeOptions: { games: ['t5'], maps: [] },
|
||||
scopeFirstSampleAt: now - 14 * 86400000,
|
||||
scopeLatestSampleAt: now,
|
||||
matches: matches(),
|
||||
summary,
|
||||
running: { average: 12, peak: 24, capturedHours: 1 },
|
||||
maps: Array.from({ length: 24 }, (_, i) => map(i + 1)),
|
||||
@@ -181,6 +202,99 @@ it('searches and sorts the complete map set before pagination and clamps invalid
|
||||
expect(metricPage([], '20')).toMatchObject({ page: 1, start: 0, end: 0, total: 0 });
|
||||
});
|
||||
|
||||
it('shows match counts, average group and solo share with explicit missing roster coverage', () => {
|
||||
const data = report();
|
||||
let body = render(MetricsSummary, { props: { report: data } }).body.replace(/\s+/g, ' ');
|
||||
expect(body).toContain('Matches started');
|
||||
expect(body).toContain('>271<');
|
||||
expect(body).toContain('Average group 2.4 players');
|
||||
expect(body).toContain('48 solo · 17.7% of matches with recorded players');
|
||||
|
||||
data.matches = matches({ started: 300 });
|
||||
body = render(MetricsSummary, { props: { report: data } }).body.replace(/\s+/g, ' ');
|
||||
expect(body).toContain('48 solo · 17.7% of matches with recorded players');
|
||||
expect(body).toContain('Players recorded for 271 of 300 matches');
|
||||
|
||||
data.matches = matches({
|
||||
started: 3,
|
||||
withRecordedPlayers: 0,
|
||||
averageGroupSize: null,
|
||||
solo: 0,
|
||||
soloShare: null
|
||||
});
|
||||
body = render(MetricsSummary, { props: { report: data } }).body.replace(/\s+/g, ' ');
|
||||
expect(body).toContain('>3<');
|
||||
expect(body).toContain('Average group unavailable');
|
||||
expect(body).toContain('Solo share unavailable');
|
||||
expect(body).toContain('Players recorded for 0 of 3 matches');
|
||||
expect(body).not.toContain('0 solo');
|
||||
});
|
||||
|
||||
it('sorts map match counts numerically, including maps without occupancy or recorded players', () => {
|
||||
const noRoster = matches({
|
||||
started: 100,
|
||||
withRecordedPlayers: 0,
|
||||
averageGroupSize: null,
|
||||
solo: 0,
|
||||
soloShare: null
|
||||
});
|
||||
const maps = [
|
||||
map(1, { matches: noRoster, observed: 0, active: 0, idle: 0, playerHours: 0 }),
|
||||
map(2, { matches: matches({ started: 2, withRecordedPlayers: 2 }) }),
|
||||
map(3, { matches: matches({ started: 10, withRecordedPlayers: 10 }) }),
|
||||
map(4, { matches: { ...noRoster, started: 0 } })
|
||||
];
|
||||
scope('?mapSort=matches&mapDirection=asc');
|
||||
let body = render(MetricsTables, { props: { maps, servers: [] } }).body;
|
||||
expect(body.indexOf('Map 04')).toBeLessThan(body.indexOf('Map 02'));
|
||||
expect(body.indexOf('Map 02')).toBeLessThan(body.indexOf('Map 03'));
|
||||
expect(body.indexOf('Map 03')).toBeLessThan(body.indexOf('Map 01'));
|
||||
expect(body).toContain('aria-sort="ascending"');
|
||||
expect(body).toContain('avg group 2.4');
|
||||
expect(body).toContain('Average group unavailable');
|
||||
expect(body.replace(/\s+/g, ' ')).toContain('0 of 100');
|
||||
|
||||
scope('?mapSort=matches&mapDirection=desc');
|
||||
body = render(MetricsTables, { props: { maps, servers: [] } }).body;
|
||||
expect(body.indexOf('Map 01')).toBeLessThan(body.indexOf('Map 03'));
|
||||
expect(body.indexOf('Map 03')).toBeLessThan(body.indexOf('Map 02'));
|
||||
expect(body.indexOf('Map 02')).toBeLessThan(body.indexOf('Map 04'));
|
||||
expect(body).toContain('aria-sort="descending"');
|
||||
});
|
||||
|
||||
it('keeps match summaries and map rows visible without any retained occupancy samples', () => {
|
||||
const data = report();
|
||||
data.summary = { observed: 0, active: 0, idle: 0, unknown: 0, playerHours: 0 };
|
||||
data.running = { average: null, peak: 0, capturedHours: 0 };
|
||||
data.latestSampleAt = null;
|
||||
data.firstRetainedSampleAt = null;
|
||||
data.scopeFirstSampleAt = null;
|
||||
data.scopeLatestSampleAt = null;
|
||||
data.servers = [];
|
||||
data.maps = [
|
||||
map(1, {
|
||||
...data.summary,
|
||||
noEmpty: 0,
|
||||
availabilityObserved: 0,
|
||||
matches: data.matches
|
||||
})
|
||||
];
|
||||
const body = render(MetricsPage, {
|
||||
props: { data: { ...data, user: null, setupRequired: false } }
|
||||
}).body;
|
||||
expect(body).toContain('Matches started');
|
||||
expect(body).toContain('Average group 2.4 players');
|
||||
expect(body).toContain('aria-label="Map metrics table"');
|
||||
expect(body).toContain('Map 01');
|
||||
expect(body).toContain('No retained observations yet');
|
||||
expect(body).not.toContain('Lobbies running over time');
|
||||
expect(body.replace(/\s+/g, ' ')).toContain('including both endpoints and all match statuses');
|
||||
expect(body.replace(/\s+/g, ' ')).toContain(
|
||||
'distinct recorded players across each match’s lifetime'
|
||||
);
|
||||
expect(body.replace(/\s+/g, ' ')).toContain('missing or purged records are not estimated');
|
||||
});
|
||||
|
||||
it('keeps unknown availability last when sorting and distinguishes it from known empty lobbies', () => {
|
||||
scope('?mapSort=noEmpty&mapDirection=asc');
|
||||
const maps = [
|
||||
|
||||
Reference in New Issue
Block a user