/** * Dashboard Feature Flow Tests * * These tests verify routing, guards, navigation, cross-screen state, and user flows * for the dashboard module. They run with real frontend and mocked contracts. * * Contracts are defined in apps/website/lib/types/generated * * @file apps/website/tests/flows/dashboard.test.ts */ import { test, expect } from '@playwright/test'; import { WebsiteAuthManager } from '../../../tests/shared/website/WebsiteAuthManager'; import { WebsiteRouteManager } from '../../../tests/shared/website/WebsiteRouteManager'; import { ConsoleErrorCapture } from '../../../tests/shared/website/ConsoleErrorCapture'; import { HttpDiagnostics } from '../../../tests/shared/website/HttpDiagnostics'; import { RouteContractSpec } from '../../../tests/shared/website/RouteContractSpec'; import { RouteScenarioMatrix } from '../../../tests/shared/website/RouteScenarioMatrix'; test.describe('Dashboard Feature Flow', () => { describe('Dashboard Navigation', () => { test('should redirect to login when accessing dashboard without authentication', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /dashboard without authentication await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to login page await expect(page).toHaveURL(/.*\/auth\/login/); // Check return URL parameter const url = new URL(page.url()); expect(url.searchParams.get('returnUrl')).toBe('/dashboard'); }); test('should allow access to dashboard with valid authentication', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock AuthSessionDTO const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession); // Login as user await authManager.loginAsUser(); // Navigate to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify dashboard loads successfully await expect(page).toHaveURL(/.*\/dashboard/); // Check for expected dashboard elements await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); await expect(page.locator('[data-testid="dashboard-header"]')).toBeVisible(); }); test('should navigate from dashboard to races page', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Click "View Full Schedule" button await page.locator('[data-testid="view-schedule-button"]').click(); // Verify navigation to /races await expect(page).toHaveURL(/.*\/races/); }); test('should handle direct navigation to dashboard routes', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login await authManager.loginAsUser(); // Attempt direct navigation to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify dashboard renders correctly await expect(page).toHaveURL(/.*\/dashboard/); await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); // Check URL remains /dashboard await expect(page).toHaveURL(/.*\/dashboard/); }); }); describe('Dashboard Data Flow', () => { test('should load and display dashboard overview data', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardPageQuery response with DashboardOverviewDTO const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify dashboard data is displayed await expect(page.locator('[data-testid="total-users"]')).toContainText('150'); await expect(page.locator('[data-testid="active-users"]')).toContainText('120'); await expect(page.locator('[data-testid="total-races"]')).toContainText('10'); await expect(page.locator('[data-testid="total-leagues"]')).toContainText('5'); }); test('should display next race information when available', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with nextRace const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, nextRace: { id: 'race-123', track: 'Monza', car: 'Ferrari 488 GT3', scheduledAt: '2024-01-25T14:00:00Z', status: 'scheduled', isMyLeague: true, }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify next race details are shown await expect(page.locator('[data-testid="next-race-track"]')).toContainText('Monza'); await expect(page.locator('[data-testid="next-race-car"]')).toContainText('Ferrari 488 GT3'); await expect(page.locator('[data-testid="next-race-time"]')).toBeVisible(); // Check for "Active Session" panel await expect(page.locator('[data-testid="active-session-panel"]')).toBeVisible(); }); test('should handle missing next race gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO without nextRace const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Active Session" panel is not displayed await expect(page.locator('[data-testid="active-session-panel"]')).not.toBeVisible(); // Check UI remains functional await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); await expect(page.locator('[data-testid="view-schedule-button"]')).toBeEnabled(); }); test('should display upcoming races schedule', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with upcomingRaces const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, upcomingRaces: [ { id: 'race-123', track: 'Monza', car: 'Ferrari 488 GT3', scheduledAt: '2024-01-25T14:00:00Z', status: 'scheduled', isMyLeague: true, }, { id: 'race-124', track: 'Spa', car: 'Porsche 911 GT3 R', scheduledAt: '2024-01-26T15:00:00Z', status: 'scheduled', isMyLeague: true, }, ], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify upcoming races are listed in "Upcoming Schedule" await expect(page.locator('[data-testid="upcoming-race-123"]')).toBeVisible(); await expect(page.locator('[data-testid="upcoming-race-124"]')).toBeVisible(); // Check for track, car, timeUntil, and formattedDate for each race await expect(page.locator('[data-testid="upcoming-race-123-track"]')).toContainText('Monza'); await expect(page.locator('[data-testid="upcoming-race-123-car"]')).toContainText('Ferrari 488 GT3'); await expect(page.locator('[data-testid="upcoming-race-123-time"]')).toBeVisible(); }); test('should handle empty upcoming races list', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with empty upcomingRaces const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, upcomingRaces: [], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Upcoming Schedule" shows appropriate empty state await expect(page.locator('[data-testid="upcoming-schedule-empty"]')).toBeVisible(); await expect(page.locator('[data-testid="upcoming-schedule-empty"]')).toContainText(/no upcoming races/i); }); test('should display league standings', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with leagueStandingsSummaries const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, leagueStandingsSummaries: [ { leagueId: 'league-1', leagueName: 'Pro League', position: 3, totalDrivers: 50, points: 1250, }, { leagueId: 'league-2', leagueName: 'Amateur League', position: 1, totalDrivers: 30, points: 850, }, ], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Championship Standings" panel shows league data await expect(page.locator('[data-testid="league-standing-league-1"]')).toBeVisible(); await expect(page.locator('[data-testid="league-standing-league-2"]')).toBeVisible(); // Check for leagueName, position, totalDrivers, points await expect(page.locator('[data-testid="league-name-league-1"]')).toContainText('Pro League'); await expect(page.locator('[data-testid="league-position-league-1"]')).toContainText('3'); await expect(page.locator('[data-testid="league-drivers-league-1"]')).toContainText('50'); await expect(page.locator('[data-testid="league-points-league-1"]')).toContainText('1250'); }); test('should handle empty league standings', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with empty leagueStandingsSummaries const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, leagueStandingsSummaries: [], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Championship Standings" shows empty state message await expect(page.locator('[data-testid="championship-standings-empty"]')).toBeVisible(); await expect(page.locator('[data-testid="championship-standings-empty"]')).toContainText(/no league standings/i); }); test('should display recent activity feed', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with feedSummary containing items const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, feedSummary: { notificationCount: 2, items: [ { id: 'feed-1', type: 'race_result', headline: 'Race completed at Monza', body: 'You finished 3rd in the Pro League race', timestamp: '2024-01-20T10:00:00Z', ctaLabel: 'View Results', ctaHref: '/races/race-123', }, { id: 'feed-2', type: 'league_update', headline: 'New league season started', body: 'The 2024 season is now live', timestamp: '2024-01-19T15:00:00Z', ctaLabel: 'View League', ctaHref: '/leagues/league-1', }, ], }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Recent Activity" panel shows feed items await expect(page.locator('[data-testid="feed-item-feed-1"]')).toBeVisible(); await expect(page.locator('[data-testid="feed-item-feed-2"]')).toBeVisible(); // Check for type, headline, formattedTime await expect(page.locator('[data-testid="feed-headline-feed-1"]')).toContainText('Race completed at Monza'); await expect(page.locator('[data-testid="feed-time-feed-1"]')).toBeVisible(); }); test('should handle empty activity feed', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with empty feedSummary const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, feedSummary: { notificationCount: 0, items: [], }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify "Recent Activity" shows empty state message await expect(page.locator('[data-testid="recent-activity-empty"]')).toBeVisible(); await expect(page.locator('[data-testid="recent-activity-empty"]')).toContainText(/no recent activity/i); }); test('should handle dashboard data loading errors', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Login as user await authManager.loginAsUser(); // Mock DashboardPageQuery to return error await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Internal Server Error', status: 500, }); // Navigate to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify error handling (likely redirects to notFound) await expect(page).toHaveURL(/.*\/not-found/); // Check error logging const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should handle dashboard access denied (403/401)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Login as user await authManager.loginAsUser(); // Mock API to return 403 error await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Access Denied', status: 403, message: 'You do not have permission to access this resource', }); // Navigate to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to login or error page await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should refresh dashboard data on page refresh', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock initial dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Mock refreshed data const refreshedData = { totalUsers: 155, activeUsers: 125, totalRaces: 12, totalLeagues: 6, }; await routeContractSpec.mockApiCall('GetDashboardData', refreshedData); // Trigger browser refresh await page.reload(); // Verify DashboardPageQuery is called again // Verify data is reloaded await expect(page.locator('[data-testid="total-users"]')).toContainText('155'); await expect(page.locator('[data-testid="active-users"]')).toContainText('125'); }); }); describe('Dashboard KPI Display', () => { test('should display all KPI items correctly', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with driver stats const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, currentDriver: { id: 'driver-123', name: 'Test Driver', country: 'USA', rating: 1850, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify KPI row shows: Rating, Rank, Starts, Wins, Podiums, Leagues await expect(page.locator('[data-testid="kpi-rating"]')).toContainText('1850'); await expect(page.locator('[data-testid="kpi-rank"]')).toContainText('42'); await expect(page.locator('[data-testid="kpi-starts"]')).toContainText('150'); await expect(page.locator('[data-testid="kpi-wins"]')).toContainText('25'); await expect(page.locator('[data-testid="kpi-podiums"]')).toContainText('60'); await expect(page.locator('[data-testid="kpi-leagues"]')).toContainText('5'); // Check proper formatting and styling await expect(page.locator('[data-testid="kpi-rating"]')).toHaveClass(/intent-primary/); await expect(page.locator('[data-testid="kpi-rank"]')).toHaveClass(/intent-warning/); }); test('should handle missing driver data gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO without currentDriver const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify KPI row handles missing data await expect(page.locator('[data-testid="kpi-rating"]')).toContainText('-'); await expect(page.locator('[data-testid="kpi-rank"]')).toContainText('-'); await expect(page.locator('[data-testid="kpi-starts"]')).toContainText('-'); await expect(page.locator('[data-testid="kpi-wins"]')).toContainText('-'); await expect(page.locator('[data-testid="kpi-podiums"]')).toContainText('-'); // Check UI doesn't break await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); await expect(page.locator('[data-testid="view-schedule-button"]')).toBeEnabled(); }); test('should apply correct intent styling to KPI items', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with driver stats const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, currentDriver: { id: 'driver-123', name: 'Test Driver', country: 'USA', rating: 1850, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify Rating has primary intent await expect(page.locator('[data-testid="kpi-rating"]')).toHaveClass(/intent-primary/); // Verify Rank has warning intent await expect(page.locator('[data-testid="kpi-rank"]')).toHaveClass(/intent-warning/); // Verify Wins has success intent await expect(page.locator('[data-testid="kpi-wins"]')).toHaveClass(/intent-success/); // Verify Podiums has warning intent await expect(page.locator('[data-testid="kpi-podiums"]')).toHaveClass(/intent-warning/); }); }); describe('Dashboard Route Guard Integration', () => { test('should enforce authentication on dashboard access', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /dashboard without auth await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to /auth/login await expect(page).toHaveURL(/.*\/auth\/login/); // Check return URL includes /dashboard const url = new URL(page.url()); expect(url.searchParams.get('returnUrl')).toBe('/dashboard'); }); test('should handle session expiration during dashboard viewing', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Mock session expiration await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Unauthorized', status: 401, message: 'Session expired', }); // Attempt interaction (e.g., click "View Full Schedule") await page.locator('[data-testid="view-schedule-button"]').click(); // Verify redirect to login await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should maintain return URL after dashboard authentication', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Attempt to access /dashboard without auth await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to login with return URL await expect(page).toHaveURL(/.*\/auth\/login/); const url = new URL(page.url()); expect(url.searchParams.get('returnUrl')).toBe('/dashboard'); // Mock dashboard data for after login const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login successfully await authManager.loginAsUser(); // Verify redirect back to /dashboard await expect(page).toHaveURL(/.*\/dashboard/); }); test('should redirect authenticated users away from auth pages', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); // Mock existing AuthSessionDTO await authManager.loginAsUser(); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify redirect to /dashboard await expect(page).toHaveURL(/.*\/dashboard/); }); }); describe('Dashboard Cross-Screen State Management', () => { test('should preserve dashboard state when navigating away and back', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Navigate to another page (e.g., /races) await page.goto(routeManager.getRoute('/races')); // Navigate back to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify data is preserved or reloaded correctly await expect(page.locator('[data-testid="total-users"]')).toContainText('150'); await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); }); test('should handle concurrent dashboard operations', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Mock refreshed data const refreshedData = { totalUsers: 155, activeUsers: 125, totalRaces: 12, totalLeagues: 6, }; await routeContractSpec.mockApiCall('GetDashboardData', refreshedData); // Trigger multiple operations quickly const refreshPromise = page.locator('[data-testid="refresh-button"]').click(); const navigatePromise = page.goto(routeManager.getRoute('/races')); // Wait for all operations await Promise.all([refreshPromise, navigatePromise]); // Verify loading states are managed (no stuck spinners) await expect(page.locator('[data-testid="loading-spinner"]')).not.toBeVisible(); // Verify UI remains usable after concurrent operations await expect(page.locator('[data-testid="navigation-menu"]')).toBeVisible(); }); test('should maintain dashboard scroll position on return', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data with many items to enable scrolling const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, upcomingRaces: Array(10).fill(null).map((_, i) => ({ id: `race-${i}`, track: `Track ${i}`, car: `Car ${i}`, scheduledAt: '2024-01-25T14:00:00Z', status: 'scheduled', isMyLeague: true, })), }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login and navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Scroll down await page.evaluate(() => window.scrollTo(0, 500)); // Navigate to /races await page.goto(routeManager.getRoute('/races')); // Navigate back to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify scroll position is preserved (or at least the page is functional) await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); // Note: Exact scroll position preservation may depend on browser implementation }); }); describe('Dashboard UI State Management', () => { test('should show loading states during data operations', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock delayed DashboardPageQuery response const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData, { delay: 500 }); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify loading state is shown await expect(page.locator('[data-testid="loading-spinner"]')).toBeVisible(); // Wait for loading to complete await expect(page.locator('[data-testid="loading-spinner"]')).not.toBeVisible(); // Verify data is displayed after loading await expect(page.locator('[data-testid="total-users"]')).toContainText('150'); }); test('should handle empty states gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with all empty arrays/nulls const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, upcomingRaces: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Verify empty state messages are shown await expect(page.locator('[data-testid="upcoming-schedule-empty"]')).toBeVisible(); await expect(page.locator('[data-testid="championship-standings-empty"]')).toBeVisible(); await expect(page.locator('[data-testid="recent-activity-empty"]')).toBeVisible(); // Verify UI remains functional await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); await expect(page.locator('[data-testid="view-schedule-button"]')).toBeEnabled(); }); test('should handle error states gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Login as user await authManager.loginAsUser(); // Mock various error scenarios await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Internal Server Error', status: 500, }); // Navigate to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify error handling (redirects to notFound) await expect(page).toHaveURL(/.*\/not-found/); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should handle network connectivity issues', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Login as user await authManager.loginAsUser(); // Mock network failure await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Network Error', status: 0, }); // Navigate to /dashboard await page.goto(routeManager.getRoute('/dashboard')); // Verify appropriate error handling await expect(page).toHaveURL(/.*\/not-found/); // Check if retry mechanism exists // Note: This would depend on the actual implementation // For now, verify the error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); }); describe('Dashboard User Interaction Flows', () => { test('should navigate to races when clicking view schedule button', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock dashboard data const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Click "View Full Schedule" button await page.locator('[data-testid="view-schedule-button"]').click(); // Verify navigation to /races await expect(page).toHaveURL(/.*\/races/); // Check URL changes correctly await expect(page).toHaveURL(/.*\/races/); }); test('should handle upcoming race item interactions', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with upcomingRaces const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, upcomingRaces: [ { id: 'race-123', track: 'Monza', car: 'Ferrari 488 GT3', scheduledAt: '2024-01-25T14:00:00Z', status: 'scheduled', isMyLeague: true, }, ], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Click on an upcoming race item await page.locator('[data-testid="upcoming-race-123"]').click(); // Verify navigation to race detail page (if applicable) // Note: This depends on the actual implementation // For now, verify the page navigates somewhere await expect(page).not.toHaveURL(/.*\/dashboard/); }); test('should handle league standing item interactions', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with leagueStandingsSummaries const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, leagueStandingsSummaries: [ { leagueId: 'league-1', leagueName: 'Pro League', position: 3, totalDrivers: 50, points: 1250, }, ], }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Click on a league standing item await page.locator('[data-testid="league-standing-league-1"]').click(); // Verify navigation to league detail page (if applicable) // Note: This depends on the actual implementation // For now, verify the page navigates somewhere await expect(page).not.toHaveURL(/.*\/dashboard/); }); test('should handle feed item interactions', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock DashboardOverviewDTO with feedSummary containing CTAs const mockDashboardData = { totalUsers: 150, activeUsers: 120, totalRaces: 10, totalLeagues: 5, feedSummary: { notificationCount: 1, items: [ { id: 'feed-1', type: 'race_result', headline: 'Race completed at Monza', body: 'You finished 3rd in the Pro League race', timestamp: '2024-01-20T10:00:00Z', ctaLabel: 'View Results', ctaHref: '/races/race-123', }, ], }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Navigate to /dashboard await authManager.loginAsUser(); await page.goto(routeManager.getRoute('/dashboard')); // Click on feed item with CTA await page.locator('[data-testid="feed-item-feed-1"]').click(); // Verify navigation to CTA href await expect(page).toHaveURL(/.*\/races\/race-123/); }); }); describe('Dashboard Performance and Edge Cases', () => { it('should handle large amounts of upcoming races', () => { // TODO: Implement test // - Mock DashboardOverviewDTO with many upcoming races // - Navigate to /dashboard // - Verify UI handles large list (virtualization, performance) // - Check only first 3 races are shown in schedule }); it('should handle large amounts of league standings', () => { // TODO: Implement test // - Mock DashboardOverviewDTO with many league standings // - Navigate to /dashboard // - Verify UI handles large list // - Check rendering performance }); it('should handle large amounts of feed items', () => { // TODO: Implement test // - Mock DashboardOverviewDTO with many feed items // - Navigate to /dashboard // - Verify UI handles large list // - Check rendering performance }); it('should handle malformed dashboard data', () => { // TODO: Implement test // - Mock DashboardPageQuery with malformed data // - Navigate to /dashboard // - Verify graceful error handling // - Check error logging }); it('should handle dashboard data with special characters', () => { // TODO: Implement test // - Mock DashboardOverviewDTO with special characters in strings // - Navigate to /dashboard // - Verify proper rendering and escaping }); it('should handle dashboard data with very long strings', () => { // TODO: Implement test // - Mock DashboardOverviewDTO with very long track names, league names, etc. // - Navigate to /dashboard // - Verify text truncation or wrapping works correctly }); }); });