From c172bb59b15cd40e9ff3af0fc0c2f6816b2d83cf Mon Sep 17 00:00:00 2001 From: AmanTahiliani Date: Mon, 25 May 2026 13:10:47 -0400 Subject: [PATCH] Phase 22: Race Story Deepening --- .../src/components/ClassificationTable.tsx | 98 ------- .../src/components/PositionEvolutionView.tsx | 239 ------------------ frontend/src/components/RaceStoryCanvas.tsx | 189 ++++++++++++++ frontend/src/components/StartingGridTable.tsx | 57 ----- frontend/src/pages/RaceHubPage.tsx | 76 +----- frontend/src/styles/app.css | 128 ++++++++++ .../src/test/ClassificationTable.test.tsx | 131 ---------- .../src/test/PositionEvolutionView.test.tsx | 200 --------------- frontend/src/test/RaceHubPage.test.tsx | 6 +- 9 files changed, 321 insertions(+), 803 deletions(-) delete mode 100644 frontend/src/components/ClassificationTable.tsx delete mode 100644 frontend/src/components/PositionEvolutionView.tsx create mode 100644 frontend/src/components/RaceStoryCanvas.tsx delete mode 100644 frontend/src/components/StartingGridTable.tsx delete mode 100644 frontend/src/test/ClassificationTable.test.tsx delete mode 100644 frontend/src/test/PositionEvolutionView.test.tsx diff --git a/frontend/src/components/ClassificationTable.tsx b/frontend/src/components/ClassificationTable.tsx deleted file mode 100644 index 7312d54..0000000 --- a/frontend/src/components/ClassificationTable.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type { EnrichedResult, EnrichedGrid } from '../types' -import { DriverCell } from './DriverCell' -import { formatDuration, formatGap, positionClass, gridDelta, gridDeltaClass } from '../utils' - -interface Props { - results: EnrichedResult[] - grid: EnrichedGrid[] -} - -export function ClassificationTable({ results, grid }: Props) { - if (results.length === 0) { - return ( -
- Results not ingested. Run{' '} - box-box --ingest-session <key> to load this dataset. -
- ) - } - - const gridByDriver = Object.fromEntries(grid.map((g) => [g.driver_number, g.position])) - - const isRace = results.some((r) => r.points > 0 || r.number_of_laps > 0) - - return ( -
- - - - - - - {isRace && } - {isRace && } - - {isRace && } - - - - {results.map((r) => { - const gridPos = gridByDriver[r.driver_number] ?? 0 - const timeStr = r.dnf - ? null - : r.dns - ? null - : r.dsq - ? null - : r.position === 1 - ? formatDuration(r.duration) - : formatGap(r.gap_to_leader) - - return ( - - - - - {isRace && ( - - )} - {isRace && ( - - )} - - {isRace && ( - - )} - - ) - })} - -
PDriverTeamGridΔTime / GapPts
- {r.position} - - - - {r.team_name} - - {gridPos || '—'} - - - {gridDelta(r.position, gridPos)} - - - {r.dnf && DNF} - {r.dns && DNS} - {r.dsq && DSQ} - {!r.dnf && !r.dns && !r.dsq && ( - {timeStr ?? '—'} - )} - 0 ? 700 : 400, color: r.points > 0 ? 'var(--text)' : 'var(--text-3)' }}> - {r.points > 0 ? r.points : '—'} -
-
- ) -} diff --git a/frontend/src/components/PositionEvolutionView.tsx b/frontend/src/components/PositionEvolutionView.tsx deleted file mode 100644 index bcd7b10..0000000 --- a/frontend/src/components/PositionEvolutionView.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import type { EnrichedResult, EnrichedGrid, PositionSample, Lap } from '../types' -import { gridDelta, gridDeltaClass } from '../utils' - -interface Props { - results: EnrichedResult[] - grid: EnrichedGrid[] - positions: PositionSample[] - laps: Lap[] - hasPositions: boolean -} - -export function PositionEvolutionView({ results, grid, positions, laps: _laps, hasPositions }: Props) { - if (!hasPositions) { - return ( -
-
- Lap-by-lap positions not available. This session does not - have ingested position samples in /api/v1/race-hub. Evolution - charts require per-driver position samples over time. -
- - {results.length > 0 && grid.length > 0 && ( - <> -
- Grid → Finish - net positions gained/lost -
- - - - - - - - - - - - {results.map((r) => { - const gridPos = - grid.find((g) => g.driver_number === r.driver_number)?.position ?? 0 - return ( - - - - - - - - ) - })} - -
DriverTeamGridFinishΔ
- - {r.name_acronym || r.driver_number} - - - {r.team_name} - - {gridPos || '—'} - {r.position} - - {gridDelta(r.position, gridPos)} - -
- - )} -
- ) - } - - // Build time-indexed position series per driver - const allTimes = [...new Set(positions.map((p) => p.date))].sort() - - if (allTimes.length === 0) { - return
No position samples in this dataset.
- } - - const tMin = new Date(allTimes[0]).getTime() - const tMax = new Date(allTimes[allTimes.length - 1]).getTime() - const tRange = Math.max(tMax - tMin, 1) - - const byDriver = new Map>() - for (const p of positions) { - if (!byDriver.has(p.driver_number)) byDriver.set(p.driver_number, []) - byDriver.get(p.driver_number)!.push({ - t: (new Date(p.date).getTime() - tMin) / tRange, - pos: p.position, - }) - } - for (const samples of byDriver.values()) { - samples.sort((a, b) => a.t - b.t) - } - - const maxPos = Math.max(...positions.map((p) => p.position), results.length, 2) - const colorByDriver = new Map(results.map((r) => [r.driver_number, r.team_colour])) - const acronymByDriver = new Map(results.map((r) => [r.driver_number, r.name_acronym])) - - const W = 640 - const H = 180 - const PL = 40 - const PR = 48 // right margin for driver labels - const PT = 8 - const PB = 8 - const plotW = W - PL - PR - const plotH = H - PT - PB - - const toX = (t: number) => PL + t * plotW - const toY = (pos: number) => PT + ((pos - 1) / Math.max(maxPos - 1, 1)) * plotH - - return ( -
-
- - {/* Horizontal grid lines + P# labels */} - {Array.from({ length: maxPos }, (_, i) => i + 1).map((pos) => ( - - - - P{pos} - - - ))} - - {/* Driver lines */} - {Array.from(byDriver.entries()).map(([dNum, samples]) => { - const colour = colorByDriver.get(dNum) - const color = colour ? `#${colour}` : '#888' - const pts = samples.map((s) => `${toX(s.t)},${toY(s.pos)}`).join(' ') - const last = samples[samples.length - 1] - - return ( - - - {samples.map((s, i) => ( - - ))} - {last && ( - - {acronymByDriver.get(dNum) ?? dNum} - - )} - - ) - })} - -
- - {/* Grid → Finish table below chart for context */} - {results.length > 0 && grid.length > 0 && ( - <> -
- Grid → Finish - net positions -
- - - - - - - - - - - {results.map((r) => { - const gridPos = - grid.find((g) => g.driver_number === r.driver_number)?.position ?? 0 - return ( - - - - - - - ) - })} - -
DriverGridFinishΔ
- - {r.name_acronym || r.driver_number} - - - {gridPos || '—'} - {r.position} - - {gridDelta(r.position, gridPos)} - -
- - )} -
- ) -} diff --git a/frontend/src/components/RaceStoryCanvas.tsx b/frontend/src/components/RaceStoryCanvas.tsx new file mode 100644 index 0000000..e40ba23 --- /dev/null +++ b/frontend/src/components/RaceStoryCanvas.tsx @@ -0,0 +1,189 @@ +import type { EnrichedResult, EnrichedGrid, PositionSample, Lap } from '../types' +import { gridDelta, gridDeltaClass, formatDuration, formatGap } from '../utils' + +interface Props { + data: { + results: EnrichedResult[] + starting_grid: EnrichedGrid[] + positions: PositionSample[] + laps: Lap[] + datasets: Record + } +} + +export function RaceStoryCanvas({ data }: Props) { + const { results, starting_grid: grid, positions, datasets } = data + const hasPositions = datasets['positions']?.status === 'available' + + // Position Evolution Chart Logic + const allTimes = [...new Set(positions.map((p) => p.date))].sort() + const hasChartData = hasPositions && allTimes.length > 0 + + let chartContent = null + if (hasChartData) { + const tMin = new Date(allTimes[0]).getTime() + const tMax = new Date(allTimes[allTimes.length - 1]).getTime() + const tRange = Math.max(tMax - tMin, 1) + + const byDriver = new Map>() + for (const p of positions) { + if (!byDriver.has(p.driver_number)) byDriver.set(p.driver_number, []) + byDriver.get(p.driver_number)!.push({ + t: (new Date(p.date).getTime() - tMin) / tRange, + pos: p.position, + }) + } + for (const samples of byDriver.values()) { + samples.sort((a, b) => a.t - b.t) + } + + const maxPos = Math.max(...positions.map((p) => p.position), results.length, 2) + const colorByDriver = new Map(results.map((r) => [r.driver_number, r.team_colour])) + const acronymByDriver = new Map(results.map((r) => [r.driver_number, r.name_acronym])) + + const W = 640 + const H = 180 + const PL = 40 + const PR = 48 + const PT = 8 + const PB = 8 + const plotW = W - PL - PR + const plotH = H - PT - PB + + const toX = (t: number) => PL + t * plotW + const toY = (pos: number) => PT + ((pos - 1) / Math.max(maxPos - 1, 1)) * plotH + + chartContent = ( +
+ + {Array.from({ length: maxPos }, (_, i) => i + 1).map((pos) => ( + + + + P{pos} + + + ))} + + {Array.from(byDriver.entries()).map(([dNum, samples]) => { + const colour = colorByDriver.get(dNum) + const color = colour ? `#${colour}` : '#888' + const pts = samples.map((s) => `${toX(s.t)},${toY(s.pos)}`).join(' ') + const last = samples[samples.length - 1] + + return ( + + + {samples.map((s, i) => ( + + ))} + {last && ( + + {acronymByDriver.get(dNum) ?? dNum} + + )} + + ) + })} + +
+ ) + } + + return ( +
+ {hasChartData ? ( + chartContent + ) : ( +
+ Lap-by-lap positions not available. This session does not + have ingested position samples in /api/v1/race-hub. +
+ )} + + {results.length > 0 && ( +
+ {results.map((r, i) => { + const gridPos = grid.find((g) => g.driver_number === r.driver_number)?.position ?? 0 + const isWinner = i === 0 && r.position === 1 + const pClass = r.position === 1 ? 'rs-pos-p1' : r.position === 2 ? 'rs-pos-p2' : r.position === 3 ? 'rs-pos-p3' : '' + + return ( +
+
+
{r.position}
+
+
+
+ {r.name_acronym || r.driver_number} + {r.team_name} +
+
+
+ +
+
+ + + {gridDelta(r.position, gridPos)} + + + Grid +
+ +
+ {isWinner ? formatDuration(r.duration) : formatGap(r.gap_to_leader)} + {isWinner ? 'Time' : 'Gap'} +
+ +
+ 0 ? 'var(--text)' : 'var(--text-3)' }}> + {r.points} + + Pts +
+
+
+ ) + })} +
+ )} +
+ ) +} diff --git a/frontend/src/components/StartingGridTable.tsx b/frontend/src/components/StartingGridTable.tsx deleted file mode 100644 index 100f4ff..0000000 --- a/frontend/src/components/StartingGridTable.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { EnrichedGrid } from '../types' -import { DriverCell } from './DriverCell' -import { formatLapTime } from '../utils' - -interface Props { - grid: EnrichedGrid[] -} - -export function StartingGridTable({ grid }: Props) { - if (grid.length === 0) { - return ( -
- Starting grid not ingested. Run{' '} - box-box --ingest-session <key> to load this dataset. -
- ) - } - - return ( -
- - - - - - - - - - - {grid.map((g) => ( - - - - - - - ))} - -
PDriverTeamLap Time
- - {g.position} - - - - - {g.team_name} - - {g.lap_duration != null ? formatLapTime(g.lap_duration) : '—'} -
-
- ) -} diff --git a/frontend/src/pages/RaceHubPage.tsx b/frontend/src/pages/RaceHubPage.tsx index 2b72d09..5b37310 100644 --- a/frontend/src/pages/RaceHubPage.tsx +++ b/frontend/src/pages/RaceHubPage.tsx @@ -8,12 +8,10 @@ import { fetchWeekend, } from '../api' import { DatasetStrip } from '../components/DatasetStrip' -import { ClassificationTable } from '../components/ClassificationTable' -import { StartingGridTable } from '../components/StartingGridTable' +import { RaceStoryCanvas } from '../components/RaceStoryCanvas' import { TabBar, type Tab } from '../components/TabBar' import { DatasetStatusView } from '../components/DatasetStatusView' import { StrategyView } from '../components/StrategyView' -import { PositionEvolutionView } from '../components/PositionEvolutionView' import { LapsView } from '../components/LapsView' import { RaceControlView } from '../components/RaceControlView' import { WeatherView } from '../components/WeatherView' @@ -33,8 +31,6 @@ interface Props { sessionKey: number } -type RaceStorySubview = 'classification' | 'grid' | 'positions' - function pickAnalysisSession(weekend: Weekend | undefined): WeekendSession | undefined { if (!weekend) return undefined const local = weekend.sessions.filter((s) => s.source === 'local') @@ -50,7 +46,6 @@ function pickAnalysisSession(weekend: Weekend | undefined): WeekendSession | und export function RaceHubPage({ sessionKey }: Props) { const navigate = useNavigate() const [activeTab, setActiveTab] = useState('overview') - const [storyView, setStoryView] = useState('classification') const [switcherOpen, setSwitcherOpen] = useState(false) // ─── Auto-redirect when no session_key is supplied ─── @@ -294,74 +289,7 @@ export function RaceHubPage({ sessionKey }: Props) { {activeTab === 'race_story' && (
-
- - - -
- - {storyView === 'classification' && ( - <> -
- Final Classification - {data.results.length > 0 && ( - {data.results.length} drivers - )} -
- - - )} - - {storyView === 'grid' && ( - <> -
- Starting Grid - {data.starting_grid.length > 0 && ( - {data.starting_grid.length} positions - )} -
- - - )} - - {storyView === 'positions' && ( - <> -
- Position Evolution -
- - - )} +
)} diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 48312bf..06cbea8 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2370,3 +2370,131 @@ a { color: inherit; text-decoration: none; } text-transform: uppercase; letter-spacing: 0.05em; } + +/* ── Race Story Canvas ── */ +.race-story-canvas { + display: flex; + flex-direction: column; + gap: var(--s6); +} + +.rs-chart-container { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--s2); + padding: var(--s4) 0; + overflow-x: auto; +} + +.rs-field-list { + display: flex; + flex-direction: column; + gap: var(--s1); +} + +.rs-driver-row { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--surface); + padding: var(--s3) var(--s4); + border: 1px solid var(--border); + border-radius: var(--s1); + transition: background 0.15s, border-color 0.15s; +} + +.rs-driver-row:hover { + background: var(--surface-h); + border-color: var(--border-2); +} + +.rs-driver-left { + display: flex; + align-items: center; + gap: var(--s5); + flex: 1; +} + +.rs-pos-col { + width: 24px; + text-align: right; + font-family: var(--f-mono); + font-weight: 600; + color: var(--text-2); +} + +.rs-pos-p1 { color: var(--yellow); } +.rs-pos-p2 { color: var(--text); } +.rs-pos-p3 { color: var(--text); } + +.rs-driver-cell { + display: flex; + align-items: center; + gap: var(--s4); + flex: 1; + min-width: 0; +} + +.rs-driver-color { + width: 4px; + height: 20px; + border-radius: 2px; + flex-shrink: 0; +} + +.rs-driver-identity { + display: flex; + flex-direction: column; + line-height: 1.2; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.rs-driver-name { + font-family: var(--f-mono); + font-weight: 600; + color: var(--text); + font-size: 13px; +} + +.rs-driver-team { + font-size: 11px; + color: var(--text-2); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.rs-driver-right { + display: flex; + align-items: center; + gap: var(--s6); + text-align: right; +} + +.rs-metric { + display: flex; + flex-direction: column; + align-items: flex-end; + font-family: var(--f-mono); + font-size: 13px; + line-height: 1.2; +} + +.rs-metric-label { + font-family: var(--f-ui); + font-size: 10px; + color: var(--text-3); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +@media (max-width: 640px) { + .rs-driver-right { + gap: var(--s4); + } + .rs-driver-team { + display: none; + } +} diff --git a/frontend/src/test/ClassificationTable.test.tsx b/frontend/src/test/ClassificationTable.test.tsx deleted file mode 100644 index 6a258f8..0000000 --- a/frontend/src/test/ClassificationTable.test.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { render, screen } from '@testing-library/react' -import { ClassificationTable } from '../components/ClassificationTable' -import type { EnrichedResult, EnrichedGrid } from '../types' - -const mockResults: EnrichedResult[] = [ - { - driver_number: 16, - position: 1, - name_acronym: 'LEC', - full_name: 'Charles Leclerc', - team_name: 'Ferrari', - team_colour: 'e8002d', - dnf: false, - dns: false, - dsq: false, - duration: 5534.456, - gap_to_leader: null, - number_of_laps: 78, - points: 25, - session_key: 9472, - meeting_key: 1234, - }, - { - driver_number: 1, - position: 2, - name_acronym: 'VER', - full_name: 'Max Verstappen', - team_name: 'Red Bull Racing', - team_colour: '3671c6', - dnf: false, - dns: false, - dsq: false, - duration: null, - gap_to_leader: 3.456, - number_of_laps: 78, - points: 18, - session_key: 9472, - meeting_key: 1234, - }, - { - driver_number: 44, - position: 5, - name_acronym: 'HAM', - full_name: 'Lewis Hamilton', - team_name: 'Ferrari', - team_colour: 'e8002d', - dnf: false, - dns: false, - dsq: false, - duration: null, - gap_to_leader: 21.234, - number_of_laps: 78, - points: 10, - session_key: 9472, - meeting_key: 1234, - }, -] - -const mockGrid: EnrichedGrid[] = [ - { - driver_number: 1, - position: 1, - name_acronym: 'VER', - full_name: 'Max Verstappen', - team_name: 'Red Bull Racing', - team_colour: '3671c6', - session_key: 9472, - meeting_key: 1234, - lap_duration: 74.892, - }, - { - driver_number: 16, - position: 3, - name_acronym: 'LEC', - full_name: 'Charles Leclerc', - team_name: 'Ferrari', - team_colour: 'e8002d', - session_key: 9472, - meeting_key: 1234, - lap_duration: 75.123, - }, -] - -describe('ClassificationTable', () => { - it('renders driver acronyms', () => { - render() - expect(screen.getByText('LEC')).toBeInTheDocument() - expect(screen.getByText('VER')).toBeInTheDocument() - expect(screen.getByText('HAM')).toBeInTheDocument() - }) - - it('renders P1 badge for LEC', () => { - const { container } = render() - const p1 = container.querySelector('.pos-p1') - expect(p1).toBeInTheDocument() - expect(p1?.textContent).toBe('1') - }) - - it('shows grid gain arrow for LEC (started P3, finished P1)', () => { - render() - expect(screen.getByText('↑2')).toBeInTheDocument() - }) - - it('shows grid loss arrow for VER (started P1, finished P2)', () => { - render() - expect(screen.getByText('↓1')).toBeInTheDocument() - }) - - it('renders missing notice when results are empty', () => { - render() - expect(screen.getByText(/not ingested/i)).toBeInTheDocument() - }) -}) - -describe('ClassificationTable — DNF/DNS/DSQ', () => { - it('shows DNF label', () => { - const dnfResult: EnrichedResult = { - ...mockResults[0], - driver_number: 23, - name_acronym: 'ALB', - position: 20, - dnf: true, - duration: null, - gap_to_leader: null, - points: 0, - } - render() - expect(screen.getByText('DNF')).toBeInTheDocument() - }) -}) diff --git a/frontend/src/test/PositionEvolutionView.test.tsx b/frontend/src/test/PositionEvolutionView.test.tsx deleted file mode 100644 index 0fba545..0000000 --- a/frontend/src/test/PositionEvolutionView.test.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { render, screen } from '@testing-library/react' -import { PositionEvolutionView } from '../components/PositionEvolutionView' -import type { EnrichedResult, EnrichedGrid, PositionSample, Lap } from '../types' - -const results: EnrichedResult[] = [ - { - driver_number: 1, - position: 1, - name_acronym: 'VER', - full_name: 'Max Verstappen', - team_name: 'Red Bull Racing', - team_colour: '3671C6', - dnf: false, - dns: false, - dsq: false, - duration: null, - gap_to_leader: null, - number_of_laps: 78, - points: 25, - session_key: 9472, - meeting_key: 1229, - }, - { - driver_number: 44, - position: 2, - name_acronym: 'HAM', - full_name: 'Lewis Hamilton', - team_name: 'Ferrari', - team_colour: 'E8002D', - dnf: false, - dns: false, - dsq: false, - duration: null, - gap_to_leader: 5.1, - number_of_laps: 78, - points: 18, - session_key: 9472, - meeting_key: 1229, - }, -] - -const grid: EnrichedGrid[] = [ - { - driver_number: 1, - position: 1, - name_acronym: 'VER', - full_name: 'Max Verstappen', - team_name: 'Red Bull Racing', - team_colour: '3671C6', - session_key: 9472, - meeting_key: 1229, - lap_duration: 71.234, - }, - { - driver_number: 44, - position: 2, - name_acronym: 'HAM', - full_name: 'Lewis Hamilton', - team_name: 'Ferrari', - team_colour: 'E8002D', - session_key: 9472, - meeting_key: 1229, - lap_duration: 71.456, - }, -] - -const positions: PositionSample[] = [ - { - session_key: 9472, - driver_number: 1, - meeting_key: 1229, - date: '2025-05-25T13:05:00+00:00', - position: 1, - }, - { - session_key: 9472, - driver_number: 1, - meeting_key: 1229, - date: '2025-05-25T13:10:00+00:00', - position: 1, - }, - { - session_key: 9472, - driver_number: 44, - meeting_key: 1229, - date: '2025-05-25T13:05:00+00:00', - position: 2, - }, -] - -const laps: Lap[] = [ - { - session_key: 9472, - driver_number: 1, - meeting_key: 1229, - lap_number: 1, - date_start: '2025-05-25T13:00:00+00:00', - lap_duration: 75.1, - is_pit_out_lap: false, - }, -] - -describe('PositionEvolutionView — positions available', () => { - it('renders the position chart container', () => { - const { container } = render( - - ) - expect(container.querySelector('[data-testid="position-chart"]')).toBeInTheDocument() - }) - - it('renders an SVG chart', () => { - const { container } = render( - - ) - expect(container.querySelector('svg')).toBeInTheDocument() - expect(container.querySelectorAll('polyline').length).toBeGreaterThan(0) - }) - - it('does not show the missing-data notice', () => { - render( - - ) - expect(screen.queryByText(/Lap-by-lap positions not available/i)).not.toBeInTheDocument() - }) - - it('renders Grid → Finish table below chart', () => { - render( - - ) - expect(screen.getByText('Grid → Finish')).toBeInTheDocument() - }) -}) - -describe('PositionEvolutionView — positions missing', () => { - it('shows the missing-data notice', () => { - render( - - ) - expect(screen.getByText(/Lap-by-lap positions not available/i)).toBeInTheDocument() - }) - - it('falls back to grid → finish table when both results and grid exist', () => { - render( - - ) - expect(screen.getByText('Grid → Finish')).toBeInTheDocument() - expect(screen.getByText('VER')).toBeInTheDocument() - expect(screen.getByText('HAM')).toBeInTheDocument() - }) - - it('does not render the position chart', () => { - const { container } = render( - - ) - expect(container.querySelector('[data-testid="position-chart"]')).not.toBeInTheDocument() - }) -}) diff --git a/frontend/src/test/RaceHubPage.test.tsx b/frontend/src/test/RaceHubPage.test.tsx index 33f26d3..4e9eb1c 100644 --- a/frontend/src/test/RaceHubPage.test.tsx +++ b/frontend/src/test/RaceHubPage.test.tsx @@ -234,10 +234,8 @@ describe('RaceHubPage', () => { fireEvent.click(screen.getByRole('tab', { name: 'Race Story' })) - expect(screen.getByRole('tab', { name: 'Classification' })).toBeInTheDocument() - expect(screen.getByRole('tab', { name: 'Starting Grid' })).toBeInTheDocument() - expect(screen.getByRole('tab', { name: 'Positions' })).toBeInTheDocument() - expect(screen.getByText('Final Classification')).toBeInTheDocument() + expect(screen.getByText('VER')).toBeInTheDocument() + }) it('keeps Data Status accessible and free of inline CLI guidance', async () => {