mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-08 04:06:18 -04:00
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the authoritative #72 Weekend Context API. - Replace the invented frontend WeekendContext with the exact backend contract (temporal_state, previous/focus/next meetings, previous_completed/active/next/ default_analysis sessions with availability). Every valid canonical payload now maps to a designed state via a total resolveViewState; a well-formed response can never fall through to the limited-data placeholder. - Make the canonical read the single source of truth: useWeekendContext no longer fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary championship + news reads run, and only once the canonical context resolves. - Fix the Prepare/analysis flow: /preview is a stable alias that renders the preparation surface (PreSessionView) instead of redirecting back to the same between-races screen. - One primary navigation system per breakpoint: the mobile top-bar links are hidden so the bottom bar is the sole primary nav, and Admin is moved out of every Primary landmark into an operator-utilities toolbar. - Add Vitest coverage for the contract mapping, every temporal state, loading/ error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav hierarchy; add hermetic Playwright journeys (seeded + injected canonical payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire the stale Command Center specs/snapshots. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
156
tests/weekend-states.spec.ts
Normal file
156
tests/weekend-states.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { test, expect, type Page } from '@playwright/test'
|
||||
|
||||
// These journeys inject the canonical /api/v1/weekend-context payload directly so
|
||||
// each temporal state is exercised deterministically and hermetically, regardless
|
||||
// of the seeded database clock. The payloads mirror internal/query.WeekendContext.
|
||||
|
||||
const availability = {
|
||||
schedule: 'available',
|
||||
live_transport: 'unknown',
|
||||
live_session: 'inactive',
|
||||
archive: 'unavailable',
|
||||
local_analysis: 'complete',
|
||||
freshness: 'fresh',
|
||||
limitations: [],
|
||||
}
|
||||
|
||||
function meeting(key: number, name: string, start: string) {
|
||||
return {
|
||||
meeting_key: key,
|
||||
meeting_name: name,
|
||||
meeting_official_name: name,
|
||||
location: name,
|
||||
country_code: 'GBR',
|
||||
country_name: 'United Kingdom',
|
||||
country_flag: '',
|
||||
circuit_key: key,
|
||||
circuit_short_name: name,
|
||||
date_start: start,
|
||||
date_end: start,
|
||||
year: 2026,
|
||||
}
|
||||
}
|
||||
|
||||
function ctxSession(key: number, name: string, start: string, meetingKey: number) {
|
||||
return {
|
||||
session: {
|
||||
session_key: key,
|
||||
session_name: name,
|
||||
session_type: name,
|
||||
meeting_key: meetingKey,
|
||||
date_start: start,
|
||||
date_end: start,
|
||||
gmt_offset: '',
|
||||
},
|
||||
meeting: meeting(meetingKey, 'British Grand Prix', start),
|
||||
availability,
|
||||
}
|
||||
}
|
||||
|
||||
async function stubContext(page: Page, body: Record<string, unknown>): Promise<void> {
|
||||
await page.route('**/api/v1/weekend-context', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', body: JSON.stringify(body) }),
|
||||
)
|
||||
// Keep supplementary reads hermetic and empty.
|
||||
await page.route('**/api/v1/news**', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', body: '[]' }),
|
||||
)
|
||||
}
|
||||
|
||||
test.describe('Weekend canonical temporal states (injected)', () => {
|
||||
test('between_weekends pairs the last completed event with the next-event countdown and Prepare CTA', async ({ page }) => {
|
||||
await stubContext(page, {
|
||||
season: 2026,
|
||||
temporal_state: 'between_weekends',
|
||||
previous_meeting: meeting(1, 'British Grand Prix', '2026-07-05T14:00:00Z'),
|
||||
previous_completed_session: ctxSession(11, 'Race', '2026-07-05T14:00:00Z', 1),
|
||||
default_analysis_session: ctxSession(11, 'Race', '2026-07-05T14:00:00Z', 1),
|
||||
next_meeting: meeting(2, 'Hungarian Grand Prix', '2026-07-24T09:00:00Z'),
|
||||
next_session: ctxSession(21, 'Practice 1', '2026-07-24T09:00:00Z', 2),
|
||||
focus_meeting: meeting(2, 'Hungarian Grand Prix', '2026-07-24T09:00:00Z'),
|
||||
championship_round: 12,
|
||||
total_championship_rounds: 24,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('weekend-between-races')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-last-event')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-next-event')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-prepare')).toHaveAttribute('href', '/preview')
|
||||
await expect(page.getByTestId('wk-season-nav')).toContainText('Round 12 of 24')
|
||||
})
|
||||
|
||||
test('between_races Prepare CTA folds into the preparation surface instead of looping', async ({ page }) => {
|
||||
await stubContext(page, {
|
||||
season: 2026,
|
||||
temporal_state: 'between_weekends',
|
||||
next_meeting: meeting(2, 'Hungarian Grand Prix', '2026-07-24T09:00:00Z'),
|
||||
next_session: ctxSession(21, 'Practice 1', '2026-07-24T09:00:00Z', 2),
|
||||
championship_round: 12,
|
||||
total_championship_rounds: 24,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await page.getByTestId('wk-prepare').click()
|
||||
await expect(page).toHaveURL(/\/preview$/)
|
||||
await expect(page.getByTestId('weekend-pre-session')).toBeVisible()
|
||||
await expect(page.getByTestId('weekend-between-races')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('pre_session surfaces the preview and the next-session countdown', async ({ page }) => {
|
||||
await stubContext(page, {
|
||||
season: 2026,
|
||||
temporal_state: 'pre_session',
|
||||
next_meeting: meeting(2, 'Hungarian Grand Prix', '2026-07-24T09:00:00Z'),
|
||||
next_session: ctxSession(21, 'Practice 1', '2026-07-24T09:00:00Z', 2),
|
||||
focus_meeting: meeting(2, 'Hungarian Grand Prix', '2026-07-24T09:00:00Z'),
|
||||
championship_round: 12,
|
||||
total_championship_rounds: 24,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('weekend-pre-session')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-pre-head')).toBeVisible()
|
||||
})
|
||||
|
||||
test('between_sessions shows the last result recap and the next-session countdown together', async ({ page }) => {
|
||||
await stubContext(page, {
|
||||
season: 2026,
|
||||
temporal_state: 'between_sessions',
|
||||
focus_meeting: meeting(1, 'British Grand Prix', '2026-07-04T10:00:00Z'),
|
||||
previous_completed_session: ctxSession(11, 'Sprint', '2026-07-04T10:00:00Z', 1),
|
||||
default_analysis_session: ctxSession(11, 'Sprint', '2026-07-04T10:00:00Z', 1),
|
||||
next_session: ctxSession(12, 'Race', '2026-07-05T14:00:00Z', 1),
|
||||
championship_round: 12,
|
||||
total_championship_rounds: 24,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('weekend-between-sessions')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-last-session')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-next-session')).toBeVisible()
|
||||
})
|
||||
|
||||
test('session_live hands off to live timing', async ({ page }) => {
|
||||
await stubContext(page, {
|
||||
season: 2026,
|
||||
temporal_state: 'session_live',
|
||||
focus_meeting: meeting(1, 'British Grand Prix', '2026-07-05T14:00:00Z'),
|
||||
active_session: ctxSession(11, 'Race', '2026-07-05T14:00:00Z', 1),
|
||||
championship_round: 12,
|
||||
total_championship_rounds: 24,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('weekend-live')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-watch-live')).toHaveAttribute('href', '/live')
|
||||
})
|
||||
|
||||
test('an error from the canonical endpoint shows an explicit error surface', async ({ page }) => {
|
||||
await page.route('**/api/v1/weekend-context', (route) =>
|
||||
route.fulfill({ status: 500, contentType: 'application/json', body: '{"error":"boom"}' }),
|
||||
)
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('weekend-error')).toBeVisible()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user