import { expect, test } from '../helpers/serve-dist'; const navHrefs = [ '#start', '#profile', '#experience', '#infrastructure', '#selected-builds', '#additional-work', '#toolkit', '#contact', ] as const; const assertVisibleUnobscuredFocus = async ( page: import('@playwright/test').Page, ): Promise => { await expect .poll(() => page.evaluate(() => { const active = document.activeElement; if (!(active instanceof HTMLElement)) return false; const rect = active.getBoundingClientRect(); const left = Math.max(0, rect.left); const right = Math.min(window.innerWidth, rect.right); const top = Math.max(0, rect.top); const bottom = Math.min(window.innerHeight, rect.bottom); if (right <= left || bottom <= top) return false; const x = left + (right - left) / 2; const y = top + (bottom - top) / 2; const topElement = document.elementFromPoint(x, y); const style = getComputedStyle(active); return ( topElement !== null && (active === topElement || active.contains(topElement) || topElement.contains(active)) && style.outlineStyle !== 'none' && Number.parseFloat(style.outlineWidth) >= 2 ); }), ) .toBe(true); }; test('tabs from the skip link through navigation and every contact action', async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); const expectedTargets = [ { selector: '.skip-link', href: '#portfolio-content' }, ...navHrefs.map((href) => ({ selector: `nav a[href="${href}"]`, href })), { selector: '#contact a[href^="mailto:"]', href: 'mailto:luca.pislaru@gmail.com', }, { selector: '#contact a[href="https://github.com/LucaPisl"]', href: 'https://github.com/LucaPisl', }, { selector: '#contact a[href="https://gitlab.com/luca_pisl"]', href: 'https://gitlab.com/luca_pisl', }, { selector: '#contact a[href="https://linkedin.com/in/luca-pisl"]', href: 'https://linkedin.com/in/luca-pisl', }, ]; for (const target of expectedTargets) { await page.keyboard.press('Tab'); const locator = page.locator(target.selector); await expect(locator).toBeFocused(); await expect(locator).toHaveAttribute('href', target.href); await assertVisibleUnobscuredFocus(page); } }); test('explicit section activation scrolls to and focuses its heading', async ({ page, distUrl, }, testInfo) => { test.skip( testInfo.project.name === 'mobile-chromium', 'The full keyboard path already covers the mobile project.', ); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); for (let index = 0; index < 5; index += 1) await page.keyboard.press('Tab'); await expect(page.locator('nav a[href="#infrastructure"]')).toBeFocused(); await page.keyboard.press('Enter'); await expect(page).toHaveURL(`${distUrl}/#infrastructure`); await expect(page.locator('#infrastructure')).toBeInViewport(); await expect(page.locator('#infrastructure-heading')).toHaveAttribute( 'tabindex', '-1', ); await expect(page.locator('#infrastructure-heading')).toBeFocused(); await assertVisibleUnobscuredFocus(page); }); test('manual wheel and PageDown scrolling never move focus', async ({ page, distUrl, }, testInfo) => { test.skip( testInfo.project.name === 'mobile-chromium', 'The focus invariant is engine-level rather than viewport-specific.', ); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); const startLink = page.locator('nav a[href="#start"]'); await expect(startLink).toBeFocused(); await page.mouse.wheel(0, 900); await expect(startLink).toBeFocused(); await page.keyboard.press('PageDown'); await expect(startLink).toBeFocused(); }); test('direct fragments do not steal focus while reduced-motion navigation remains accessible', async ({ page, distUrl, }, testInfo) => { test.skip( testInfo.project.name === 'mobile-chromium', 'The full keyboard path already covers the mobile project.', ); await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto(`${distUrl}/#toolkit`, { waitUntil: 'domcontentloaded' }); await expect(page.locator('#toolkit-heading')).not.toBeFocused(); await page.locator('nav a[href="#contact"]').click(); await expect(page).toHaveURL(`${distUrl}/#contact`); await expect(page.locator('#contact-heading')).toBeFocused(); await expect(page.locator('#contact')).toBeInViewport(); });