mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56: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:
@@ -1,77 +0,0 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const FULL_SESSION = 9472
|
||||
|
||||
test.describe('Command Center', () => {
|
||||
test('loads as default route with weekend identity band', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-focus')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-session-9472')).toBeVisible()
|
||||
await expect(page.getByTestId('hero-last-race-link')).toBeVisible()
|
||||
})
|
||||
|
||||
test('nav link reaches command center from race hub', async ({ page }) => {
|
||||
await page.goto(`/race-hub?session_key=${FULL_SESSION}`)
|
||||
await page.getByRole('link', { name: 'Command' }).click()
|
||||
await expect(page).toHaveURL('/')
|
||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||
})
|
||||
|
||||
test('open analysis action opens race hub for the focus session', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('hero-last-race-link')).toBeVisible()
|
||||
await page.getByTestId('hero-last-race-link').click()
|
||||
await expect(page).toHaveURL(new RegExp(`/race-hub\\?session_key=${FULL_SESSION}`))
|
||||
await expect(page.getByTestId('rh-identity')).toBeVisible()
|
||||
await expect(page.getByTestId(`rh-session-${FULL_SESSION}`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('archived live snapshot does not mark command center live', async ({ page }) => {
|
||||
await page.route('**/api/v1/live/state', (route) =>
|
||||
route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
is_live: false,
|
||||
data: null,
|
||||
last_snapshot: {
|
||||
Drivers: { '1': { RacingNumber: '1', Position: 1 } },
|
||||
DriverInfo: { '1': { RacingNumber: '1', Tla: 'VER', TeamColour: '3671C6' } },
|
||||
Tyres: {},
|
||||
RCMessages: [],
|
||||
Weather: {},
|
||||
Session: { MeetingName: 'Archived GP', SessionName: 'Race', SessionType: 'Race' },
|
||||
TeamRadio: [],
|
||||
SessionStatus: 'Finished',
|
||||
TrackStatus: '1',
|
||||
CurrentLap: 57,
|
||||
TotalLaps: 57,
|
||||
Clock: '',
|
||||
ClockRefTime: '',
|
||||
ClockExtrapolating: false,
|
||||
Stints: {},
|
||||
},
|
||||
last_snapshot_at: '2026-07-04T14:00:00Z',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-live-status')).toContainText('No live session')
|
||||
await expect(page.getByTestId('cc-live-status')).not.toContainText('Live session active')
|
||||
})
|
||||
|
||||
test('existing routes continue to work', 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()
|
||||
})
|
||||
})
|
||||
@@ -3,11 +3,11 @@ import { test, expect } from '@playwright/test'
|
||||
const FULL_SESSION = 9472
|
||||
|
||||
test.describe('Production serving (Go + built React)', () => {
|
||||
test('serves command center as default route', async ({ page }) => {
|
||||
test('serves the Weekend home as default route', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-session-9472')).toBeVisible()
|
||||
await expect(page.getByTestId('weekend-page')).toBeVisible()
|
||||
await expect(page.getByTestId('weekend-between-races')).toBeVisible()
|
||||
})
|
||||
|
||||
test('serves race hub workspace from built assets', async ({ page }) => {
|
||||
@@ -44,16 +44,21 @@ test.describe('Production serving (Go + built React)', () => {
|
||||
await expect(page.getByText('No live session active')).toBeVisible()
|
||||
})
|
||||
|
||||
test('nav links work from built SPA', async ({ page }) => {
|
||||
test('primary nav links and Admin utility work from built SPA', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await page.locator('.app-nav').getByRole('link', { name: 'Race Hub' }).click()
|
||||
await expect(page).toHaveURL(/\/race-hub/)
|
||||
const primary = page.getByRole('navigation', { name: 'Primary' }).first()
|
||||
|
||||
await page.locator('.app-nav').getByRole('link', { name: 'Admin', exact: true }).click()
|
||||
await primary.getByRole('link', { name: 'Championship', exact: true }).click()
|
||||
await expect(page).toHaveURL(/\/championship/)
|
||||
|
||||
await primary.getByRole('link', { name: 'Explore', exact: true }).click()
|
||||
await expect(page).toHaveURL(/\/explore/)
|
||||
|
||||
await primary.getByRole('link', { name: 'Weekend', exact: true }).click()
|
||||
await expect(page).toHaveURL('/')
|
||||
|
||||
// Admin is an operator utility outside the Primary landmark.
|
||||
await page.getByRole('toolbar', { name: 'Operator utilities' }).getByRole('link', { name: 'Admin' }).click()
|
||||
await expect(page).toHaveURL(/\/admin/)
|
||||
|
||||
await page.getByRole('link', { name: 'Live', exact: true }).click()
|
||||
await expect(page).toHaveURL(/\/live/)
|
||||
await expect(page.getByTestId('live-empty')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 KiB |
BIN
tests/visual/__snapshots__/desktop/weekend.png
Normal file
BIN
tests/visual/__snapshots__/desktop/weekend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 96 KiB |
BIN
tests/visual/__snapshots__/mobile/weekend.png
Normal file
BIN
tests/visual/__snapshots__/mobile/weekend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 129 KiB |
BIN
tests/visual/__snapshots__/tablet/weekend.png
Normal file
BIN
tests/visual/__snapshots__/tablet/weekend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -14,16 +14,13 @@ export async function waitForScreenshotReady(page: Page): Promise<void> {
|
||||
await page.waitForTimeout(150)
|
||||
}
|
||||
|
||||
export async function gotoCommandCenterReady(page: Page): Promise<void> {
|
||||
export async function gotoWeekendReady(page: Page): Promise<void> {
|
||||
await page.goto('/')
|
||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-focus')).toBeVisible()
|
||||
await expect(page.getByTestId('cc-session-9472')).toBeVisible()
|
||||
// The e2e stack runs with an unreachable OpenF1 base URL, so wait for the
|
||||
// season-calendar query to settle on its local fallback before screenshotting.
|
||||
await expect(
|
||||
page.getByText('Using local meetings because the full calendar could not load.'),
|
||||
).toBeVisible()
|
||||
await expect(page.getByTestId('weekend-page')).toBeVisible()
|
||||
// The seeded hermetic DB (Monaco 2025, completed) resolves to season_complete,
|
||||
// rendered by the between-races surface.
|
||||
await expect(page.getByTestId('weekend-between-races')).toBeVisible()
|
||||
await expect(page.getByTestId('wk-last-event')).toBeVisible()
|
||||
await waitForScreenshotReady(page)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test } from '@playwright/test'
|
||||
import {
|
||||
gotoCommandCenterReady,
|
||||
gotoWeekendReady,
|
||||
gotoDataLibraryReady,
|
||||
gotoLiveEmptyReady,
|
||||
gotoRaceHubReady,
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
} from './helpers'
|
||||
|
||||
test.describe('MVP visual regression', () => {
|
||||
test('command-center', async ({ page }) => {
|
||||
await gotoCommandCenterReady(page)
|
||||
await screenshotPage(page, 'command-center')
|
||||
test('weekend', async ({ page }) => {
|
||||
await gotoWeekendReady(page)
|
||||
await screenshotPage(page, 'weekend')
|
||||
})
|
||||
|
||||
test('race-hub', async ({ page }) => {
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
118
tests/weekend.spec.ts
Normal file
118
tests/weekend.spec.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
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()
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user