import { describe, test, expect } from 'vitest'; import { ContactForm } from './ContactForm'; const toHtml = (ContactForm as any).toHtml; describe('ContactForm.toHtml relay wiring', () => { test('with recipientEmail: emits marker, placeholder action, honeypot', () => { const { html } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, ''); expect(html).toMatch(//); expect(html).toMatch(/action="__WHP_FORM_ACTION__F_[0-9a-z]+__"/); expect(html).toContain('method="POST"'); expect(html).toContain('name="_gotcha"'); // marker id and action id match const mid = html.match(/id="(F_[0-9a-z]+)"/)![1]; expect(html).toContain(`__WHP_FORM_ACTION__${mid}__`); }); test('without recipientEmail: no marker, falls back to formAction', () => { const { html } = toHtml({ formAction: '/legacy', fields: [] }, ''); expect(html).not.toContain('WHP-FORM'); expect(html).toContain('action="/legacy"'); expect(html).not.toContain('_gotcha'); // Backward-compat: ensure non-relay output is byte-identical (no extra blank lines from honeypot) expect(html).not.toMatch(/]*>\n\s*\n/); }); test('without recipientEmail + real fields: byte-clean legacy output (realistic case)', () => { // The empty-fields case is NOT byte-identical to the old code (the old // template emitted a stray whitespace line when fields was empty; the new // ternary drops it). Real forms always have fields, so pin THAT scenario: // no marker, no honeypot, and no whitespace-only line between
and // the first field. const fields = [{ type: 'text', label: 'Name', name: 'name', placeholder: 'Your name', required: true }]; const { html } = toHtml({ formAction: '/legacy', fields }, ''); expect(html).not.toContain('WHP-FORM'); expect(html).not.toContain('_gotcha'); expect(html).toContain('action="/legacy"'); expect(html).not.toMatch(/]*>\n\s*\n/); // First field renders directly after the form tag (no stray blank line). expect(html).toMatch(/]*>\n\s*
{ // The published form-sender relay (form-sender/app/submit.php) delivers // success via a full-page 303 redirect to thankYouUrl or a hosted // thanks.php page -- there is no in-page JS to reveal an inline success // element. So successMessage is emitted as a forward-compatible data // attribute for a future AJAX/JS submission mode, not a live DOM element. test('with successMessage set: emits it as an escaped data attribute on the form', () => { const { html } = toHtml({ successMessage: "We'll be in touch!", fields: [] }, ''); expect(html).toContain('data-whp-success-message="We'll be in touch!"'); }); test('without successMessage: no data attribute emitted', () => { const { html } = toHtml({ fields: [] }, ''); expect(html).not.toContain('data-whp-success-message'); }); test('escapes attribute-breakout attempts in successMessage', () => { const { html } = toHtml({ successMessage: 'x" onerror="alert(1)', fields: [] }, ''); expect(html).not.toContain('onerror="alert(1)"'); }); }); describe('ContactForm.toHtml relay marker deterministic + unique via node id (no Math.random)', () => { test('same node id -> identical marker+placeholder ids across two calls', () => { const { html: html1 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1'); const { html: html2 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1'); expect(html1).toBe(html2); }); test('marker id always equals the placeholder id it pairs with', () => { const { html } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1'); const mid = html.match(/