Files

88 lines
3.8 KiB
TypeScript
Raw Permalink Normal View History

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