diff --git a/tests/contact.test.js b/tests/contact.test.js new file mode 100644 index 0000000..d2a848d --- /dev/null +++ b/tests/contact.test.js @@ -0,0 +1,109 @@ +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/); +});