Add championship what-if simulator

Fourth view on the championship page: assign P1-P10 per remaining
round and see projected standings update live, with position deltas
and mathematically alive/eliminated title states. Pure projection
logic in lib/simulator.ts (2025 points system, standard GPs only);
scenarios persist to localStorage per season. 169 frontend tests
passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 00:31:09 -04:00
parent e5cb4e4ed2
commit 5cfaed30ba
8 changed files with 1013 additions and 1 deletions

View File

@@ -95,6 +95,7 @@ function renderPage() {
describe('ChampionshipPage', () => {
beforeEach(() => {
vi.clearAllMocks()
window.localStorage.clear()
mockFetchSeasons.mockResolvedValue([2025])
mockFetchHub.mockResolvedValue(hub)
})
@@ -127,6 +128,18 @@ describe('ChampionshipPage', () => {
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()