mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
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>
119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
|
|
const FULL_SESSION = 9472
|
|
|
|
async function documentOverflow(page: Page): Promise<number> {
|
|
return page.evaluate(() => {
|
|
const doc = document.documentElement
|
|
return doc.scrollWidth - doc.clientWidth
|
|
})
|
|
}
|
|
|
|
test.describe('Weekend home (seeded canonical context)', () => {
|
|
test('renders the Weekend home from the canonical endpoint, not the limited fallback', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
await expect(page.getByTestId('weekend-page')).toBeVisible()
|
|
// The seeded DB (Monaco 2025, completed) resolves to season_complete, driven
|
|
// by the canonical /api/v1/weekend-context endpoint.
|
|
await expect(page.getByTestId('weekend-page')).toHaveAttribute('data-temporal-state', 'season_complete')
|
|
await expect(page.getByTestId('weekend-between-races')).toBeVisible()
|
|
await expect(page.getByTestId('weekend-limited')).toHaveCount(0)
|
|
// No ingest/dataset terminology on the Weekend surface.
|
|
await expect(page.getByText(/ingest/i)).toHaveCount(0)
|
|
})
|
|
|
|
test('the completed-event CTA opens the canonical analysis session in Race Hub', async ({ page }) => {
|
|
await page.goto('/')
|
|
await expect(page.getByTestId('wk-last-event')).toBeVisible()
|
|
await page.getByTestId('wk-explore-race-story').click()
|
|
await expect(page).toHaveURL(/\/race-hub\?session_key=\d+/)
|
|
await expect(page.getByTestId('race-hub')).toBeVisible()
|
|
})
|
|
|
|
test('/preview alias resolves without looping back to the same screen', async ({ page }) => {
|
|
await page.goto('/preview')
|
|
// In the seeded season-complete state there is no next event, so /preview
|
|
// resolves to the Weekend home rather than a redirect loop.
|
|
await expect(page.getByTestId('weekend-page')).toBeVisible()
|
|
await expect(page).toHaveURL(/\/preview$/)
|
|
})
|
|
|
|
test('existing deep-link routes remain valid', async ({ page }) => {
|
|
await page.goto(`/race-hub?session_key=${FULL_SESSION}`)
|
|
await expect(page.getByTestId('race-hub')).toBeVisible()
|
|
await expect(page.getByTestId('rh-overview')).toBeVisible()
|
|
|
|
await page.goto('/admin')
|
|
await expect(page.getByTestId('data-library')).toBeVisible()
|
|
|
|
await page.goto('/live')
|
|
await expect(page.getByTestId('live-empty')).toBeVisible()
|
|
|
|
await page.goto('/explore')
|
|
await expect(page.getByTestId('explore-page')).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Weekend navigation hierarchy', () => {
|
|
test('all four destinations are reachable from primary navigation', async ({ page }) => {
|
|
await page.goto('/')
|
|
const primary = page.getByRole('navigation', { name: 'Primary' }).first()
|
|
for (const [label, url] of [
|
|
['Championship', /\/championship/],
|
|
['Briefing', /\/briefing/],
|
|
['Explore', /\/explore/],
|
|
['Weekend', /\/$/],
|
|
] as const) {
|
|
await primary.getByRole('link', { name: label, exact: true }).click()
|
|
await expect(page).toHaveURL(url)
|
|
}
|
|
})
|
|
|
|
test('Admin is an operator utility outside the Primary landmark', async ({ page }) => {
|
|
await page.goto('/')
|
|
const primaries = page.getByRole('navigation', { name: 'Primary' })
|
|
await expect(primaries.first()).toBeVisible()
|
|
// Admin must not appear inside any Primary landmark.
|
|
await expect(primaries.getByRole('link', { name: /Admin/i })).toHaveCount(0)
|
|
await expect(
|
|
page.getByRole('toolbar', { name: 'Operator utilities' }).getByRole('link', { name: 'Admin' }),
|
|
).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Weekend responsive — no global horizontal overflow', () => {
|
|
for (const { name, width, height } of [
|
|
{ name: 'mobile 390', width: 390, height: 844 },
|
|
{ name: 'tablet 768', width: 768, height: 1024 },
|
|
{ name: 'desktop 1440', width: 1440, height: 900 },
|
|
]) {
|
|
test(`no document overflow at ${name}`, async ({ page }) => {
|
|
await page.setViewportSize({ width, height })
|
|
await page.goto('/')
|
|
await expect(page.getByTestId('weekend-between-races')).toBeVisible()
|
|
expect(await documentOverflow(page)).toBeLessThanOrEqual(1)
|
|
})
|
|
}
|
|
|
|
test('mobile shows exactly one visible primary navigation (bottom bar)', async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await page.goto('/')
|
|
await expect(page.getByTestId('weekend-page')).toBeVisible()
|
|
|
|
// The bottom bar is the single visible primary nav; the top bar's links are
|
|
// hidden by CSS at this breakpoint.
|
|
const bottom = page.locator('.app-bottom-nav')
|
|
await expect(bottom).toBeVisible()
|
|
await expect(bottom.getByRole('link')).toHaveCount(4)
|
|
|
|
const topLinks = page.locator('.app-nav .nav-links')
|
|
await expect(topLinks).toBeHidden()
|
|
|
|
// All four destinations remain reachable via the bottom bar.
|
|
for (const label of ['Weekend', 'Championship', 'Briefing', 'Explore']) {
|
|
await expect(bottom.getByRole('link', { name: new RegExp(label) })).toBeVisible()
|
|
}
|
|
})
|
|
})
|