/* RFC-1192: tests for the conditional cookie-consent surface — self-gating on the host node, recorded-choice skip, accept/decline flag writes, and the wg:cookie-consent CustomEvent (AC-2 conditional render, AC-3 flag record). Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-1192: initial consent-surface tests. RFC-1198: cookie-free referral attribution — /r/ partner landings + voluntary referrer field Replace the RFC-1191 wg_ref cookie capture with URL+form attribution: static /r/ and //r/ partner landings (noindex, zero JS, consent-gated via publicNameConsentRef), ?ref prefill on the existing referrerField rail, server-side slug resolution against the bundled partners.yaml, referral_partner_slug/name on the Lagebild ingress contract, legacy /?ref -> /r/ 302 in the generated worker, and full removal of the cookie capture module (wg_ref, referral.attribution entitlement, payload.attribution). */ import { afterEach, describe, expect, it, vi } from "vitest"; // RFC-1198: the live catalog is empty (consent surface dormant). The surface // mechanism is still contract-covered — the tests inject a synthetic catalog // entry so the gating/choice behavior stays exercised for future modules. vi.mock("../../policy/cookies.ts", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, COOKIE_MODULES: [ { moduleId: "example.module", entitlement: "example.module", cookies: [{ name: "example_pref", maxAgeDays: 30, sameSite: "Lax", purpose: "test" }], }, ], }; }); import { COOKIE_CONSENT_EVENT, initCookieConsent } from "../cookie-consent.ts"; import { CONSENT_FLAG_KEY, readConsentFlag } from "../cookie-write-gate.ts"; interface FakeElement { attrs: Record; children: FakeElement[]; listeners: Record void) | undefined>; style: { cssText: string }; textContent: string; type: string; setAttribute: (name: string, value: string) => void; appendChild: (child: FakeElement) => void; addEventListener: (type: string, fn: () => void) => void; remove: ReturnType; click: () => void; } function fakeElement(): FakeElement { const el: FakeElement = { attrs: {}, children: [], listeners: {}, style: { cssText: "" }, textContent: "", type: "", setAttribute: (n, v) => { el.attrs[n] = v; }, appendChild: (c) => { el.children.push(c); }, addEventListener: (t, fn) => { el.listeners[t] = fn; }, remove: vi.fn(), click: () => el.listeners.click?.(), }; return el; } function stubDom(opts: { hostPresent: boolean; flagRaw?: string | null }) { const data = new Map(); if (opts.flagRaw !== undefined && opts.flagRaw !== null) { data.set(CONSENT_FLAG_KEY, opts.flagRaw); } const host = fakeElement(); const documentStub = { querySelector: vi.fn((sel: string) => sel === "[data-cookie-consent-host]" && opts.hostPresent ? host : null, ), createElement: vi.fn(() => fakeElement()), }; const windowStub = { localStorage: { getItem: (k: string) => data.get(k) ?? null, setItem: (k: string, v: string) => void data.set(k, v), removeItem: (k: string) => void data.delete(k), }, dispatchEvent: vi.fn(), }; class FakeCustomEvent { type: string; detail: unknown; constructor(type: string, init?: { detail?: unknown }) { this.type = type; this.detail = init?.detail; } } vi.stubGlobal("document", documentStub); vi.stubGlobal("window", windowStub); vi.stubGlobal("CustomEvent", FakeCustomEvent); return { host, documentStub, windowStub, data }; } afterEach(() => { vi.unstubAllGlobals(); }); describe("initCookieConsent gating (AC-2)", () => { it("renders nothing when the host node is absent (unentitled site)", () => { const { host, documentStub } = stubDom({ hostPresent: false }); initCookieConsent(["example.module"]); expect(documentStub.createElement).not.toHaveBeenCalled(); expect(host.children).toHaveLength(0); }); it("renders nothing when a consent flag already exists", () => { const { documentStub } = stubDom({ hostPresent: true, flagRaw: JSON.stringify({ v: 1, at: new Date().toISOString(), granted: false }), }); initCookieConsent(["example.module"]); expect(documentStub.createElement).not.toHaveBeenCalled(); }); it("renders nothing when no catalog modules are active for the site", () => { const { host, documentStub } = stubDom({ hostPresent: true }); initCookieConsent([]); expect(documentStub.createElement).not.toHaveBeenCalled(); expect(host.children).toHaveLength(0); }); }); describe("initCookieConsent choices (AC-3)", () => { it("accept writes {v:1, at, granted:true} and dispatches the consent event", () => { const { host, windowStub } = stubDom({ hostPresent: true }); initCookieConsent(["example.module"]); expect(host.children).toHaveLength(1); const banner = host.children[0]; // last two appended children are the accept and decline buttons const accept = banner.children[banner.children.length - 2]; accept.click(); expect(readConsentFlag()?.granted).toBe(true); expect(windowStub.dispatchEvent).toHaveBeenCalledWith( expect.objectContaining({ type: COOKIE_CONSENT_EVENT, detail: { granted: true } }), ); expect(banner.remove).toHaveBeenCalled(); }); it("decline writes granted:false and dispatches the consent event", () => { const { host, windowStub } = stubDom({ hostPresent: true }); initCookieConsent(["example.module"]); const banner = host.children[0]; const decline = banner.children[banner.children.length - 1]; decline.click(); expect(readConsentFlag()?.granted).toBe(false); expect(windowStub.dispatchEvent).toHaveBeenCalledWith( expect.objectContaining({ type: COOKIE_CONSENT_EVENT, detail: { granted: false } }), ); }); });