test: add 29 tests for template branch coverage

Cover receipt and invoice optional field branches:
- from.address present/absent
- to.name present/absent
- paymentMethod present/absent
- dueDate, paymentDetails, notes present/absent
- from/to fallback to empty object
- Item default values (quantity, unitPrice, amount)

Templates branch coverage: 80.76% → 98.07%
This commit is contained in:
OpenClaw Subagent 2026-03-21 11:07:23 +01:00
parent 0a4fcd2e50
commit e6188369f1

View file

@ -0,0 +1,276 @@
import { describe, it, expect } from "vitest";
import { renderTemplate, TemplateData } from "../services/templates.js";
describe("Receipt template optional branches", () => {
const baseReceipt = {
receiptNumber: "R-100",
date: "2026-03-01",
items: [{ description: "Item", amount: 10 }],
};
it("renders from.address when present", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop", address: "123 Main St" },
} as TemplateData);
expect(html).toContain("123 Main St");
expect(html).toContain('class="center"');
});
it("omits from.address when absent", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop" },
} as TemplateData);
// The "Thank you!" div has class="center", but no address div should exist
const addressDivCount = (html.match(/class="center"/g) || []).length;
// Only the "Thank you!" div should have class="center", not an address div
expect(addressDivCount).toBe(1);
});
it("renders to.name (Customer line) when present", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop" },
to: { name: "John Doe" },
} as TemplateData);
expect(html).toContain("Customer: John Doe");
});
it("omits Customer line when to.name is absent", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop" },
} as TemplateData);
expect(html).not.toContain("Customer:");
});
it("renders paymentMethod when present", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop" },
paymentMethod: "Credit Card",
} as TemplateData);
expect(html).toContain("Paid via: Credit Card");
});
it("omits paymentMethod when absent", () => {
const html = renderTemplate("receipt", {
...baseReceipt,
from: { name: "Shop" },
} as TemplateData);
expect(html).not.toContain("Paid via:");
});
});
describe("Receipt template fallback branches", () => {
it("renders without from (falls back to empty object)", () => {
const html = renderTemplate("receipt", {
receiptNumber: "R-200",
date: "2026-03-01",
items: [{ description: "Item", amount: 5 }],
} as TemplateData);
expect(html).toContain("R-200");
expect(html).toContain("€5.00");
});
it("renders without to (falls back to empty object)", () => {
const html = renderTemplate("receipt", {
receiptNumber: "R-201",
date: "2026-03-01",
from: { name: "Shop" },
items: [{ description: "Item", amount: 5 }],
} as TemplateData);
expect(html).not.toContain("Customer:");
});
it("renders with custom currency", () => {
const html = renderTemplate("receipt", {
receiptNumber: "R-202",
date: "2026-03-01",
from: { name: "Shop" },
currency: "$",
items: [{ description: "Item", amount: 7 }],
} as TemplateData);
expect(html).toContain("$7.00");
});
it("renders with empty items array", () => {
const html = renderTemplate("receipt", {
receiptNumber: "R-203",
date: "2026-03-01",
from: { name: "Shop" },
} as TemplateData);
expect(html).toContain("€0.00");
});
});
describe("Invoice template fallback branches", () => {
it("renders without from (falls back to empty object)", () => {
const html = renderTemplate("invoice", {
invoiceNumber: "INV-300",
date: "2026-03-01",
to: { name: "Buyer" },
items: [{ description: "Widget", quantity: 1, unitPrice: 10 }],
} as TemplateData);
expect(html).toContain("INV-300");
});
it("renders without to (falls back to empty object)", () => {
const html = renderTemplate("invoice", {
invoiceNumber: "INV-301",
date: "2026-03-01",
from: { name: "Seller" },
items: [{ description: "Widget", quantity: 1, unitPrice: 10 }],
} as TemplateData);
expect(html).toContain("INV-301");
});
});
describe("Invoice item default values", () => {
it("defaults quantity to 1 when missing", () => {
const html = renderTemplate("invoice", {
invoiceNumber: "INV-400",
date: "2026-03-01",
from: { name: "S" }, to: { name: "B" },
items: [{ description: "Widget", unitPrice: 10 }],
} as TemplateData);
expect(html).toContain("€10.00"); // 1 * 10
});
it("defaults unitPrice to 0 when missing", () => {
const html = renderTemplate("invoice", {
invoiceNumber: "INV-401",
date: "2026-03-01",
from: { name: "S" }, to: { name: "B" },
items: [{ description: "Free item", quantity: 2 }],
} as TemplateData);
expect(html).toContain("€0.00");
});
it("defaults items to empty array when missing", () => {
const html = renderTemplate("invoice", {
invoiceNumber: "INV-402",
date: "2026-03-01",
from: { name: "S" }, to: { name: "B" },
} as TemplateData);
expect(html).toContain("€0.00");
});
});
describe("Receipt item default values", () => {
it("defaults amount to 0 when missing/invalid", () => {
const html = renderTemplate("receipt", {
receiptNumber: "R-300",
date: "2026-03-01",
from: { name: "Shop" },
items: [{ description: "Bad item", amount: "not-a-number" }],
} as unknown as TemplateData);
expect(html).toContain("€0.00");
});
});
describe("Invoice template optional branches", () => {
const baseInvoice = {
invoiceNumber: "INV-100",
date: "2026-03-01",
from: { name: "Seller" },
to: { name: "Buyer" },
items: [{ description: "Widget", quantity: 1, unitPrice: 10 }],
};
it("renders dueDate when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
dueDate: "2026-04-01",
} as TemplateData);
expect(html).toContain("Due: 2026-04-01");
});
it("omits dueDate when absent", () => {
const html = renderTemplate("invoice", baseInvoice as TemplateData);
expect(html).not.toContain("Due:");
});
it("renders from.address when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
from: { name: "Seller", address: "456 Elm St\nSuite 2" },
} as TemplateData);
expect(html).toContain("456 Elm St<br>Suite 2");
});
it("omits from.address when absent", () => {
const html = renderTemplate("invoice", baseInvoice as TemplateData);
// from section should have name but no address paragraph
const fromSection = html.split('<h3>From</h3>')[1]?.split('<h3>To</h3>')[0];
expect(fromSection).not.toContain('<p>456');
});
it("renders from.email when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
from: { name: "Seller", email: "seller@test.com" },
} as TemplateData);
expect(html).toContain("seller@test.com");
});
it("renders from.vatId when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
from: { name: "Seller", vatId: "ATU12345678" },
} as TemplateData);
expect(html).toContain("VAT: ATU12345678");
});
it("renders to.address when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
to: { name: "Buyer", address: "789 Oak Ave" },
} as TemplateData);
expect(html).toContain("789 Oak Ave");
});
it("renders to.email when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
to: { name: "Buyer", email: "buyer@test.com" },
} as TemplateData);
expect(html).toContain("buyer@test.com");
});
it("renders to.vatId when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
to: { name: "Buyer", vatId: "DE999999999" },
} as TemplateData);
expect(html).toContain("VAT: DE999999999");
});
it("renders paymentDetails when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
paymentDetails: "IBAN: AT123456\nBIC: BKAUATWW",
} as TemplateData);
expect(html).toContain("Payment Details");
expect(html).toContain("IBAN: AT123456<br>BIC: BKAUATWW");
});
it("omits paymentDetails when absent", () => {
const html = renderTemplate("invoice", baseInvoice as TemplateData);
expect(html).not.toContain("Payment Details");
});
it("renders notes when present", () => {
const html = renderTemplate("invoice", {
...baseInvoice,
notes: "Thank you for your business",
} as TemplateData);
expect(html).toContain("Thank you for your business");
});
it("omits notes when absent", () => {
const html = renderTemplate("invoice", baseInvoice as TemplateData);
expect(html).not.toContain("<strong>Notes</strong>");
});
});