import { expect, test } from '@playwright/test'; test.describe('keyboard and palette', () => { test('skip link is first and moves focus to main', async ({ page }) => { await page.goto('/'); await page.keyboard.press('Tab'); const skip = page.locator('.skip-link'); await expect(skip).toBeFocused(); await page.keyboard.press('Enter'); await expect(page.locator('#main-content')).toBeFocused(); }); test('mobile nav toggle keeps aria-expanded in sync', async ({ page, request }) => { const ssr = await request.get('/'); expect(ssr.ok()).toBe(true); const html = await ssr.text(); expect(html).toContain('aria-expanded="true"'); expect(html).toContain('id="primary-nav"'); expect(html).toContain('class="site-nav"'); expect(html).toMatch(/]*class="site"/); expect(html).not.toMatch(/]*class="[^"]*\bsite\b[^"]*\bnav-collapsed\b/); await page.setViewportSize({ width: 390, height: 844 }); await page.goto('/'); const toggle = page.locator('.nav-toggle'); await expect(toggle).toBeVisible(); await expect(toggle).toHaveAttribute('aria-expanded', 'false'); await toggle.click(); await expect(toggle).toHaveAttribute('aria-expanded', 'true'); await toggle.click(); await expect(toggle).toHaveAttribute('aria-expanded', 'false'); }); test('collapsed header stays under 25% of the viewport and is not sticky below md', async ({ page, }) => { const viewport = { width: 390, height: 844 }; await page.setViewportSize(viewport); await page.goto('/'); const header = page.locator('.site-header'); const box = await header.boundingBox(); expect(box).toBeTruthy(); expect(box!.height).toBeLessThan(viewport.height * 0.25); await expect .poll(async () => header.evaluate((element) => getComputedStyle(element).position)) .not.toBe('sticky'); await page.locator('.nav-toggle').click(); await expect(page.locator('.nav-toggle')).toHaveAttribute('aria-expanded', 'true'); expect(await header.evaluate((element) => getComputedStyle(element).position)).not.toBe( 'sticky', ); }); test('palette opens with Control+K, traps focus, locks scroll and restores on Escape', async ({ page, }) => { await page.goto('/'); const trigger = page.locator('.command-palette-trigger'); await trigger.focus(); await page.keyboard.press('Control+k'); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); await expect(page.locator('.site')).toHaveAttribute('inert', ''); expect(await page.evaluate(() => document.body.style.overflow)).toBe('hidden'); const focusable = dialog.locator( 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled])', ); const count = await focusable.count(); const first = focusable.first(); const last = focusable.nth(count - 1); await last.focus(); await page.keyboard.press('Tab'); await expect(first).toBeFocused(); await page.keyboard.press('Shift+Tab'); await expect(last).toBeFocused(); await page.keyboard.press('Escape'); await expect(dialog).toHaveCount(0); await expect(page.locator('.site')).not.toHaveAttribute('inert'); expect(await page.evaluate(() => document.body.style.overflow)).toBe(''); await expect(trigger).toBeFocused(); }); });