import { expect, test } from '@playwright/test'; import { pagePath } from './helpers'; test.describe('keyboard and terminal dock', () => { 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', ); for (const width of [768, 820, 1024, 1280, 1440]) { await page.setViewportSize({ width, height: 900 }); await page.goto('/'); expect( await header.evaluate((element) => getComputedStyle(element).position), `header must not be sticky at ${width}px`, ).not.toBe('sticky'); } }); test('compact primary nav stays a single row without a services submenu', async ({ page, }, testInfo) => { test.skip(testInfo.project.name === 'mobile', 'compact nav applies from md up'); await page.setViewportSize({ width: 1024, height: 900 }); await page.goto('/'); const services = page.locator(`.primary-nav > li > a[href="${pagePath('services', 'de')}"]`); await expect(services).toBeVisible(); await expect(page.locator('.primary-nav .submenu')).toHaveCount(0); await expect(page.locator('.primary-nav a')).toHaveCount(5); await services.click(); await page.waitForURL('**/leistungen'); await expect(services).toHaveAttribute('aria-current', 'page'); const overflow = await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth + 1, ); expect(overflow, 'horizontal overflow at 1024px').toBe(true); }); test('terminal dock is open on load, does not lock the page, and restores on Escape', async ({ page, }) => { await page.goto('/'); const trigger = page.locator('.terminal-dock-trigger'); const panel = page.locator('.terminal-dock-panel'); await expect(panel).toBeVisible(); await expect(trigger).toBeHidden(); await expect(page.locator('#terminal-dock-input')).not.toBeFocused(); await expect(page.locator('.site')).not.toHaveAttribute('inert'); expect(await page.evaluate(() => document.body.style.overflow)).not.toBe('hidden'); await expect(page.locator('.terminal-dock [role="dialog"]')).toHaveCount(0); await page.locator('#terminal-dock-input').focus(); await page.keyboard.press('Escape'); await expect(panel).toHaveCount(0); await expect(trigger).toBeFocused(); }); });