Rebuild the Classic template on the engine with A4/Letter and a template harness

Classic now renders through buildRenderModel, computeFrame, the template
and renderCore, with S.No and HSN/SAC columns, GST slots only through
checked Slot components, taxable value, reverse-charge line, cancelled tag,
continuation band and Page n of N. Letter absorbs its height deficit in
elastic gaps. A harness runs 15 fixtures on A4 and Letter plus an item
sweep and asserts zero audit errors, repeated headers, no split blocks,
rupee extraction and deterministic output. The legacy InvoiceDocument is
removed.
This commit is contained in:
2026-10-04 05:31:14 +05:30
parent 8d4f8e666b
commit a014a907e4
40 changed files with 2185 additions and 832 deletions
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { computeLogoPlacement } from "./logo";
const BOX = { w: 170, h: 54 };
const base = { box: BOX, availableWidth: 299, minTextWidth: 160 };
describe("computeLogoPlacement", () => {
it("the Test Vendor trimmed 4.6:1 logo is width-limited and stacks above the text", () => {
const p = computeLogoPlacement({ ...base, aspect: 4.6 });
expect(p.w).toBeCloseTo(170, 6);
expect(p.h).toBeCloseTo(170 / 4.6, 6);
expect(p.stacked).toBe(true);
});
it("an untrimmed 3:1 logo is height-limited (54 pt) and still stacks", () => {
const p = computeLogoPlacement({ ...base, aspect: 3 });
expect(p.h).toBe(54);
expect(p.w).toBeCloseTo(162, 6);
expect(p.stacked).toBe(true);
});
it("a square logo is 54 x 54 and sits beside the text", () => {
const p = computeLogoPlacement({ ...base, aspect: 1 });
expect(p).toEqual({ w: 54, h: 54, stacked: false });
});
it("stacks exactly when the text beside the logo would drop under the minimum", () => {
// 299 - 54 - 14 = 231 beside a square; a 2:1 logo (108 wide) leaves 177; a 2.5:1 one (135) leaves 150.
expect(computeLogoPlacement({ ...base, aspect: 2 }).stacked).toBe(false);
expect(computeLogoPlacement({ ...base, aspect: 2.5 }).stacked).toBe(true);
// The boundary: leaves exactly 160.
const w = 299 - 14 - 160;
expect(computeLogoPlacement({ ...base, aspect: w / 54 }).stacked).toBe(false);
expect(computeLogoPlacement({ ...base, aspect: (w + 0.01) / 54 }).stacked).toBe(true);
});
it("gives the logo more room on a wider column (Letter)", () => {
const a4 = computeLogoPlacement({ ...base, aspect: 2.4, availableWidth: 299.28 });
const letter = computeLogoPlacement({ ...base, aspect: 2.4, availableWidth: 316 });
expect(a4.stacked).toBe(true);
expect(letter.stacked).toBe(false);
});
it("never exceeds the box, whatever the aspect", () => {
for (const aspect of [0.2, 0.5, 1, 1.5, 3, 4.6, 10, 40]) {
const p = computeLogoPlacement({ ...base, aspect });
expect(p.w).toBeLessThanOrEqual(170 + 1e-9);
expect(p.h).toBeLessThanOrEqual(54 + 1e-9);
expect(p.w / p.h).toBeCloseTo(aspect, 6);
}
});
it("falls back to the box aspect for a bad aspect", () => {
const p = computeLogoPlacement({ ...base, aspect: Number.NaN });
expect(p.w).toBeCloseTo(170, 6);
expect(p.h).toBeCloseTo(54, 6);
});
});