Files
box-box/frontend/src/test/live.test.ts

214 lines
7.1 KiB
TypeScript
Raw Normal View History

2026-05-25 02:56:06 -04:00
import { describe, expect, it } from 'vitest'
import {
compoundClass,
compoundLetter,
2026-05-25 02:56:06 -04:00
extrapolateClock,
latestRaceControl,
loadPinnedDrivers,
2026-05-25 10:45:35 -04:00
positionDeltaClass,
2026-05-25 02:56:06 -04:00
parseLiveStateEvent,
2026-05-25 10:45:35 -04:00
rcFlagClass,
savePinnedDrivers,
2026-05-25 02:56:06 -04:00
sortLiveTimingRows,
togglePin,
trackStatusInfo,
2026-05-25 02:56:06 -04:00
trackStatusLabel,
tyreClass,
tyreLabel,
windDirectionLabel,
2026-05-25 02:56:06 -04:00
} from '../lib/live'
import type { LiveStreamData } from '../types'
const snapshot: LiveStreamData = {
Drivers: {
'16': {
RacingNumber: '16',
Position: 1,
PrevPosition: 2,
GapToLeader: '',
Interval: '',
LastLapTime: '1:14.100',
LastLapPB: true,
LastLapOB: false,
BestLapTime: '1:13.900',
BestLapPB: false,
BestLapOB: false,
BestLapNum: 20,
InPit: false,
PitOut: false,
Retired: false,
KnockedOut: false,
Cutoff: false,
OnFlyingLap: false,
NumberOfLaps: 21,
SpeedTrap: '',
Sectors: [],
},
'1': {
RacingNumber: '1',
Position: 2,
PrevPosition: 1,
GapToLeader: '+1.200',
Interval: '+1.200',
LastLapTime: '1:14.300',
LastLapPB: false,
LastLapOB: false,
BestLapTime: '1:13.800',
BestLapPB: false,
BestLapOB: true,
BestLapNum: 19,
InPit: false,
PitOut: false,
Retired: false,
KnockedOut: false,
Cutoff: false,
OnFlyingLap: false,
NumberOfLaps: 21,
SpeedTrap: '',
Sectors: [],
},
},
DriverInfo: {
'16': {
RacingNumber: '16',
BroadcastName: 'C LECLERC',
Tla: 'LEC',
TeamName: 'Ferrari',
TeamColour: 'e8002d',
FirstName: 'Charles',
LastName: 'Leclerc',
},
'44': {
RacingNumber: '44',
BroadcastName: 'L HAMILTON',
Tla: 'HAM',
TeamName: 'Ferrari',
TeamColour: 'e8002d',
FirstName: 'Lewis',
LastName: 'Hamilton',
},
},
Tyres: {
'16': { Compound: 'MEDIUM', New: false, Age: 8 },
},
RCMessages: [
{ Time: '14:01', Category: 'Flag', Flag: 'GREEN', Message: 'GREEN LIGHT', Lap: 0 },
{ Time: '14:08', Category: 'Drs', Flag: '', Message: 'DRS ENABLED', Lap: 3 },
],
Weather: { AirTemp: 20, TrackTemp: 31, Humidity: 55, WindSpeed: 2, WindDir: 180, Rainfall: false },
Session: { MeetingName: 'Monaco Grand Prix', CircuitName: 'Monaco', SessionType: 'Race', SessionName: 'Race' },
TrackStatus: '1',
CurrentLap: 21,
TotalLaps: 78,
Clock: '01:20:00',
ClockRefTime: '2026-05-25T12:00:00Z',
ClockExtrapolating: true,
Stints: {},
}
describe('live transforms', () => {
it('parses live EventSource snapshots without changing PascalCase data', () => {
const parsed = parseLiveStateEvent(JSON.stringify({ is_live: true, data: snapshot }))
expect(parsed?.is_live).toBe(true)
expect(parsed?.data?.Drivers['16'].RacingNumber).toBe('16')
})
it('sorts timing rows by live position and includes drivers with metadata only', () => {
const rows = sortLiveTimingRows(snapshot)
expect(rows.map((row) => row.RacingNumber)).toEqual(['16', '1', '44'])
expect(rows[2].Position).toBe(3)
})
it('formats tyre labels and classes', () => {
expect(tyreLabel({ Compound: 'MEDIUM', New: false, Age: 8 })).toBe('M +8')
expect(tyreClass({ Compound: 'INTERMEDIATE', New: true, Age: 1 })).toBe('tyre-inter')
expect(tyreLabel(undefined)).toBe('?')
})
it('maps track status and race control ordering', () => {
expect(trackStatusLabel('4')).toBe('SC')
expect(latestRaceControl(snapshot.RCMessages, 1)[0].Message).toBe('DRS ENABLED')
})
2026-05-25 10:45:35 -04:00
it('maps position delta and race-control flag classes', () => {
expect(positionDeltaClass(snapshot.Drivers['16'])).toBe('pos-gain')
expect(positionDeltaClass(snapshot.Drivers['1'])).toBe('pos-loss')
expect(positionDeltaClass({ ...snapshot.Drivers['1'], PrevPosition: 2, Position: 2 })).toBe('')
expect(rcFlagClass('GREEN')).toBe('rc-flag-green')
expect(rcFlagClass('safety car')).toBe('rc-flag-sc')
expect(rcFlagClass('virtual safety car')).toBe('rc-flag-vsc')
expect(rcFlagClass('unknown')).toBe('')
})
2026-05-25 02:56:06 -04:00
it('extrapolates the session clock from the reference time', () => {
expect(extrapolateClock('01:20:00', '2026-05-25T12:00:00Z', true, Date.parse('2026-05-25T12:00:30Z'))).toBe('01:19:30')
})
})
describe('track status mapping', () => {
it('maps all known raw statuses to banner info', () => {
expect(trackStatusInfo('1')).toMatchObject({ key: 'green', label: 'TRACK CLEAR' })
expect(trackStatusInfo('2')).toMatchObject({ key: 'yellow', label: 'YELLOW FLAG' })
expect(trackStatusInfo('4')).toMatchObject({ key: 'sc', label: 'SAFETY CAR' })
expect(trackStatusInfo('5')).toMatchObject({ key: 'red', label: 'RED FLAG' })
expect(trackStatusInfo('6')).toMatchObject({ key: 'vsc', label: 'VIRTUAL SAFETY CAR' })
expect(trackStatusInfo('7')).toMatchObject({ key: 'vsc', label: 'VSC ENDING' })
})
it('falls back to a neutral display for unknown values', () => {
expect(trackStatusInfo('9')).toMatchObject({ key: 'unknown', label: 'TRACK STATUS 9' })
expect(trackStatusInfo('')).toMatchObject({ key: 'unknown', label: 'TRACK STATUS UNKNOWN' })
expect(trackStatusInfo(undefined)).toMatchObject({ key: 'unknown' })
})
it('keeps the compact label helper defensive', () => {
expect(trackStatusLabel('7')).toBe('VSC ENDING')
expect(trackStatusLabel('99')).toBe('99')
expect(trackStatusLabel('')).toBe('UNKNOWN')
})
})
describe('weather and stint helpers', () => {
it('maps wind direction degrees to compass points', () => {
expect(windDirectionLabel(0)).toBe('N')
expect(windDirectionLabel(90)).toBe('E')
expect(windDirectionLabel(180)).toBe('S')
expect(windDirectionLabel(315)).toBe('NW')
expect(windDirectionLabel(359)).toBe('N')
expect(windDirectionLabel(null)).toBe('')
})
it('maps stint compounds to classes and letters', () => {
expect(compoundClass('SOFT')).toBe('tyre-soft')
expect(compoundClass('INTERMEDIATE')).toBe('tyre-inter')
expect(compoundClass('')).toBe('tyre-unknown')
expect(compoundLetter('MEDIUM')).toBe('M')
expect(compoundLetter(undefined)).toBe('?')
})
})
describe('pinned drivers', () => {
it('toggles pins with a max of three, dropping the oldest', () => {
expect(togglePin([], '1')).toEqual(['1'])
expect(togglePin(['1'], '1')).toEqual([])
expect(togglePin(['1', '4'], '16')).toEqual(['1', '4', '16'])
expect(togglePin(['1', '4', '16'], '44')).toEqual(['4', '16', '44'])
expect(togglePin(['1', '4', '16'], '4')).toEqual(['1', '16'])
})
it('round-trips pins through storage and survives corrupt data', () => {
const store = new Map<string, string>()
const storage = {
getItem: (key: string) => store.get(key) ?? null,
setItem: (key: string, value: string) => void store.set(key, value),
}
savePinnedDrivers(['1', '44'], storage)
expect(loadPinnedDrivers(storage)).toEqual(['1', '44'])
store.set('box-box.live.pins', 'not json {{')
expect(loadPinnedDrivers(storage)).toEqual([])
store.set('box-box.live.pins', '{"nope":true}')
expect(loadPinnedDrivers(storage)).toEqual([])
})
})