diff --git a/frontend/e2e/admin-access.spec.ts b/frontend/e2e/admin-access.spec.ts index 6daa498..c99b409 100644 --- a/frontend/e2e/admin-access.spec.ts +++ b/frontend/e2e/admin-access.spec.ts @@ -13,7 +13,7 @@ test.describe('Admin Access Control', () => { // Auth already cached in storage state (loginViaUI removed for performance) // Navigate to authenticated page to test authenticated header (not homepage) - await page.goto('/settings'); + await page.goto('/en/settings'); await page.waitForSelector('h1:has-text("Settings")'); // Should not see admin link in authenticated header navigation @@ -28,10 +28,10 @@ test.describe('Admin Access Control', () => { // Auth already cached in storage state (loginViaUI removed for performance) // Try to access admin page directly - await page.goto('/admin'); + await page.goto('/en/admin'); // Should be redirected away from admin (to login or home) - await page.waitForURL(/\/(auth\/login|$)/); + await page.waitForURL(/\/en\/(auth\/login|$)/); expect(page.url()).not.toContain('/admin'); }); @@ -43,7 +43,7 @@ test.describe('Admin Access Control', () => { // Navigate to settings page to ensure user state is loaded // (AuthGuard fetches user on protected pages) - await page.goto('/settings'); + await page.goto('/en/settings'); await page.waitForSelector('h1:has-text("Settings")'); // Should see admin link in header navigation bar @@ -52,7 +52,7 @@ test.describe('Admin Access Control', () => { .locator('header nav') .getByRole('link', { name: 'Admin', exact: true }); await expect(headerAdminLink).toBeVisible(); - await expect(headerAdminLink).toHaveAttribute('href', '/admin'); + await expect(headerAdminLink).toHaveAttribute('href', '/en/admin'); }); test('superuser should be able to access admin dashboard', async ({ page }) => { @@ -61,10 +61,10 @@ test.describe('Admin Access Control', () => { // Auth already cached in storage state (loginViaUI removed for performance) // Navigate to admin page - await page.goto('/admin'); + await page.goto('/en/admin'); // Should see admin dashboard - await expect(page).toHaveURL('/admin'); + await expect(page).toHaveURL('/en/admin'); await expect(page.locator('h1')).toContainText('Admin Dashboard'); }); }); @@ -73,7 +73,7 @@ test.describe('Admin Dashboard', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display page title and description', async ({ page }) => { @@ -120,7 +120,7 @@ test.describe('Admin Navigation', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display admin sidebar', async ({ page }) => { @@ -143,9 +143,9 @@ test.describe('Admin Navigation', () => { }); test('should navigate to users page', async ({ page }) => { - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); - await expect(page).toHaveURL('/admin/users'); + await expect(page).toHaveURL('/en/admin/users'); await expect(page.locator('h1')).toContainText('User Management'); // Breadcrumbs should show Admin > Users @@ -158,9 +158,9 @@ test.describe('Admin Navigation', () => { }); test('should navigate to organizations page', async ({ page }) => { - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); - await expect(page).toHaveURL('/admin/organizations'); + await expect(page).toHaveURL('/en/admin/organizations'); await expect(page.getByRole('heading', { name: 'All Organizations' })).toBeVisible(); // Breadcrumbs should show Admin > Organizations @@ -173,9 +173,9 @@ test.describe('Admin Navigation', () => { }); test('should navigate to settings page', async ({ page }) => { - await page.goto('/admin/settings'); + await page.goto('/en/admin/settings'); - await expect(page).toHaveURL('/admin/settings'); + await expect(page).toHaveURL('/en/admin/settings'); await expect(page.locator('h1')).toContainText('System Settings'); // Breadcrumbs should show Admin > Settings @@ -208,14 +208,14 @@ test.describe('Admin Navigation', () => { }); test('should navigate back to dashboard from users page', async ({ page }) => { - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); // Click dashboard link in sidebar const dashboardLink = page.getByTestId('nav-dashboard'); await dashboardLink.click(); - await page.waitForURL('/admin'); - await expect(page).toHaveURL('/admin'); + await page.waitForURL('/en/admin'); + await expect(page).toHaveURL('/en/admin'); await expect(page.locator('h1')).toContainText('Admin Dashboard'); }); }); @@ -227,7 +227,7 @@ test.describe('Admin Breadcrumbs', () => { }); test('should show single breadcrumb on dashboard', async ({ page }) => { - await page.goto('/admin'); + await page.goto('/en/admin'); const breadcrumbs = page.getByTestId('breadcrumbs'); await expect(breadcrumbs).toBeVisible(); @@ -239,12 +239,12 @@ test.describe('Admin Breadcrumbs', () => { }); test('should show clickable parent breadcrumb', async ({ page }) => { - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); // 'Admin' should be a clickable link (test ID is on the Link element itself) const adminBreadcrumb = page.getByTestId('breadcrumb-admin'); await expect(adminBreadcrumb).toBeVisible(); - await expect(adminBreadcrumb).toHaveAttribute('href', '/admin'); + await expect(adminBreadcrumb).toHaveAttribute('href', '/en/admin'); // 'Users' should be current page (not a link, so it's a span) const usersBreadcrumb = page.getByTestId('breadcrumb-users'); @@ -253,13 +253,13 @@ test.describe('Admin Breadcrumbs', () => { }); test('should navigate via breadcrumb link', async ({ page }) => { - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); // Click 'Admin' breadcrumb to go back to dashboard const adminBreadcrumb = page.getByTestId('breadcrumb-admin'); - await Promise.all([page.waitForURL('/admin'), adminBreadcrumb.click()]); + await Promise.all([page.waitForURL('/en/admin'), adminBreadcrumb.click()]); - await expect(page).toHaveURL('/admin'); + await expect(page).toHaveURL('/en/admin'); }); }); diff --git a/frontend/e2e/admin-dashboard.spec.ts b/frontend/e2e/admin-dashboard.spec.ts index 03b5e83..40b0daf 100644 --- a/frontend/e2e/admin-dashboard.spec.ts +++ b/frontend/e2e/admin-dashboard.spec.ts @@ -10,11 +10,11 @@ test.describe('Admin Dashboard - Page Load', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display admin dashboard page', async ({ page }) => { - await expect(page).toHaveURL('/admin'); + await expect(page).toHaveURL('/en/admin'); await expect(page.getByRole('heading', { name: 'Admin Dashboard' })).toBeVisible(); await expect(page.getByText('Manage users, organizations, and system settings')).toBeVisible(); @@ -29,7 +29,7 @@ test.describe('Admin Dashboard - Statistics Cards', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display all stat cards', async ({ page }) => { @@ -62,7 +62,7 @@ test.describe('Admin Dashboard - Quick Actions', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display quick actions section', async ({ page }) => { @@ -86,9 +86,9 @@ test.describe('Admin Dashboard - Quick Actions', () => { test('should navigate to users page when clicking user management', async ({ page }) => { const userManagementLink = page.getByRole('link', { name: /User Management/i }); - await Promise.all([page.waitForURL('/admin/users'), userManagementLink.click()]); + await Promise.all([page.waitForURL('/en/admin/users'), userManagementLink.click()]); - await expect(page).toHaveURL('/admin/users'); + await expect(page).toHaveURL('/en/admin/users'); }); test('should navigate to organizations page when clicking organizations', async ({ page }) => { @@ -96,9 +96,9 @@ test.describe('Admin Dashboard - Quick Actions', () => { const quickActionsSection = page.locator('h2:has-text("Quick Actions")').locator('..'); const organizationsLink = quickActionsSection.getByRole('link', { name: /Organizations/i }); - await Promise.all([page.waitForURL('/admin/organizations'), organizationsLink.click()]); + await Promise.all([page.waitForURL('/en/admin/organizations'), organizationsLink.click()]); - await expect(page).toHaveURL('/admin/organizations'); + await expect(page).toHaveURL('/en/admin/organizations'); }); }); @@ -106,7 +106,7 @@ test.describe('Admin Dashboard - Analytics Charts', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should display analytics overview section', async ({ page }) => { @@ -151,7 +151,7 @@ test.describe('Admin Dashboard - Accessibility', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin'); + await page.goto('/en/admin'); }); test('should have proper heading hierarchy', async ({ page }) => { diff --git a/frontend/e2e/admin-organization-members.spec.ts b/frontend/e2e/admin-organization-members.spec.ts index 9ce3b73..73bef79 100644 --- a/frontend/e2e/admin-organization-members.spec.ts +++ b/frontend/e2e/admin-organization-members.spec.ts @@ -11,7 +11,7 @@ test.describe('Admin Organization Members - Navigation from Organizations List', test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); }); @@ -24,12 +24,12 @@ test.describe('Admin Organization Members - Navigation from Organizations List', // Click "View Members" await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), page.getByText('View Members').click(), ]); // Should be on members page - await expect(page).toHaveURL(/\/admin\/organizations\/[^/]+\/members/); + await expect(page).toHaveURL(/\/en\/admin\/organizations\/[^/]+\/members/); }); test('should navigate to members page when clicking member count', async ({ page }) => { @@ -39,12 +39,12 @@ test.describe('Admin Organization Members - Navigation from Organizations List', // Click on member count await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), memberButton.click(), ]); // Should be on members page - await expect(page).toHaveURL(/\/admin\/organizations\/[^/]+\/members/); + await expect(page).toHaveURL(/\/en\/admin\/organizations\/[^/]+\/members/); }); }); @@ -52,7 +52,7 @@ test.describe('Admin Organization Members - Page Structure', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); // Navigate to members page @@ -60,13 +60,13 @@ test.describe('Admin Organization Members - Page Structure', () => { await actionButton.click(); await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), page.getByText('View Members').click(), ]); }); test('should display organization members page', async ({ page }) => { - await expect(page).toHaveURL(/\/admin\/organizations\/[^/]+\/members/); + await expect(page).toHaveURL(/\/en\/admin\/organizations\/[^/]+\/members/); // Wait for page to load await page.waitForSelector('table'); @@ -123,7 +123,7 @@ test.describe('Admin Organization Members - AddMemberDialog E2E Tests', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); // Navigate to members page @@ -131,7 +131,7 @@ test.describe('Admin Organization Members - AddMemberDialog E2E Tests', () => { await actionButton.click(); await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), page.getByText('View Members').click(), ]); diff --git a/frontend/e2e/admin-organizations.spec.ts b/frontend/e2e/admin-organizations.spec.ts index 411f97f..1a14bff 100644 --- a/frontend/e2e/admin-organizations.spec.ts +++ b/frontend/e2e/admin-organizations.spec.ts @@ -10,11 +10,11 @@ test.describe('Admin Organization Management - Page Load', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); }); test('should display organization management page', async ({ page }) => { - await expect(page).toHaveURL('/admin/organizations'); + await expect(page).toHaveURL('/en/admin/organizations'); // Wait for page to load await page.waitForSelector('table'); @@ -41,7 +41,7 @@ test.describe('Admin Organization Management - Organization List Table', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); }); test('should display organization list table with headers', async ({ page }) => { @@ -107,7 +107,7 @@ test.describe('Admin Organization Management - Pagination', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); }); test('should display pagination info', async ({ page }) => { @@ -127,7 +127,7 @@ test.describe('Admin Organization Management - Create Organization Button', () = test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); }); test('should display create organization button', async ({ page }) => { @@ -140,7 +140,7 @@ test.describe('Admin Organization Management - Action Menu', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); }); @@ -192,12 +192,12 @@ test.describe('Admin Organization Management - Action Menu', () => { // Click view members - use Promise.all for Next.js Link navigation await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), page.getByText('View Members').click(), ]); // Should navigate to members page - await expect(page).toHaveURL(/\/admin\/organizations\/[^/]+\/members/); + await expect(page).toHaveURL(/\/en\/admin\/organizations\/[^/]+\/members/); }); test('should show delete confirmation dialog when clicking delete', async ({ page }) => { @@ -248,7 +248,7 @@ test.describe('Admin Organization Management - Edit Organization Dialog', () => test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); }); @@ -297,7 +297,7 @@ test.describe('Admin Organization Management - Member Count Interaction', () => test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); await page.waitForSelector('table tbody tr'); }); @@ -308,12 +308,12 @@ test.describe('Admin Organization Management - Member Count Interaction', () => // Click on member count - use Promise.all for Next.js Link navigation await Promise.all([ - page.waitForURL(/\/admin\/organizations\/[^/]+\/members/), + page.waitForURL(/\/en\/admin\/organizations\/[^/]+\/members/), memberButton.click(), ]); // Should navigate to members page - await expect(page).toHaveURL(/\/admin\/organizations\/[^/]+\/members/); + await expect(page).toHaveURL(/\/en\/admin\/organizations\/[^/]+\/members/); }); }); @@ -321,7 +321,7 @@ test.describe('Admin Organization Management - Accessibility', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/organizations'); + await page.goto('/en/admin/organizations'); }); test('should have proper heading hierarchy', async ({ page }) => { diff --git a/frontend/e2e/admin-users.spec.ts b/frontend/e2e/admin-users.spec.ts index ade3f38..f90f834 100644 --- a/frontend/e2e/admin-users.spec.ts +++ b/frontend/e2e/admin-users.spec.ts @@ -10,11 +10,11 @@ test.describe('Admin User Management - Page Load', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should display user management page', async ({ page }) => { - await expect(page).toHaveURL('/admin/users'); + await expect(page).toHaveURL('/en/admin/users'); await expect(page.locator('h1')).toContainText('User Management'); }); @@ -38,7 +38,7 @@ test.describe('Admin User Management - User List Table', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should display user list table with headers', async ({ page }) => { @@ -101,7 +101,7 @@ test.describe('Admin User Management - Search and Filters', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should display search input', async ({ page }) => { @@ -244,7 +244,7 @@ test.describe('Admin User Management - Pagination', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should display pagination info', async ({ page }) => { @@ -262,7 +262,7 @@ test.describe('Admin User Management - Row Selection', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); await page.waitForSelector('table tbody tr'); }); @@ -325,7 +325,7 @@ test.describe('Admin User Management - Create User Dialog', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should open create user dialog', async ({ page }) => { @@ -449,7 +449,7 @@ test.describe('Admin User Management - Action Menu', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); await page.waitForSelector('table tbody tr'); }); @@ -502,7 +502,7 @@ test.describe('Admin User Management - Edit User Dialog', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); await page.waitForSelector('table tbody tr'); }); @@ -561,7 +561,7 @@ test.describe('Admin User Management - Bulk Actions', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); await page.waitForSelector('table tbody tr'); }); @@ -631,7 +631,7 @@ test.describe('Admin User Management - Accessibility', () => { test.beforeEach(async ({ page }) => { await setupSuperuserMocks(page); // Auth already cached in storage state (loginViaUI removed for performance) - await page.goto('/admin/users'); + await page.goto('/en/admin/users'); }); test('should have proper heading hierarchy', async ({ page }) => { diff --git a/frontend/e2e/auth-guard.spec.ts b/frontend/e2e/auth-guard.spec.ts index 7b47c27..543f6c8 100644 --- a/frontend/e2e/auth-guard.spec.ts +++ b/frontend/e2e/auth-guard.spec.ts @@ -4,7 +4,7 @@ test.describe('AuthGuard - Route Protection', () => { test.beforeEach(async ({ page, context }) => { // Clear storage before each test to ensure clean state await context.clearCookies(); - await page.goto('/'); + await page.goto('/en'); await page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); @@ -15,7 +15,7 @@ test.describe('AuthGuard - Route Protection', () => { // Try to access a protected route (if you have one) // For now, we'll test the root if it's protected // Adjust the route based on your actual protected routes - await page.goto('/'); + await page.goto('/en'); // If root is protected, should redirect to login or show homepage // Wait for page to stabilize @@ -28,24 +28,24 @@ test.describe('AuthGuard - Route Protection', () => { test('should allow access to public routes without auth', async ({ page }) => { // Test login page - await page.goto('/login'); - await expect(page).toHaveURL('/login'); + await page.goto('/en/login'); + await expect(page).toHaveURL('/en/login'); await expect(page.locator('h2')).toContainText('Sign in to your account'); // Test register page - await page.goto('/register'); - await expect(page).toHaveURL('/register'); + await page.goto('/en/register'); + await expect(page).toHaveURL('/en/register'); await expect(page.locator('h2')).toContainText('Create your account'); // Test password reset page - await page.goto('/password-reset'); - await expect(page).toHaveURL('/password-reset'); + await page.goto('/en/password-reset'); + await expect(page).toHaveURL('/en/password-reset'); await expect(page.locator('h2')).toContainText('Reset your password'); }); test('should persist authentication across page reloads', async ({ page }) => { // Manually set a mock token in localStorage for testing - await page.goto('/'); + await page.goto('/en'); await page.evaluate(() => { const mockToken = { access_token: 'mock-access-token', @@ -73,7 +73,7 @@ test.describe('AuthGuard - Route Protection', () => { test('should clear authentication on logout', async ({ page }) => { // Set up authenticated state - await page.goto('/'); + await page.goto('/en'); await page.evaluate(() => { const mockToken = { access_token: 'mock-access-token', @@ -110,7 +110,7 @@ test.describe('AuthGuard - Route Protection', () => { test('should not allow access to auth pages when already logged in', async ({ page }) => { // Set up authenticated state - await page.goto('/'); + await page.goto('/en'); await page.evaluate(() => { const mockToken = { access_token: 'mock-access-token', @@ -127,7 +127,7 @@ test.describe('AuthGuard - Route Protection', () => { }); // Try to access login page - await page.goto('/login'); + await page.goto('/en/login'); // Wait a bit for potential redirect await page.waitForTimeout(2000); @@ -141,7 +141,7 @@ test.describe('AuthGuard - Route Protection', () => { test('should handle expired tokens gracefully', async ({ page }) => { // Set up authenticated state with expired token - await page.goto('/'); + await page.goto('/en'); await page.evaluate(() => { const expiredToken = { access_token: 'expired-access-token', @@ -171,7 +171,7 @@ test.describe('AuthGuard - Route Protection', () => { test('should preserve intended destination after login', async ({ page }) => { // This is a nice-to-have feature that requires protected routes // For now, just verify the test doesn't crash - await page.goto('/'); + await page.goto('/en'); // Login (via localStorage for testing) await page.evaluate(() => { diff --git a/frontend/e2e/auth-login.spec.ts b/frontend/e2e/auth-login.spec.ts index 2f3a1fe..3fd2ea0 100644 --- a/frontend/e2e/auth-login.spec.ts +++ b/frontend/e2e/auth-login.spec.ts @@ -28,7 +28,7 @@ test.describe('Login Flow', () => { }); // Navigate to login page before each test - await page.goto('/login'); + await page.goto('/en/login'); }); test.afterEach(async ({ page }, testInfo) => { @@ -128,7 +128,7 @@ test.describe('Login Flow', () => { await page.waitForTimeout(1000); // Should stay on login page (validation failed) - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); }); test('should show error for invalid credentials', async ({ page }) => { @@ -162,10 +162,10 @@ test.describe('Login Flow', () => { // Click forgot password link - use Promise.all to wait for navigation const forgotLink = page.getByRole('link', { name: 'Forgot password?' }); - await Promise.all([page.waitForURL('/password-reset'), forgotLink.click()]); + await Promise.all([page.waitForURL('/en/password-reset'), forgotLink.click()]); // Should be on password reset page - await expect(page).toHaveURL('/password-reset'); + await expect(page).toHaveURL('/en/password-reset'); await expect(page.locator('h2')).toContainText('Reset your password'); }); @@ -173,10 +173,10 @@ test.describe('Login Flow', () => { // Click sign up link - use Promise.all to wait for navigation const signupLink = page.getByRole('link', { name: 'Sign up' }); - await Promise.all([page.waitForURL('/register'), signupLink.click()]); + await Promise.all([page.waitForURL('/en/register'), signupLink.click()]); // Should be on register page - await expect(page).toHaveURL('/register'); + await expect(page).toHaveURL('/en/register'); await expect(page.locator('h2')).toContainText('Create your account'); }); diff --git a/frontend/e2e/auth-password-reset.spec.ts b/frontend/e2e/auth-password-reset.spec.ts index 0ad7d2f..e74457b 100644 --- a/frontend/e2e/auth-password-reset.spec.ts +++ b/frontend/e2e/auth-password-reset.spec.ts @@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test'; test.describe('Password Reset Request Flow', () => { test.beforeEach(async ({ page }) => { // Navigate to password reset page - await page.goto('/password-reset'); + await page.goto('/en/password-reset'); }); test('should display password reset request form', async ({ page }) => { @@ -37,7 +37,7 @@ test.describe('Password Reset Request Flow', () => { await page.waitForTimeout(1000); // Should stay on password reset page (validation failed) - await expect(page).toHaveURL('/password-reset'); + await expect(page).toHaveURL('/en/password-reset'); }); test('should successfully submit password reset request', async ({ page }) => { @@ -55,10 +55,10 @@ test.describe('Password Reset Request Flow', () => { // Click back to login link - use Promise.all to wait for navigation const loginLink = page.getByRole('link', { name: 'Back to login' }); - await Promise.all([page.waitForURL('/login'), loginLink.click()]); + await Promise.all([page.waitForURL('/en/login'), loginLink.click()]); // Should be on login page - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); await expect(page.locator('h2')).toContainText('Sign in to your account'); }); @@ -84,7 +84,7 @@ test.describe('Password Reset Request Flow', () => { test.describe('Password Reset Confirm Flow', () => { test('should display error for missing token', async ({ page }) => { // Navigate without token - await page.goto('/password-reset/confirm'); + await page.goto('/en/password-reset/confirm'); // Should show error message await expect(page.locator('h2')).toContainText(/Invalid/i); @@ -95,7 +95,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should display password reset confirm form with valid token', async ({ page }) => { // Navigate with token (using a dummy token for UI testing) - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); // Check page title await expect(page.locator('h2')).toContainText('Set new password'); @@ -108,7 +108,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should show validation errors for empty form', async ({ page }) => { // Navigate with token - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); // Click submit without filling form await page.locator('button[type="submit"]').click(); @@ -120,7 +120,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should show validation error for weak password', async ({ page }) => { // Navigate with token - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); // Fill with weak password await page.locator('input[name="new_password"]').fill('weak'); @@ -136,7 +136,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should show validation error for mismatched passwords', async ({ page }) => { // Navigate with token - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); // Fill with mismatched passwords await page.locator('input[name="new_password"]').fill('Password123!'); @@ -152,7 +152,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should show error for invalid token', async ({ page }) => { // Navigate with invalid token - await page.goto('/password-reset/confirm?token=invalid-token'); + await page.goto('/en/password-reset/confirm?token=invalid-token'); // Fill form with valid passwords await page.locator('input[name="new_password"]').fill('NewPassword123!'); @@ -172,7 +172,7 @@ test.describe('Password Reset Confirm Flow', () => { // In real scenario, you'd generate a token via API or use a test fixture // For UI testing, we use a dummy token - backend will reject it - await page.goto('/password-reset/confirm?token=valid-test-token-from-backend'); + await page.goto('/en/password-reset/confirm?token=valid-test-token-from-backend'); // Fill form with valid passwords await page.locator('input[name="new_password"]').fill('NewPassword123!'); @@ -188,21 +188,21 @@ test.describe('Password Reset Confirm Flow', () => { test('should navigate to request new reset link', async ({ page }) => { // Navigate without token to trigger error state - await page.goto('/password-reset/confirm'); + await page.goto('/en/password-reset/confirm'); // Click request new reset link - use Promise.all to wait for navigation const resetLink = page.getByRole('link', { name: 'Request new reset link' }); - await Promise.all([page.waitForURL('/password-reset'), resetLink.click()]); + await Promise.all([page.waitForURL('/en/password-reset'), resetLink.click()]); // Should be on password reset request page - await expect(page).toHaveURL('/password-reset'); + await expect(page).toHaveURL('/en/password-reset'); await expect(page.locator('h2')).toContainText('Reset your password'); }); test('should toggle password visibility', async ({ page }) => { // Navigate with token - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); const passwordInput = page.locator('input[name="new_password"]'); const confirmPasswordInput = page.locator('input[name="confirm_password"]'); @@ -216,7 +216,7 @@ test.describe('Password Reset Confirm Flow', () => { test('should disable submit button while loading', async ({ page }) => { // Navigate with token - await page.goto('/password-reset/confirm?token=dummy-test-token-123'); + await page.goto('/en/password-reset/confirm?token=dummy-test-token-123'); // Fill form await page.locator('input[name="new_password"]').fill('NewPassword123!'); diff --git a/frontend/e2e/auth-register.spec.ts b/frontend/e2e/auth-register.spec.ts index b3666b5..ccd370a 100644 --- a/frontend/e2e/auth-register.spec.ts +++ b/frontend/e2e/auth-register.spec.ts @@ -28,7 +28,7 @@ test.describe('Registration Flow', () => { }); // Navigate to register page before each test - await page.goto('/register'); + await page.goto('/en/register'); }); test.afterEach(async ({ page }, testInfo) => { @@ -222,10 +222,10 @@ test.describe('Registration Flow', () => { const loginLink = page.getByRole('link', { name: 'Sign in' }); // Use Promise.all to wait for navigation - await Promise.all([page.waitForURL('/login'), loginLink.click()]); + await Promise.all([page.waitForURL('/en/login'), loginLink.click()]); // Should be on login page - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); await expect(page.locator('h2')).toContainText('Sign in to your account'); }); diff --git a/frontend/e2e/helpers/auth.ts b/frontend/e2e/helpers/auth.ts index ead319e..faa7b45 100644 --- a/frontend/e2e/helpers/auth.ts +++ b/frontend/e2e/helpers/auth.ts @@ -100,15 +100,15 @@ export async function loginViaUI( email = 'test@example.com', password = 'Password123!' ): Promise { - // Navigate to login page - await page.goto('/login'); + // Navigate to login page (with locale prefix) + await page.goto('/en/login'); // Fill login form await page.locator('input[name="email"]').fill(email); await page.locator('input[name="password"]').fill(password); - // Submit and wait for navigation to home - await Promise.all([page.waitForURL('/'), page.locator('button[type="submit"]').click()]); + // Submit and wait for navigation to home (with locale prefix) + await Promise.all([page.waitForURL('/en'), page.locator('button[type="submit"]').click()]); // Wait for auth to settle await page.waitForTimeout(500); diff --git a/frontend/e2e/homepage.spec.ts b/frontend/e2e/homepage.spec.ts index d92ff7e..76125d4 100644 --- a/frontend/e2e/homepage.spec.ts +++ b/frontend/e2e/homepage.spec.ts @@ -8,7 +8,7 @@ import { test, expect } from '@playwright/test'; test.describe('Homepage - Desktop Navigation', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); // Wait for page to be fully loaded }); @@ -36,11 +36,11 @@ test.describe('Homepage - Desktop Navigation', () => { // Verify link exists and has correct href await expect(componentsLink).toBeVisible(); - await expect(componentsLink).toHaveAttribute('href', '/dev'); + await expect(componentsLink).toHaveAttribute('href', '/en/dev'); // Click and wait for navigation await componentsLink.click(); - await page.waitForURL('/dev', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/dev', { timeout: 10000 }).catch(() => {}); // Verify URL (might not navigate if /dev page has issues, that's ok for this test) const currentUrl = page.url(); @@ -54,11 +54,11 @@ test.describe('Homepage - Desktop Navigation', () => { // Verify link exists and has correct href await expect(adminLink).toBeVisible(); - await expect(adminLink).toHaveAttribute('href', '/admin'); + await expect(adminLink).toHaveAttribute('href', '/en/admin'); // Click and wait for navigation await adminLink.click(); - await page.waitForURL('/admin', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/admin', { timeout: 10000 }).catch(() => {}); // Verify URL (might not navigate if /admin requires auth, that's ok for this test) const currentUrl = page.url(); @@ -70,9 +70,9 @@ test.describe('Homepage - Desktop Navigation', () => { const header = page.locator('header').first(); const headerLoginLink = header.getByRole('link', { name: /^Login$/i }); - await Promise.all([page.waitForURL('/login'), headerLoginLink.click()]); + await Promise.all([page.waitForURL('/en/login'), headerLoginLink.click()]); - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); }); test.skip('should open demo credentials modal when clicking Try Demo', async ({ page }) => { @@ -113,7 +113,7 @@ test.describe('Homepage - Mobile Menu Interactions', () => { test.beforeEach(async ({ page }) => { // Set mobile viewport await page.setViewportSize({ width: 375, height: 667 }); - await page.goto('/'); + await page.goto('/en'); await page.waitForLoadState('domcontentloaded'); }); @@ -146,11 +146,11 @@ test.describe('Homepage - Mobile Menu Interactions', () => { const componentsLink = mobileMenu.getByRole('link', { name: 'Components' }); // Verify link has correct href - await expect(componentsLink).toHaveAttribute('href', '/dev'); + await expect(componentsLink).toHaveAttribute('href', '/en/dev'); // Click and wait for navigation await componentsLink.click(); - await page.waitForURL('/dev', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/dev', { timeout: 10000 }).catch(() => {}); // Verify URL (might not navigate if /dev page has issues, that's ok) const currentUrl = page.url(); @@ -164,11 +164,11 @@ test.describe('Homepage - Mobile Menu Interactions', () => { const adminLink = mobileMenu.getByRole('link', { name: 'Admin Demo' }); // Verify link has correct href - await expect(adminLink).toHaveAttribute('href', '/admin'); + await expect(adminLink).toHaveAttribute('href', '/en/admin'); // Click and wait for navigation await adminLink.click(); - await page.waitForURL('/admin', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/admin', { timeout: 10000 }).catch(() => {}); // Verify URL (might not navigate if /admin requires auth, that's ok) const currentUrl = page.url(); @@ -204,9 +204,9 @@ test.describe('Homepage - Mobile Menu Interactions', () => { const loginLink = mobileMenu.getByRole('link', { name: /Login/i }); await loginLink.waitFor({ state: 'visible' }); - await Promise.all([page.waitForURL('/login'), loginLink.click()]); + await Promise.all([page.waitForURL('/en/login'), loginLink.click()]); - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); }); test.skip('should close mobile menu when clicking outside', async ({ page }) => { @@ -223,7 +223,7 @@ test.describe('Homepage - Mobile Menu Interactions', () => { test.describe('Homepage - Hero Section', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test('should display main headline', async ({ page }) => { @@ -255,11 +255,11 @@ test.describe('Homepage - Hero Section', () => { const exploreLink = page.getByRole('link', { name: /Explore Components/i }).first(); // Verify link has correct href - await expect(exploreLink).toHaveAttribute('href', '/dev'); + await expect(exploreLink).toHaveAttribute('href', '/en/dev'); // Click and try to navigate await exploreLink.click(); - await page.waitForURL('/dev', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/dev', { timeout: 10000 }).catch(() => {}); // Verify URL (flexible to handle auth redirects) const currentUrl = page.url(); @@ -269,7 +269,7 @@ test.describe('Homepage - Hero Section', () => { test.describe('Homepage - Demo Credentials Modal', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test.skip('should display regular and admin credentials', async ({ page }) => { @@ -321,9 +321,9 @@ test.describe('Homepage - Demo Credentials Modal', () => { const loginLink = dialog.getByRole('link', { name: /Go to Login/i }); - await Promise.all([page.waitForURL('/login'), loginLink.click()]); + await Promise.all([page.waitForURL('/en/login'), loginLink.click()]); - await expect(page).toHaveURL('/login'); + await expect(page).toHaveURL('/en/login'); }); test.skip('should close modal when clicking close button', async ({ page }) => { @@ -344,7 +344,7 @@ test.describe('Homepage - Demo Credentials Modal', () => { test.describe('Homepage - Animated Terminal', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test('should display terminal section', async ({ page }) => { @@ -387,11 +387,11 @@ test.describe('Homepage - Animated Terminal', () => { const terminalDemoLink = demoLinks.last(); // Last one should be from terminal section // Verify link has correct href - await expect(terminalDemoLink).toHaveAttribute('href', '/login'); + await expect(terminalDemoLink).toHaveAttribute('href', '/en/login'); // Click and try to navigate await terminalDemoLink.click(); - await page.waitForURL('/login', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/login', { timeout: 10000 }).catch(() => {}); // Verify URL (flexible to handle redirects) const currentUrl = page.url(); @@ -401,7 +401,7 @@ test.describe('Homepage - Animated Terminal', () => { test.describe('Homepage - Feature Sections', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test('should display feature grid section', async ({ page }) => { @@ -417,11 +417,11 @@ test.describe('Homepage - Feature Sections', () => { const authLink = page.getByRole('link', { name: /View Auth Flow/i }); // Verify link has correct href - await expect(authLink).toHaveAttribute('href', '/login'); + await expect(authLink).toHaveAttribute('href', '/en/login'); // Click and try to navigate await authLink.click(); - await page.waitForURL('/login', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/login', { timeout: 10000 }).catch(() => {}); // Verify URL (flexible to handle redirects) const currentUrl = page.url(); @@ -432,11 +432,11 @@ test.describe('Homepage - Feature Sections', () => { const adminLink = page.getByRole('link', { name: /Try Admin Panel/i }); // Verify link has correct href - await expect(adminLink).toHaveAttribute('href', '/admin'); + await expect(adminLink).toHaveAttribute('href', '/en/admin'); // Click and try to navigate await adminLink.click(); - await page.waitForURL('/admin', { timeout: 10000 }).catch(() => {}); + await page.waitForURL('/en/admin', { timeout: 10000 }).catch(() => {}); // Verify URL (flexible to handle auth redirects) const currentUrl = page.url(); @@ -462,7 +462,7 @@ test.describe('Homepage - Feature Sections', () => { test.describe('Homepage - Footer', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test('should display footer with copyright', async ({ page }) => { @@ -475,7 +475,7 @@ test.describe('Homepage - Footer', () => { test.describe('Homepage - Accessibility', () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); + await page.goto('/en'); }); test('should have proper heading hierarchy', async ({ page }) => { diff --git a/frontend/e2e/settings-navigation.spec.ts b/frontend/e2e/settings-navigation.spec.ts index 00ea0c5..d1bd00a 100644 --- a/frontend/e2e/settings-navigation.spec.ts +++ b/frontend/e2e/settings-navigation.spec.ts @@ -17,14 +17,14 @@ test.describe('Settings Navigation', () => { test('should navigate from home to settings profile', async ({ page }) => { // Start at home page (auth already cached in storage state) - await page.goto('/'); + await page.goto('/en'); await expect(page).toHaveURL('/'); // Navigate to settings/profile - await page.goto('/settings/profile'); + await page.goto('/en/settings/profile'); // Verify navigation successful - await expect(page).toHaveURL('/settings/profile'); + await expect(page).toHaveURL('/en/settings/profile'); // Verify page loaded - use specific heading selector await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible(); @@ -32,14 +32,14 @@ test.describe('Settings Navigation', () => { test('should navigate from home to settings password', async ({ page }) => { // Start at home page (auth already cached in storage state) - await page.goto('/'); + await page.goto('/en'); await expect(page).toHaveURL('/'); // Navigate to settings/password - await page.goto('/settings/password'); + await page.goto('/en/settings/password'); // Verify navigation successful - await expect(page).toHaveURL('/settings/password'); + await expect(page).toHaveURL('/en/settings/password'); // Verify page loaded - use specific heading selector await expect(page.getByRole('heading', { name: 'Password' })).toBeVisible(); @@ -47,24 +47,24 @@ test.describe('Settings Navigation', () => { test('should navigate between settings pages', async ({ page }) => { // Start at profile page - await page.goto('/settings/profile'); + await page.goto('/en/settings/profile'); await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible(); // Navigate to password page - await page.goto('/settings/password'); + await page.goto('/en/settings/password'); await expect(page.getByRole('heading', { name: 'Password' })).toBeVisible(); // Navigate back to profile page - await page.goto('/settings/profile'); + await page.goto('/en/settings/profile'); await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible(); }); test('should redirect from /settings to /settings/profile', async ({ page }) => { // Navigate to base settings page - await page.goto('/settings'); + await page.goto('/en/settings'); // Should redirect to profile page - await expect(page).toHaveURL('/settings/profile'); + await expect(page).toHaveURL('/en/settings/profile'); // Verify profile page loaded - use specific heading selector await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible(); @@ -72,10 +72,10 @@ test.describe('Settings Navigation', () => { test('should display preferences page placeholder', async ({ page }) => { // Navigate to preferences page - await page.goto('/settings/preferences'); + await page.goto('/en/settings/preferences'); // Verify navigation successful - await expect(page).toHaveURL('/settings/preferences'); + await expect(page).toHaveURL('/en/settings/preferences'); // Verify page loaded with placeholder content await expect(page.getByRole('heading', { name: 'Preferences' })).toBeVisible(); diff --git a/frontend/e2e/settings-password.spec.ts b/frontend/e2e/settings-password.spec.ts index 313dfa3..746954d 100644 --- a/frontend/e2e/settings-password.spec.ts +++ b/frontend/e2e/settings-password.spec.ts @@ -15,7 +15,7 @@ test.describe('Password Change', () => { // Auth already cached in storage state (loginViaUI removed for performance) // Navigate to password page - await page.goto('/settings/password'); + await page.goto('/en/settings/password'); // Wait for form to be visible await page.getByLabel(/current password/i).waitFor({ state: 'visible' }); diff --git a/frontend/e2e/settings-profile.spec.ts b/frontend/e2e/settings-profile.spec.ts index 2f57320..7873e94 100644 --- a/frontend/e2e/settings-profile.spec.ts +++ b/frontend/e2e/settings-profile.spec.ts @@ -15,7 +15,7 @@ test.describe('Profile Settings', () => { // Auth already cached in storage state (loginViaUI removed for performance) // Navigate to profile page - await page.goto('/settings/profile'); + await page.goto('/en/settings/profile'); // Wait for page to render await page.waitForTimeout(1000); diff --git a/frontend/e2e/theme-toggle.spec.ts b/frontend/e2e/theme-toggle.spec.ts index b2938bf..06194e8 100644 --- a/frontend/e2e/theme-toggle.spec.ts +++ b/frontend/e2e/theme-toggle.spec.ts @@ -8,12 +8,12 @@ import { test, expect } from '@playwright/test'; test.describe('Theme Toggle on Public Pages', () => { test.beforeEach(async ({ page }) => { // Clear localStorage before each test - await page.goto('/login'); + await page.goto('/en/login'); await page.evaluate(() => localStorage.clear()); }); test('theme is applied on login page', async ({ page }) => { - await page.goto('/login'); + await page.goto('/en/login'); // Wait for page to load and theme to be applied await page.waitForTimeout(500); @@ -27,7 +27,7 @@ test.describe('Theme Toggle on Public Pages', () => { }); test('theme persists across page navigation', async ({ page }) => { - await page.goto('/login'); + await page.goto('/en/login'); await page.waitForTimeout(500); // Set theme to dark via localStorage @@ -43,14 +43,14 @@ test.describe('Theme Toggle on Public Pages', () => { await expect(page.locator('html')).toHaveClass(/dark/); // Navigate to register page - await page.goto('/register'); + await page.goto('/en/register'); await page.waitForTimeout(500); // Theme should still be dark await expect(page.locator('html')).toHaveClass(/dark/); // Navigate to password reset - await page.goto('/password-reset'); + await page.goto('/en/password-reset'); await page.waitForTimeout(500); // Theme should still be dark @@ -58,7 +58,7 @@ test.describe('Theme Toggle on Public Pages', () => { }); test('can switch theme programmatically', async ({ page }) => { - await page.goto('/login'); + await page.goto('/en/login'); // Set to light theme await page.evaluate(() => { diff --git a/frontend/jest.config.js b/frontend/jest.config.js index 76f0e8c..c2c411f 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.js @@ -10,6 +10,8 @@ const customJestConfig = { setupFilesAfterEnv: ['/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { + '^next-intl/routing$': '/tests/__mocks__/next-intl-routing.tsx', + '^next-intl/navigation$': '/tests/__mocks__/next-intl-navigation.tsx', '^@/(.*)$': '/src/$1', }, testMatch: ['/tests/**/*.test.ts', '/tests/**/*.test.tsx'], diff --git a/frontend/tests/__mocks__/next-intl-navigation.tsx b/frontend/tests/__mocks__/next-intl-navigation.tsx new file mode 100644 index 0000000..71bf32b --- /dev/null +++ b/frontend/tests/__mocks__/next-intl-navigation.tsx @@ -0,0 +1,29 @@ +/** + * Mock for next-intl/navigation + */ + +// Create shared mock instances that tests can manipulate +// Note: next-intl's usePathname returns paths WITHOUT locale prefix +export const mockUsePathname = jest.fn(() => '/'); +export const mockPush = jest.fn(); +export const mockReplace = jest.fn(); +export const mockUseRouter = jest.fn(() => ({ + push: mockPush, + replace: mockReplace, + prefetch: jest.fn(), + back: jest.fn(), + forward: jest.fn(), + refresh: jest.fn(), +})); +export const mockRedirect = jest.fn(); + +export const createNavigation = (_routing: any) => ({ + Link: ({ children, href, ...props }: any) => ( + + {children} + + ), + redirect: mockRedirect, + usePathname: mockUsePathname, + useRouter: mockUseRouter, +}); diff --git a/frontend/tests/__mocks__/next-intl-routing.tsx b/frontend/tests/__mocks__/next-intl-routing.tsx new file mode 100644 index 0000000..730759e --- /dev/null +++ b/frontend/tests/__mocks__/next-intl-routing.tsx @@ -0,0 +1,23 @@ +/** + * Mock for next-intl/routing + */ + +export const defineRouting = (config: any) => config; + +export const createNavigation = (_routing: any) => ({ + Link: ({ children, href, ...props }: any) => ( + + {children} + + ), + redirect: jest.fn(), + usePathname: () => '/en/test', + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + prefetch: jest.fn(), + back: jest.fn(), + forward: jest.fn(), + refresh: jest.fn(), + }), +}); diff --git a/frontend/tests/app/(auth)/login/page.test.tsx b/frontend/tests/app/(auth)/login/page.test.tsx index 05bc3a4..3bdcee1 100644 --- a/frontend/tests/app/(auth)/login/page.test.tsx +++ b/frontend/tests/app/(auth)/login/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import LoginPage from '@/app/(auth)/login/page'; +import LoginPage from '@/app/[locale]/(auth)/login/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ diff --git a/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx b/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx index 8000971..e0a93a9 100644 --- a/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx +++ b/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx @@ -4,20 +4,20 @@ */ import { render, screen, act } from '@testing-library/react'; -import { useSearchParams, useRouter } from 'next/navigation'; -import PasswordResetConfirmContent from '@/app/(auth)/password-reset/confirm/PasswordResetConfirmContent'; +import { useSearchParams } from 'next/navigation'; +import { useRouter } from '@/lib/i18n/routing'; +import PasswordResetConfirmContent from '@/app/[locale]/(auth)/password-reset/confirm/PasswordResetConfirmContent'; // Mock Next.js navigation jest.mock('next/navigation', () => ({ useSearchParams: jest.fn(), - useRouter: jest.fn(), default: jest.fn(), })); -// Mock Next.js Link -jest.mock('next/link', () => ({ - __esModule: true, - default: ({ children, href }: { children: React.ReactNode; href: string }) => ( +// Mock i18n routing +jest.mock('@/lib/i18n/routing', () => ({ + useRouter: jest.fn(), + Link: ({ children, href }: { children: React.ReactNode; href: string }) => ( {children} ), })); diff --git a/frontend/tests/app/(auth)/password-reset/confirm/page.test.tsx b/frontend/tests/app/(auth)/password-reset/confirm/page.test.tsx index 6f92406..7d12f38 100644 --- a/frontend/tests/app/(auth)/password-reset/confirm/page.test.tsx +++ b/frontend/tests/app/(auth)/password-reset/confirm/page.test.tsx @@ -4,10 +4,10 @@ */ import { render, screen } from '@testing-library/react'; -import PasswordResetConfirmPage from '@/app/(auth)/password-reset/confirm/page'; +import PasswordResetConfirmPage from '@/app/[locale]/(auth)/password-reset/confirm/page'; // Mock the content component -jest.mock('@/app/(auth)/password-reset/confirm/PasswordResetConfirmContent', () => ({ +jest.mock('@/app/[locale]/(auth)/password-reset/confirm/PasswordResetConfirmContent', () => ({ __esModule: true, default: () =>
Content
, })); diff --git a/frontend/tests/app/(auth)/password-reset/page.test.tsx b/frontend/tests/app/(auth)/password-reset/page.test.tsx index 989b6ef..e20da90 100644 --- a/frontend/tests/app/(auth)/password-reset/page.test.tsx +++ b/frontend/tests/app/(auth)/password-reset/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import PasswordResetPage from '@/app/(auth)/password-reset/page'; +import PasswordResetPage from '@/app/[locale]/(auth)/password-reset/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ diff --git a/frontend/tests/app/(auth)/register/page.test.tsx b/frontend/tests/app/(auth)/register/page.test.tsx index 3d39622..1772236 100644 --- a/frontend/tests/app/(auth)/register/page.test.tsx +++ b/frontend/tests/app/(auth)/register/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import RegisterPage from '@/app/(auth)/register/page'; +import RegisterPage from '@/app/[locale]/(auth)/register/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ diff --git a/frontend/tests/app/(authenticated)/settings/page.test.tsx b/frontend/tests/app/(authenticated)/settings/page.test.tsx index 7764ab6..59cf5a1 100644 --- a/frontend/tests/app/(authenticated)/settings/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/page.test.tsx @@ -4,7 +4,7 @@ */ import { redirect } from 'next/navigation'; -import SettingsPage from '@/app/(authenticated)/settings/page'; +import SettingsPage from '@/app/[locale]/(authenticated)/settings/page'; // Mock Next.js navigation - redirect throws to interrupt execution jest.mock('next/navigation', () => ({ @@ -18,8 +18,9 @@ describe('SettingsPage', () => { jest.clearAllMocks(); }); - it('redirects to /settings/profile', () => { - expect(() => SettingsPage()).toThrow('NEXT_REDIRECT'); - expect(redirect).toHaveBeenCalledWith('/settings/profile'); + it('redirects to /settings/profile with locale prefix', async () => { + const params = Promise.resolve({ locale: 'en' }); + await expect(SettingsPage({ params })).rejects.toThrow('NEXT_REDIRECT'); + expect(redirect).toHaveBeenCalledWith('/en/settings/profile'); }); }); diff --git a/frontend/tests/app/(authenticated)/settings/password/page.test.tsx b/frontend/tests/app/(authenticated)/settings/password/page.test.tsx index b19d0d5..665addd 100644 --- a/frontend/tests/app/(authenticated)/settings/password/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/password/page.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import PasswordSettingsPage from '@/app/(authenticated)/settings/password/page'; +import PasswordSettingsPage from '@/app/[locale]/(authenticated)/settings/password/page'; describe('PasswordSettingsPage', () => { const queryClient = new QueryClient({ diff --git a/frontend/tests/app/(authenticated)/settings/preferences/page.test.tsx b/frontend/tests/app/(authenticated)/settings/preferences/page.test.tsx index 8ca2f24..c073bdf 100644 --- a/frontend/tests/app/(authenticated)/settings/preferences/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/preferences/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import PreferencesPage from '@/app/(authenticated)/settings/preferences/page'; +import PreferencesPage from '@/app/[locale]/(authenticated)/settings/preferences/page'; describe('PreferencesPage', () => { it('renders page title', () => { diff --git a/frontend/tests/app/(authenticated)/settings/profile/page.test.tsx b/frontend/tests/app/(authenticated)/settings/profile/page.test.tsx index 495b964..8227b16 100644 --- a/frontend/tests/app/(authenticated)/settings/profile/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/profile/page.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import ProfileSettingsPage from '@/app/(authenticated)/settings/profile/page'; +import ProfileSettingsPage from '@/app/[locale]/(authenticated)/settings/profile/page'; import { AuthProvider } from '@/lib/auth/AuthContext'; // Mock API hooks diff --git a/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx b/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx index e2d50fc..d45c245 100644 --- a/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import SessionsPage from '@/app/(authenticated)/settings/sessions/page'; +import SessionsPage from '@/app/[locale]/(authenticated)/settings/sessions/page'; // Mock the API client jest.mock('@/lib/api/client', () => ({ diff --git a/frontend/tests/app/admin/layout.test.tsx b/frontend/tests/app/admin/layout.test.tsx index 6e53f5f..df7edd6 100644 --- a/frontend/tests/app/admin/layout.test.tsx +++ b/frontend/tests/app/admin/layout.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import AdminLayout from '@/app/admin/layout'; +import AdminLayout from '@/app/[locale]/admin/layout'; import { useAuth } from '@/lib/auth/AuthContext'; // Mock dependencies diff --git a/frontend/tests/app/admin/organizations/[id]/members/page.test.tsx b/frontend/tests/app/admin/organizations/[id]/members/page.test.tsx index 2538d0f..f65db3d 100644 --- a/frontend/tests/app/admin/organizations/[id]/members/page.test.tsx +++ b/frontend/tests/app/admin/organizations/[id]/members/page.test.tsx @@ -3,7 +3,7 @@ */ import { render, screen } from '@testing-library/react'; -import OrganizationMembersPage from '@/app/admin/organizations/[id]/members/page'; +import OrganizationMembersPage from '@/app/[locale]/admin/organizations/[id]/members/page'; // Mock Next.js Link jest.mock('next/link', () => ({ diff --git a/frontend/tests/app/admin/organizations/page.test.tsx b/frontend/tests/app/admin/organizations/page.test.tsx index c34f1c0..e4a2b43 100644 --- a/frontend/tests/app/admin/organizations/page.test.tsx +++ b/frontend/tests/app/admin/organizations/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import AdminOrganizationsPage from '@/app/admin/organizations/page'; +import AdminOrganizationsPage from '@/app/[locale]/admin/organizations/page'; // Mock the entire OrganizationManagementContent component jest.mock('@/components/admin/organizations/OrganizationManagementContent', () => ({ diff --git a/frontend/tests/app/admin/page.test.tsx b/frontend/tests/app/admin/page.test.tsx index 5a4f25c..69613de 100644 --- a/frontend/tests/app/admin/page.test.tsx +++ b/frontend/tests/app/admin/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import AdminPage from '@/app/admin/page'; +import AdminPage from '@/app/[locale]/admin/page'; import { useAdminStats } from '@/lib/api/hooks/useAdmin'; // Mock the useAdminStats hook diff --git a/frontend/tests/app/admin/settings/page.test.tsx b/frontend/tests/app/admin/settings/page.test.tsx index 481e1d8..8dc8151 100644 --- a/frontend/tests/app/admin/settings/page.test.tsx +++ b/frontend/tests/app/admin/settings/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import AdminSettingsPage from '@/app/admin/settings/page'; +import AdminSettingsPage from '@/app/[locale]/admin/settings/page'; describe('AdminSettingsPage', () => { it('renders page title', () => { diff --git a/frontend/tests/app/admin/users/page.test.tsx b/frontend/tests/app/admin/users/page.test.tsx index 881653c..b52a61e 100644 --- a/frontend/tests/app/admin/users/page.test.tsx +++ b/frontend/tests/app/admin/users/page.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import AdminUsersPage from '@/app/admin/users/page'; +import AdminUsersPage from '@/app/[locale]/admin/users/page'; import { useAuth } from '@/lib/auth/AuthContext'; import { useAdminUsers } from '@/lib/api/hooks/useAdmin'; diff --git a/frontend/tests/app/demos/page.test.tsx b/frontend/tests/app/demos/page.test.tsx index 9bc1cd3..3fbc286 100644 --- a/frontend/tests/app/demos/page.test.tsx +++ b/frontend/tests/app/demos/page.test.tsx @@ -3,7 +3,7 @@ */ import { render, screen, within } from '@testing-library/react'; -import DemoTourPage from '@/app/demos/page'; +import DemoTourPage from '@/app/[locale]/demos/page'; // Mock Next.js Link jest.mock('next/link', () => ({ diff --git a/frontend/tests/app/forbidden/page.test.tsx b/frontend/tests/app/forbidden/page.test.tsx index 59e4cde..6b4640f 100644 --- a/frontend/tests/app/forbidden/page.test.tsx +++ b/frontend/tests/app/forbidden/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen } from '@testing-library/react'; -import ForbiddenPage, { metadata } from '@/app/forbidden/page'; +import ForbiddenPage, { metadata } from '@/app/[locale]/forbidden/page'; describe('ForbiddenPage', () => { it('has correct metadata', () => { diff --git a/frontend/tests/app/page.test.tsx b/frontend/tests/app/page.test.tsx index 9f6cdfa..2b96893 100644 --- a/frontend/tests/app/page.test.tsx +++ b/frontend/tests/app/page.test.tsx @@ -4,7 +4,7 @@ */ import { render, screen, within, fireEvent } from '@testing-library/react'; -import Home from '@/app/page'; +import Home from '@/app/[locale]/page'; // Mock Next.js components jest.mock('next/image', () => ({ diff --git a/frontend/tests/components/admin/AdminSidebar.test.tsx b/frontend/tests/components/admin/AdminSidebar.test.tsx index 951bed3..37dc220 100644 --- a/frontend/tests/components/admin/AdminSidebar.test.tsx +++ b/frontend/tests/components/admin/AdminSidebar.test.tsx @@ -7,7 +7,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AdminSidebar } from '@/components/admin/AdminSidebar'; import { useAuth } from '@/lib/auth/AuthContext'; -import { usePathname } from 'next/navigation'; +import { mockUsePathname } from 'next-intl/navigation'; import type { User } from '@/lib/stores/authStore'; // Mock dependencies @@ -16,10 +16,6 @@ jest.mock('@/lib/auth/AuthContext', () => ({ AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}, })); -jest.mock('next/navigation', () => ({ - usePathname: jest.fn(), -})); - // Helper to create mock user function createMockUser(overrides: Partial = {}): User { return { @@ -39,7 +35,7 @@ function createMockUser(overrides: Partial = {}): User { describe('AdminSidebar', () => { beforeEach(() => { jest.clearAllMocks(); - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); @@ -97,7 +93,7 @@ describe('AdminSidebar', () => { describe('Active State Highlighting', () => { it('highlights dashboard link when on /admin', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -106,7 +102,7 @@ describe('AdminSidebar', () => { }); it('highlights users link when on /admin/users', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -115,7 +111,7 @@ describe('AdminSidebar', () => { }); it('highlights users link when on /admin/users/123', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); render(); @@ -124,7 +120,7 @@ describe('AdminSidebar', () => { }); it('highlights organizations link when on /admin/organizations', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/organizations'); + mockUsePathname.mockReturnValue('/admin/organizations'); render(); @@ -133,7 +129,7 @@ describe('AdminSidebar', () => { }); it('highlights settings link when on /admin/settings', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/settings'); + mockUsePathname.mockReturnValue('/admin/settings'); render(); @@ -142,7 +138,7 @@ describe('AdminSidebar', () => { }); it('does not highlight dashboard when on other admin routes', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); diff --git a/frontend/tests/components/admin/Breadcrumbs.test.tsx b/frontend/tests/components/admin/Breadcrumbs.test.tsx index a9dbd44..856ccde 100644 --- a/frontend/tests/components/admin/Breadcrumbs.test.tsx +++ b/frontend/tests/components/admin/Breadcrumbs.test.tsx @@ -5,21 +5,17 @@ import { render, screen } from '@testing-library/react'; import { Breadcrumbs } from '@/components/admin/Breadcrumbs'; -import { usePathname } from 'next/navigation'; - -// Mock dependencies -jest.mock('next/navigation', () => ({ - usePathname: jest.fn(), -})); +import { mockUsePathname } from 'next-intl/navigation'; describe('Breadcrumbs', () => { beforeEach(() => { jest.clearAllMocks(); + mockUsePathname.mockReturnValue('/'); }); describe('Rendering', () => { it('renders breadcrumbs container with correct test id', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -27,7 +23,7 @@ describe('Breadcrumbs', () => { }); it('renders breadcrumbs with proper aria-label', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -36,7 +32,7 @@ describe('Breadcrumbs', () => { }); it('returns null for empty pathname', () => { - (usePathname as jest.Mock).mockReturnValue(''); + mockUsePathname.mockReturnValue(''); const { container } = render(); @@ -44,7 +40,7 @@ describe('Breadcrumbs', () => { }); it('returns null for root pathname', () => { - (usePathname as jest.Mock).mockReturnValue('/'); + mockUsePathname.mockReturnValue('/'); const { container } = render(); @@ -54,7 +50,7 @@ describe('Breadcrumbs', () => { describe('Single Level Navigation', () => { it('renders single breadcrumb for /admin', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -63,7 +59,7 @@ describe('Breadcrumbs', () => { }); it('renders current page without link', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -75,7 +71,7 @@ describe('Breadcrumbs', () => { describe('Multi-Level Navigation', () => { it('renders breadcrumbs for /admin/users', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -84,7 +80,7 @@ describe('Breadcrumbs', () => { }); it('renders parent breadcrumbs as links', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -94,7 +90,7 @@ describe('Breadcrumbs', () => { }); it('renders last breadcrumb as current page', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -104,7 +100,7 @@ describe('Breadcrumbs', () => { }); it('renders breadcrumbs for /admin/organizations', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/organizations'); + mockUsePathname.mockReturnValue('/admin/organizations'); render(); @@ -113,7 +109,7 @@ describe('Breadcrumbs', () => { }); it('renders breadcrumbs for /admin/settings', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/settings'); + mockUsePathname.mockReturnValue('/admin/settings'); render(); @@ -124,7 +120,7 @@ describe('Breadcrumbs', () => { describe('Three-Level Navigation', () => { it('renders all levels correctly', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); render(); @@ -134,7 +130,7 @@ describe('Breadcrumbs', () => { }); it('renders all parent links correctly', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); render(); @@ -146,7 +142,7 @@ describe('Breadcrumbs', () => { }); it('renders last level as current page', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); render(); @@ -158,7 +154,7 @@ describe('Breadcrumbs', () => { describe('Separator Icons', () => { it('renders separator between breadcrumbs', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); const { container } = render(); @@ -168,7 +164,7 @@ describe('Breadcrumbs', () => { }); it('does not render separator before first breadcrumb', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); const { container } = render(); @@ -178,7 +174,7 @@ describe('Breadcrumbs', () => { }); it('renders correct number of separators', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); const { container } = render(); @@ -190,7 +186,7 @@ describe('Breadcrumbs', () => { describe('Label Mapping', () => { it('uses predefined label for admin', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -198,7 +194,7 @@ describe('Breadcrumbs', () => { }); it('uses predefined label for users', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -206,7 +202,7 @@ describe('Breadcrumbs', () => { }); it('uses predefined label for organizations', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/organizations'); + mockUsePathname.mockReturnValue('/admin/organizations'); render(); @@ -214,7 +210,7 @@ describe('Breadcrumbs', () => { }); it('uses predefined label for settings', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/settings'); + mockUsePathname.mockReturnValue('/admin/settings'); render(); @@ -222,7 +218,7 @@ describe('Breadcrumbs', () => { }); it('uses pathname segment for unmapped paths', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/unknown-path'); + mockUsePathname.mockReturnValue('/admin/unknown-path'); render(); @@ -230,7 +226,7 @@ describe('Breadcrumbs', () => { }); it('displays numeric IDs as-is', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users/123'); + mockUsePathname.mockReturnValue('/admin/users/123'); render(); @@ -240,7 +236,7 @@ describe('Breadcrumbs', () => { describe('Styling', () => { it('applies correct styles to parent links', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -250,7 +246,7 @@ describe('Breadcrumbs', () => { }); it('applies correct styles to current page', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -262,7 +258,7 @@ describe('Breadcrumbs', () => { describe('Accessibility', () => { it('has proper navigation role', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -270,7 +266,7 @@ describe('Breadcrumbs', () => { }); it('has aria-label for navigation', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); render(); @@ -279,7 +275,7 @@ describe('Breadcrumbs', () => { }); it('marks current page with aria-current', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); @@ -288,7 +284,7 @@ describe('Breadcrumbs', () => { }); it('marks separator icons as aria-hidden', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); const { container } = render(); @@ -299,7 +295,7 @@ describe('Breadcrumbs', () => { }); it('parent breadcrumbs are keyboard accessible', () => { - (usePathname as jest.Mock).mockReturnValue('/admin/users'); + mockUsePathname.mockReturnValue('/admin/users'); render(); diff --git a/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx b/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx index 2831558..0497f9d 100644 --- a/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx +++ b/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx @@ -5,18 +5,17 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; import { OrganizationManagementContent } from '@/components/admin/organizations/OrganizationManagementContent'; import { useAuth } from '@/lib/auth/AuthContext'; import { useAdminOrganizations } from '@/lib/api/hooks/useAdmin'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { mockPush } from 'next-intl/navigation'; // Mock Next.js navigation -const mockPush = jest.fn(); const mockSearchParams = new URLSearchParams(); jest.mock('next/navigation', () => ({ - useRouter: jest.fn(), useSearchParams: jest.fn(), })); @@ -52,7 +51,6 @@ jest.mock('@/components/admin/organizations/OrganizationFormDialog', () => ({ ) : null, })); -const mockUseRouter = useRouter as jest.MockedFunction; const mockUseSearchParams = useSearchParams as jest.MockedFunction; const mockUseAuth = useAuth as jest.MockedFunction; const mockUseAdminOrganizations = useAdminOrganizations as jest.MockedFunction< @@ -101,12 +99,6 @@ describe('OrganizationManagementContent', () => { jest.clearAllMocks(); - mockUseRouter.mockReturnValue({ - push: mockPush, - replace: jest.fn(), - prefetch: jest.fn(), - } as any); - mockUseSearchParams.mockReturnValue(mockSearchParams as any); mockUseAuth.mockReturnValue({ diff --git a/frontend/tests/components/auth/AuthGuard.test.tsx b/frontend/tests/components/auth/AuthGuard.test.tsx index 43039e4..e80e42e 100644 --- a/frontend/tests/components/auth/AuthGuard.test.tsx +++ b/frontend/tests/components/auth/AuthGuard.test.tsx @@ -6,17 +6,7 @@ import { render, screen, waitFor, act } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthGuard } from '@/components/auth/AuthGuard'; - -// Mock Next.js navigation -const mockPush = jest.fn(); -const mockPathname = '/protected'; - -jest.mock('next/navigation', () => ({ - useRouter: () => ({ - push: mockPush, - }), - usePathname: () => mockPathname, -})); +import { mockUsePathname, mockPush } from 'next-intl/navigation'; // Mock auth state via Context let mockAuthState: { @@ -64,6 +54,10 @@ describe('AuthGuard', () => { beforeEach(() => { jest.clearAllMocks(); jest.useFakeTimers(); + + // Configure pathname mock + mockUsePathname.mockReturnValue('/protected'); + // Reset to default unauthenticated state mockAuthState = { isAuthenticated: false, diff --git a/frontend/tests/components/layout/Header.test.tsx b/frontend/tests/components/layout/Header.test.tsx index 0701158..d6049ef 100644 --- a/frontend/tests/components/layout/Header.test.tsx +++ b/frontend/tests/components/layout/Header.test.tsx @@ -8,7 +8,7 @@ import userEvent from '@testing-library/user-event'; import { Header } from '@/components/layout/Header'; import { useAuth } from '@/lib/auth/AuthContext'; import { useLogout } from '@/lib/api/hooks/useAuth'; -import { usePathname } from 'next/navigation'; +import { mockUsePathname } from 'next-intl/navigation'; import type { User } from '@/lib/stores/authStore'; // Mock dependencies @@ -21,10 +21,6 @@ jest.mock('@/lib/api/hooks/useAuth', () => ({ useLogout: jest.fn(), })); -jest.mock('next/navigation', () => ({ - usePathname: jest.fn(), -})); - jest.mock('@/components/theme', () => ({ ThemeToggle: () =>
Theme Toggle
, })); @@ -51,7 +47,7 @@ describe('Header', () => { beforeEach(() => { jest.clearAllMocks(); - (usePathname as jest.Mock).mockReturnValue('/'); + mockUsePathname.mockReturnValue('/'); (useLogout as jest.Mock).mockReturnValue({ mutate: mockLogout, @@ -156,7 +152,7 @@ describe('Header', () => { }); it('highlights active navigation link', () => { - (usePathname as jest.Mock).mockReturnValue('/admin'); + mockUsePathname.mockReturnValue('/admin'); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: true }), });