80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test';
|
|
import { APP_LOCALES } from '../src/app/core/i18n/locale';
|
|
import { pagePath } from './helpers';
|
|
|
|
const VIEWPORTS = [768, 820, 1024, 1280, 1440] as const;
|
|
const VIEWPORT_HEIGHT = 900;
|
|
const MAX_HEADER_HEIGHT = 200;
|
|
|
|
async function waitForScrollSettle(page: Page): Promise<void> {
|
|
await page.evaluate(async () => {
|
|
await new Promise<void>((resolve) => {
|
|
let last = window.scrollY;
|
|
let stableFrames = 0;
|
|
const tick = () => {
|
|
if (window.scrollY === last) {
|
|
stableFrames += 1;
|
|
if (stableFrames >= 8) {
|
|
resolve();
|
|
return;
|
|
}
|
|
} else {
|
|
stableFrames = 0;
|
|
last = window.scrollY;
|
|
}
|
|
requestAnimationFrame(tick);
|
|
};
|
|
requestAnimationFrame(tick);
|
|
});
|
|
});
|
|
}
|
|
|
|
test.describe('fragment scrolling', () => {
|
|
test('case headings stay fully visible under the compact header', async ({ page }, testInfo) => {
|
|
test.skip(
|
|
testInfo.project.name === 'mobile',
|
|
'desktop widths are measured here to avoid a duplicate run',
|
|
);
|
|
|
|
for (const width of VIEWPORTS) {
|
|
await page.setViewportSize({ width, height: VIEWPORT_HEIGHT });
|
|
|
|
for (const locale of APP_LOCALES) {
|
|
const path = `${pagePath('projects', locale)}#myspa`;
|
|
const label = `${width}px ${locale}`;
|
|
await page.goto(path, { waitUntil: 'networkidle' });
|
|
|
|
const header = page.locator('.site-header');
|
|
const heading = page.locator('#myspa h2');
|
|
await expect(heading).toBeVisible();
|
|
|
|
await waitForScrollSettle(page);
|
|
|
|
const headerBox = await header.boundingBox();
|
|
expect(headerBox, label).toBeTruthy();
|
|
expect(
|
|
headerBox!.height,
|
|
`header height at ${label} must be at most ${MAX_HEADER_HEIGHT}px`,
|
|
).toBeLessThanOrEqual(MAX_HEADER_HEIGHT);
|
|
expect(
|
|
headerBox!.height,
|
|
`header height at ${label} must be at most 25% of the viewport`,
|
|
).toBeLessThanOrEqual(VIEWPORT_HEIGHT * 0.25);
|
|
|
|
await expect(header).not.toHaveCSS('position', 'sticky');
|
|
await expect(heading).toBeInViewport({ ratio: 1 });
|
|
|
|
const headingOwnsCentre = await heading.evaluate((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
const node = document.elementFromPoint(
|
|
rect.left + rect.width / 2,
|
|
rect.top + rect.height / 2,
|
|
);
|
|
return node !== null && (element === node || element.contains(node));
|
|
});
|
|
expect(headingOwnsCentre, `heading centre must not be covered at ${label}`).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
});
|