Files
Voiced/src/pdf/templates/harness.test.tsx
T
xavierk c95514ee8d Add Ledger and Band template families with decor primitives
Crimson Grid, Tangerine Ledger, Slate Band and Teal Swoosh are rebuilt from
the Canva PDFs with grid and band tables that survive page breaks,
full-bleed fixed bands, pennant tabs, gradient corner swooshes with
keep-outs, Carbon icon circles, and the user's logo in the measured slot
(knockout or white chip on the dark Slate band). Adds static Poppins
500-700 and Inter fonts. Thumbnails regenerated for all eight templates;
the item-count sweeps run in their own test file.
2026-10-04 08:34:18 +05:30

82 lines
4.3 KiB
TypeScript

import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import type { AuditIssue } from "../engine/audit";
import { PAGE_SIZES } from "../engine/geometry";
import { PAGE_LABEL_ID, TABLE_HEAD_ID, TABLE_ROW_PREFIX, CLOSING_ID, TOTALS_ID } from "../engine/ids";
import { walk, type LayoutNode, type LayoutPage, type Rect } from "../engine/layoutTree";
import { forbiddenProbe, SLOT_RULES, slotContent, type SlotName } from "../model/slots";
import { frameForPrefs, renderInvoicePdf } from "../render/invoice";
import { loadCanvas, pagePng } from "../testing/rasterize";
import { SLOT_ID } from "../blocks/slots";
import { META_ID, SUPPLIER_ID, TITLE_ID } from "./classic/ids";
import { HEADER_GAP, headerCols } from "./classic/plan";
import { FIXTURE_GSTIN, FIXTURES, buildFixture, ensureSetup, makeItems, PAGE_SIZE_IDS, prefsFor, renderFixture, type FixtureSpec, type Rendered } from "./harness";
import { TEMPLATES } from "./registry";
/**
* Template harness: every registered template x {A4, Letter} x the fixture matrix, plus an item-count
* sweep. A failure here means the template or a primitive is wrong, not the assertion.
*/
import { checkRendered } from "./harness.check";
const OUT_DIR = process.env.PDF_OUT_DIR ?? "";
describe("template harness: fixtures", () => {
for (const template of Object.values(TEMPLATES)) {
describe(template.id, () => {
for (const size of PAGE_SIZE_IDS) {
for (const spec of FIXTURES) {
it(`${spec.name} on ${size}`, async () => {
const prefs = prefsFor(size, { templateId: template.id });
const r = await renderFixture(spec, prefs);
checkRendered(r, `${template.id}/${spec.name}/${size}`);
});
}
}
});
}
});
describe("template harness: determinism", () => {
for (const template of Object.values(TEMPLATES)) {
for (const size of PAGE_SIZE_IDS) {
for (const name of ["cgst-sgst", "rows-60", "cancelled", "unregistered"]) {
it(`${template.id}/${name}/${size}: same input gives identical bytes and fingerprint`, async () => {
ensureSetup();
const spec = FIXTURES.find((f) => f.name === name) as FixtureSpec;
const { model } = buildFixture(spec);
const prefs = prefsFor(size, { templateId: template.id });
const a = await renderInvoicePdf({ model, prefs });
const b = await renderInvoicePdf({ model, prefs });
expect(Buffer.from(a.bytes).equals(Buffer.from(b.bytes))).toBe(true);
expect(b.fingerprint).toBe(a.fingerprint);
});
}
}
}
});
describe.skipIf(!OUT_DIR)("template harness: samples for visual review", () => {
const samples: Array<{ file: string; spec: FixtureSpec; size: "A4" | "LETTER"; pages: number }> = [
{ file: "classic-A4-tax", spec: { name: "s1", items: 3, signature: true, invoice: { poNumber: "PO-2026-0042", notes: "Payment due within 30 days.\nLate payments attract 2% monthly interest." } }, size: "A4", pages: 1 },
{ file: "classic-Letter-tax", spec: { name: "s2", items: 3, signature: true, invoice: { poNumber: "PO-2026-0042", notes: "Payment due within 30 days.\nLate payments attract 2% monthly interest." } }, size: "LETTER", pages: 1 },
{ file: "classic-A4-unregistered", spec: { name: "s3", registered: false, items: 3, invoice: { notes: "Thank you for your business." } }, size: "A4", pages: 1 },
{ file: "classic-A4-60rows", spec: { name: "s4", items: 60, signature: true }, size: "A4", pages: 2 },
{ file: "classic-Letter-60rows", spec: { name: "s5", items: 60, signature: true }, size: "LETTER", pages: 2 },
{ file: "classic-A4-cancelled", spec: { name: "s6", items: 3, invoice: { status: "cancelled" }, signature: true }, size: "A4", pages: 1 },
];
for (const s of samples) {
it(s.file, async () => {
mkdirSync(OUT_DIR, { recursive: true });
const r = await renderFixture(s.spec, prefsFor(s.size));
writeFileSync(path.join(OUT_DIR, `${s.file}.pdf`), r.result.bytes);
const canvas = await loadCanvas();
if (!canvas) return;
for (let p = 1; p <= Math.min(s.pages, r.result.pages); p++) {
writeFileSync(path.join(OUT_DIR, `${s.file}-p${p}.png`), await pagePng(canvas, r.result.bytes, p, 1.5));
}
});
}
});