127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { findEvery, findFirst, textOf } from "../../../tests/setup/html.js";
|
|
import { renderToHtml } from "../../renderer/renderer.js";
|
|
import { renderVNode } from "../../vnode/vnode.js";
|
|
import { InvoiceEmail, invoiceTemplate } from "./invoice.js";
|
|
|
|
const baseProps = {
|
|
recipientName: "Jane",
|
|
invoiceNumber: "INV-001",
|
|
invoiceDate: "2026-01-01",
|
|
items: [
|
|
{ description: "Widget", quantity: 2, unitPrice: 10, total: 20 },
|
|
{ description: "Gizmo", quantity: 1, unitPrice: 15.5, total: 15.5 },
|
|
],
|
|
subtotal: 35.5,
|
|
total: 35.5,
|
|
};
|
|
|
|
describe("invoiceTemplate", () => {
|
|
it("renders line-item rows", { timeout: 5000 }, () => {
|
|
const { html } = renderToHtml(invoiceTemplate, baseProps);
|
|
const widget = findFirst(
|
|
html,
|
|
(el) => el.name === "td" && textOf(el).includes("Widget")
|
|
);
|
|
expect(widget).not.toBeNull();
|
|
const gizmo = findFirst(
|
|
html,
|
|
(el) => el.name === "td" && textOf(el).includes("Gizmo")
|
|
);
|
|
expect(gizmo).not.toBeNull();
|
|
});
|
|
|
|
it("renders the invoice number in the subject and heading", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const { html, subject } = renderToHtml(invoiceTemplate, baseProps);
|
|
expect(subject).toBe("Invoice #INV-001");
|
|
expect(html).toContain("Invoice #INV-001");
|
|
});
|
|
|
|
it("formats currency to two decimal places", { timeout: 5000 }, () => {
|
|
const html = renderVNode(InvoiceEmail(baseProps));
|
|
expect(html).toContain("$10.00");
|
|
expect(html).toContain("$15.50");
|
|
expect(html).toContain("$35.50");
|
|
});
|
|
|
|
it("uses custom currency symbol", { timeout: 5000 }, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({
|
|
...baseProps,
|
|
currency: "€",
|
|
})
|
|
);
|
|
expect(html).toContain("€10.00");
|
|
expect(html).not.toContain("$10.00");
|
|
});
|
|
|
|
it("omits dueDate when not provided", { timeout: 5000 }, () => {
|
|
const html = renderVNode(InvoiceEmail(baseProps));
|
|
expect(html).toContain("Date: 2026-01-01");
|
|
expect(html).not.toContain("Due:");
|
|
});
|
|
|
|
it("renders dueDate when provided", { timeout: 5000 }, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({ ...baseProps, dueDate: "2026-01-31" })
|
|
);
|
|
expect(html).toContain("Due: 2026-01-31");
|
|
});
|
|
|
|
it("renders tax row when tax provided", { timeout: 5000 }, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({ ...baseProps, tax: 3.55, total: 39.05 })
|
|
);
|
|
expect(html).toContain("Tax: $3.55");
|
|
});
|
|
|
|
it("omits tax row when not provided", { timeout: 5000 }, () => {
|
|
const html = renderVNode(InvoiceEmail(baseProps));
|
|
expect(html).not.toContain("Tax:");
|
|
});
|
|
|
|
it("renders payment button when paymentUrl provided", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({ ...baseProps, paymentUrl: "https://x/pay" })
|
|
);
|
|
const a = findFirst(
|
|
html,
|
|
(el) => el.name === "a" && el.attribs.href === "https://x/pay"
|
|
);
|
|
expect(a).not.toBeNull();
|
|
});
|
|
|
|
it("omits payment button when paymentUrl absent", { timeout: 5000 }, () => {
|
|
const html = renderVNode(InvoiceEmail(baseProps));
|
|
expect(html).not.toContain("Pay Now");
|
|
});
|
|
|
|
it("uses explicit companyName", { timeout: 5000 }, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({ ...baseProps, companyName: "Acme" })
|
|
);
|
|
expect(html).toContain("from Acme");
|
|
});
|
|
|
|
it("renders the column headers", { timeout: 5000 }, () => {
|
|
const html = renderVNode(InvoiceEmail(baseProps));
|
|
const headers = findEvery(html, (el) => el.name === "th");
|
|
const labels = headers.map((h) => textOf(h));
|
|
expect(labels).toEqual(["Item", "Qty", "Price", "Total"]);
|
|
});
|
|
|
|
it("renders an empty line-items section when items=[]", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const html = renderVNode(
|
|
InvoiceEmail({ ...baseProps, items: [], subtotal: 0, total: 0 })
|
|
);
|
|
const tbody = findFirst(html, (el) => el.name === "tbody");
|
|
expect(tbody).not.toBeNull();
|
|
});
|
|
});
|