From 24c98e160fc28b5a092fee7dd37c1fd526293bf7 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 10 Oct 2026 21:45:25 +0200 Subject: [PATCH] test(cv): reproduce initially hidden preview pagination readiness --- .../p5-15/p5-15-04/mobile-preview/README.md | 11 ++++++ job-tracker-ui/e2e/cv-pagination.spec.ts | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/redesign/phase-5/p5-15/p5-15-04/mobile-preview/README.md diff --git a/docs/redesign/phase-5/p5-15/p5-15-04/mobile-preview/README.md b/docs/redesign/phase-5/p5-15/p5-15-04/mobile-preview/README.md new file mode 100644 index 0000000..e43aa26 --- /dev/null +++ b/docs/redesign/phase-5/p5-15/p5-15-04/mobile-preview/README.md @@ -0,0 +1,11 @@ +# P5-15-04-CV-01 — Initially hidden preview lifecycle + +## Reproduction checkpoint + +The unchanged owned mobile/PDF audit fails again: the hidden measurement iframe has physical A4 width, but the initially hidden visible-page iframe publishes ready with a zero-width content flow. Preview and native PDF both have one page; the demonstrated defect is clipping/incorrect placement, not a page-count mismatch. The native PDF export remains correct. + +Two additional native Chromium regressions in `e2e/cv-pagination.spec.ts` reproduce the lifecycle independently of the application server, using retained deterministic renderer HTML: `default-modern` (one page) and `long-en-minimal` (four pages). Both fail before correction because the hidden document has width zero but `cvPreviewReady=true`. They also require physical width and exact page count after revealing, with no second ready event after hide/show, orientation and scale changes. + +The injected one-shot script measures `.page` before its display:none ancestor becomes visible, constructs permanent zero-width column flow and announces a settled layout. Fonts/images readiness does not establish usable geometry. Correction must await actual layout without altering PDF, page dimensions, print spacing, fonts or existing fidelity tolerances. + +PRES-C06 is FAIL at this reproduction checkpoint; C09/C12 retain independently passing PDF evidence and incomplete acceptance. This record makes no repair/full-suite claim. Screen-reader, real-device and production font/photo qualifications remain incomplete. diff --git a/job-tracker-ui/e2e/cv-pagination.spec.ts b/job-tracker-ui/e2e/cv-pagination.spec.ts index 292fb31..aab004f 100644 --- a/job-tracker-ui/e2e/cv-pagination.spec.ts +++ b/job-tracker-ui/e2e/cv-pagination.spec.ts @@ -49,3 +49,42 @@ test("[PRES-C06 C09] pagination waits for image layout and publishes one settled await page.evaluate(() => new Promise(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(''); + 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" }); + }); +}