96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
import { afterEach, expect, it, vi } from 'vitest';
|
|
import { flushSync, mount, unmount } from 'svelte';
|
|
import { writable } from 'svelte/store';
|
|
import MatchHistory from '../../src/lib/ui/components/MatchHistory.svelte';
|
|
import type { MatchHistoryResponse, ServerMatch } from '../../src/lib/match-history.js';
|
|
import type { UserRole } from '../../src/lib/auth.js';
|
|
|
|
const currentPage = writable({
|
|
url: new URL('http://localhost/matches'),
|
|
data: { user: { role: 'moderator' as UserRole } }
|
|
});
|
|
const navigation = vi.hoisted(() => ({ goto: vi.fn().mockResolvedValue(undefined) }));
|
|
vi.mock('$app/stores', () => ({
|
|
page: {
|
|
subscribe: (run: Parameters<typeof currentPage.subscribe>[0]) => currentPage.subscribe(run)
|
|
}
|
|
}));
|
|
vi.mock('$app/navigation', () => ({ goto: navigation.goto }));
|
|
vi.mock('../../src/lib/ui/poll.js', () => ({ pollVisible: () => () => {} }));
|
|
|
|
let component: ReturnType<typeof mount> | undefined;
|
|
afterEach(async () => {
|
|
if (component) await unmount(component);
|
|
component = undefined;
|
|
document.body.replaceChildren();
|
|
navigation.goto.mockClear();
|
|
});
|
|
const statuses: ServerMatch['status'][] = [
|
|
'completed',
|
|
'interrupted',
|
|
'end-report-missing',
|
|
'active'
|
|
];
|
|
const report: MatchHistoryResponse = {
|
|
total: 4,
|
|
page: 1,
|
|
limit: 25,
|
|
maps: [],
|
|
matches: statuses.map((status, index) => ({
|
|
id: index + 1,
|
|
game: 't5',
|
|
server: 't5_zombie_theater_1',
|
|
map: 'Kino Der Toten',
|
|
observedRound: 1,
|
|
finalRound: status === 'completed' ? 1 : null,
|
|
status,
|
|
reporterMatchKey: null,
|
|
missingReports: null,
|
|
startedAt: '2026-10-17T10:00:00Z',
|
|
lastReportedAt: '2026-10-17T10:01:00Z',
|
|
endedAt: status === 'completed' ? '2026-10-17T10:01:00Z' : null,
|
|
durationMs: null,
|
|
startPlayers: 1,
|
|
uniquePlayers: 1,
|
|
endPlayers: status === 'completed' ? 1 : null
|
|
}))
|
|
};
|
|
function show(search = '', role: UserRole = 'moderator') {
|
|
currentPage.set({
|
|
url: new URL(`http://localhost/matches${search}`),
|
|
data: { user: { role } }
|
|
});
|
|
component = mount(MatchHistory, { target: document.body, props: { initial: report } });
|
|
flushSync();
|
|
}
|
|
|
|
it('enables the moderation archive through a namespaced URL while preserving filters', async () => {
|
|
show('?matchGame=t5&matchPage=2');
|
|
document.querySelector<HTMLInputElement>('input[type="checkbox"]')!.click();
|
|
await vi.waitFor(() => expect(navigation.goto).toHaveBeenCalledOnce());
|
|
expect(navigation.goto.mock.calls[0][0]).toBe('/matches?matchGame=t5&matchArchive=1');
|
|
});
|
|
|
|
it('links round-one and unfinished archive rows to retained match detail with return context', () => {
|
|
show('?matchArchive=1&matchGame=t5');
|
|
expect(document.querySelector<HTMLInputElement>('input[type="checkbox"]')?.checked).toBe(true);
|
|
const links = [...document.querySelectorAll<HTMLAnchorElement>('tbody a.match-link')];
|
|
expect(links).toHaveLength(4);
|
|
for (const [index, link] of links.entries()) {
|
|
const url = new URL(link.href);
|
|
expect(url.pathname).toBe(`/matches/${index + 1}`);
|
|
expect(url.searchParams.get('matchArchive')).toBe('1');
|
|
expect(url.searchParams.get('returnTo')).toBe('/matches?matchArchive=1&matchGame=t5');
|
|
}
|
|
expect(document.body.textContent).toContain('Highest observed');
|
|
expect(document.body.textContent).toContain('Last report');
|
|
});
|
|
|
|
it('keeps ordinary history navigation and hides archive controls from viewers', () => {
|
|
show('', 'viewer');
|
|
expect(document.querySelector('input[type="checkbox"]')).toBeNull();
|
|
const links = [...document.querySelectorAll<HTMLAnchorElement>('tbody a.match-link')];
|
|
expect(links).toHaveLength(1);
|
|
expect(new URL(links[0].href).searchParams.has('matchArchive')).toBe(false);
|
|
});
|