200 lines
5.7 KiB
TypeScript
200 lines
5.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { LeagueStandingDTO } from '../types/generated/LeagueStandingDTO';
|
|
import { StandingEntryViewModel } from './StandingEntryViewModel';
|
|
|
|
describe('StandingEntryViewModel', () => {
|
|
const createMockStanding = (overrides?: Partial<LeagueStandingDTO>): LeagueStandingDTO => ({
|
|
driverId: 'driver-1',
|
|
driver: {
|
|
id: 'driver-1',
|
|
iracingId: '12345',
|
|
name: 'Test Driver',
|
|
country: 'US',
|
|
joinedAt: '2025-01-01T00:00:00Z',
|
|
},
|
|
position: 1,
|
|
points: 100,
|
|
wins: 3,
|
|
podiums: 5,
|
|
races: 8,
|
|
positionChange: 0,
|
|
lastRacePoints: 0,
|
|
droppedRaceIds: [],
|
|
...overrides,
|
|
});
|
|
|
|
it('should create instance with all properties', () => {
|
|
const dto = createMockStanding();
|
|
const viewModel = new StandingEntryViewModel({
|
|
...dto,
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
expect(viewModel.driverId).toBe('driver-1');
|
|
expect(viewModel.position).toBe(1);
|
|
expect(viewModel.points).toBe(100);
|
|
expect(viewModel.wins).toBe(3);
|
|
expect(viewModel.podiums).toBe(5);
|
|
expect(viewModel.races).toBe(8);
|
|
});
|
|
|
|
it('should return position as badge string', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 5 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
expect(viewModel.positionBadge).toBe('P5');
|
|
});
|
|
|
|
it('should calculate points gap to leader correctly', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 2, points: 85 }),
|
|
leaderPoints: 100, // leader points
|
|
nextPoints: 70, // next points
|
|
currentUserId: 'driver-2',
|
|
});
|
|
|
|
expect(viewModel.pointsGapToLeader).toBe(-15);
|
|
});
|
|
|
|
it('should show zero gap when driver is leader', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 1, points: 100 }),
|
|
leaderPoints: 100, // leader points
|
|
nextPoints: 85, // next points
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
expect(viewModel.pointsGapToLeader).toBe(0);
|
|
});
|
|
|
|
it('should calculate points gap to next position correctly', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 2, points: 85 }),
|
|
leaderPoints: 100, // leader points
|
|
nextPoints: 70, // next points
|
|
currentUserId: 'driver-2',
|
|
});
|
|
|
|
expect(viewModel.pointsGapToNext).toBe(15);
|
|
});
|
|
|
|
it('should identify current user correctly', () => {
|
|
const viewModel1 = new StandingEntryViewModel({
|
|
...createMockStanding({ driverId: 'driver-1' }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
const viewModel2 = new StandingEntryViewModel({
|
|
...createMockStanding({ driverId: 'driver-1' }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-2',
|
|
});
|
|
|
|
expect(viewModel1.isCurrentUser).toBe(true);
|
|
expect(viewModel2.isCurrentUser).toBe(false);
|
|
});
|
|
|
|
it('should return "same" trend when no previous position', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 1 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
expect(viewModel.trend).toBe('same');
|
|
});
|
|
|
|
it('should return "up" trend when position improved', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 1 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
previousPosition: 3, // previous position was 3rd
|
|
});
|
|
|
|
expect(viewModel.trend).toBe('up');
|
|
});
|
|
|
|
it('should return "down" trend when position worsened', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 5 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
previousPosition: 2, // previous position was 2nd
|
|
});
|
|
|
|
expect(viewModel.trend).toBe('down');
|
|
});
|
|
|
|
it('should return "same" trend when position unchanged', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 3 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
previousPosition: 3, // same position
|
|
});
|
|
|
|
expect(viewModel.trend).toBe('same');
|
|
});
|
|
|
|
it('should return correct trend arrow for up', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 1 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
previousPosition: 3,
|
|
});
|
|
|
|
expect(viewModel.trendArrow).toBe('↑');
|
|
});
|
|
|
|
it('should return correct trend arrow for down', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 5 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
previousPosition: 2,
|
|
});
|
|
|
|
expect(viewModel.trendArrow).toBe('↓');
|
|
});
|
|
|
|
it('should return correct trend arrow for same', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 3 }),
|
|
leaderPoints: 100,
|
|
nextPoints: 85,
|
|
currentUserId: 'driver-1',
|
|
});
|
|
|
|
expect(viewModel.trendArrow).toBe('-');
|
|
});
|
|
|
|
it('should handle edge case of last place with no one behind', () => {
|
|
const viewModel = new StandingEntryViewModel({
|
|
...createMockStanding({ position: 10, points: 20 }),
|
|
leaderPoints: 100, // leader points
|
|
nextPoints: 20, // same points (last place)
|
|
currentUserId: 'driver-10',
|
|
});
|
|
|
|
expect(viewModel.pointsGapToNext).toBe(0);
|
|
expect(viewModel.pointsGapToLeader).toBe(-80);
|
|
});
|
|
});
|