diff --git a/src/__tests__/templates-branch-coverage.test.ts b/src/__tests__/templates-branch-coverage.test.ts new file mode 100644 index 0000000..c1c7ed3 --- /dev/null +++ b/src/__tests__/templates-branch-coverage.test.ts @@ -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
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('

From

')[1]?.split('

To

')[0]; + expect(fromSection).not.toContain('

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
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("Notes"); + }); +});