import AxeBuilder from '@axe-core/playwright'; import { expect, test } from '../helpers/serve-dist'; test('start navigation, contact identity, and toolkit icons use the approved content', async ({ page, distUrl }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); const response = await page.goto(distUrl); const html = await response!.text(); expect(html).not.toContain('luca.pislaru@gmail.com'); expect(html).not.toContain('mailto:'); await expect(page.locator('.contact-email')).toHaveAttribute('data-enhanced', 'true'); await page.locator('.hero-journey__action--primary').click(); await expect(page).toHaveURL(/#contact$/); await expect(page.locator('#contact')).toBeInViewport(); await expect(page.locator('.contact-avatar')).toBeVisible(); await expect(page.locator('.contact-social-link')).toHaveText(['LinkedIn', 'GitHub', 'GitLab']); const missing = await page.locator('[data-testid="toolkit-technology"]').evaluateAll(items => items.filter(item => !item.querySelector('svg')).map(item => item.textContent)); expect(missing).toEqual([]); for (const name of ['SPSS', 'SoccerNet', 'Microsoft Excel', 'Btrfs', 'rsync']) { await expect(page.locator('#toolkit')).not.toContainText(name); } }); test('email is revealed on focus, with an explicit disclosure and copy fallback', async ({ page, distUrl }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto(`${distUrl}/#contact`); const email = page.getByRole('link', { name: 'Email Me', exact: true }); await expect(email).toHaveAttribute('href', '#contact-email-details'); await expect(page.locator('.contact-email__preview')).not.toBeVisible(); await email.focus(); await expect(email).toHaveAttribute('href', 'mailto:luca.pislaru@gmail.com'); await page.locator('#contact summary').click(); await expect(page.locator('.contact-email__address')).toHaveText('luca.pislaru@gmail.com'); await page.getByRole('button', { name: 'Copy email' }).click(); await expect(page.locator('.contact-email__status')).toHaveText(/Copied|Select the address to copy it\./); await page.keyboard.press('Escape'); await expect(page.locator('#contact summary')).toBeFocused(); await expect(page.locator('#contact-email-details')).not.toHaveAttribute('open'); }); test('contact fits desktop and stays aligned and usable across narrow widths', async ({ page, distUrl }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'Viewport matrix is run once.'); await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto(`${distUrl}/#contact`); for (const [width, height] of [[1440, 900], [1280, 720], [768, 1024], [390, 844], [320, 740], [844, 390]]) { await page.setViewportSize({ width: width!, height: height! }); await page.locator('#contact').scrollIntoViewIfNeeded(); const geometry = await page.locator('#contact').evaluate(section => { const box = section.getBoundingClientRect(); const avatar = section.querySelector('.contact-avatar')!.getBoundingClientRect(); return { height: box.height, overflow: document.documentElement.scrollWidth > innerWidth, socialY: [...section.querySelectorAll('.contact-social-link')].map(el => el.getBoundingClientRect().y), avatarInside: avatar.right <= box.right && avatar.bottom < section.querySelector('.contact-destination__copy')!.getBoundingClientRect().top, }; }); expect(geometry.overflow, `${width}px overflow`).toBe(false); expect(Math.max(...geometry.socialY) - Math.min(...geometry.socialY), `${width}px social alignment`).toBeLessThan(1); expect(geometry.avatarInside, `${width}px avatar`).toBe(true); if (width! >= 1280) expect(geometry.height).toBeLessThan(height! - 140); await page.screenshot({ path: testInfo.outputPath(`contact-${width}x${height}.png`) }); } }); test('native email disclosure remains usable without JavaScript', async ({ browser, distUrl }) => { const context = await browser.newContext({ javaScriptEnabled: false, viewport: { width: 390, height: 844 } }); const page = await context.newPage(); await page.goto(`${distUrl}/#contact`); await expect(page.locator('.contact-email__address')).not.toBeVisible(); await page.locator('#contact summary').click(); await expect(page.locator('.contact-email__address')).toHaveText('luca.pislaru [at] gmail [dot] com'); await expect(page.locator('.video-fallback')).toHaveAttribute('href', '/assets/project-private-game-loop.mp4'); await context.close(); }); test('reduced motion keeps the gameplay still until Play and supports pause', async ({ page, distUrl }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto(distUrl); const figure = page.locator('.project-media--video'); await figure.scrollIntoViewIfNeeded(); await expect(figure.locator('source')).toHaveCount(0); await figure.getByRole('button', { name: 'Play video' }).click(); await expect(figure.getByRole('button', { name: 'Pause video' })).toBeVisible(); expect(await figure.locator('video').evaluate(element => { const video = element as HTMLVideoElement; return { muted: video.muted, loop: video.loop, paused: video.paused }; })).toEqual({ muted: true, loop: true, paused: false }); await figure.getByRole('button', { name: 'Pause video' }).click(); await expect(figure.getByRole('button', { name: 'Play video' })).toBeVisible(); await expect(figure.locator('source')).toHaveCount(0); }); test('contact and supporting media have no serious accessibility violations', async ({ page, distUrl }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto(`${distUrl}/#contact`); await page.locator('#contact summary').click(); const result = await new AxeBuilder({ page }).include('#contact').include('#additional-work').analyze(); expect(result.violations.filter(violation => ['serious', 'critical'].includes(violation.impact ?? ''))).toEqual([]); });