Files
supabase/packages/common/telemetry-first-touch-store.test.ts
Sean Oliver c8e98cec0e feat(growth): in-memory first-touch attribution store (#43570)
## Changes

Introduces two new files in `packages/common`:

- **`telemetry-first-touch-store.ts`** — a module-scoped singleton that
holds first-touch attribution data (referrer, UTM params, page URL) in
memory. Writes once on first load, cleared after the initial pageview
event fires or on opt-out. No device storage involved.
- **`useFirstTouchStore.tsx`** — a React hook that captures attribution
data on initial page load and writes it into the store, gated on the
`enabled` flag so it only runs where consent has been handled.

Trade-off: data is lost on a hard reload before consent is granted —
accepted edge case per GROWTH-656.

Follows the same module-scope pattern already used by `posthogClient`
and `consentState`.

## Testing

- Verify first-touch data is captured on initial load and readable by
`PageTelemetry` after consent
- Verify no cookie is set before consent
- Verify data is cleared after initial pageview fires

GROWTH-656
2026-03-30 11:00:35 -07:00

61 lines
1.6 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest'
import {
clearFirstTouchData,
getFirstTouchData,
setFirstTouchData,
} from './telemetry-first-touch-store'
const makeFakeData = (pathname: string) =>
({
page_url: `https://supabase.com${pathname}`,
pathname,
page_title: 'Test',
session_id: 'test-session',
ph: { referrer: 'https://google.com' },
}) as ReturnType<typeof getFirstTouchData> & {}
describe('telemetry-first-touch-store', () => {
// Reset between tests so module-scoped state doesn't leak
afterEach(() => {
clearFirstTouchData()
})
it('returns null before any write', () => {
expect(getFirstTouchData()).toBeNull()
})
it('stores data and returns it on read', () => {
const data = makeFakeData('/pricing')
setFirstTouchData(data)
expect(getFirstTouchData()).toEqual(data)
})
it('is write-once: a second call with different data is a no-op', () => {
const first = makeFakeData('/pricing')
const second = makeFakeData('/docs')
setFirstTouchData(first)
setFirstTouchData(second)
expect(getFirstTouchData()).toEqual(first)
})
it('clearFirstTouchData resets to null', () => {
setFirstTouchData(makeFakeData('/pricing'))
clearFirstTouchData()
expect(getFirstTouchData()).toBeNull()
})
it('allows writing again after clearFirstTouchData', () => {
const first = makeFakeData('/pricing')
const second = makeFakeData('/docs')
setFirstTouchData(first)
clearFirstTouchData()
setFirstTouchData(second)
expect(getFirstTouchData()).toEqual(second)
})
})