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

83 lines
2.4 KiB
TypeScript
Raw Normal View History

feat(race-hub): trustworthy defaults and fan-facing analysis hierarchy (#75) Make bare /race-hub resolve to a completed session and never open empty post-session analysis for a future race. - Backend: add `default_analysis_session` to the Weekend context. It never resolves to a future session (picks the richest completed session, ties toward the later one; 0 when everything is upcoming). Existing `default_session_key` and deep links are unchanged. - Frontend default resolution prefers the most recently completed weekend (`pickAnalysisFocusMeeting`) and consumes `default_analysis_session`, falling back to the switcher when only upcoming sessions exist. - New `sessionState` lib maps timing + coverage to user language (upcoming/live/preparing/partial/ready/cancelled); the session rail, active sub-bar, and WeekendSwitcher now label states instead of raw x/11 counts. - Future sessions render a purpose-built PreSessionView (expected availability + countdown) instead of empty Winner/Podium/Pole/Strategy/Compare cards. - Analysis navigation regrouped into Story / Analysis / Data & Context; every existing tab is preserved. Diagnostics (renamed from Data Status) is now a secondary action and the raw dataset strip is hidden behind an explicit toggle, so operational coverage no longer precedes fan content. - Loading/error states offer Retry and a path back to Weekend. Tests: Go query tests for future-exclusion; Vitest for default selection, future pre-session, partial state, error/retry, grouped nav, and sessionState; hermetic Playwright for bare/completed/future/return-to-Weekend; new race-hub-future visual snapshots. Seed adds a far-future session inside the Monaco meeting (kept in-meeting so Command Center focus is unaffected). Note: `default_analysis_session` is an additive field on the existing `/weekend` contract (no new endpoint), per the spec's "context contract" scope. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:29:34 -04:00
import { describe, it, expect } from 'vitest'
import { sessionState, sessionStateLabel } from '../lib/sessionState'
import type { DatasetInfo, Session, WeekendSession } from '../types'
const NOW = new Date('2025-06-01T00:00:00Z')
function mk(
overrides: Partial<Session>,
source: WeekendSession['source'],
datasets: Record<string, DatasetInfo> = {},
): WeekendSession {
return {
session: {
session_key: 1,
session_name: 'Race',
session_type: 'Race',
meeting_key: 1,
date_start: '2025-05-25T13:00:00+00:00',
date_end: '2025-05-25T15:00:00+00:00',
gmt_offset: '00:00:00',
...overrides,
},
source,
datasets,
}
}
const FULL: Record<string, DatasetInfo> = Object.fromEntries(
[
'meeting',
'session',
'drivers',
'results',
'starting_grid',
'stints',
'pit_stops',
'positions',
'race_control',
'weather',
'laps',
].map((k) => [k, { status: 'available', source: 'local', count: 1 }]),
)
describe('sessionState', () => {
it('marks a future session as upcoming', () => {
const s = mk({ date_start: '2099-05-25T13:00:00+00:00', date_end: '2099-05-25T15:00:00+00:00' }, 'none')
expect(sessionState(s, NOW)).toBe('upcoming')
})
it('marks a running session as live', () => {
const start = new Date(NOW.getTime() - 60_000).toISOString()
const end = new Date(NOW.getTime() + 60_000).toISOString()
const s = mk({ date_start: start, date_end: end }, 'partial')
expect(sessionState(s, NOW)).toBe('live')
})
it('marks a finished session with full local data as ready', () => {
const s = mk({}, 'local', FULL)
expect(sessionState(s, NOW)).toBe('ready')
})
it('marks a finished session with no data as preparing', () => {
const s = mk({}, 'none', {})
expect(sessionState(s, NOW)).toBe('preparing')
})
it('marks a finished session with partial data as partial', () => {
const s = mk({}, 'partial', { drivers: { status: 'available', source: 'local', count: 20 } })
expect(sessionState(s, NOW)).toBe('partial')
})
it('marks a cancelled session as cancelled', () => {
const s = mk({}, 'cancelled')
expect(sessionState(s, NOW)).toBe('cancelled')
})
it('uses user language labels rather than coverage counts', () => {
expect(sessionStateLabel('ready')).toBe('Ready')
expect(sessionStateLabel('upcoming')).toBe('Upcoming')
expect(sessionStateLabel('partial')).toBe('Partial')
})
})