Files
Portfolio/tests/browser/semantic.spec.ts

220 lines
7.2 KiB
TypeScript

import { request as httpRequest } from 'node:http';
import { symlink, unlink } from 'node:fs/promises';
import { resolve } from 'node:path';
import { expect, test } from '../helpers/serve-dist';
const sectionIds = [
'start',
'profile',
'experience',
'infrastructure',
'selected-builds',
'additional-work',
'toolkit',
'contact',
] as const;
const sectionHeadings = [
'Profile',
'Experience',
'Infrastructure',
'Selected builds',
'Additional work',
'Toolkit',
'Contact',
] as const;
const rawStatus = async (
origin: string,
path: string,
method = 'GET',
): Promise<number | undefined> =>
new Promise((resolve, reject) => {
const url = new URL(origin);
const request = httpRequest(
{
host: url.hostname,
method,
path,
port: url.port,
},
(response) => {
response.resume();
response.once('end', () => resolve(response.statusCode));
},
);
request.once('error', reject);
request.end();
});
test('serves only the owned generated preview surface', async ({
distUrl,
request,
}, testInfo) => {
const document = await request.get(`${distUrl}/`);
expect(document.status()).toBe(200);
expect(document.headers()['content-type']).toBe('text/html; charset=utf-8');
const unsafeMethod = await request.post(`${distUrl}/index.html`);
expect(unsafeMethod.status()).toBe(405);
expect(unsafeMethod.headers().allow).toBe('GET, HEAD');
expect(await rawStatus(distUrl, '/%2e%2e/package.json')).toBe(403);
expect(
(await request.get(`${distUrl}/not-a-preview-resource`)).status(),
).toBe(404);
const linkName = `.task-7-escape-${process.pid}-${testInfo.workerIndex}`;
const linkPath = resolve(process.cwd(), 'dist', linkName);
await symlink(resolve(process.cwd(), 'package.json'), linkPath);
try {
expect((await request.get(`${distUrl}/${linkName}`)).status()).toBe(403);
} finally {
await unlink(linkPath);
}
});
test('exposes the complete semantic portfolio from the generated document', async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
await expect(page.locator('h1:visible')).toHaveCount(1);
await expect(page.locator('#start h1')).toHaveText('Luca Pîslaru');
await expect(page.locator('.hero-journey__role')).toHaveText(
'System Administrator & Full-Stack Developer',
);
await expect(page.locator('main')).toHaveCount(1);
await expect(
page.locator('nav[aria-label="Portfolio sections"]'),
).toHaveCount(1);
await expect(page.locator('footer')).toHaveCount(1);
expect(
await page
.locator('main > section')
.evaluateAll((sections) => sections.map((section) => section.id)),
).toEqual(sectionIds);
expect(await page.locator('main > section > h2').allTextContents()).toEqual(
sectionHeadings,
);
await expect(page.locator('.route-label')).toHaveCount(0);
await expect(page.getByRole('link', { name: 'Explore work' })).toHaveAttribute('href', '#selected-builds');
await expect(page.getByRole('link', { name: 'Email Luca' })).toHaveCount(2);
await expect(page.locator('[data-featured-operation]')).toContainText('without data loss');
expect(await page.locator('[data-testid="toolkit-technology"]', { hasText: 'DNS' }).locator('span').evaluate(
(label) => getComputedStyle(label).gridColumn,
)).toBe('1 / -1');
expect(await page.locator('.hero-journey').evaluate((hero) => {
const content = hero.querySelector('.hero-journey__content')!;
const heroStyle = getComputedStyle(hero);
const contentStyle = getComputedStyle(content);
return {
preservesPadding: Math.abs(Number.parseFloat(heroStyle.paddingTop) - innerHeight * 0.12) < 0.01,
preservesWidth: Math.abs(Number.parseFloat(contentStyle.width) - Math.min(544, innerWidth * 0.39)) < 0.1,
position: contentStyle.position,
zIndex: contentStyle.zIndex,
};
})).toEqual({ preservesPadding: true, preservesWidth: true, position: 'static', zIndex: 'auto' });
expect(
await page.locator('h1, h2, h3').evaluateAll((headings) => {
const levels = headings.map((heading) =>
Number(heading.tagName.slice(1)),
);
return levels.every(
(level, index) => index === 0 || level <= levels[index - 1]! + 1,
);
}),
).toBe(true);
});
test('loads the self-hosted Geist families for information and route text', async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
await page.evaluate(() => document.fonts.ready);
const fonts = await page.evaluate(() => ({
sansLoaded: document.fonts.check('16px "Geist Variable"'),
monoLoaded: document.fonts.check('16px "Geist Mono Variable"'),
headingFamily: getComputedStyle(document.querySelector('h1')!).fontFamily,
routeFamily: getComputedStyle(document.querySelector('.section-eyebrow')!).fontFamily,
}));
expect(fonts).toMatchObject({ sansLoaded: true, monoLoaded: true });
expect(fonts.headingFamily).toContain('Geist Variable');
expect(fonts.routeFamily).toContain('Geist Mono Variable');
});
test('keeps canonical native anchors and safe external destinations', async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
const navigation = page.locator('nav[aria-label="Portfolio sections"] a');
expect(
await navigation.evaluateAll((links) =>
links.map((link) => link.getAttribute('href')),
),
).toEqual(sectionIds.map((id) => `#${id}`));
for (const id of sectionIds) {
await expect(page.locator(`#${id}`)).toHaveCount(1);
await expect(page.locator(`#${id}-heading`)).toHaveCount(1);
}
const email = page.locator('a[href^="mailto:"]');
await expect(email).toHaveCount(2);
await expect(email).toHaveText(['Email Luca', 'Email Luca']);
expect(await email.evaluateAll((links) => [...new Set(links.map((link) => link.getAttribute('href')))])).toEqual(
['mailto:luca.pislaru@gmail.com'],
);
const externalLinks = page.locator('#contact a[target="_blank"]');
await expect(externalLinks).toHaveCount(3);
expect(
await externalLinks.evaluateAll((links) =>
links.every((link) => {
const rel = new Set((link.getAttribute('rel') ?? '').split(/\s+/u));
return (
link instanceof HTMLAnchorElement &&
link.protocol === 'https:' &&
rel.has('me') &&
rel.has('noopener') &&
rel.has('noreferrer')
);
}),
),
).toBe(true);
});
test('never links private projects or packages a résumé download', async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
for (const title of [
'Private self-hosted medication tracker',
'Private healthcare platform',
'Private game prototype',
]) {
await expect(
page.locator('article').filter({ hasText: title }).locator('a'),
).toHaveCount(0);
}
await expect(page.locator('a[download]')).toHaveCount(0);
expect(
await page
.locator('a')
.evaluateAll((links) =>
links.some(
(link) =>
/(?:résumé|resume|\bcv\b)/iu.test(link.textContent ?? '') ||
/\.(?:pdf|docx?)(?:$|[?#])/iu.test(link.getAttribute('href') ?? ''),
),
),
).toBe(false);
});