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();
});
});