Files
Portfolio/tests/browser/contact-refinements.spec.ts

97 lines
5.8 KiB
TypeScript

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