Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 70x 70x 70x 70x 70x 77x 77x 77x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 77x 77x 77x 70x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 1x | import { DyE2E_Viewport_Interface } from '../../contracts/_models/interfaces/viewport.interface';
import { DyE2E_RouteDescriptor_Interface } from '../_models/route-descriptor.interface';
import { DyE2E_SpecEmission_Util } from './spec-emission.util';
/**
* MP-04 — per-route × per-viewport × per-theme spec-block emission.
*
* Minden block:
* 1. setViewportSize
* 2. (theme-class set)
* 3. page.goto
* 4. (optional) overflow-check
* 5. (optional) touch-target-check
* 6. (optional) screenshot
*/
export class DyE2E_ViewportEmission_Util {
static emitRouteViewportBlock(
route: DyE2E_RouteDescriptor_Interface,
viewport: DyE2E_Viewport_Interface,
theme: 'dark' | 'light',
opts: {
takeScreenshot: boolean;
checkOverflow: boolean;
checkTouchTargets: boolean;
touchTargetMinPx: number;
screenshotDir: string;
},
): string {
const title: string = `${route.key} × ${viewport.key} × ${theme}`;
const readyWait: string = route.readyLocator
? `await page.locator(${DyE2E_SpecEmission_Util.valueLiteral(route.readyLocator)}).first().waitFor({ state: 'visible' });`
: `await page.waitForLoadState('networkidle');`;
const themeApply: string = theme === 'dark'
? `await page.evaluate((): void => { document.documentElement.classList.add('dark'); });`
: `await page.evaluate((): void => { document.documentElement.classList.remove('dark'); });`;
const fullPath: string = route.search ? `${route.path}${route.search}` : route.path;
const overflowBlock: string = opts.checkOverflow ? `
const overflow = await page.evaluate((): { scrollWidth: number; innerWidth: number } => ({
scrollWidth: document.documentElement.scrollWidth,
innerWidth: window.innerWidth,
}));
expect(overflow.scrollWidth, 'horizontal overflow detected').toBeLessThanOrEqual(overflow.innerWidth);
` : '';
const touchTargetBlock: string = opts.checkTouchTargets && viewport.hasTouch ? `
const tooSmall = await page.evaluate((minPx: number): number => {
const tappable: NodeListOf<HTMLElement> = document.querySelectorAll('button, a, [role="button"], input[type="checkbox"], input[type="radio"]');
let undersize: number = 0;
tappable.forEach((el: HTMLElement): void => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0 && (rect.width < minPx || rect.height < minPx)) {
undersize++;
}
});
return undersize;
}, ${opts.touchTargetMinPx});
expect(tooSmall, 'tappable elements smaller than ${opts.touchTargetMinPx}px').toBe(0);
` : '';
const screenshotBlock: string = opts.takeScreenshot ? `
await page.screenshot({ path: ${DyE2E_SpecEmission_Util.valueLiteral(`${opts.screenshotDir}/${route.key}/${viewport.key}-${theme}.png`)}, fullPage: true });
` : '';
const body: string = `await page.setViewportSize({ width: ${viewport.width}, height: ${viewport.height} });
const response = await page.goto(${DyE2E_SpecEmission_Util.valueLiteral(fullPath)});
expect(response?.ok()).toBeTruthy();
${themeApply}
${readyWait}${overflowBlock}${touchTargetBlock}${screenshotBlock}`;
return `test('${DyE2E_SpecEmission_Util.escapeString(title)}', async ({ page }): Promise<void> => {
${DyE2E_SpecEmission_Util.indent(body, 1)}
});`;
}
}
|