From f2769dce54ca6c3ee9c344eec195f036ba14645f Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sun, 6 Sep 2026 05:56:20 +0200 Subject: [PATCH] test(pdf): strengthen item pagination coverage --- .../remediation-issue-3350-item-pagination.md | 17 +++++++ .../templates/shared/item-pagination.test.tsx | 50 ++++++++++++++----- 2 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 .orchestration/remediation-issue-3350-item-pagination.md diff --git a/.orchestration/remediation-issue-3350-item-pagination.md b/.orchestration/remediation-issue-3350-item-pagination.md new file mode 100644 index 000000000..6ab351f6f --- /dev/null +++ b/.orchestration/remediation-issue-3350-item-pagination.md @@ -0,0 +1,17 @@ +# Issue 3350 remediation evidence + +## Findings addressed + +- `item-pagination.test.tsx` now derives complete numbered-token inventories for each generated fixture and asserts every token exactly once. Sampled token-to-physical-page placement checks remain separate. +- Pagination fixtures snapshot `metadata.layout.pages` before rendering and assert authored layout pages are unchanged afterward. Overflow fixtures also assert physical PDF page count exceeds authored page count. +- Unsafe `wrap={false}` renderer coverage remains diagnostic-only; no item controls, schema flags, or runtime behavior were added. + +## Verification + +- `rtk proxy pnpm --filter @reactive-resume/pdf exec vitest run src/semantic/pagination.test.tsx src/templates/shared/item-pagination.test.tsx` — 2 files, 11 tests passed. +- `rtk proxy pnpm --filter @reactive-resume/pdf typecheck` — passed. +- `rtk proxy pnpm exec biome check packages/pdf/src/templates/shared/item-pagination.test.tsx` — passed. +- `rtk proxy pnpm exec turbo boundaries` — passed; 1109 files checked. +- `rtk git diff --check origin/main...HEAD` — passed. + +Only PDF test coverage and this evidence file changed; production behavior remains untouched. diff --git a/packages/pdf/src/templates/shared/item-pagination.test.tsx b/packages/pdf/src/templates/shared/item-pagination.test.tsx index c10376491..4d428a359 100644 --- a/packages/pdf/src/templates/shared/item-pagination.test.tsx +++ b/packages/pdf/src/templates/shared/item-pagination.test.tsx @@ -59,8 +59,13 @@ const makeFixture = (items: ResumeData["sections"]["experience"]["items"]): Resu return data; }; +const numberedTokens = (prefix: string, count: number) => + Array.from({ length: count }, (_value, index) => `${prefix}_${String(index + 1).padStart(3, "0")}`); + const numberedParagraphs = (prefix: string, count: number) => - Array.from({ length: count }, (_value, index) => `

${prefix}_${String(index + 1).padStart(3, "0")}

`).join(""); + numberedTokens(prefix, count) + .map((token) => `

${token}

`) + .join(""); const expectTokensExactlyOnce = (pages: string[], tokens: string[]) => { const renderedText = pages.join(" "); @@ -75,9 +80,11 @@ describe("item pagination token matrix", () => { makeItem("boundary", numberedParagraphs("BOUNDARY", 26)), makeItem("fit", numberedParagraphs("FIT", 4)), ]), - tokens: ["FIT_001", "FIT_002", "FIT_003", "FIT_004"], + allTokens: [...numberedTokens("BOUNDARY", 26), ...numberedTokens("FIT", 4)], + sampledTokens: ["FIT_001", "FIT_002", "FIT_003", "FIT_004"], expectedPages: 1, expectedTokenPages: [0, 0, 0, 0], + expectsPhysicalOverflow: false, }, { name: "item fits a full page but not remaining space", @@ -85,16 +92,20 @@ describe("item pagination token matrix", () => { makeItem("boundary", numberedParagraphs("BOUNDARY", 48)), makeItem("full-page", numberedParagraphs("FULL_PAGE", 42)), ]), - tokens: ["FULL_PAGE_001", "FULL_PAGE_021", "FULL_PAGE_042"], + allTokens: [...numberedTokens("BOUNDARY", 48), ...numberedTokens("FULL_PAGE", 42)], + sampledTokens: ["FULL_PAGE_001", "FULL_PAGE_021", "FULL_PAGE_042"], expectedPages: 3, expectedTokenPages: [1, 1, 2], + expectsPhysicalOverflow: true, }, { name: "item taller than a page", data: makeFixture([makeItem("oversized", numberedParagraphs("OVERSIZED", 180))]), - tokens: ["OVERSIZED_001", "OVERSIZED_090", "OVERSIZED_180"], + allTokens: numberedTokens("OVERSIZED", 180), + sampledTokens: ["OVERSIZED_001", "OVERSIZED_090", "OVERSIZED_180"], expectedPages: 5, expectedTokenPages: [0, 2, 4], + expectsPhysicalOverflow: true, }, { name: "two-line paragraph near boundary", @@ -102,9 +113,11 @@ describe("item pagination token matrix", () => { makeItem("boundary", numberedParagraphs("BOUNDARY", 52)), makeItem("paragraph", "

PARAGRAPH_001 first line
PARAGRAPH_002 second line

"), ]), - tokens: ["PARAGRAPH_001", "PARAGRAPH_002"], + allTokens: [...numberedTokens("BOUNDARY", 52), "PARAGRAPH_001", "PARAGRAPH_002"], + sampledTokens: ["PARAGRAPH_001", "PARAGRAPH_002"], expectedPages: 2, expectedTokenPages: [1, 1], + expectsPhysicalOverflow: true, }, { name: "nested bullets", @@ -114,16 +127,24 @@ describe("item pagination token matrix", () => { "", ), ]), - tokens: ["NESTED_001", "NESTED_002", "NESTED_003"], + allTokens: ["NESTED_001", "NESTED_002", "NESTED_003"], + sampledTokens: ["NESTED_001", "NESTED_002", "NESTED_003"], expectedPages: 1, expectedTokenPages: [0, 0, 0], + expectsPhysicalOverflow: false, }, - ])("preserves every token exactly once: $name", async ({ data, tokens, expectedPages, expectedTokenPages }) => { - const pages = await readPhysicalPages(await parsePdf(await renderPdf(data))); - expect(pages).toHaveLength(expectedPages); - expect(tokens.map((token) => pages.findIndex((page) => page.includes(token)))).toEqual(expectedTokenPages); - expectTokensExactlyOnce(pages, tokens); - }); + ])( + "preserves every token exactly once: $name", + async ({ data, allTokens, sampledTokens, expectedPages, expectedTokenPages, expectsPhysicalOverflow }) => { + const authoredPagesBeforeRender = structuredClone(data.metadata.layout.pages); + const pages = await readPhysicalPages(await parsePdf(await renderPdf(data))); + expect(pages).toHaveLength(expectedPages); + expect(sampledTokens.map((token) => pages.findIndex((page) => page.includes(token)))).toEqual(expectedTokenPages); + expectTokensExactlyOnce(pages, allTokens); + expect(data.metadata.layout.pages).toEqual(authoredPagesBeforeRender); + if (expectsPhysicalOverflow) expect(pages.length).toBeGreaterThan(authoredPagesBeforeRender.length); + }, + ); it("preserves built-in and custom items across an Azurill sidebar and main-column overflow", async () => { const data = makeFixture([makeItem("builtin", numberedParagraphs("BUILTIN", 150))]); @@ -156,6 +177,7 @@ describe("item pagination token matrix", () => { ]; data.metadata.layout.pages[0]?.main.push("custom-experience"); + const authoredPagesBeforeRender = structuredClone(data.metadata.layout.pages); const pages = await readPhysicalPages(await parsePdf(await renderPdf(data, "azurill"))); expect(pages).toHaveLength(5); expect( @@ -163,7 +185,9 @@ describe("item pagination token matrix", () => { pages.findIndex((page) => page.includes(token)), ), ).toEqual([0, 3, 4, 4, 0]); - expectTokensExactlyOnce(pages, ["BUILTIN_001", "BUILTIN_150", "CUSTOM_001", "CUSTOM_030", "SIDEBAR_001"]); + expectTokensExactlyOnce(pages, [...numberedTokens("BUILTIN", 150), ...numberedTokens("CUSTOM", 30), "SIDEBAR_001"]); + expect(data.metadata.layout.pages).toEqual(authoredPagesBeforeRender); + expect(pages.length).toBeGreaterThan(authoredPagesBeforeRender.length); }); it("records unsafe renderer fallback for an oversized non-wrapping item", async () => {