Add the approved name animation, rounded docking navigation, localized keyboard focus cue, unified builds card, and destination copy hierarchy.
Introduce lightweight one-shot meteors and a sun ring that repeats every three seconds at Contact. Open the email action in a separate browsing context. Preserve reduced-motion behavior and keep private handoff and visual-check artifacts out of version control.
Validation: 649 tests passed; all 20 failures in world-models and SceneCanvas reproduce on baseline 103427f. Typecheck, artifact verification, and transfer budgets passed. Client/SSR preview build passed before this commit. Existing baseline test failures remain unchanged.
135 lines
8.1 KiB
TypeScript
135 lines
8.1 KiB
TypeScript
import AxeBuilder from '@axe-core/playwright';
|
|
import { expect, test } from '../helpers/serve-dist';
|
|
|
|
test('builds heading shares the first project surface and contact keeps a clear copy hierarchy', async ({ page, distUrl }) => {
|
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
await page.goto(distUrl);
|
|
const joined = await page.locator('#selected-builds-heading').evaluate(heading => {
|
|
const surface = heading.closest('.project-stage__lead');
|
|
return Boolean(surface?.querySelector('.project-case-study') &&
|
|
getComputedStyle(surface).backgroundColor !== 'rgba(0, 0, 0, 0)');
|
|
});
|
|
expect(joined).toBe(true);
|
|
const hierarchy = await page.locator('#contact').evaluate(section => {
|
|
const lead = section.querySelector('.contact-destination__copy');
|
|
const availability = section.querySelector('.contact-destination__availability');
|
|
return Boolean(lead && availability &&
|
|
parseFloat(getComputedStyle(lead).fontSize) > parseFloat(getComputedStyle(availability).fontSize));
|
|
});
|
|
expect(hierarchy).toBe(true);
|
|
});
|
|
|
|
test('the live reading mask measures the inset builds heading', async ({ page, distUrl }) => {
|
|
await page.goto(distUrl);
|
|
await expect(page.locator('html')).toHaveAttribute('data-expressway', 'live');
|
|
const geometry = await page.locator('#selected-builds').evaluate(section => ({
|
|
offset: parseFloat((section as HTMLElement).style.getPropertyValue('--chapter-heading-offset')) || 0,
|
|
actual: section.querySelector('h2')!.getBoundingClientRect().top - section.getBoundingClientRect().top,
|
|
}));
|
|
expect(geometry.offset).toBeGreaterThan(20);
|
|
expect(geometry.offset).toBeCloseTo(geometry.actual, 1);
|
|
const disclosure = page.locator('.site-navigation summary');
|
|
if (await disclosure.isVisible()) await disclosure.click();
|
|
await page.locator('.site-navigation a[href="#selected-builds"]').click();
|
|
await expect.poll(() => page.locator('#selected-builds-heading').evaluate(heading =>
|
|
Number(getComputedStyle(heading).opacity))).toBe(1);
|
|
});
|
|
|
|
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('.journey-world')).toHaveAttribute('data-arrived', 'true');
|
|
await expect(page.locator('.journey-world')).toHaveAttribute('data-static-fallback', 'true');
|
|
await expect(page.locator('.journey-canvas')).toHaveCount(0);
|
|
expect(await page.locator('.journey-poster').evaluate(poster => getComputedStyle(poster).filter)).not.toBe('none');
|
|
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([]);
|
|
});
|