test(pdf): enforce date layout baselines

This commit is contained in:
Amruth Pillai
2026-09-06 06:05:04 +02:00
parent 58ee4eead7
commit 1232d5dfb2
11 changed files with 89 additions and 17 deletions
@@ -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<Template, readonly DateMarker[]>;
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<string, unknown>) => {
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<string, unknown>): 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);
}
});
@@ -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`).
@@ -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.
@@ -9,6 +9,10 @@
"itemCount": 38
}
],
"rasterSha256": [
"5b673865c8e5c8d847f5afad2d80d541639b5db8276e25b18ee4572a79564066",
"2e72c805845fea245b51427b104e901538af8e5c594b51810e5ee23df5bb23a3"
],
"coordinates": {
"EXP_LONG": [
{
@@ -9,6 +9,10 @@
"itemCount": 41
}
],
"rasterSha256": [
"e192c24c35e5442b4ae0aa278b84f7991b54c4be5fb82ca13642dc6f2c6bde32",
"631686b14720c12e67f426ab56c65343a83997836bc7d9db371b81f8b02f7d2e"
],
"coordinates": {
"EXP_LONG": [
{
@@ -9,6 +9,10 @@
"itemCount": 40
}
],
"rasterSha256": [
"055026dce70c3ae91d94283c2cfa120ac5c2cddf56c220913d93d0b58223deb2",
"24e180ef8b68eb27e25ded90d608696a47212ac9d954a5c05e3d77b88d71dbc5"
],
"coordinates": {
"EXP_LONG": [
{
@@ -9,6 +9,10 @@
"itemCount": 42
}
],
"rasterSha256": [
"25e05e71a84e043e3e925f2580044d7493ec6f51fc3e22454edcbe562767f5a6",
"ea56e1dc8f6e96999270a7bc20864cbffb3723b07d3e72557e4828440b7f044d"
],
"coordinates": {
"EXP_LONG": [
{