@@ -234,6 +234,161 @@ describe('init', () => {
234234 } ) ;
235235 } ) ;
236236
237+ describe ( 'Spotlight environment variable support' , ( ) => {
238+ let originalProcess : typeof globalThis . process | undefined ;
239+
240+ afterEach ( ( ) => {
241+ if ( originalProcess !== undefined ) {
242+ globalThis . process = originalProcess ;
243+ } else {
244+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245+ delete ( globalThis as any ) . process ;
246+ }
247+ } ) ;
248+
249+ it ( 'uses environment variable when options.spotlight is undefined' , ( ) => {
250+ originalProcess = globalThis . process ;
251+ globalThis . process = {
252+ env : {
253+ SENTRY_SPOTLIGHT : 'true' ,
254+ } ,
255+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256+ } as any ;
257+
258+ // @ts -expect-error this is fine for testing
259+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
260+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : undefined } ) ;
261+ init ( options ) ;
262+
263+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
264+ // Spotlight integration should be added
265+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
266+ expect ( spotlightIntegration ) . toBeDefined ( ) ;
267+ } ) ;
268+
269+ it ( 'does not add Spotlight when environment variable is false' , ( ) => {
270+ originalProcess = globalThis . process ;
271+ globalThis . process = {
272+ env : {
273+ SENTRY_SPOTLIGHT : 'false' ,
274+ } ,
275+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276+ } as any ;
277+
278+ // @ts -expect-error this is fine for testing
279+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
280+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : undefined } ) ;
281+ init ( options ) ;
282+
283+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
284+ // Spotlight integration should NOT be added
285+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
286+ expect ( spotlightIntegration ) . toBeUndefined ( ) ;
287+ } ) ;
288+
289+ it ( 'options.spotlight=false takes precedence over environment variable' , ( ) => {
290+ originalProcess = globalThis . process ;
291+ globalThis . process = {
292+ env : {
293+ SENTRY_SPOTLIGHT : 'true' ,
294+ } ,
295+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
296+ } as any ;
297+
298+ // @ts -expect-error this is fine for testing
299+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
300+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : false } ) ;
301+ init ( options ) ;
302+
303+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
304+ // Spotlight integration should NOT be added even though env var is true
305+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
306+ expect ( spotlightIntegration ) . toBeUndefined ( ) ;
307+ } ) ;
308+
309+ it ( 'options.spotlight=url takes precedence over environment variable' , ( ) => {
310+ originalProcess = globalThis . process ;
311+ const customUrl = 'http://custom:1234/stream' ;
312+ globalThis . process = {
313+ env : {
314+ SENTRY_SPOTLIGHT : 'http://env:5678/stream' ,
315+ } ,
316+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
317+ } as any ;
318+
319+ // @ts -expect-error this is fine for testing
320+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
321+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : customUrl } ) ;
322+ init ( options ) ;
323+
324+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
325+ // Spotlight integration should be added (we can't easily check the URL here without deeper inspection)
326+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
327+ expect ( spotlightIntegration ) . toBeDefined ( ) ;
328+ } ) ;
329+
330+ it ( 'uses environment variable URL when options.spotlight=true' , ( ) => {
331+ originalProcess = globalThis . process ;
332+ globalThis . process = {
333+ env : {
334+ SENTRY_SPOTLIGHT : 'http://env:5678/stream' ,
335+ } ,
336+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
337+ } as any ;
338+
339+ // @ts -expect-error this is fine for testing
340+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
341+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : true } ) ;
342+ init ( options ) ;
343+
344+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
345+ // Spotlight integration should be added
346+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
347+ expect ( spotlightIntegration ) . toBeDefined ( ) ;
348+ } ) ;
349+
350+ it ( 'respects priority order: SENTRY_SPOTLIGHT over PUBLIC_SENTRY_SPOTLIGHT' , ( ) => {
351+ originalProcess = globalThis . process ;
352+ globalThis . process = {
353+ env : {
354+ SENTRY_SPOTLIGHT : 'true' ,
355+ PUBLIC_SENTRY_SPOTLIGHT : 'false' ,
356+ } ,
357+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
358+ } as any ;
359+
360+ // @ts -expect-error this is fine for testing
361+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
362+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : undefined } ) ;
363+ init ( options ) ;
364+
365+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
366+ // Spotlight integration should be added (SENTRY_SPOTLIGHT=true wins)
367+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
368+ expect ( spotlightIntegration ) . toBeDefined ( ) ;
369+ } ) ;
370+
371+ it ( 'uses framework-specific prefix when base is not set' , ( ) => {
372+ originalProcess = globalThis . process ;
373+ globalThis . process = {
374+ env : {
375+ NEXT_PUBLIC_SENTRY_SPOTLIGHT : 'true' ,
376+ } ,
377+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
378+ } as any ;
379+
380+ // @ts -expect-error this is fine for testing
381+ const initAndBindSpy = vi . spyOn ( SentryCore , 'initAndBind' ) . mockImplementationOnce ( ( ) => { } ) ;
382+ const options = getDefaultBrowserOptions ( { dsn : PUBLIC_DSN , spotlight : undefined } ) ;
383+ init ( options ) ;
384+
385+ const optionsPassed = initAndBindSpy . mock . calls [ 0 ] ?. [ 1 ] ;
386+ // Spotlight integration should be added
387+ const spotlightIntegration = optionsPassed ?. integrations . find ( ( i : Integration ) => i . name === 'SpotlightBrowser' ) ;
388+ expect ( spotlightIntegration ) . toBeDefined ( ) ;
389+ } ) ;
390+ } ) ;
391+
237392 it ( 'returns a client from init' , ( ) => {
238393 const client = init ( ) ;
239394 expect ( client ) . not . toBeUndefined ( ) ;
0 commit comments