integration: compact the wide primary navigation and keep fragment targets visible
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,47 +1,79 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
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 clear the sticky header at desktop width', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name === 'mobile', 'sticky header applies from md up');
|
||||
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',
|
||||
);
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
for (const width of VIEWPORTS) {
|
||||
await page.setViewportSize({ width, height: VIEWPORT_HEIGHT });
|
||||
|
||||
for (const locale of APP_LOCALES) {
|
||||
const path = `${pagePath('projects', locale)}#myspa`;
|
||||
await page.goto(path, { waitUntil: 'networkidle' });
|
||||
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();
|
||||
const header = page.locator('.site-header');
|
||||
const heading = page.locator('#myspa h2');
|
||||
await expect(heading).toBeVisible();
|
||||
|
||||
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);
|
||||
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));
|
||||
});
|
||||
});
|
||||
|
||||
const headerBox = await header.boundingBox();
|
||||
const headingBox = await heading.boundingBox();
|
||||
expect(headerBox, path).toBeTruthy();
|
||||
expect(headingBox, path).toBeTruthy();
|
||||
expect(headingBox!.y, path).toBeGreaterThanOrEqual(headerBox!.y + headerBox!.height - 1);
|
||||
expect(headingOwnsCentre, `heading centre must not be covered at ${label}`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user