diff --git a/packages/pdf/src/templates/shared/date-layout.test.tsx b/packages/pdf/src/templates/shared/date-layout.test.tsx index 20d0004ce..3fa20b217 100644 --- a/packages/pdf/src/templates/shared/date-layout.test.tsx +++ b/packages/pdf/src/templates/shared/date-layout.test.tsx @@ -4,8 +4,9 @@ import type { Template } from "@reactive-resume/schema/templates"; import type { TextItem } from "pdfjs-dist/types/src/display/api"; import type { RasterizedPdfPage } from "../../semantic/test/rasterize-pdf"; import { createHash } from "node:crypto"; -import { mkdirSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { pdf, renderToBuffer } from "@react-pdf/renderer"; import { encode } from "fast-png"; @@ -57,6 +58,26 @@ const dateMarkers = [ type DateMarker = (typeof dateMarkers)[number]; +const expectedMissingMarkers = { + azurill: [], + bronzor: [], + chikorita: [], + ditgar: [], + ditto: [], + gengar: [], + glalie: [], + kakuna: [], + lapras: [], + leafish: [], + meowth: ["EXP_NO_PERIOD"], + onyx: [], + pikachu: [], + rhyhorn: [], + scizor: [], +} as const satisfies Record; + +const baselineDirectory = join(dirname(fileURLToPath(import.meta.url)), "../../../test-artifacts/date-layout"); + type PdfPageText = { pageNumber: number; items: TextItem[]; @@ -418,6 +439,17 @@ const coordinatesFor = (result: RenderedFixture, markers: readonly DateMarker[]) ]), ); +const sha256 = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex"); + +const fixtureEvidence = (result: RenderedFixture, coordinates: Record) => { + const rasterPngs = result.raster.map((page) => encode(page)); + return { + pages: result.pages.map((page) => ({ pageNumber: page.pageNumber, itemCount: page.items.length })), + rasterSha256: rasterPngs.map(sha256), + coordinates, + }; +}; + const requiredMarkers = (result: RenderedFixture) => { const text = textFor(result); for (const marker of dateMarkers) expect(text).toContain(marker); @@ -428,23 +460,22 @@ const writeArtifacts = (name: string, result: RenderedFixture, coordinates: Reco const output = process.env.DATE_LAYOUT_ARTIFACT_DIR; if (!output) return; mkdirSync(output, { recursive: true }); - writeFileSync(join(output, `${name}.pdf`), result.bytes); - writeFileSync( - join(output, `${name}.json`), - JSON.stringify( - { - pages: result.pages.map((page) => ({ pageNumber: page.pageNumber, itemCount: page.items.length })), - coordinates, - }, - null, - 2, - ), - ); + writeFileSync(join(output, `${name}.json`), `${JSON.stringify(fixtureEvidence(result, coordinates), null, "\t")}\n`); for (const [index, page] of result.raster.entries()) { writeFileSync(join(output, `${name}-page-${index + 1}.png`), encode(page)); } }; +const assertFixtureBaseline = (name: string, result: RenderedFixture, coordinates: Record): void => { + if (process.env.DATE_LAYOUT_ARTIFACT_DIR) return; + const evidence = fixtureEvidence(result, coordinates); + expect(JSON.parse(readFileSync(join(baselineDirectory, `${name}.json`), "utf8"))).toEqual(evidence); + for (const index of result.raster.keys()) { + const expectedPng = readFileSync(join(baselineDirectory, `${name}-page-${index + 1}.png`)); + expect(sha256(expectedPng)).toBe(evidence.rasterSha256[index]); + } +}; + type HostNode = { type: string; style?: unknown; @@ -508,6 +539,7 @@ describe("date layout characterization (#3155, #2841)", () => { } for (const marker of dateMarkers) expect(markerItems(result, marker), marker).toHaveLength(1); writeArtifacts(`${template}-${direction}`, result, coordinates); + assertFixtureBaseline(`${template}-${direction}`, result, coordinates); }); it("records default date evidence for every template without claiming parity geometry", async () => { @@ -515,10 +547,12 @@ describe("date layout characterization (#3155, #2841)", () => { for (const template of templates) { const result = await renderFixture(dateFixture(), template); const coordinates = coordinatesFor(result, dateMarkers); + const missingMarkers = dateMarkers.filter((marker) => markerItems(result, marker).length === 0); + expect(missingMarkers, template).toEqual(expectedMissingMarkers[template]); evidence[template] = { pageCount: result.pages.length, textItemCount: allItems(result).length, - missingMarkers: dateMarkers.filter((marker) => markerItems(result, marker).length === 0), + missingMarkers, rasterSha256: result.raster.map((page) => createHash("sha256").update(page.data).digest("hex")), coordinates, }; @@ -526,7 +560,9 @@ describe("date layout characterization (#3155, #2841)", () => { const output = process.env.DATE_LAYOUT_ARTIFACT_DIR; if (output) { mkdirSync(output, { recursive: true }); - writeFileSync(join(output, "all-templates.json"), JSON.stringify(evidence, null, 2)); + writeFileSync(join(output, "all-templates.json"), `${JSON.stringify(evidence, null, "\t")}\n`); + } else { + expect(JSON.parse(readFileSync(join(baselineDirectory, "all-templates.json"), "utf8"))).toEqual(evidence); } }); diff --git a/packages/pdf/test-artifacts/date-layout/README.md b/packages/pdf/test-artifacts/date-layout/README.md index a3fee1d48..34673859b 100644 --- a/packages/pdf/test-artifacts/date-layout/README.md +++ b/packages/pdf/test-artifacts/date-layout/README.md @@ -1,9 +1,15 @@ # Plan 24 Step 1 — date layout characterization -Revision under test: `66c25efe1` (`origin/main`), branch `codex/issue-3155-date-layout-characterization`. +Generation revision: `58ee4eead7843105da20f8a177e7d250b24c3a87` (pre-remediation characterization merge). +Comparison ref: `368858a56fc9c3152b540c39829908e2c3ea04c5` (`origin/main` at review). +Branch: `codex/issue-3155-date-layout-characterization`. + +JSON and PNG files are executable baselines. Raw PDFs are intentionally omitted because renderer metadata is nondeterministic. The durable fixture is [`date-layout.test.tsx`](../../src/templates/shared/date-layout.test.tsx). It renders the same unchanged resume data through Chikorita and Ditto in LTR and RTL modes, extracts PDF text coordinates, rasterizes each PDF page, and records default evidence for all 15 templates. +Remediation details: [`REMEDIATION.md`](REMEDIATION.md). + ## Matrix coverage - Standard date-bearing sections: Awards (`date`), Certifications (`date`), Education (`period`), Experience (`period` and two nested role periods), Projects (`period`), Publications (`date`), and Volunteer (`period`). diff --git a/packages/pdf/test-artifacts/date-layout/REMEDIATION.md b/packages/pdf/test-artifacts/date-layout/REMEDIATION.md new file mode 100644 index 000000000..63b4997a9 --- /dev/null +++ b/packages/pdf/test-artifacts/date-layout/REMEDIATION.md @@ -0,0 +1,14 @@ +# Date-layout baseline remediation + +- Normal focused runs compare Chikorita/Ditto LTR+RTL JSON coordinates, page counts, and PNG hashes against checked-in artifacts. +- All-template evidence asserts exact `missingMarkers`: `[]` for 14 templates and `['EXP_NO_PERIOD']` for Meowth, the documented blank-period exception. +- Raw PDFs are omitted; renderer `CreationDate` metadata made PDF bytes nondeterministic. +- Provenance is fixed to generation revision `58ee4eead7843105da20f8a177e7d250b24c3a87` and comparison ref `368858a56fc9c3152b540c39829908e2c3ea04c5`. + +Verification: + +- Focused Vitest: `pnpm exec vitest run src/templates/shared/date-layout.test.tsx` — 1 file, 7 tests, twice with baseline comparison. +- PDF typecheck: `pnpm --filter @reactive-resume/pdf run typecheck` — pass. +- Targeted Biome: `pnpm exec biome check packages/pdf/src/templates/shared/date-layout.test.tsx` — pass. +- Boundaries: `pnpm exec turbo boundaries` — pass. +- Diff checks: `git diff --check` — pass. diff --git a/packages/pdf/test-artifacts/date-layout/chikorita-ltr.json b/packages/pdf/test-artifacts/date-layout/chikorita-ltr.json index 3f8bfd23a..d236bc37c 100644 --- a/packages/pdf/test-artifacts/date-layout/chikorita-ltr.json +++ b/packages/pdf/test-artifacts/date-layout/chikorita-ltr.json @@ -9,6 +9,10 @@ "itemCount": 38 } ], + "rasterSha256": [ + "5b673865c8e5c8d847f5afad2d80d541639b5db8276e25b18ee4572a79564066", + "2e72c805845fea245b51427b104e901538af8e5c594b51810e5ee23df5bb23a3" + ], "coordinates": { "EXP_LONG": [ { diff --git a/packages/pdf/test-artifacts/date-layout/chikorita-ltr.pdf b/packages/pdf/test-artifacts/date-layout/chikorita-ltr.pdf deleted file mode 100644 index e25a48107..000000000 Binary files a/packages/pdf/test-artifacts/date-layout/chikorita-ltr.pdf and /dev/null differ diff --git a/packages/pdf/test-artifacts/date-layout/chikorita-rtl.json b/packages/pdf/test-artifacts/date-layout/chikorita-rtl.json index bd45b1670..1a762b846 100644 --- a/packages/pdf/test-artifacts/date-layout/chikorita-rtl.json +++ b/packages/pdf/test-artifacts/date-layout/chikorita-rtl.json @@ -9,6 +9,10 @@ "itemCount": 41 } ], + "rasterSha256": [ + "e192c24c35e5442b4ae0aa278b84f7991b54c4be5fb82ca13642dc6f2c6bde32", + "631686b14720c12e67f426ab56c65343a83997836bc7d9db371b81f8b02f7d2e" + ], "coordinates": { "EXP_LONG": [ { diff --git a/packages/pdf/test-artifacts/date-layout/chikorita-rtl.pdf b/packages/pdf/test-artifacts/date-layout/chikorita-rtl.pdf deleted file mode 100644 index cba468750..000000000 Binary files a/packages/pdf/test-artifacts/date-layout/chikorita-rtl.pdf and /dev/null differ diff --git a/packages/pdf/test-artifacts/date-layout/ditto-ltr.json b/packages/pdf/test-artifacts/date-layout/ditto-ltr.json index 337db4185..be58dd3f7 100644 --- a/packages/pdf/test-artifacts/date-layout/ditto-ltr.json +++ b/packages/pdf/test-artifacts/date-layout/ditto-ltr.json @@ -9,6 +9,10 @@ "itemCount": 40 } ], + "rasterSha256": [ + "055026dce70c3ae91d94283c2cfa120ac5c2cddf56c220913d93d0b58223deb2", + "24e180ef8b68eb27e25ded90d608696a47212ac9d954a5c05e3d77b88d71dbc5" + ], "coordinates": { "EXP_LONG": [ { diff --git a/packages/pdf/test-artifacts/date-layout/ditto-ltr.pdf b/packages/pdf/test-artifacts/date-layout/ditto-ltr.pdf deleted file mode 100644 index 78380975e..000000000 Binary files a/packages/pdf/test-artifacts/date-layout/ditto-ltr.pdf and /dev/null differ diff --git a/packages/pdf/test-artifacts/date-layout/ditto-rtl.json b/packages/pdf/test-artifacts/date-layout/ditto-rtl.json index 584ec8707..184232e2d 100644 --- a/packages/pdf/test-artifacts/date-layout/ditto-rtl.json +++ b/packages/pdf/test-artifacts/date-layout/ditto-rtl.json @@ -9,6 +9,10 @@ "itemCount": 42 } ], + "rasterSha256": [ + "25e05e71a84e043e3e925f2580044d7493ec6f51fc3e22454edcbe562767f5a6", + "ea56e1dc8f6e96999270a7bc20864cbffb3723b07d3e72557e4828440b7f044d" + ], "coordinates": { "EXP_LONG": [ { diff --git a/packages/pdf/test-artifacts/date-layout/ditto-rtl.pdf b/packages/pdf/test-artifacts/date-layout/ditto-rtl.pdf deleted file mode 100644 index d830823aa..000000000 Binary files a/packages/pdf/test-artifacts/date-layout/ditto-rtl.pdf and /dev/null differ