152 lines
3.6 KiB
TypeScript
152 lines
3.6 KiB
TypeScript
/**
|
|
* Viewport profile helpers for responsive testing
|
|
*/
|
|
|
|
export type ViewportSize = "mobile" | "tablet" | "desktop" | "widescreen";
|
|
|
|
export interface ViewportDimensions {
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
export const VIEWPORTS: Record<ViewportSize, ViewportDimensions> = {
|
|
mobile: { width: 375, height: 667 }, // iPhone SE / similar
|
|
tablet: { width: 768, height: 1024 }, // iPad / similar
|
|
desktop: { width: 1280, height: 720 }, // Standard desktop
|
|
widescreen: { width: 1920, height: 1080 }, // Large desktop
|
|
} as const;
|
|
|
|
export const VIEWPORT_SIZES: ViewportSize[] = [
|
|
"mobile",
|
|
"tablet",
|
|
"desktop",
|
|
"widescreen",
|
|
];
|
|
|
|
/**
|
|
* Breakpoint definitions matching CSS media queries
|
|
*/
|
|
export const BREAKPOINTS = {
|
|
sm: 640, // Small devices
|
|
md: 768, // Medium devices (tablet)
|
|
lg: 1024, // Large devices (desktop)
|
|
xl: 1280, // Extra large
|
|
"2xl": 1536, // 2X large
|
|
} as const;
|
|
|
|
/**
|
|
* Set viewport size
|
|
*/
|
|
export async function setViewport(
|
|
page: any,
|
|
size: ViewportSize,
|
|
): Promise<void> {
|
|
const dimensions = VIEWPORTS[size];
|
|
await page.setViewportSize(dimensions);
|
|
}
|
|
|
|
/**
|
|
* Get current viewport size
|
|
*/
|
|
export async function getViewport(page: any): Promise<ViewportDimensions> {
|
|
return page.evaluate(() => ({
|
|
width: window.innerWidth,
|
|
height: window.innerHeight,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Assert viewport is at expected size
|
|
*/
|
|
export async function assertViewport(
|
|
page: any,
|
|
size: ViewportSize,
|
|
): Promise<void> {
|
|
const expected = VIEWPORTS[size];
|
|
const actual = await getViewport(page);
|
|
|
|
if (actual.width !== expected.width || actual.height !== expected.height) {
|
|
throw new Error(
|
|
`Expected viewport ${size} (${expected.width}x${expected.height}) ` +
|
|
`but got ${actual.width}x${actual.height}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if element has horizontal overflow
|
|
*/
|
|
export async function hasHorizontalOverflow(
|
|
page: any,
|
|
selector: string,
|
|
): Promise<boolean> {
|
|
return page.evaluate((sel: string) => {
|
|
const element = document.querySelector(sel);
|
|
if (!element) return false;
|
|
return element.scrollWidth > element.clientWidth;
|
|
}, selector);
|
|
}
|
|
|
|
/**
|
|
* Check if element is clipped (overflow hidden with content exceeding bounds)
|
|
*/
|
|
export async function isClipped(page: any, selector: string): Promise<boolean> {
|
|
return page.evaluate((sel: string) => {
|
|
const element = document.querySelector(sel);
|
|
if (!element) return false;
|
|
|
|
const style = window.getComputedStyle(element);
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
// Check if element has overflow hidden and children exceed bounds
|
|
const children = element.children;
|
|
for (const child of children) {
|
|
const childRect = child.getBoundingClientRect();
|
|
if (childRect.right > rect.right || childRect.bottom > rect.bottom) {
|
|
return (
|
|
style.overflow === "hidden" ||
|
|
style.overflowX === "hidden" ||
|
|
style.overflowY === "hidden"
|
|
);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}, selector);
|
|
}
|
|
|
|
/**
|
|
* Run a test across all viewport sizes
|
|
*/
|
|
export function testAllViewports(
|
|
name: string,
|
|
testFn: (size: ViewportSize) => Promise<void> | void,
|
|
): void {
|
|
for (const size of VIEWPORT_SIZES) {
|
|
test(`${name} - ${size}`, async () => {
|
|
await testFn(size);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check sticky element position
|
|
*/
|
|
export async function getStickyPosition(
|
|
page: any,
|
|
selector: string,
|
|
): Promise<{ top: number; isSticky: boolean }> {
|
|
return page.evaluate((sel: string) => {
|
|
const element = document.querySelector(sel);
|
|
if (!element) return { top: 0, isSticky: false };
|
|
|
|
const style = window.getComputedStyle(element);
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
return {
|
|
top: rect.top,
|
|
isSticky: style.position === "sticky" || style.position === "fixed",
|
|
};
|
|
}, selector);
|
|
}
|