Files
insights-js/packages/next/tests/InsightsProvider.test.tsx
zeekay ec6f6b50c1 chore: kill ALL posthog — zero refs, zero filenames (1452 -> 0)
We own the ingestion server (hanzoai/insights: Django + rust/capture + nodejs
plugin) AND this SDK, so there is no external wire contract to preserve. The
server is already 100% posthog-free; this SDK was the last laggard.

- $last_posthog_reset -> $last_insights_reset. This FIXES A LIVE MISMATCH: the
  server taxonomy (insights/taxonomy/taxonomy.py, insights/insightsql/ai.py)
  already expected the insights form while the SDK emitted the posthog form.
- __PosthogExtensions__ -> __InsightsExtensions__; all PostHog/posthog/POSTHOG
  identifiers -> Insights/insights/INSIGHTS (src, tests, playground, playwright,
  scripts, rrweb subpackages, package.json keywords).
- Hosts: DEFAULT_PLUGIN_HOST + DEFAULT_NUXT_HOST + the core host default pointed
  at PostHog's cloud (us.i.posthog.com) — a real bug. All -> insights.hanzo.ai.
- Removed the us-assets/eu-assets CDN split in getRemoteConfig (PostHog-specific;
  Hanzo serves remote config from the same host) — it had degenerated to a no-op.
- Fixed generate-docs writing to a stray references/@hanzo/ subdir.
- Renamed the 3 posthog-named test files; dropped 58MB of stale reference
  snapshots named for upstream posthog versions we never shipped (they are
  generated output — references/ is now gitignored).

Verified: typecheck errors 42 (HEAD) -> 18 (all pre-existing test-mock
looseness); types package 0; every package.json valid with @hanzo/* names.
Guard: grep -rioE 'posthog' packages ... | wc -l  => 0
2026-07-16 09:09:18 -07:00

488 lines
18 KiB
TypeScript

import React from 'react'
import { render, screen } from '@testing-library/react'
import { InsightsProvider } from '../src/app/InsightsProvider'
// Mock ClientInsightsProvider
const mockClientProvider = jest.fn(({ children }: { children: React.ReactNode }) => (
<div data-testid="client-provider">{children}</div>
))
jest.mock('../src/client/ClientInsightsProvider', () => ({
ClientInsightsProvider: (props: any) => mockClientProvider(props),
}))
// Mock next/headers
jest.mock('next/headers.js', () => ({
cookies: jest.fn(),
}))
// Mock nodeClientCache — the mock fn must be declared with `var` so it's
// hoisted and available inside the jest.mock factory (which Jest hoists above
// `const`/`let` declarations).
var mockGetAllFlagsAndPayloads = jest.fn()
jest.mock('../src/server/nodeClientCache', () => ({
getOrCreateNodeClient: jest.fn().mockImplementation(() => ({
getAllFlagsAndPayloads: (...args: any[]) => mockGetAllFlagsAndPayloads(...args),
})),
}))
// in test, just call the function directly (no caching)
;(React as any).cache = (fn: Function) => fn
describe('InsightsProvider', () => {
const originalEnv = process.env
beforeEach(() => {
mockClientProvider.mockClear()
process.env = { ...originalEnv }
})
afterAll(() => {
process.env = originalEnv
})
it('renders children via ClientInsightsProvider', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div data-testid="child">Hello</div>,
})
render(element)
expect(screen.getByTestId('child')).toBeInTheDocument()
})
it('passes apiKey and clientOptions to ClientInsightsProvider', async () => {
const clientOptions = { api_host: 'https://custom.insights.example.com' }
const element = await InsightsProvider({
apiKey: 'phc_test123',
clientOptions,
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
apiKey: 'phc_test123',
options: expect.objectContaining({ api_host: 'https://custom.insights.example.com' }),
})
)
})
it('trims apiKey and api_host before passing them to ClientInsightsProvider', async () => {
const element = await InsightsProvider({
apiKey: ' phc_test123\n',
clientOptions: { api_host: ' https://custom.insights.example.com/\t ' },
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
apiKey: 'phc_test123',
options: expect.objectContaining({ api_host: 'https://custom.insights.example.com/' }),
})
)
})
it('does not pass bootstrap when bootstrapFlags is not set', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
bootstrap: undefined,
})
)
})
describe('Next.js client defaults', () => {
it('applies Next.js-specific defaults when no options provided', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
api_host: 'https://insights.hanzo.ai',
persistence: 'localStorage+cookie',
opt_out_capturing_persistence_type: 'cookie',
opt_out_persistence_by_default: true,
}),
})
)
})
it('allows user clientOptions to override defaults', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
clientOptions: {
persistence: 'memory',
opt_out_persistence_by_default: false,
},
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
persistence: 'memory',
opt_out_capturing_persistence_type: 'cookie',
opt_out_persistence_by_default: false,
}),
})
)
})
})
it('does not call cookies() when bootstrapFlags is off (static-safe)', async () => {
const { cookies } = require('next/headers.js')
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div>Child</div>,
})
render(element)
expect(cookies).not.toHaveBeenCalled()
})
it('warns and renders children without ClientInsightsProvider when apiKey is empty and env var is not set', async () => {
delete process.env.NEXT_PUBLIC_INSIGHTS_KEY
const warnSpy = jest.spyOn(console, 'warn').mockImplementation()
const element = await InsightsProvider({
apiKey: '',
children: <div data-testid="child">Child</div>,
})
render(element)
expect(screen.getByTestId('child')).toBeInTheDocument()
expect(mockClientProvider).not.toHaveBeenCalled()
expect(warnSpy).toHaveBeenCalledWith('[Insights Next.js] apiKey is required — Insights will not be initialized')
warnSpy.mockRestore()
})
it('warns when apiKey does not start with phc_', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation()
const element = await InsightsProvider({
apiKey: 'not_a_valid_key',
children: <div>Child</div>,
})
render(element)
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('does not start with "phc_"'))
warnSpy.mockRestore()
})
it('does not warn when apiKey starts with phc_', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation()
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div>Child</div>,
})
render(element)
expect(warnSpy).not.toHaveBeenCalled()
warnSpy.mockRestore()
})
describe('environment variable defaults', () => {
it('reads apiKey from NEXT_PUBLIC_INSIGHTS_KEY when prop is omitted', async () => {
process.env.NEXT_PUBLIC_INSIGHTS_KEY = 'phc_from_env'
const element = await InsightsProvider({
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'phc_from_env' }))
})
it('prefers apiKey prop over env var', async () => {
process.env.NEXT_PUBLIC_INSIGHTS_KEY = 'phc_from_env'
const element = await InsightsProvider({
apiKey: 'phc_from_prop',
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'phc_from_prop' }))
})
it('reads api_host from NEXT_PUBLIC_INSIGHTS_HOST when not in clientOptions', async () => {
process.env.NEXT_PUBLIC_INSIGHTS_HOST = 'https://insights.hanzo.ai'
const element = await InsightsProvider({
apiKey: 'phc_test123',
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({ api_host: 'https://insights.hanzo.ai' }),
})
)
})
it('trims apiKey and api_host from env vars', async () => {
process.env.NEXT_PUBLIC_INSIGHTS_KEY = ' phc_from_env\n'
process.env.NEXT_PUBLIC_INSIGHTS_HOST = ' https://insights.hanzo.ai/\t '
const element = await InsightsProvider({
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
apiKey: 'phc_from_env',
options: expect.objectContaining({ api_host: 'https://insights.hanzo.ai/' }),
})
)
})
it('prefers clientOptions.api_host over env var', async () => {
process.env.NEXT_PUBLIC_INSIGHTS_HOST = 'https://insights.hanzo.ai'
const element = await InsightsProvider({
apiKey: 'phc_test123',
clientOptions: { api_host: 'https://custom.insights.example.com' },
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({ api_host: 'https://custom.insights.example.com' }),
})
)
})
})
describe('with bootstrapFlags', () => {
const identifiedCookieValue = JSON.stringify({
distinct_id: 'user_abc',
$device_id: 'device_xyz',
$user_state: 'identified',
})
const anonymousCookieValue = JSON.stringify({ distinct_id: 'device_xyz', $device_id: 'device_xyz' })
function setupCookieMock(cookieValue: string) {
const { cookies } = require('next/headers.js')
cookies.mockResolvedValue({
get: jest.fn((name: string) => {
if (name === 'ph_phc_test123_insights') {
return { name, value: cookieValue }
}
// Consent cookie — opted in so flag evaluation proceeds
if (name === '__ph_opt_in_out_phc_test123') {
return { name, value: '1' }
}
return undefined
}),
})
}
beforeEach(() => {
mockGetAllFlagsAndPayloads.mockReset()
mockGetAllFlagsAndPayloads.mockResolvedValue({
featureFlags: { 'flag-1': true },
featureFlagPayloads: { 'flag-1': { color: 'blue' } },
})
setupCookieMock(identifiedCookieValue)
})
it('evaluates all flags when bootstrapFlags is true', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).toHaveBeenCalledWith('user_abc', {})
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
bootstrap: expect.objectContaining({
featureFlags: { 'flag-1': true },
featureFlagPayloads: { 'flag-1': { color: 'blue' } },
}),
})
)
})
it('evaluates specific flags when flagKeys provided', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: { flags: ['flag-1'] },
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).toHaveBeenCalledWith('user_abc', { flagKeys: ['flag-1'] })
})
it('always includes payloads in bootstrap', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).toHaveBeenCalled()
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
bootstrap: expect.objectContaining({
featureFlagPayloads: { 'flag-1': { color: 'blue' } },
}),
})
)
})
it('disables first flag load on client when bootstrap succeeds', async () => {
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
advanced_disable_feature_flags_on_first_load: true,
}),
})
)
})
it('renders without bootstrap when flag evaluation fails', async () => {
mockGetAllFlagsAndPayloads.mockRejectedValue(new Error('network timeout'))
const warnSpy = jest.spyOn(console, 'warn').mockImplementation()
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
bootstrap: undefined,
})
)
expect(warnSpy).toHaveBeenCalledWith(
'[Insights Next.js] Failed to evaluate bootstrap flags:',
expect.any(Error)
)
warnSpy.mockRestore()
})
it('does not disable first flag load when bootstrap fails', async () => {
mockGetAllFlagsAndPayloads.mockRejectedValue(new Error('network timeout'))
jest.spyOn(console, 'warn').mockImplementation()
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockClientProvider).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.not.objectContaining({
advanced_disable_feature_flags_on_first_load: true,
}),
})
)
})
})
describe('consent awareness', () => {
const identifiedCookieValue = JSON.stringify({
distinct_id: 'user_abc',
$device_id: 'device_xyz',
$user_state: 'identified',
})
function setupCookiesWithConsent(cookies: Record<string, string>) {
const { cookies: cookiesFn } = require('next/headers.js')
cookiesFn.mockResolvedValue({
get: jest.fn((name: string) => {
const value = cookies[name]
return value !== undefined ? { name, value } : undefined
}),
})
}
beforeEach(() => {
mockGetAllFlagsAndPayloads.mockReset()
mockGetAllFlagsAndPayloads.mockResolvedValue({
featureFlags: { 'flag-1': true },
featureFlagPayloads: { 'flag-1': { color: 'blue' } },
})
})
it('skips flag evaluation when consent cookie is 0', async () => {
setupCookiesWithConsent({
ph_phc_test123_insights: identifiedCookieValue,
__ph_opt_in_out_phc_test123: '0',
})
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).not.toHaveBeenCalled()
expect(mockClientProvider).toHaveBeenCalledWith(expect.objectContaining({ bootstrap: undefined }))
})
it('evaluates flags when consent cookie is 1', async () => {
setupCookiesWithConsent({
ph_phc_test123_insights: identifiedCookieValue,
__ph_opt_in_out_phc_test123: '1',
})
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).toHaveBeenCalledWith('user_abc', {})
})
it('skips flag evaluation when no consent cookie and opt_out_capturing_by_default is true', async () => {
setupCookiesWithConsent({
ph_phc_test123_insights: identifiedCookieValue,
})
const element = await InsightsProvider({
apiKey: 'phc_test123',
clientOptions: { opt_out_capturing_by_default: true },
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).not.toHaveBeenCalled()
expect(mockClientProvider).toHaveBeenCalledWith(expect.objectContaining({ bootstrap: undefined }))
})
it('evaluates flags when no consent cookie and opt_out_capturing_by_default is false (default)', async () => {
setupCookiesWithConsent({
ph_phc_test123_insights: identifiedCookieValue,
})
const element = await InsightsProvider({
apiKey: 'phc_test123',
bootstrapFlags: true,
children: <div>Child</div>,
})
render(element)
expect(mockGetAllFlagsAndPayloads).toHaveBeenCalledWith('user_abc', {})
})
})
})