@@ -4,6 +4,8 @@ import { AllExtensions } from '../extensions/extension-bundles'
44import { Autocapture } from '../autocapture'
55import { SessionRecording } from '../extensions/replay/session-recording'
66import { createPosthogInstance } from './helpers/posthog-instance'
7+ import { uuidv7 } from '../uuidv7'
8+ import { assignableWindow } from '../utils/globals'
79
810describe ( '__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