Replace the sample data with synthetic fixtures and regenerate goldens and thumbnails

The template harness, tests and thumbnails now use a neutral vendor, bank account and
a generated sample wordmark (scripts/logo/make-sample-logo.mjs). Layout goldens and the
14 picker thumbnails are regenerated. Fixture strings keep their original lengths where
layout tests depend on them.
This commit is contained in:
2026-10-05 10:18:53 +05:30
parent c3e2e05e89
commit c78bf1a851
35 changed files with 64 additions and 44 deletions
+1 -1
View File
@@ -173,7 +173,7 @@ describe("Slate Band: dark band and logo", () => {
for (const [i, page] of r.pdf.pages.entries()) {
const text = page.items.map((t) => t.str).join(" ");
expect(text, `page ${i + 1} phone`).toContain("9000000000");
expect(text, `page ${i + 1} e-mail`).toContain("vendor@example.com");
expect(text, `page ${i + 1} e-mail`).toContain("vendor.test@example.in");
}
});
});
+1 -1
View File
@@ -316,7 +316,7 @@ describe("closing and footer", () => {
expect(closing.box.y + closing.box.h).toBeCloseTo(frame.page.height - frame.pad.bottom, 0);
const text = r.pdf.pages[last].items.map((i) => i.str).join(" ");
expect(text).toContain("9000000000");
expect(text).toContain("vendor@example.com");
expect(text).toContain("vendor.test@example.in");
});
it("a pasted signature image replaces the script: right-aligned, at most 36 pt tall, with the signatory name and designation under it", async () => {
+5 -5
View File
@@ -31,8 +31,8 @@ const FIXTURE_CLIENT_GSTIN = "29ABCDE1234F1Z5";
const baseVendor: Settings = {
vendorName: "Test Vendor",
vendorAddress: "1 Test Road, Bangalore - 560001",
vendorEmail: "vendor@example.com",
vendorAddress: "27 Example Apartments, Sample Layout, Test Nagar, Bangalore - 560001",
vendorEmail: "vendor.test@example.in",
vendorPhone: "9000000000",
vendorPan: "ABCDE1234F",
vendorGstin: "",
@@ -165,7 +165,7 @@ export function fixtureLogo(kind: LogoKind): LogoInput | null {
let hit = cache.get(kind);
if (!hit) {
if (kind === "real") {
// 2172 x 724: the untrimmed Test Vendor logo, a 3:1 aspect.
// 2172 x 724: the untrimmed sample logo, a 3:1 aspect.
const png = readFileSync(path.resolve(process.cwd(), "src/pdf/testing/fixtures/sample-wordmark-logo.png"));
const [width, height] = [png.readUInt32BE(16), png.readUInt32BE(20)];
hit = { dataUri: dataUri(png), width, height, aspect: width / height, density: 0.21, kind: "wordmark", includesName: true };
@@ -183,7 +183,7 @@ export function fixtureLogo(kind: LogoKind): LogoInput | null {
includesName: true,
};
} else if (kind === "wide") {
// The trimmed Test Vendor ink box is 4.6:1.
// The trimmed sample ink box is 4.6:1.
hit = { dataUri: dataUri(solidPng(460, 100)), width: 460, height: 100, aspect: 4.6, density: 1, kind: "wordmark", includesName: true };
} else {
hit = { dataUri: dataUri(solidPng(120, 120)), width: 120, height: 120, aspect: 1, density: 1, kind: "mark", includesName: false };
@@ -281,7 +281,7 @@ export const FIXTURES: FixtureSpec[] = [
{
name: "id-tokens",
vendor: {
vendorEmail: "Sample.longsurname.voiceover.studio.bangalore@examplestudios-india.co.in",
vendorEmail: "anon.longsurname.voiceover.studio.bangalore@examplestudios-india.co.in",
vendorPhone: "+91 97382 84459 / +91 80 4123 9988 (studio)",
vendorName: "Venkatachalam Longsurname Ramasubramanian Voice Studios Pvt",
},
@@ -321,7 +321,7 @@ describe("totals and closing", () => {
const last = r.result.pages - 1;
const closing = byId(r, CLOSING_ID, last);
const lines = textLines(closing).map((l) => l.text);
expect(lines).toEqual(expect.arrayContaining(["THANK YOU", "9000000000", "vendor@example.com", "Payment due within 30 days."]));
expect(lines).toEqual(expect.arrayContaining(["THANK YOU", "9000000000", "vendor.test@example.in", "Payment due within 30 days."]));
expect(onPage(r, last).filter((n) => n.type === "svg" && n.box.y >= closing.box.y)).toHaveLength(3);
const sign = byId(r, SLOT_ID.signatory, last);
expect(sign.box.w).toBeCloseTo(V.closing.ruledW, 0);
+3 -3
View File
@@ -119,14 +119,14 @@ describe("what the header does with a logo", () => {
expect(img.box.w).toBeLessThanOrEqual(lockup.w + 0.01);
expect(img.box.x + img.box.w).toBeLessThanOrEqual(frameForPrefs(wide.prefs).page.width - frameForPrefs(wide.prefs).pad.right + 0.5);
// The business name is not repeated as text in the header (the party heading still prints it once).
expect(textOf(wide)[0].match(/Sample P/g)?.length).toBe(1);
expect(textOf(wide)[0].match(/TEST VENDOR/g)?.length).toBe(1);
const square = await renderFixture({ ...withLogo, logo: "square" }, prefsFor("A4", { templateId: "crimson-grid" }));
const [mark] = imageBoxes(square);
const markBox = SLOTS["crimson-grid"]!.mark!;
expect(mark.box.h).toBeLessThanOrEqual(markBox.h + 0.01);
expect(mark.box.w).toBeLessThanOrEqual(markBox.w + 0.01);
expect(textOf(square)[0].match(/Sample P/g)?.length).toBe(2);
expect(textOf(square)[0].match(/TEST VENDOR/g)?.length).toBe(2);
});
it("Tangerine: the logo stays in its 220 x 36 allowance at the left", async () => {
@@ -148,7 +148,7 @@ describe("what the header does with a logo", () => {
const r = await renderFixture({ ...withLogo, logo: "none" }, prefsFor("A4", { templateId: t.id }));
expect(imageBoxes(r), t.id).toHaveLength(0);
// The brand text in the header, the party heading and the footer: three prints of the name, one of them at the top.
const names = r.pdf.pages[0].items.filter((i) => /^Sample p$/i.test(i.str.trim()));
const names = r.pdf.pages[0].items.filter((i) => /^test vendor$/i.test(i.str.trim()));
expect(names.length, t.id).toBe(3);
expect(names.filter((i) => i.bottom < 150).length, t.id).toBe(1);
expect(r.pdf.raw, t.id).toContain("Poppins");
+4 -4
View File
@@ -157,7 +157,7 @@ describe("header", () => {
it("the vendor name stands in the empty left corner, level with the title, in Lustria caps", async () => {
const r = await renderFixture(PLAIN, prefsFor("A4", { templateId: "marble" }));
const name = items(r, "Sample P")[0];
const name = items(r, "TEST VENDOR")[0];
expect(name.bottom).toBeCloseTo(124.9, 0);
expect(name.x0).toBeCloseTo(38.5, 0);
});
@@ -218,7 +218,7 @@ describe("logo", () => {
it("without the opt-in no logo is drawn, even when the invoice has one", async () => {
const r = await renderFixture({ name: "o", items: 2, logo: "real" }, prefsFor("A4", { templateId: "marble" }));
expect(imageBoxes(r)).toHaveLength(0);
expect(items(r, "Sample P")).toHaveLength(1);
expect(items(r, "TEST VENDOR")).toHaveLength(1);
});
it("every opted-in logo fits the 150 x 50 slot, starts at the text inset and rests on its bottom edge", async () => {
@@ -250,7 +250,7 @@ describe("logo", () => {
});
it("a wordmark replaces the vendor name text; a mark keeps it under the mark", async () => {
const header = (r: R) => r.pdf.pages[0].items.filter((i) => squash(i.str) === "SampleP" && i.bottom < 200);
const header = (r: R) => r.pdf.pages[0].items.filter((i) => squash(i.str) === "TESTVENDOR" && i.bottom < 200);
const wide = await renderFixture({ name: "o", items: 2, logo: "real" }, optIn());
expect(header(wide)).toHaveLength(0);
const square = await renderFixture({ name: "o", items: 2, logo: "square" }, optIn());
@@ -416,7 +416,7 @@ describe("long text", () => {
it("the foot band's text stays inside the band whatever the address, and its two columns never overlap", async () => {
for (const size of ["A4", "LETTER"] as const) {
const r = await renderFixture({ name: "long", items: 3, vendor: { vendorAddress: Array.from({ length: 8 }, (_, i) => `Address line ${i + 1} of a very long Indian address with a locality name`).join("\n"), vendorEmail: "Sample.longsurname.voiceover.studio.bangalore@examplestudios-india.co.in", vendorName: "Supercalifragilisticexpialidocious-Pneumonoultramicroscopic-1234567890" } }, prefsFor(size, { templateId: "marble" }));
const r = await renderFixture({ name: "long", items: 3, vendor: { vendorAddress: Array.from({ length: 8 }, (_, i) => `Address line ${i + 1} of a very long Indian address with a locality name`).join("\n"), vendorEmail: "anon.longsurname.voiceover.studio.bangalore@examplestudios-india.co.in", vendorName: "Supercalifragilisticexpialidocious-Pneumonoultramicroscopic-1234567890" } }, prefsFor(size, { templateId: "marble" }));
const frame = frameForPrefs(r.prefs);
const decorText = onPage(r, 0).filter((n) => n.decor && n.type === "text" && n.box.y > frame.page.height - V.bands.bottom);
const lines = decorText.flatMap((n) => textLines(n));
+3 -3
View File
@@ -122,8 +122,8 @@ describe("page geometry", () => {
describe("helpers", () => {
it("initialsOf takes the first and last word", () => {
expect(initialsOf("Test Vendor")).toBe("AP");
expect(initialsOf("Venkatachalam Longsurname Ramasubramanian Voice Studios Pvt")).toBe("AP");
expect(initialsOf("Test Vendor")).toBe("TV");
expect(initialsOf("Venkatachalam Longsurname Ramasubramanian Voice Studios Pvt")).toBe("VP");
expect(initialsOf("Bongbetic")).toBe("B");
expect(initialsOf(" ")).toBe("");
expect(initialsOf("& Co")).toBe("C");
@@ -151,7 +151,7 @@ describe("what the layout does with a logo", () => {
const none = await renderFixture({ ...withLogo, logo: "none" }, prefsFor("A4", { templateId: t.id }));
expect(imageCount(none), t.id).toBe(0);
const text = none.pdf.pages[0].items.map((i) => i.str).join(" ");
if (t.id === "monogram") expect(text).toContain("AP");
if (t.id === "monogram") expect(text).toContain("TV");
// No reference ring or stamp is ever drawn.
expect(nodes(none.result.core.layout[0].nodes).filter((n) => n.type === "svg" && !n.decor), t.id).toHaveLength(0);
}
@@ -340,7 +340,7 @@ describe("Citrus Split", () => {
for (const i of icons) expect(i.box.x + i.box.w).toBeLessThanOrEqual(frame.page.width - frame.pad.right + 0.5);
const text = textsOf(r, r.result.pages - 1);
expect(text).toContain("9000000000");
expect(text).toContain("vendor@example.com");
expect(text).toContain("vendor.test@example.in");
});
it("the logo stays inside its measured lockup box; a wordmark replaces the name, a mark keeps it", async () => {
+1 -1
View File
@@ -6,7 +6,7 @@ import { SLOTS } from "./slots.generated";
* is a free area named in docs/templates/NN-*.md and is an opt-in position, off by default (the plan
* keeps Linea logo-free so it still matches format.pdf). Hand-written; not produced by logo:measure.
*
* With no placeholder ink to match, the density is a typical logo's (Test Vendor is 0.437), so the weight
* With no placeholder ink to match, the density is a typical logo's (the sample logo is 0.437), so the weight
* factor in computeLogoPlacement only shrinks logos that are denser than that.
*/
const TYPICAL_DENSITY = 0.4;