Files
gridpilot.gg/apps/website/lib/view-models/RaceStewardingViewModel.test.ts
2025-12-20 00:31:31 +01:00

141 lines
3.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { RaceStewardingViewModel } from './RaceStewardingViewModel';
const createRaceStewardingDto = () => ({
raceDetail: {
race: {
id: 'race-1',
track: 'Spa-Francorchamps',
scheduledAt: '2024-01-01T20:00:00Z',
status: 'completed',
},
league: {
id: 'league-1',
name: 'Test League',
},
},
protests: {
protests: [
{
id: 'p1',
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: { lap: 1, description: 'Turn 1 divebomb' },
filedAt: '2024-01-01T21:00:00Z',
status: 'pending',
},
{
id: 'p2',
protestingDriverId: 'd3',
accusedDriverId: 'd4',
incident: { lap: 5, description: 'Blocking' },
filedAt: '2024-01-01T21:05:00Z',
status: 'under_review',
},
{
id: 'p3',
protestingDriverId: 'd5',
accusedDriverId: 'd6',
incident: { lap: 10, description: 'Contact' },
filedAt: '2024-01-01T21:10:00Z',
status: 'upheld',
},
{
id: 'p4',
protestingDriverId: 'd7',
accusedDriverId: 'd8',
incident: { lap: 12, description: 'Off-track overtake' },
filedAt: '2024-01-01T21:15:00Z',
status: 'dismissed',
},
{
id: 'p5',
protestingDriverId: 'd9',
accusedDriverId: 'd10',
incident: { lap: 15, description: 'Withdrawn protest' },
filedAt: '2024-01-01T21:20:00Z',
status: 'withdrawn',
},
],
driverMap: {
d1: { id: 'd1', name: 'Driver 1' },
d2: { id: 'd2', name: 'Driver 2' },
},
},
penalties: {
penalties: [
{
id: 'pen1',
driverId: 'd2',
type: 'time',
value: 5,
reason: 'Avoidable contact',
},
{
id: 'pen2',
driverId: 'd3',
type: 'points',
value: 2,
reason: 'Reckless driving',
},
],
driverMap: {
d3: { id: 'd3', name: 'Driver 3' },
d4: { id: 'd4', name: 'Driver 4' },
},
},
});
describe('RaceStewardingViewModel', () => {
it('maps core race, league, protests, penalties and driver map', () => {
const dto = createRaceStewardingDto();
const viewModel = new RaceStewardingViewModel(dto as any);
expect(viewModel.race).toEqual(dto.raceDetail.race);
expect(viewModel.league).toEqual(dto.raceDetail.league);
expect(viewModel.protests).toEqual(dto.protests.protests);
expect(viewModel.penalties).toEqual(dto.penalties.penalties);
expect(viewModel.driverMap).toEqual({
...dto.protests.driverMap,
...dto.penalties.driverMap,
});
});
it('derives pending and resolved protest buckets and counts', () => {
const dto = createRaceStewardingDto();
const viewModel = new RaceStewardingViewModel(dto as any);
const pendingIds = viewModel.pendingProtests.map(p => p.id);
const resolvedIds = viewModel.resolvedProtests.map(p => p.id);
expect(pendingIds.sort()).toEqual(['p1', 'p2']);
expect(resolvedIds.sort()).toEqual(['p3', 'p4', 'p5']);
expect(viewModel.pendingCount).toBe(2);
expect(viewModel.resolvedCount).toBe(3);
});
it('derives penalties count from penalties list', () => {
const dto = createRaceStewardingDto();
const viewModel = new RaceStewardingViewModel(dto as any);
expect(viewModel.penaltiesCount).toBe(2);
});
it('handles empty protests and penalties gracefully', () => {
const dto = createRaceStewardingDto();
dto.protests.protests = [];
dto.penalties.penalties = [];
const viewModel = new RaceStewardingViewModel(dto as any);
expect(viewModel.pendingProtests).toEqual([]);
expect(viewModel.resolvedProtests).toEqual([]);
expect(viewModel.pendingCount).toBe(0);
expect(viewModel.resolvedCount).toBe(0);
expect(viewModel.penaltiesCount).toBe(0);
});
});