Phase 22: Race Story Deepening

This commit is contained in:
2026-05-25 13:10:47 -04:00
parent 7ce2b1ace4
commit c172bb59b1
9 changed files with 321 additions and 803 deletions

View File

@@ -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(<ClassificationTable results={mockResults} grid={mockGrid} />)
expect(screen.getByText('LEC')).toBeInTheDocument()
expect(screen.getByText('VER')).toBeInTheDocument()
expect(screen.getByText('HAM')).toBeInTheDocument()
})
it('renders P1 badge for LEC', () => {
const { container } = render(<ClassificationTable results={mockResults} grid={mockGrid} />)
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(<ClassificationTable results={mockResults} grid={mockGrid} />)
expect(screen.getByText('↑2')).toBeInTheDocument()
})
it('shows grid loss arrow for VER (started P1, finished P2)', () => {
render(<ClassificationTable results={mockResults} grid={mockGrid} />)
expect(screen.getByText('↓1')).toBeInTheDocument()
})
it('renders missing notice when results are empty', () => {
render(<ClassificationTable results={[]} grid={[]} />)
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(<ClassificationTable results={[dnfResult]} grid={[]} />)
expect(screen.getByText('DNF')).toBeInTheDocument()
})
})

View File

@@ -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(
<PositionEvolutionView
results={results}
grid={grid}
positions={positions}
laps={laps}
hasPositions={true}
/>
)
expect(container.querySelector('[data-testid="position-chart"]')).toBeInTheDocument()
})
it('renders an SVG chart', () => {
const { container } = render(
<PositionEvolutionView
results={results}
grid={grid}
positions={positions}
laps={laps}
hasPositions={true}
/>
)
expect(container.querySelector('svg')).toBeInTheDocument()
expect(container.querySelectorAll('polyline').length).toBeGreaterThan(0)
})
it('does not show the missing-data notice', () => {
render(
<PositionEvolutionView
results={results}
grid={grid}
positions={positions}
laps={laps}
hasPositions={true}
/>
)
expect(screen.queryByText(/Lap-by-lap positions not available/i)).not.toBeInTheDocument()
})
it('renders Grid → Finish table below chart', () => {
render(
<PositionEvolutionView
results={results}
grid={grid}
positions={positions}
laps={laps}
hasPositions={true}
/>
)
expect(screen.getByText('Grid → Finish')).toBeInTheDocument()
})
})
describe('PositionEvolutionView — positions missing', () => {
it('shows the missing-data notice', () => {
render(
<PositionEvolutionView
results={results}
grid={grid}
positions={[]}
laps={[]}
hasPositions={false}
/>
)
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(
<PositionEvolutionView
results={results}
grid={grid}
positions={[]}
laps={[]}
hasPositions={false}
/>
)
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(
<PositionEvolutionView
results={results}
grid={grid}
positions={[]}
laps={[]}
hasPositions={false}
/>
)
expect(container.querySelector('[data-testid="position-chart"]')).not.toBeInTheDocument()
})
})

View File

@@ -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 () => {