Plan Your Visit landing page for visit.waterschurch.org
Next.js 16 static export + PHP 8 API for cPanel/Apache hosting. - Location-aware campus finder (IP estimate, optional precise location, ZIP search) - Three-step Plan Your Visit form with separate email/SMS consent - PHP API: validation, anti-spam, rate limiting, SQLite lead store, Mailchimp and Sinch Engage adapters, idempotent retries via cron - Attribution capture (utm_*, gclid/gbraid/wbraid, fbclid, ttclid, twclid) and consent-aware analytics hooks - Vitest and PHP test suites, cPanel deployment script and docs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { attributionForApi, getAttribution, inferPlatform, parseAttribution } from "@/lib/attribution";
|
||||
|
||||
describe("parseAttribution", () => {
|
||||
const now = new Date("2026-09-22T12:00:00Z");
|
||||
it("captures utm, click ids, variant, referrer and landing url", () => {
|
||||
const a = parseAttribution(
|
||||
"https://visit.waterschurch.org/?utm_source=facebook&utm_medium=paid_social&utm_campaign=fall-visit&utm_content=video1&utm_term=church+near+me&fbclid=ABC123&v=b",
|
||||
"https://l.facebook.com/",
|
||||
now,
|
||||
"a",
|
||||
);
|
||||
expect(a.utm_source).toBe("facebook");
|
||||
expect(a.utm_medium).toBe("paid_social");
|
||||
expect(a.utm_campaign).toBe("fall-visit");
|
||||
expect(a.utm_content).toBe("video1");
|
||||
expect(a.utm_term).toBe("church near me");
|
||||
expect(a.fbclid).toBe("ABC123");
|
||||
expect(a.variant).toBe("b");
|
||||
expect(a.referrer).toBe("https://l.facebook.com/");
|
||||
expect(a.landing_url).toContain("utm_campaign=fall-visit");
|
||||
expect(a.first_seen_at).toBe(now.toISOString());
|
||||
});
|
||||
it("supports Google, TikTok and X identifiers", () => {
|
||||
const a = parseAttribution("https://x.test/?gclid=G1&gbraid=GB&wbraid=WB&ttclid=T1&twclid=X1", "", now, "a");
|
||||
expect(a).toMatchObject({ gclid: "G1", gbraid: "GB", wbraid: "WB", ttclid: "T1", twclid: "X1" });
|
||||
});
|
||||
it("accepts tt_click_id as a TikTok alias", () => {
|
||||
expect(parseAttribution("https://x.test/?tt_click_id=T9", "", now, "a").ttclid).toBe("T9");
|
||||
});
|
||||
it("tolerates missing or malformed parameters", () => {
|
||||
const a = parseAttribution("not a url", "", now, "a");
|
||||
expect(a.variant).toBe("a");
|
||||
expect(a.utm_source).toBeUndefined();
|
||||
const b = parseAttribution("https://x.test/?utm_source=&gclid=%3Cscript%3Ealert(1)%3C/script%3E", "", now, "a");
|
||||
expect(b.utm_source).toBeUndefined();
|
||||
expect(b.gclid).toBe("scriptalert(1)/script");
|
||||
});
|
||||
it("caps overly long values", () => {
|
||||
const long = "x".repeat(500);
|
||||
expect(parseAttribution(`https://x.test/?utm_campaign=${long}`, "", now, "a").utm_campaign).toHaveLength(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("inferPlatform", () => {
|
||||
it("derives the ad platform", () => {
|
||||
expect(inferPlatform({ gclid: "1" })).toBe("google");
|
||||
expect(inferPlatform({ fbclid: "1" })).toBe("facebook");
|
||||
expect(inferPlatform({ utm_source: "instagram" })).toBe("instagram");
|
||||
expect(inferPlatform({ ttclid: "1" })).toBe("tiktok");
|
||||
expect(inferPlatform({ utm_source: "x" })).toBe("x");
|
||||
expect(inferPlatform({})).toBe("direct");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAttribution (browser)", () => {
|
||||
beforeEach(() => {
|
||||
window.sessionStorage.clear();
|
||||
window.history.replaceState({}, "", "/?utm_source=tiktok&utm_campaign=c1&ttclid=T1");
|
||||
});
|
||||
it("captures once and persists across calls", () => {
|
||||
const first = getAttribution("a");
|
||||
expect(first.utm_source).toBe("tiktok");
|
||||
window.history.replaceState({}, "", "/");
|
||||
const second = getAttribution("a");
|
||||
expect(second.utm_campaign).toBe("c1");
|
||||
expect(second.first_seen_at).toBe(first.first_seen_at);
|
||||
});
|
||||
it("merges a new click id from a second ad click without losing first-touch data", () => {
|
||||
const first = getAttribution("a");
|
||||
window.history.replaceState({}, "", "/?utm_source=facebook&fbclid=F1");
|
||||
const merged = getAttribution("a");
|
||||
expect(merged.fbclid).toBe("F1");
|
||||
expect(merged.ttclid).toBe("T1");
|
||||
expect(merged.first_seen_at).toBe(first.first_seen_at);
|
||||
});
|
||||
it("lets an explicit ?v= override the session variant", () => {
|
||||
expect(getAttribution("a").variant).toBe("a");
|
||||
window.history.replaceState({}, "", "/?v=b");
|
||||
expect(getAttribution("a").variant).toBe("b");
|
||||
});
|
||||
it("flattens to strings for the API", () => {
|
||||
const flat = attributionForApi(getAttribution("a"));
|
||||
for (const v of Object.values(flat)) expect(typeof v).toBe("string");
|
||||
expect(flat.utm_source).toBe("tiktok");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user