91 lines
7.0 KiB
TypeScript
91 lines
7.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { previewDocumentHtml } from "../src/cvPreview";
|
|
|
|
const original = path.resolve("../docs/redesign/phase-5/p5-09-readiness/evidence/fidelity");
|
|
|
|
test("[PRES-C09 C12] original flowing/boundary fixtures retain native PDF page membership", async ({ page }) => {
|
|
const baseline = JSON.parse(await fs.readFile(path.join(original, "fidelity-results.json"), "utf8"));
|
|
for (const fixture of baseline.fixtures.filter((fixture: { pass: boolean }) => !fixture.pass)) {
|
|
await page.setContent(previewDocumentHtml(await fs.readFile(path.join(original, fixture.fixture, "render.html"), "utf8"), 1122.52));
|
|
await expect.poll(() => page.evaluate(() => document.documentElement.dataset.cvPreviewReady)).toBe("true");
|
|
expect(await page.evaluate(() => Number(document.documentElement.dataset.cvPreviewPages)), fixture.fixture).toBe(fixture.pdfPages);
|
|
const membership = await page.evaluate(() => {
|
|
const width = Number(document.documentElement.dataset.cvPreviewWidth);
|
|
const walker = document.createTreeWalker(document.querySelector(".page")!, NodeFilter.SHOW_TEXT);
|
|
const markers: { token: string; page: number }[] = [];
|
|
let node;
|
|
while ((node = walker.nextNode())) for (const match of (node.textContent || "").matchAll(/\b[SEBPR]\d{2}\b/g)) {
|
|
const range = document.createRange();
|
|
range.setStart(node, match.index!); range.setEnd(node, match.index! + match[0].length);
|
|
markers.push({ token: match[0], page: Math.floor(range.getClientRects()[0].x / width) + 1 });
|
|
}
|
|
return markers;
|
|
});
|
|
expect(membership, fixture.fixture).toEqual(fixture.comparisons.map((marker: { token: string; pdfPage: number }) => ({ token: marker.token, page: marker.pdfPage })));
|
|
expect(await page.evaluate(() => document.body.innerText)).not.toContain("HIDDEN-MARKER");
|
|
}
|
|
});
|
|
|
|
test("[PRES-C06 C09] pagination waits for image layout and publishes one settled measurement", async ({ page }) => {
|
|
let releaseImage!: () => void;
|
|
const gate = new Promise<void>(resolve => { releaseImage = resolve; });
|
|
await page.route("**/pagination-photo.svg", async route => {
|
|
await gate;
|
|
await route.fulfill({ contentType: "image/svg+xml", body: '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100"><rect width="200" height="100" fill="navy"/></svg>' });
|
|
});
|
|
const html = '<html><head><style>:root{--cv-page-margin:16mm}body{margin:0}.page{width:210mm;min-height:297mm}@media print{.page{width:auto;min-height:0}}</style></head><body><div class="page"><img alt="Synthetic fixture" src="http://localhost:3300/pagination-photo.svg" style="width:20mm"></div></body></html>';
|
|
await page.setContent(previewDocumentHtml(html, 1122.52), { waitUntil: "domcontentloaded" });
|
|
expect(await page.evaluate(() => document.documentElement.dataset.cvPreviewReady)).toBeUndefined();
|
|
await page.evaluate(() => {
|
|
document.documentElement.dataset.readyEvents = "0";
|
|
window.addEventListener("cv-preview-ready", () => { document.documentElement.dataset.readyEvents = String(Number(document.documentElement.dataset.readyEvents) + 1); });
|
|
});
|
|
releaseImage();
|
|
await expect.poll(() => page.evaluate(() => document.documentElement.dataset.cvPreviewReady)).toBe("true");
|
|
expect(await page.evaluate(() => ({ events: document.documentElement.dataset.readyEvents, pages: document.documentElement.dataset.cvPreviewPages }))).toEqual({ events: "1", pages: "1" });
|
|
expect(await page.evaluate(() => document.querySelector("img")!.getBoundingClientRect().height)).toBeCloseTo(10 * 96 / 25.4, 1);
|
|
await page.evaluate(() => new Promise<void>(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
|
|
expect(await page.evaluate(() => document.documentElement.dataset.readyEvents)).toBe("1");
|
|
});
|
|
|
|
for (const fixture of [{ name: "default-modern", pages: 1 }, { name: "long-en-minimal", pages: 4 }]) {
|
|
test(`[PRES-C06 C09] initially hidden ${fixture.name} preview waits for physical layout`, async ({ page }, info) => {
|
|
const html = await fs.readFile(path.join(original, fixture.name, "render.html"), "utf8");
|
|
await page.setContent('<div id="preview-pane" style="display:none"><iframe title="Fixture CV" style="width:210mm;height:297mm;border:0"></iframe></div>');
|
|
await page.locator("iframe").evaluate((frame, documentHtml) => { (frame as HTMLIFrameElement).srcdoc = documentHtml; }, previewDocumentHtml(html, 1122.52));
|
|
const documentPage = page.frameLocator("iframe").locator(".page");
|
|
await expect(documentPage).toBeAttached();
|
|
const before = await documentPage.evaluate(async () => {
|
|
await document.fonts.ready;
|
|
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
document.documentElement.dataset.readyEvents = "0";
|
|
window.addEventListener("cv-preview-ready", () => { document.documentElement.dataset.readyEvents = String(Number(document.documentElement.dataset.readyEvents) + 1); });
|
|
return { width: document.querySelector(".page")!.getBoundingClientRect().width, ready: document.documentElement.dataset.cvPreviewReady };
|
|
});
|
|
expect(before.width).toBe(0);
|
|
expect(before.ready, "a hidden document must not publish a settled zero-width layout").toBeUndefined();
|
|
await page.locator("#preview-pane").evaluate(element => { (element as HTMLElement).style.display = "block"; });
|
|
await expect.poll(() => documentPage.evaluate(() => document.documentElement.dataset.cvPreviewReady)).toBe("true");
|
|
const after = await documentPage.evaluate(() => ({
|
|
width: Number(document.documentElement.dataset.cvPreviewWidth),
|
|
flowWidth: document.querySelector("[data-cv-preview-flow]")!.getBoundingClientRect().width,
|
|
pages: Number(document.documentElement.dataset.cvPreviewPages),
|
|
events: document.documentElement.dataset.readyEvents,
|
|
durationMs: Number(document.documentElement.dataset.cvPreviewDurationMs),
|
|
}));
|
|
expect(after.width).toBeCloseTo(210 * 96 / 25.4, 1);
|
|
expect(after.flowWidth).toBeCloseTo(210 * 96 / 25.4, 1);
|
|
expect(after.pages).toBe(fixture.pages);
|
|
expect(after.events).toBe("1");
|
|
await page.locator("#preview-pane").evaluate(element => { (element as HTMLElement).style.display = "none"; });
|
|
await page.setViewportSize({ width: 844, height: 390 });
|
|
await page.locator("#preview-pane").evaluate(element => { (element as HTMLElement).style.display = "block"; element.querySelector("iframe")!.style.transform = "scale(.45)"; });
|
|
await documentPage.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
|
expect(await documentPage.evaluate(() => ({ width: Number(document.documentElement.dataset.cvPreviewWidth), pages: Number(document.documentElement.dataset.cvPreviewPages), events: document.documentElement.dataset.readyEvents })))
|
|
.toEqual({ width: after.width, pages: fixture.pages, events: "1" });
|
|
await info.attach("initially-hidden-preview", { body: JSON.stringify({ fixture: fixture.name, before, after }), contentType: "application/json" });
|
|
});
|
|
}
|