Files
gridpilot.gg/apps/website/tests/flows/dashboard.test.ts
Marc Mintel fb1221701d
Some checks failed
Contract Testing / contract-tests (push) Failing after 6m7s
Contract Testing / contract-snapshot (push) Failing after 4m46s
add tests
2026-01-22 11:52:42 +01:00

1152 lines
42 KiB
TypeScript

/**
* 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
});
});
});