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

131 lines
4.0 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { RaceResultsDetailViewModel } from './RaceResultsDetailViewModel';
import { RaceResultViewModel } from './RaceResultViewModel';
import type { RaceResultsDetailDTO } from '../types/generated/RaceResultsDetailDTO';
import type { RaceResultDTO } from '../types/generated/RaceResultDTO';
const createResult = (overrides: Partial<RaceResultDTO> = {}): RaceResultDTO => ({
driverId: 'driver-1',
driverName: 'Driver One',
avatarUrl: 'https://example.com/avatar.jpg',
position: 2,
startPosition: 3,
incidents: 1,
fastestLap: 90.5,
positionChange: 1,
isPodium: true,
isClean: true,
...overrides,
});
const createDto = (overrides: Partial<RaceResultsDetailDTO & { results?: RaceResultDTO[] }> = {}): RaceResultsDetailDTO & { results?: RaceResultDTO[] } => ({
raceId: 'race-1',
track: 'Spa',
...overrides,
});
describe('RaceResultsDetailViewModel', () => {
it('maps basic fields and wraps results into view models', () => {
const dto = createDto({
results: [createResult({ driverId: 'driver-1' }), createResult({ driverId: 'driver-2' })],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
expect(viewModel.raceId).toBe('race-1');
expect(viewModel.track).toBe('Spa');
expect(viewModel.results).toHaveLength(2);
expect(viewModel.results[0]).toBeInstanceOf(RaceResultViewModel);
});
it('sorts results by position', () => {
const dto = createDto({
results: [
createResult({ driverId: 'driver-1', position: 3 }),
createResult({ driverId: 'driver-2', position: 1 }),
createResult({ driverId: 'driver-3', position: 2 }),
],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
const byPosition = viewModel.resultsByPosition;
expect(byPosition.map(r => r.position)).toEqual([1, 2, 3]);
});
it('sorts results by fastest lap', () => {
const dto = createDto({
results: [
createResult({ driverId: 'driver-1', fastestLap: 95 }),
createResult({ driverId: 'driver-2', fastestLap: 90 }),
createResult({ driverId: 'driver-3', fastestLap: 100 }),
],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
const byLap = viewModel.resultsByFastestLap;
expect(byLap.map(r => r.fastestLap)).toEqual([90, 95, 100]);
});
it('filters clean drivers', () => {
const dto = createDto({
results: [
createResult({ driverId: 'driver-1', isClean: true }),
createResult({ driverId: 'driver-2', isClean: false }),
],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
const clean = viewModel.cleanDrivers;
expect(clean).toHaveLength(1);
expect(clean[0].driverId).toBe('driver-1');
});
it('finds current user result by driver id', () => {
const dto = createDto({
results: [
createResult({ driverId: 'driver-1' }),
createResult({ driverId: 'driver-2' }),
],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-2');
expect(viewModel.currentUserResult?.driverId).toBe('driver-2');
});
it('computes stats for total drivers, clean rate and average incidents', () => {
const dto = createDto({
results: [
createResult({ driverId: 'driver-1', isClean: true, incidents: 0 }),
createResult({ driverId: 'driver-2', isClean: false, incidents: 4 }),
],
});
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
const stats = viewModel.stats;
expect(stats.totalDrivers).toBe(2);
expect(stats.cleanRate).toBeCloseTo(50);
expect(stats.averageIncidents).toBeCloseTo(2);
});
it('returns zeroed stats when there are no results', () => {
const dto = createDto({ results: [] });
const viewModel = new RaceResultsDetailViewModel(dto, 'driver-1');
const stats = viewModel.stats;
expect(stats.totalDrivers).toBe(0);
expect(stats.cleanRate).toBe(0);
expect(stats.averageIncidents).toBe(0);
});
});