fix(race-hub): consume canonical weekend context for #75 review
Address PR #82 review blockers: drop the competing /weekend default_analysis_session resolver, land bare /race-hub via /api/v1/weekend-context, derive Live/preparing/partial/unavailable from authoritative context with a moving clock, hide Local Coverage behind Diagnostics, isolate the future-session fixture from the shared seed, and strengthen return-to-Weekend context coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
78
tests/fixtures/future-session.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
/** Isolated future-session key — not present in the shared e2e seed DB. */
|
||||
export const FUTURE_SESSION = 9600
|
||||
export const FUTURE_MEETING = 1300
|
||||
|
||||
const emptyDatasets = {}
|
||||
|
||||
const futureMeeting = {
|
||||
meeting_key: FUTURE_MEETING,
|
||||
meeting_name: 'Future Grand Prix',
|
||||
meeting_official_name: 'FORMULA 1 FUTURE GRAND PRIX 2099',
|
||||
location: 'Futureville',
|
||||
country_name: 'Testland',
|
||||
country_code: 'TST',
|
||||
country_flag: '',
|
||||
circuit_short_name: 'Future',
|
||||
date_start: '2099-05-23T00:00:00+00:00',
|
||||
date_end: '2099-05-25T00:00:00+00:00',
|
||||
year: 2099,
|
||||
}
|
||||
|
||||
const futureSession = {
|
||||
session_key: FUTURE_SESSION,
|
||||
session_name: 'Race',
|
||||
session_type: 'Race',
|
||||
meeting_key: FUTURE_MEETING,
|
||||
date_start: '2099-05-25T13:00:00+00:00',
|
||||
date_end: '2099-05-25T15:00:00+00:00',
|
||||
gmt_offset: '00:00:00',
|
||||
}
|
||||
|
||||
const futureRaceHub = {
|
||||
source: 'none',
|
||||
session_key: FUTURE_SESSION,
|
||||
datasets: emptyDatasets,
|
||||
meeting: futureMeeting,
|
||||
session: futureSession,
|
||||
drivers: [],
|
||||
results: [],
|
||||
starting_grid: [],
|
||||
stints: [],
|
||||
pit_stops: [],
|
||||
positions: [],
|
||||
race_control: [],
|
||||
weather: [],
|
||||
laps: [],
|
||||
chapters: [],
|
||||
}
|
||||
|
||||
const futureWeekend = {
|
||||
source: 'none',
|
||||
meeting_key: FUTURE_MEETING,
|
||||
meeting: futureMeeting,
|
||||
default_session_key: FUTURE_SESSION,
|
||||
sessions: [{ session: futureSession, source: 'none', datasets: emptyDatasets }],
|
||||
}
|
||||
|
||||
/**
|
||||
* Route-mock a far-future session without contaminating the shared Monaco seed
|
||||
* (which would rewrite Command Center / Data Library baselines).
|
||||
*/
|
||||
export async function mockFutureRaceHubSession(page: Page): Promise<void> {
|
||||
await page.route(`**/api/v1/race-hub?session_key=${FUTURE_SESSION}`, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(futureRaceHub),
|
||||
})
|
||||
})
|
||||
await page.route(`**/api/v1/weekend?meeting_key=${FUTURE_MEETING}`, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(futureWeekend),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { FUTURE_SESSION, mockFutureRaceHubSession } from './fixtures/future-session'
|
||||
|
||||
const FULL_SESSION = 9472
|
||||
const CORE_ONLY_SESSION = 9000
|
||||
const FUTURE_SESSION = 9600
|
||||
|
||||
test.describe('Race Hub Weekend Workspace', () => {
|
||||
test('lands on the Overview tab with workspace identity', async ({ page }) => {
|
||||
@@ -16,6 +16,7 @@ test.describe('Race Hub Weekend Workspace', () => {
|
||||
'aria-selected',
|
||||
'true',
|
||||
)
|
||||
await expect(page.getByText('Local Coverage')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('shows final running order when switching to Race Story', async ({ page }) => {
|
||||
@@ -88,7 +89,6 @@ test.describe('Race Hub Weekend Workspace', () => {
|
||||
await page.getByTestId('rh-switch-weekend').click()
|
||||
|
||||
await expect(page.getByTestId('rh-switcher')).toBeVisible()
|
||||
// Active session is already loaded; just confirm a session button is reachable
|
||||
await expect(page.getByTestId(`rh-switcher-session-${FULL_SESSION}`)).toBeVisible()
|
||||
})
|
||||
|
||||
@@ -101,7 +101,6 @@ test.describe('Race Hub Weekend Workspace', () => {
|
||||
'href',
|
||||
'/admin',
|
||||
)
|
||||
// Raw coverage strip stays hidden until explicitly requested.
|
||||
await expect(page.getByTestId('rh-dataset-strip')).toHaveCount(0)
|
||||
await page.getByTestId('rh-diagnostics-toggle').click()
|
||||
await expect(page.getByTestId('rh-dataset-strip')).toBeVisible()
|
||||
@@ -114,12 +113,12 @@ test.describe('Race Hub Weekend Workspace', () => {
|
||||
await expect(page.getByTestId('rh-tabgroup-context')).toBeVisible()
|
||||
})
|
||||
|
||||
test('bare /race-hub resolves to a completed session, never a future one', async ({ page }) => {
|
||||
test('bare /race-hub resolves to a completed session via Weekend Context', async ({ page }) => {
|
||||
await page.goto('/race-hub')
|
||||
await expect(page).toHaveURL(/session_key=\d+/)
|
||||
await expect(page.getByTestId('race-hub')).toBeVisible()
|
||||
// It must not land on the future session.
|
||||
await expect(page).not.toHaveURL(new RegExp(`session_key=${FUTURE_SESSION}`))
|
||||
await expect(page).toHaveURL(new RegExp(`session_key=${FULL_SESSION}`))
|
||||
await expect(page.getByTestId('rh-identity')).toContainText('Monaco')
|
||||
})
|
||||
|
||||
test('explicit completed session deep link stays stable and shows analysis', async ({ page }) => {
|
||||
@@ -128,20 +127,34 @@ test.describe('Race Hub Weekend Workspace', () => {
|
||||
await expect(page.getByTestId('rh-overview')).toBeVisible()
|
||||
})
|
||||
|
||||
test('explicit future session renders the pre-session view, not empty analysis', async ({ page }) => {
|
||||
test('explicit future session renders the pre-session view, not empty analysis', async ({
|
||||
page,
|
||||
}) => {
|
||||
await mockFutureRaceHubSession(page)
|
||||
await page.goto(`/race-hub?session_key=${FUTURE_SESSION}`)
|
||||
await expect(page.getByTestId('race-hub')).toBeVisible()
|
||||
await expect(page.getByTestId('rh-presession')).toBeVisible()
|
||||
await expect(page.getByTestId('rh-overview')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('returning to Weekend from an analysis view preserves the meeting context', async ({ page }) => {
|
||||
test('returning to Weekend from analysis preserves meeting and session context', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(`/race-hub?session_key=${FULL_SESSION}`)
|
||||
await expect(page.getByTestId('rh-identity')).toContainText('Monaco')
|
||||
await page.getByRole('tab', { name: 'Strategy' }).click()
|
||||
await expect(page.locator('[data-testid="strategy-chart"]')).toBeVisible()
|
||||
|
||||
await page.getByTestId('rh-switch-weekend').click()
|
||||
await expect(page.getByTestId('rh-switcher')).toBeVisible()
|
||||
// The current session remains reachable/selected from the switcher.
|
||||
await expect(page.getByTestId(`rh-switcher-session-${FULL_SESSION}`)).toBeVisible()
|
||||
// Navigate to the sibling core-only session within the same weekend.
|
||||
await page.getByTestId(`rh-session-${CORE_ONLY_SESSION}`).click()
|
||||
await expect(page).toHaveURL(new RegExp(`session_key=${CORE_ONLY_SESSION}`))
|
||||
await expect(page.getByTestId('rh-identity')).toContainText('Monaco')
|
||||
|
||||
// Back to Weekend via bare /race-hub — Weekend Context should restore the
|
||||
// same meeting's default analysis session.
|
||||
await page.goto('/race-hub')
|
||||
await expect(page).toHaveURL(new RegExp(`session_key=${FULL_SESSION}`))
|
||||
await expect(page.getByTestId('rh-identity')).toContainText('Monaco')
|
||||
await expect(page.getByTestId('rh-overview')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
@@ -1,4 +1,5 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test'
|
||||
import { FUTURE_SESSION, mockFutureRaceHubSession } from '../fixtures/future-session'
|
||||
|
||||
export const VIEWPORTS = {
|
||||
desktop: { width: 1280, height: 800 },
|
||||
@@ -7,7 +8,7 @@ export const VIEWPORTS = {
|
||||
} as const
|
||||
|
||||
export const FULL_SESSION = 9472
|
||||
export const FUTURE_SESSION = 9600
|
||||
export { FUTURE_SESSION }
|
||||
|
||||
/** Wait for web fonts and layout to settle before screenshots. */
|
||||
export async function waitForScreenshotReady(page: Page): Promise<void> {
|
||||
@@ -38,6 +39,7 @@ export async function gotoRaceHubFutureReady(
|
||||
page: Page,
|
||||
sessionKey = FUTURE_SESSION,
|
||||
): Promise<void> {
|
||||
await mockFutureRaceHubSession(page)
|
||||
await page.goto(`/race-hub?session_key=${sessionKey}`)
|
||||
await expect(page.getByTestId('race-hub')).toBeVisible()
|
||||
await expect(page.getByTestId('rh-presession')).toBeVisible()
|
||||
|
||||