import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { MAILTO_TARGET, buildContactMailto, handleContactSubmit, readFormValues, } from "../js/contact.js"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const contactHtml = readFileSync(join(root, "contact.html"), "utf8"); const indexHtml = readFileSync(join(root, "index.html"), "utf8"); /** Minimal stand-in for a DOM form (has querySelectorAll("[name]")). */ function fakeForm(values) { return { querySelectorAll(selector) { assert.equal(selector, "[name]"); return Object.entries(values).map(([name, value]) => ({ name, value })); }, }; } // --------------------------------------------------------------------------- // Component tests: the contact page renders the three required fields // --------------------------------------------------------------------------- test("contact page renders a form with name, email, and message fields", () => { assert.match(contactHtml, /]*id="contact-form"/); assert.match(contactHtml, /]*type="text"[^>]*id="name"[^>]*name="name"/); assert.match(contactHtml, /]*type="email"[^>]*id="email"[^>]*name="email"/); assert.match(contactHtml, /]*id="message"[^>]*name="message"/); }); test("all three contact form fields are required", () => { assert.match(contactHtml, /]*id="name"[^>]*required/); assert.match(contactHtml, /]*id="email"[^>]*required/); assert.match(contactHtml, /]*id="message"[^>]*required/); }); test("contact page loads the module that wires form submission", () => { assert.match(contactHtml, /]*type="module"[^>]*src="js\/contact\.js"/); }); // --------------------------------------------------------------------------- // Component tests: the page is linked from site navigation // --------------------------------------------------------------------------- test("home page nav links to the contact page", () => { assert.match(indexHtml, /Contact<\/a>/); assert.match(indexHtml, /class="nav-links"/); }); test("contact page nav marks the contact link as the current page", () => { assert.match(contactHtml, /]*aria-current="page"[^>]*>Contact<\/a>/); }); // --------------------------------------------------------------------------- // Unit tests: submitting the form opens the mail client pre-filled // --------------------------------------------------------------------------- test("buildContactMailto pre-fills subject and body from entered values", () => { const url = buildContactMailto( { name: "Ada Lovelace", email: "ada@example.com", message: "Hi there!" }, "owner@example.com" ); assert.ok(url.startsWith("mailto:owner@example.com?")); const params = new URL(url).searchParams; assert.equal(params.get("subject"), "Contact form submission from Ada Lovelace"); assert.match(params.get("body"), /Name: Ada Lovelace/); assert.match(params.get("body"), /Email: ada@example\.com/); assert.match(params.get("body"), /Hi there!/); }); test("buildContactMailto defaults to the configured mailto target", () => { const url = buildContactMailto({ name: "A", email: "a@b.c", message: "m" }); assert.ok(url.startsWith(`mailto:${MAILTO_TARGET}?`)); }); test("readFormValues collects name, email, and message", () => { const values = readFormValues(fakeForm({ name: "N", email: "E", message: "M" })); assert.deepEqual(values, { name: "N", email: "E", message: "M" }); }); test("handleContactSubmit navigates the visitor to the built mailto URL", () => { const form = fakeForm({ name: "Grace Hopper", email: "grace@example.com", message: "Hello!", }); const navigated = []; const url = handleContactSubmit(form, { to: "owner@example.com", navigate: (target) => navigated.push(target), }); assert.equal(navigated.length, 1); assert.ok(navigated[0].startsWith("mailto:owner@example.com?")); assert.equal(navigated[0], url); const params = new URL(navigated[0]).searchParams; assert.match(params.get("body"), /Grace Hopper/); });