diff --git a/frontend/src/lib/components/common/DataTable.svelte b/frontend/src/lib/components/common/DataTable.svelte index 42d11be6..0c80d5a6 100644 --- a/frontend/src/lib/components/common/DataTable.svelte +++ b/frontend/src/lib/components/common/DataTable.svelte @@ -32,9 +32,9 @@ const sorting = writable([]); - const options: TableOptions = { - data, - columns, + const options: TableOptions = $derived({ + get data() { return data; }, + get columns() { return columns; }, state: { get sorting() { return $sorting; @@ -49,7 +49,7 @@ }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel() - }; + }); const table = createSvelteTable(options); diff --git a/frontend/tests/e2e/auth.spec.js b/frontend/tests/e2e/auth.spec.js index a87f5bc9..512b3140 100644 --- a/frontend/tests/e2e/auth.spec.js +++ b/frontend/tests/e2e/auth.spec.js @@ -159,8 +159,9 @@ test.describe('Authentication E2E', () => { await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); - // Click logout - await page.click('text=Logout'); + // Open user menu dropdown first, then click logout + await page.click('[data-testid="user-menu"] button'); + await page.click('[data-testid="user-menu"] button:has-text("Logout")'); // Should redirect to login await page.waitForURL('/login'); diff --git a/frontend/tests/e2e/dashboard.spec.ts b/frontend/tests/e2e/dashboard.spec.ts index 35ed11c7..ac03efb4 100644 --- a/frontend/tests/e2e/dashboard.spec.ts +++ b/frontend/tests/e2e/dashboard.spec.ts @@ -24,23 +24,25 @@ test.describe('Dashboard Page', () => { await expect(page.getByRole('button', { name: /New Allocation/i })).toBeVisible(); // Check all 4 StatCards render - await expect(page.getByText('Active Projects')).toBeVisible(); - await expect(page.getByText('Team Members')).toBeVisible(); - await expect(page.getByText('Allocations (hrs)')).toBeVisible(); - await expect(page.getByText('Avg Utilization')).toBeVisible(); + // Check all 4 StatCards render (use specific selector to avoid matching sidebar/user menu) + const mainContent = page.getByTestId('layout-content'); + await expect(mainContent.getByText('Active Projects')).toBeVisible(); + await expect(mainContent.getByText('Team Members')).toBeVisible(); + await expect(mainContent.getByText('Allocations (hrs)')).toBeVisible(); + await expect(mainContent.getByText('Avg Utilization')).toBeVisible(); - // Check stat values - await expect(page.getByText('14')).toBeVisible(); // Active Projects - await expect(page.getByText('8')).toBeVisible(); // Team Members - await expect(page.getByText('186')).toBeVisible(); // Allocations + // Check stat values (use exact match to avoid matching '8' in '186' or '87%') + await expect(page.getByText('14', { exact: true })).toBeVisible(); // Active Projects + await expect(page.getByText('8', { exact: true })).toBeVisible(); // Team Members + await expect(page.getByText('186', { exact: true })).toBeVisible(); // Allocations await expect(page.getByText('87%')).toBeVisible(); // Avg Utilization - // Check Quick Actions section - await expect(page.getByText('Quick Actions')).toBeVisible(); - await expect(page.locator('a[href="/team-members"]')).toBeVisible(); - await expect(page.locator('a[href="/projects"]')).toBeVisible(); - await expect(page.locator('a[href="/allocations"]')).toBeVisible(); - await expect(page.locator('a[href="/reports/forecast"]')).toBeVisible(); + // Check Quick Actions section (scope to main content to avoid sidebar/user menu) + await expect(mainContent.getByText('Quick Actions')).toBeVisible(); + await expect(mainContent.locator('a[href="/team-members"]')).toBeVisible(); + await expect(mainContent.locator('a[href="/projects"]')).toBeVisible(); + await expect(mainContent.locator('a[href="/allocations"]')).toBeVisible(); + await expect(mainContent.locator('a[href="/reports/forecast"]')).toBeVisible(); // Check Allocation Preview section await expect(page.getByRole('heading', { name: 'Allocation Preview' })).toBeVisible(); diff --git a/frontend/tests/e2e/layout.spec.ts b/frontend/tests/e2e/layout.spec.ts index ad237ea9..5ec8dbd9 100644 --- a/frontend/tests/e2e/layout.spec.ts +++ b/frontend/tests/e2e/layout.spec.ts @@ -142,8 +142,8 @@ test.describe('Layout E2E', () => { for (const [width, height, expected] of breakpoints) { await page.setViewportSize({ width, height }); - await page.goto('/login'); - await page.evaluate(() => localStorage.setItem('headroom_sidebar_state', 'expanded')); + // Clear localStorage to test default breakpoint behavior + await page.evaluate(() => localStorage.removeItem('headroom_sidebar_state')); await openDashboard(page); await expect .poll(async () => page.evaluate(() => document.documentElement.getAttribute('data-sidebar'))) @@ -155,7 +155,14 @@ test.describe('Layout E2E', () => { await page.setViewportSize({ width: 1280, height: 900 }); await openDashboard(page); + // Wait for sidebar to be fully mounted with event listeners + await expect(page.locator('[data-testid="sidebar"]')).toBeVisible(); + await page.waitForTimeout(100); + const before = await page.evaluate(() => document.documentElement.getAttribute('data-sidebar')); + + // Focus on the page body to ensure keyboard events are captured + await page.locator('body').click(); await page.keyboard.down('Control'); await page.keyboard.press('\\'); await page.keyboard.up('Control'); diff --git a/frontend/tests/e2e/projects.spec.ts b/frontend/tests/e2e/projects.spec.ts index 9c25aaa0..87f5fe9c 100644 --- a/frontend/tests/e2e/projects.spec.ts +++ b/frontend/tests/e2e/projects.spec.ts @@ -21,8 +21,8 @@ test.describe('Projects Page', () => { }); test('search filters projects', async ({ page }) => { - // Wait for data to load - await page.waitForTimeout(500); + // Wait for the table to render (not loading state) + await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); @@ -38,8 +38,8 @@ test.describe('Projects Page', () => { }); test('status filter works', async ({ page }) => { - // Wait for data to load - await page.waitForTimeout(500); + // Wait for the table to render (not loading state) + await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); diff --git a/frontend/tests/e2e/team-members.spec.ts b/frontend/tests/e2e/team-members.spec.ts index 81c451aa..910c3d90 100644 --- a/frontend/tests/e2e/team-members.spec.ts +++ b/frontend/tests/e2e/team-members.spec.ts @@ -21,8 +21,8 @@ test.describe('Team Members Page', () => { }); test('search filters team members', async ({ page }) => { - // Wait for data to load - await page.waitForTimeout(500); + // Wait for the table to render (not loading state) + await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); @@ -38,8 +38,8 @@ test.describe('Team Members Page', () => { }); test('status filter works', async ({ page }) => { - // Wait for data to load - await page.waitForTimeout(500); + // Wait for the table to render (not loading state) + await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count();