ContactForm relay wiring (recipient, thank-you, honeypot, marker) #2
@@ -0,0 +1,43 @@
|
|||||||
|
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(/<!--WHP-FORM id="F[0-9a-z]+" recipient="a@b.com" thankyou="\/thx"-->/);
|
||||||
|
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(/<form[^>]*>\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 <form> 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(/<form[^>]*>\n\s*\n/);
|
||||||
|
// First field renders directly after the form tag (no stray blank line).
|
||||||
|
expect(html).toMatch(/<form[^>]*>\n\s*<div/);
|
||||||
|
expect(html).toContain('Name');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -21,6 +21,8 @@ interface ContactFormProps {
|
|||||||
labelColor?: string;
|
labelColor?: string;
|
||||||
inputBg?: string;
|
inputBg?: string;
|
||||||
inputBorder?: string;
|
inputBorder?: string;
|
||||||
|
recipientEmail?: string;
|
||||||
|
thankYouUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFields: ContactFormField[] = [
|
const defaultFields: ContactFormField[] = [
|
||||||
@@ -187,6 +189,23 @@ const ContactFormSettings: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Relay recipient */}
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Send submissions to (email)</label>
|
||||||
|
<input type="email" value={props.recipientEmail || ''}
|
||||||
|
onChange={(e) => setProp((p: ContactFormProps) => { p.recipientEmail = e.target.value; })}
|
||||||
|
placeholder="you@example.com" style={{ ...inputStyle, padding: '4px 8px', fontSize: 12 }} />
|
||||||
|
<p style={{ fontSize: 10, color: '#71717a', margin: '4px 0 0' }}>
|
||||||
|
Delivered via the site's contact-form relay. Requires the relay to be enabled on this server.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Thank-you page URL (optional)</label>
|
||||||
|
<input type="text" value={props.thankYouUrl || ''}
|
||||||
|
onChange={(e) => setProp((p: ContactFormProps) => { p.thankYouUrl = e.target.value; })}
|
||||||
|
placeholder="/thank-you (blank = hosted page)" style={{ ...inputStyle, padding: '4px 8px', fontSize: 12 }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Success Message */}
|
{/* Success Message */}
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Success Message</label>
|
<label style={{ fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }}>Success Message</label>
|
||||||
@@ -358,6 +377,8 @@ ContactForm.craft = {
|
|||||||
labelColor: '#374151',
|
labelColor: '#374151',
|
||||||
inputBg: '#ffffff',
|
inputBg: '#ffffff',
|
||||||
inputBorder: '#d1d5db',
|
inputBorder: '#d1d5db',
|
||||||
|
recipientEmail: '',
|
||||||
|
thankYouUrl: '',
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
canDrag: () => true,
|
canDrag: () => true,
|
||||||
@@ -414,10 +435,19 @@ ContactForm.craft = {
|
|||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const useRelay = !!props.recipientEmail;
|
||||||
|
const fid = 'F' + Math.random().toString(36).slice(2, 8);
|
||||||
|
const actionAttr = useRelay ? `__WHP_FORM_ACTION__${fid}__` : esc(props.formAction || '#');
|
||||||
|
const honeypot = useRelay
|
||||||
|
? `<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true">`
|
||||||
|
: '';
|
||||||
|
const marker = useRelay
|
||||||
|
? `<!--WHP-FORM id="${fid}" recipient="${esc(props.recipientEmail)}" thankyou="${esc(props.thankYouUrl || '')}"-->`
|
||||||
|
: '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
html: `<form action="${esc(props.formAction || '#')}" method="POST"${formStyle ? ` style="${formStyle}"` : ''}>
|
html: `${marker}<form action="${actionAttr}" method="POST"${formStyle ? ` style="${formStyle}"` : ''}>
|
||||||
${fieldsHtml}
|
${honeypot ? ` ${honeypot}\n` : ''}${fieldsHtml ? ` ${fieldsHtml}\n` : ''} <button type="submit"${btnStyle ? ` style="${btnStyle}"` : ''}>${esc(props.submitText || 'Send Message')}</button>
|
||||||
<button type="submit"${btnStyle ? ` style="${btnStyle}"` : ''}>${esc(props.submitText || 'Send Message')}</button>
|
|
||||||
</form>`,
|
</form>`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user