fix(builder): deterministic ids for NumberCounter + form-relay marker
Two components of the det-id bug class were missed in the earlier migration: NumberCounter's wrapper/counter ids and count-up script, and the shared form-relay-wiring fid (used by ContactForm/FormContainer). Both used Math.random() for exported HTML ids, breaking caching/diffing across exports. Migrate both to scopeId(nodeId, fallbackSeed, prefix), threading nodeId through NumberCounter.toHtml and relayFormWiring (via ContactForm.toHtml and FormContainer.toHtml, both now passing nodeId as their 3rd arg).
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { relayFormWiring } from './form-relay-wiring';
|
||||
|
||||
describe('relayFormWiring deterministic + unique fid (thread node id, no Math.random)', () => {
|
||||
test('no recipient: no relay, no marker, no fid needed', () => {
|
||||
const wiring = relayFormWiring(undefined, undefined, '/legacy', 'node-1');
|
||||
expect(wiring.useRelay).toBe(false);
|
||||
expect(wiring.marker).toBe('');
|
||||
});
|
||||
|
||||
test('same node id -> identical marker + placeholder ids across two calls (deterministic)', () => {
|
||||
const w1 = relayFormWiring('a@b.com', '/thx', undefined, 'node-form1');
|
||||
const w2 = relayFormWiring('a@b.com', '/thx', undefined, 'node-form1');
|
||||
expect(w1.marker).toBe(w2.marker);
|
||||
expect(w1.actionAttr).toBe(w2.actionAttr);
|
||||
});
|
||||
|
||||
test('marker id and placeholder id always match each other', () => {
|
||||
const w = relayFormWiring('a@b.com', '/thx', undefined, 'node-form1');
|
||||
const mid = w.marker.match(/id="([^"]+)"/)![1];
|
||||
expect(w.actionAttr).toBe(`__WHP_FORM_ACTION__${mid}__`);
|
||||
});
|
||||
|
||||
test('different node ids -> different fids (no collision)', () => {
|
||||
const w1 = relayFormWiring('a@b.com', '/thx', undefined, 'node-form1');
|
||||
const w2 = relayFormWiring('a@b.com', '/thx', undefined, 'node-form2');
|
||||
const mid1 = w1.marker.match(/id="([^"]+)"/)![1];
|
||||
const mid2 = w2.marker.match(/id="([^"]+)"/)![1];
|
||||
expect(mid1).not.toBe(mid2);
|
||||
});
|
||||
|
||||
test('no nodeId (legacy call): still deterministic across repeated calls, not random', () => {
|
||||
const w1 = relayFormWiring('a@b.com', '/thx', undefined);
|
||||
const w2 = relayFormWiring('a@b.com', '/thx', undefined);
|
||||
expect(w1.marker).toBe(w2.marker);
|
||||
expect(w1.actionAttr).toBe(w2.actionAttr);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@
|
||||
* HTML — see PR #47 review).
|
||||
*/
|
||||
|
||||
import { escapeAttr, safeUrl } from './escape';
|
||||
import { escapeAttr, safeUrl, scopeId } from './escape';
|
||||
|
||||
export interface RelayWiring {
|
||||
/** true when a recipient is set (relay path); false = legacy formAction fallback */
|
||||
@@ -26,16 +26,21 @@ export interface RelayWiring {
|
||||
* @param recipientEmail the "Send submissions to" address (empty/undefined = no relay)
|
||||
* @param thankYouUrl optional post-submit redirect (blank = hosted thank-you page)
|
||||
* @param fallbackAction the form's existing action to use when no recipient is set
|
||||
* @param nodeId the Craft node id of the calling form component, used to scope
|
||||
* the marker/placeholder id deterministically and uniquely --
|
||||
* see `scopeId` in ./escape. Falls back to a stable hash of the
|
||||
* recipient/thankYouUrl/fallbackAction when omitted (never random).
|
||||
*/
|
||||
export function relayFormWiring(
|
||||
recipientEmail: string | undefined,
|
||||
thankYouUrl: string | undefined,
|
||||
fallbackAction: string | undefined,
|
||||
nodeId?: string,
|
||||
): RelayWiring {
|
||||
if (!recipientEmail) {
|
||||
return { useRelay: false, marker: '', actionAttr: escapeAttr(safeUrl(fallbackAction || '#')), honeypot: '' };
|
||||
}
|
||||
const fid = 'F' + Math.random().toString(36).slice(2, 8);
|
||||
const fid = scopeId(nodeId, `${recipientEmail}::${thankYouUrl || ''}::${fallbackAction || ''}`, 'F');
|
||||
return {
|
||||
useRelay: true,
|
||||
marker: `<!--WHP-FORM id="${fid}" recipient="${escapeAttr(recipientEmail)}" thankyou="${escapeAttr(thankYouUrl || '')}"-->`,
|
||||
|
||||
Reference in New Issue
Block a user