Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -8,27 +8,65 @@ import { matchStartedLabel, matchInstanceLabel } from '../../src/lib/ui/match-hi
|
||||
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')); });
|
||||
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;
|
||||
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('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 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');
|
||||
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;
|
||||
@@ -40,27 +78,59 @@ it('keeps mobile filters and sorting in one labelled sheet with distinct control
|
||||
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]);
|
||||
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 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('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;
|
||||
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');
|
||||
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');
|
||||
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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user