mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Add shared Meaning primitive and pure interpretation helpers for interval, tyre age, and championship gap columns on the live tower, tyre deg panel, and championship hub. Thresholds are exported consts (undercut window tied to PIT_LOSS_SECONDS from #13). Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
5.0 KiB
TypeScript
154 lines
5.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||
import { render, screen, waitFor, fireEvent } from '@testing-library/react'
|
||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||
import { RouterProvider, createRouter, createRootRoute, createRoute } from '@tanstack/react-router'
|
||
import { ChampionshipPage } from '../pages/ChampionshipPage'
|
||
import type { ChampHubDriver, ChampHubTeam, ChampionshipHub } from '../types'
|
||
|
||
vi.mock('../api', () => ({
|
||
fetchSeasons: vi.fn(),
|
||
fetchChampionshipHub: vi.fn(),
|
||
}))
|
||
|
||
import { fetchSeasons, fetchChampionshipHub } from '../api'
|
||
|
||
const mockFetchSeasons = vi.mocked(fetchSeasons)
|
||
const mockFetchHub = vi.mocked(fetchChampionshipHub)
|
||
|
||
function driver(over: Partial<ChampHubDriver>): ChampHubDriver {
|
||
return {
|
||
driver_number: 1,
|
||
name_acronym: 'VER',
|
||
full_name: 'Max Verstappen',
|
||
team_name: 'Red Bull',
|
||
team_colour: '3671c6',
|
||
points: 200,
|
||
position: 1,
|
||
wins: 5,
|
||
podiums: 8,
|
||
poles: 4,
|
||
form: [25, 18, 25, 15, 25],
|
||
cumulative: [25, 43, 68, 83, 108, 200],
|
||
teammate_wins: 9,
|
||
teammate_losses: 1,
|
||
...over,
|
||
}
|
||
}
|
||
|
||
const drivers: ChampHubDriver[] = [
|
||
driver({ driver_number: 1, name_acronym: 'VER', team_name: 'Red Bull', points: 200, position: 1 }),
|
||
driver({
|
||
driver_number: 4,
|
||
name_acronym: 'NOR',
|
||
full_name: 'Lando Norris',
|
||
team_name: 'McLaren',
|
||
team_colour: 'ff8000',
|
||
points: 160,
|
||
position: 2,
|
||
wins: 3,
|
||
cumulative: [18, 36, 54, 80, 120, 160],
|
||
}),
|
||
driver({
|
||
driver_number: 16,
|
||
name_acronym: 'LEC',
|
||
full_name: 'Charles Leclerc',
|
||
team_name: 'Ferrari',
|
||
team_colour: 'e8002d',
|
||
points: 120,
|
||
position: 3,
|
||
wins: 1,
|
||
cumulative: [15, 28, 40, 60, 90, 120],
|
||
}),
|
||
]
|
||
|
||
const teams: ChampHubTeam[] = [
|
||
{ team_name: 'Red Bull', team_colour: '3671c6', points: 260, position: 1, wins: 6 },
|
||
{ team_name: 'McLaren', team_colour: 'ff8000', points: 220, position: 2, wins: 3 },
|
||
{ team_name: 'Ferrari', team_colour: 'e8002d', points: 180, position: 3, wins: 1 },
|
||
]
|
||
|
||
const hub: ChampionshipHub = {
|
||
season: 2025,
|
||
round: 6,
|
||
total_rounds: 10,
|
||
rounds_left: 4,
|
||
last_race: 'Monaco GP',
|
||
round_labels: ['R1', 'R2', 'R3', 'R4', 'R5', 'R6'],
|
||
drivers,
|
||
teams,
|
||
}
|
||
|
||
function renderPage() {
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||
const rootRoute = createRootRoute({
|
||
component: () => (
|
||
<QueryClientProvider client={queryClient}>
|
||
<ChampionshipPage />
|
||
</QueryClientProvider>
|
||
),
|
||
})
|
||
const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: ChampionshipPage })
|
||
const router = createRouter({ routeTree: rootRoute.addChildren([indexRoute]) })
|
||
return render(<RouterProvider router={router} />)
|
||
}
|
||
|
||
describe('ChampionshipPage', () => {
|
||
beforeEach(() => {
|
||
vi.clearAllMocks()
|
||
window.localStorage.clear()
|
||
mockFetchSeasons.mockResolvedValue([2025])
|
||
mockFetchHub.mockResolvedValue(hub)
|
||
})
|
||
|
||
it('renders the drivers view with leader and title math', async () => {
|
||
renderPage()
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByTestId('championship')).toBeInTheDocument()
|
||
})
|
||
|
||
expect(screen.getByTestId('champ-view-drivers')).toBeInTheDocument()
|
||
// Leader code shows in the stat rail and the table.
|
||
expect(screen.getAllByText('VER').length).toBeGreaterThan(0)
|
||
expect(screen.getByText('Monaco GP', { exact: false })).toBeInTheDocument()
|
||
expect(screen.getByTestId('champ-titlemath')).toHaveTextContent('mathematically win the title')
|
||
expect(screen.getAllByText('~10 pts/round').length).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('switches to constructors and progression views', async () => {
|
||
renderPage()
|
||
|
||
await waitFor(() => expect(screen.getByTestId('championship')).toBeInTheDocument())
|
||
|
||
fireEvent.click(screen.getByTestId('champ-tab-constructors'))
|
||
expect(screen.getByTestId('champ-view-constructors')).toBeInTheDocument()
|
||
expect(screen.getAllByText('Red Bull', { exact: false }).length).toBeGreaterThan(0)
|
||
|
||
fireEvent.click(screen.getByTestId('champ-tab-progression'))
|
||
expect(screen.getByTestId('champ-view-progression')).toBeInTheDocument()
|
||
expect(screen.getByText('Cumulative points', { exact: false })).toBeInTheDocument()
|
||
})
|
||
|
||
it('switches to the simulator view and projects standings', async () => {
|
||
renderPage()
|
||
|
||
await waitFor(() => expect(screen.getByTestId('championship')).toBeInTheDocument())
|
||
|
||
fireEvent.click(screen.getByTestId('champ-tab-simulator'))
|
||
expect(screen.getByTestId('champ-view-simulator')).toBeInTheDocument()
|
||
expect(screen.getByTestId('sim-projected')).toBeInTheDocument()
|
||
// 4 rounds left, default scenario: VER projects to 200 + 4×25 = 300.
|
||
expect(screen.getByText('300')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows the empty state when no drivers are returned', async () => {
|
||
mockFetchHub.mockResolvedValue({ ...hub, drivers: [], teams: [] })
|
||
renderPage()
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByTestId('championship-empty')).toBeInTheDocument()
|
||
})
|
||
expect(screen.getByText('No championship data')).toBeInTheDocument()
|
||
})
|
||
})
|