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"); }); });