import { assertStringIncludes } from "@std/assert"; import { expect } from "@std/expect"; import { describe, it as test } from "@std/testing/bdd"; import { makeTestEntry as makeEntry } from "#test-utils/factories.ts"; import { buildTestData, describeEmailRenderer, TICKET_URL, } from "./test-helpers.ts"; describeEmailRenderer(() => { describe("buildTemplateData", () => { test("builds correct data shape from single entry", async () => { const data = await buildTestData([makeEntry()]); expect(data.listing_names).toBe("Test Listing"); expect(data.ticket_url).toBe(TICKET_URL); expect(data.currency).toBe("GBP"); expect(data.entries.length).toBe(1); expect(data.entries[0]!.listing.name).toBe("Test Listing"); expect(data.entries[0]!.listing.slug).toBe("test-listing"); expect(data.entries[0]!.listing.is_paid).toBe(false); expect(data.attendee.name).toBe("Jane Doe"); expect(data.attendee.email).toBe("jane@example.com"); }); test("carries each entry's answer lines into the attendee shape", async () => { const data = await buildTestData([makeEntry()], { answerLines: new Map([ [42, new Map([[1, [{ question: "Any allergies?", text: "None" }]]])], ]), }); expect(data.entries[0]!.attendee.answers).toEqual([ { question: "Any allergies?", text: "None" }, ]); expect(data.attendee.answers).toEqual(data.entries[0]!.attendee.answers); }); test("lists each member's answers once on a collapsed hidden package's row", async () => { const diet = { question: "Diet?", text: "Vegan" }; const data = await buildTestData( [ makeEntry({}, { package_group_id: 5 }), makeEntry({ id: 2, name: "Member" }, { package_group_id: 5 }), ], { answerLines: new Map([ [ 42, new Map([ [1, [diet]], [2, [diet, { question: "Shoe size?", text: "9" }]], ]), ], ]), hidePackageMembers: true, packageDisplays: new Map([ [5, { hideListings: true, name: "Hidden bundle" }], ]), }, ); expect(data.entries).toHaveLength(1); expect(data.entries[0]!.listing.name).toBe("Hidden bundle"); expect(data.entries[0]!.attendee.answers).toEqual([ diet, { question: "Shoe size?", text: "9" }, ]); }); test("builds correct data shape from multiple entries", async () => { const data = await buildTestData([ makeEntry({ name: "Listing A" }), makeEntry({ name: "Listing B" }), ]); expect(data.listing_names).toBe("Listing A and Listing B"); expect(data.entries.length).toBe(2); expect(data.attendee.name).toBe("Jane Doe"); }); test("formats three or more listing names with commas and 'and'", async () => { const data = await buildTestData([ makeEntry({ name: "Listing A" }), makeEntry({ name: "Listing B" }), makeEntry({ name: "Listing C" }), ]); expect(data.listing_names).toBe("Listing A, Listing B, and Listing C"); }); test("marks paid listings correctly", async () => { const data = await buildTestData([makeEntry({ unit_price: 1000 })]); expect(data.entries[0]!.listing.is_paid).toBe(true); }); test("marks can_pay_more listings as paid", async () => { const data = await buildTestData([ makeEntry({ can_pay_more: true, unit_price: 0 }), ]); expect(data.entries[0]!.listing.is_paid).toBe(true); }); test("marks a free-base entry as paid from its booking price", async () => { // A package override can charge a member whose base listing is free. const data = await buildTestData([ makeEntry({ unit_price: 0 }, { price_paid: "1" }), ]); expect(data.entries[0]!.listing.is_paid).toBe(true); }); test("includes attendee date when present", async () => { const data = await buildTestData([makeEntry({}, { date: "2026-04-15" })]); expect(data.entries[0]!.attendee.date).toBe("2026-04-15"); }); const dateRangeLabelFor = async ( listing: Partial[0]>, attendee: Partial[1]>, ): Promise => (await buildTestData([makeEntry(listing, attendee)])).entries[0]!.attendee .date_range_label; test("date_range_label: single-day daily booking formats as a date", async () => { assertStringIncludes( await dateRangeLabelFor( { duration_days: 1, listing_type: "daily" }, { date: "2026-04-15" }, ), "15 April", ); }); test("date_range_label: multi-day booking uses en dash", async () => { expect( await dateRangeLabelFor( { duration_days: 3, listing_type: "daily" }, { date: "2026-04-15", end_date: "2026-04-18" }, ), ).toBe("15\u201317 April 2026"); }); test("date_range_label: empty when no booking date", async () => { expect(await dateRangeLabelFor({}, { date: null })).toBe(""); }); }); });