mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Upgrade live timing page for race weekends
- Track status flag banner (green/yellow/SC/VSC/red, defensive mapping) - Weather strip: air/track temp, humidity, wind with compass, rain badge - Gap trend sparklines from a per-driver interval ring buffer - Battle detection: consecutive cars within 1.0s chained and highlighted - Stint history column with compound sequence per driver - Pinnable drivers (max 3) with focus cards, persisted to localStorage Pure logic lives in lib/gapHistory.ts and lib/battles.ts with unit tests; 137 frontend tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
compoundClass,
|
||||
compoundLetter,
|
||||
extrapolateClock,
|
||||
latestRaceControl,
|
||||
loadPinnedDrivers,
|
||||
positionDeltaClass,
|
||||
parseLiveStateEvent,
|
||||
rcFlagClass,
|
||||
savePinnedDrivers,
|
||||
sortLiveTimingRows,
|
||||
togglePin,
|
||||
trackStatusInfo,
|
||||
trackStatusLabel,
|
||||
tyreClass,
|
||||
tyreLabel,
|
||||
windDirectionLabel,
|
||||
} from '../lib/live'
|
||||
import type { LiveStreamData } from '../types'
|
||||
|
||||
@@ -137,3 +144,70 @@ describe('live transforms', () => {
|
||||
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([])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user