Files
electric-cherry/tests/unit/ui-match-history.test.ts
T

137 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, afterEach, expect, it, vi } from 'vitest';
import { render } from 'svelte/server';
import { writable, type Writable } from 'svelte/store';
import { page } from '$app/stores';
import MatchHistory from '../../src/lib/ui/components/MatchHistory.svelte';
import MatchHistoryPage from '../../src/routes/matches/+page.svelte';
import { matchStartedLabel, matchInstanceLabel } from '../../src/lib/ui/match-history.js';
import type { MatchHistoryResponse, ServerMatch } from '../../src/lib/match-history.js';
vi.mock('$app/stores', () => ({ page: writable({ url: new URL('http://localhost/matches') }) }));
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
const match: ServerMatch = {
id: 1,
server: 't6_zombie_town_1',
game: 't6',
map: 'Town Survival',
observedRound: 20,
finalRound: 20,
status: 'completed',
startedAt: '2026-10-06T17:26:00Z',
endedAt: '2026-10-06T18:04:00Z',
durationMs: 2280000,
startPlayers: 2,
uniquePlayers: 4,
endPlayers: 2
};
const report = (): MatchHistoryResponse => ({
matches: [match],
total: 40,
page: 1,
limit: 25,
maps: [{ key: 't6:town', game: 't6', name: 'Town Survival' }]
});
function scope(search = '') {
(page as unknown as Writable<{ url: URL }>).set({
url: new URL(`http://localhost/matches${search}`)
});
}
beforeEach(() => {
scope();
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-10-06T20:00:00Z'));
});
afterEach(() => vi.useRealTimers());
it('renders one page heading, six columns, a combined identity, and compact dates', () => {
const body = render(MatchHistoryPage, {
props: { data: { history: report(), user: null, setupRequired: false } }
}).body;
expect(body.match(/<h1[ >]/g)).toHaveLength(1);
expect(body.match(/scope="col"/g)).toHaveLength(6);
expect(body).toContain('2 h ago');
expect(body).toContain('Lobby 1 · Black Ops II');
expect(body).not.toContain('>Server</th>');
expect(body).not.toContain('Show 25 more');
expect(body).toContain('matchPage=2');
});
it('preserves filters and page in match links and avoids linking deleted instances', () => {
scope('?matchGame=t6&matchPage=2&matchSort=round&matchStatus=completed');
const data = report();
data.page = 2;
data.matches = [{ ...match, server_deleted_at: '2026-10-06' }];
const body = render(MatchHistory, { props: { initial: data } }).body;
expect(body).toContain('/matches/1?returnTo=%2Fmatches');
expect(body).toContain('matchPage%3D2');
expect(body).toContain('matchSort%3Dround');
expect(body).toContain('Historical instance');
expect(body).not.toContain('href="/servers/');
expect(body).not.toContain('>Deleted<');
expect(body).toContain('26–26 of 40 matches');
expect(body).toContain('Filters · 2');
});
it('keeps mobile filters and sorting in one labelled sheet with distinct controls and one named match link', () => {
const body = render(MatchHistory, { props: { initial: report() } }).body;
expect(body).toContain('Match filters');
expect(body).toContain('id="match-map-mobile"');
expect(body).toContain('id="match-sort-mobile"');
expect(body).toContain('id="match-order-mobile"');
expect(body).toContain('Show results');
const rows = body.slice(body.indexOf('<tbody'), body.indexOf('</tbody>'));
expect(rows.match(/<a\b/g)).toHaveLength(1);
expect(rows).toContain('Town Survival · Match #1');
const ids = [...body.matchAll(/\bid="([^"]+)"/g)].map((match) => match[1]);
expect(new Set(ids).size).toBe(ids.length);
expect(body).toContain('Updates every 10 seconds');
});
it('labels unfinished rounds and missing durations without inventing final results or detail links', () => {
const data = report();
data.matches = [
{ ...match, status: 'interrupted', finalRound: null, durationMs: null, uniquePlayers: 0 }
];
const body = render(MatchHistory, { props: { initial: data } }).body;
expect(body).toContain('Interrupted');
expect(body).toContain('Highest observed');
expect(body).not.toContain('href="/matches/1');
expect(body).toContain('>—</span>');
expect(body).toContain('>0</td>');
});
it('offers filter reset for an empty selection and retains the scoped instance view', () => {
scope('?matchQ=missing');
const data = report();
data.matches = [];
data.total = 0;
const body = render(MatchHistory, { props: { initial: data } }).body;
expect(body).toContain('No matches found');
expect(body).toContain('Clear filters');
const scoped = render(MatchHistory, {
props: { initial: report(), server: match.server }
}).body;
expect(scoped).not.toContain('id="match-search"');
expect(scoped).not.toContain('Historical instance');
});
it('keeps custom instance names and calculates yesterday across DST and year boundaries', () => {
expect(matchInstanceLabel({ ...match, server: 'Weekend lobby' })).toBe(
'Weekend lobby · Black Ops II'
);
expect(
matchStartedLabel(
'2026-03-08T06:30:00Z',
'America/New_York',
new Date('2026-03-09T04:15:00Z')
)
).toBe('21 h ago');
expect(
matchStartedLabel(
'2025-10-06T17:26:00Z',
'America/New_York',
new Date('2026-10-06T20:00:00Z')
)
).toBe('Oct 6, 2025, 1:26 PM EDT');
expect(
matchStartedLabel(
'2026-10-06 17:26:00',
'America/New_York',
new Date('2026-10-06T20:00:00Z')
)
).toBe('2 h ago');
});