Skip to content

Commit 60847a5

Browse files
committed
test: feature flag tree shaking and graceful degredation
1 parent 12f2a42 commit 60847a5

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

packages/browser/src/__tests__/extension-classes.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { AllExtensions } from '../extensions/extension-bundles'
44
import { Autocapture } from '../autocapture'
55
import { SessionRecording } from '../extensions/replay/session-recording'
66
import { createPosthogInstance } from './helpers/posthog-instance'
7+
import { uuidv7 } from '../uuidv7'
8+
import { assignableWindow } from '../utils/globals'
79

810
describe('__extensionClasses enrollment', () => {
911
let savedDefaults: PostHogConfig['__extensionClasses']
@@ -130,6 +132,7 @@ describe('extension lifecycle', () => {
130132
'exceptionObserver',
131133
'exceptions',
132134
'experiments',
135+
'featureFlags',
133136
'heatmaps',
134137
'historyAutocapture',
135138
'logs',
@@ -260,5 +263,100 @@ describe('extension lifecycle', () => {
260263

261264
expect(callback).toHaveBeenCalledWith([], { isLoaded: false, error: 'Surveys module not available' })
262265
})
266+
267+
it('featureFlags getter returns a stub when extension is not loaded', async () => {
268+
PostHog.__defaultExtensionClasses = {}
269+
270+
const posthog = await createPosthogInstance(undefined, {
271+
__preview_deferred_init_extensions: false,
272+
capture_pageview: false,
273+
})
274+
275+
expect((posthog as any)._featureFlags).toBeUndefined()
276+
277+
// The getter should return a stub, not throw
278+
const flags = posthog.featureFlags
279+
expect(flags).toBeDefined()
280+
expect(flags.getFeatureFlag('test')).toBeUndefined()
281+
expect(flags.isFeatureEnabled('test')).toBeUndefined()
282+
})
283+
284+
it('onFeatureFlags calls back with error when extension is not loaded', async () => {
285+
PostHog.__defaultExtensionClasses = {}
286+
287+
const posthog = await createPosthogInstance(undefined, {
288+
__preview_deferred_init_extensions: false,
289+
capture_pageview: false,
290+
})
291+
292+
const callback = jest.fn()
293+
const unsubscribe = posthog.onFeatureFlags(callback)
294+
295+
expect(callback).toHaveBeenCalledWith([], {}, { errorsLoading: true })
296+
expect(unsubscribe).toEqual(expect.any(Function))
297+
})
298+
299+
it('getFeatureFlag returns undefined when extension is not loaded', async () => {
300+
PostHog.__defaultExtensionClasses = {}
301+
302+
const posthog = await createPosthogInstance(undefined, {
303+
__preview_deferred_init_extensions: false,
304+
capture_pageview: false,
305+
})
306+
307+
expect(posthog.getFeatureFlag('test-flag')).toBeUndefined()
308+
})
309+
310+
it('reloadFeatureFlags is a no-op when extension is not loaded', async () => {
311+
PostHog.__defaultExtensionClasses = {}
312+
313+
const posthog = await createPosthogInstance(undefined, {
314+
__preview_deferred_init_extensions: false,
315+
capture_pageview: false,
316+
})
317+
318+
expect(() => posthog.reloadFeatureFlags()).not.toThrow()
319+
})
320+
})
321+
322+
describe('with AllExtensions loaded', () => {
323+
it('featureFlags getter returns the real instance', async () => {
324+
PostHog.__defaultExtensionClasses = AllExtensions
325+
326+
const posthog = await createPosthogInstance(undefined, {
327+
__preview_deferred_init_extensions: false,
328+
capture_pageview: false,
329+
})
330+
331+
expect((posthog as any)._featureFlags).toBeDefined()
332+
expect(posthog.featureFlags).toBe((posthog as any)._featureFlags)
333+
})
334+
335+
it('bootstrap feature flags work through extension initialize()', async () => {
336+
PostHog.__defaultExtensionClasses = AllExtensions
337+
const token = uuidv7()
338+
339+
assignableWindow._POSTHOG_REMOTE_CONFIG = {
340+
[token]: { config: {}, siteApps: [] },
341+
} as any
342+
343+
const posthog = await createPosthogInstance(token, {
344+
__preview_deferred_init_extensions: false,
345+
capture_pageview: false,
346+
bootstrap: {
347+
featureFlags: {
348+
'test-flag': true,
349+
'variant-flag': 'control',
350+
'disabled-flag': false,
351+
},
352+
},
353+
})
354+
355+
expect(posthog.getFeatureFlag('test-flag')).toBe(true)
356+
expect(posthog.getFeatureFlag('variant-flag')).toBe('control')
357+
// Disabled flags should not be returned
358+
expect(posthog.getFeatureFlag('disabled-flag')).toBeUndefined()
359+
expect(posthog.flagsEndpointWasHit).toBe(true)
360+
})
263361
})
264362
})

0 commit comments

Comments
 (0)