@@ -329,9 +329,11 @@ describe('google Maps Regressions', () => {
329329 map . setOptions ( options )
330330 }
331331
332- // Simulate the fixed watcher: strips center and zoom before calling setOptions
332+ // Simulate the fixed watcher: strips center, zoom, mapId, and colorScheme
333+ // before calling setOptions. mapId/colorScheme are init-only in Google Maps
334+ // and are handled by a dedicated re-init watcher (see #726 regression suite).
333335 function applyOptionsFixed ( map : ReturnType < typeof createMockMap > , options : Record < string , any > ) {
334- const { center : _ , zoom : __ , ...rest } = options
336+ const { center : _ , zoom : __ , mapId : ___ , colorScheme : ____ , ...rest } = options
335337 map . setOptions ( rest )
336338 }
337339
@@ -348,19 +350,22 @@ describe('google Maps Regressions', () => {
348350 )
349351 } )
350352
351- it ( 'fixed behavior: setOptions excludes zoom and center ' , ( ) => {
353+ it ( 'fixed behavior: setOptions excludes zoom, center, mapId, and colorScheme ' , ( ) => {
352354 const map = createMockMap ( )
353- const options = { center : { lat : 40 , lng : - 74 } , zoom : 12 , mapId : 'abc' }
355+ const options = { center : { lat : 40 , lng : - 74 } , zoom : 12 , mapId : 'abc' , disableDefaultUI : true }
354356
355357 applyOptionsFixed ( map , options )
356358
357- expect ( map . setOptions ) . toHaveBeenCalledWith ( { mapId : 'abc' } )
359+ expect ( map . setOptions ) . toHaveBeenCalledWith ( { disableDefaultUI : true } )
358360 expect ( map . setOptions ) . not . toHaveBeenCalledWith (
359361 expect . objectContaining ( { center : expect . anything ( ) } ) ,
360362 )
361363 expect ( map . setOptions ) . not . toHaveBeenCalledWith (
362364 expect . objectContaining ( { zoom : expect . anything ( ) } ) ,
363365 )
366+ expect ( map . setOptions ) . not . toHaveBeenCalledWith (
367+ expect . objectContaining ( { mapId : expect . anything ( ) } ) ,
368+ )
364369 } )
365370
366371 it ( 'old behavior: repeated overlay toggles reset zoom/center every time' , ( ) => {
@@ -502,4 +507,118 @@ describe('google Maps Regressions', () => {
502507 expect ( iw . close ) . not . toHaveBeenCalled ( )
503508 } )
504509 } )
510+
511+ describe ( 'color-mode reactivity for cloud-based map IDs (#726)' , ( ) => {
512+ // Regression: toggling color mode with `mapIds` set (or with cloud-based
513+ // styling on a single mapId) did not update the map. The old code passed
514+ // the resolved mapId via `setOptions`, which Google Maps refuses
515+ // ("A Map's mapId property cannot be changed after initial Map render").
516+ // Both `mapId` and `colorScheme` are init-only; the fix excludes them
517+ // from the generic setOptions call and re-initialises the Map instance
518+ // when either changes.
519+
520+ function resolveMapId ( props : {
521+ mapIds ?: { light ?: string , dark ?: string }
522+ mapOptions ?: { mapId ?: string }
523+ } , colorMode : 'light' | 'dark' ) {
524+ if ( ! props . mapIds )
525+ return props . mapOptions ?. mapId
526+ return props . mapIds [ colorMode ] || props . mapIds . light || props . mapOptions ?. mapId
527+ }
528+
529+ function resolveColorScheme ( props : {
530+ mapIds ?: { light ?: string , dark ?: string }
531+ colorMode ?: 'light' | 'dark'
532+ hasNuxtColorMode ?: boolean
533+ } , currentColorMode : 'light' | 'dark' ) {
534+ if ( ! props . mapIds && ! props . colorMode && ! props . hasNuxtColorMode )
535+ return undefined
536+ return currentColorMode === 'dark' ? 'DARK' : 'LIGHT'
537+ }
538+
539+ function applyOptionsFixed ( map : ReturnType < typeof createMockMap > , options : Record < string , any > ) {
540+ const { center : _ , zoom : __ , mapId : ___ , colorScheme : ____ , ...rest } = options
541+ map . setOptions ( rest )
542+ }
543+
544+ it ( 'strips mapId and colorScheme from setOptions to avoid the init-only warning' , ( ) => {
545+ const map = createMockMap ( )
546+ const options = {
547+ center : { lat : 40 , lng : - 74 } ,
548+ zoom : 12 ,
549+ mapId : 'abc' ,
550+ colorScheme : 'DARK' ,
551+ disableDefaultUI : true ,
552+ }
553+
554+ applyOptionsFixed ( map , options )
555+
556+ expect ( map . setOptions ) . toHaveBeenCalledWith ( { disableDefaultUI : true } )
557+ expect ( map . setOptions ) . not . toHaveBeenCalledWith (
558+ expect . objectContaining ( { mapId : expect . anything ( ) } ) ,
559+ )
560+ expect ( map . setOptions ) . not . toHaveBeenCalledWith (
561+ expect . objectContaining ( { colorScheme : expect . anything ( ) } ) ,
562+ )
563+ } )
564+
565+ it ( 'resolves a different mapId per color mode when both light and dark are provided' , ( ) => {
566+ const props = { mapIds : { light : 'LIGHT_ID' , dark : 'DARK_ID' } }
567+
568+ expect ( resolveMapId ( props , 'light' ) ) . toBe ( 'LIGHT_ID' )
569+ expect ( resolveMapId ( props , 'dark' ) ) . toBe ( 'DARK_ID' )
570+ } )
571+
572+ it ( 'emits a colorScheme so a single mapId with cloud-based light/dark styling can re-init' , ( ) => {
573+ // User configured one mapId in Cloud Console with both Light and Dark
574+ // schemes. mapIds resolves to the same id in both modes, so the only
575+ // signal that triggers re-init is the colorScheme value.
576+ const props = { mapIds : { light : 'SAME_ID' , dark : 'SAME_ID' } }
577+
578+ expect ( resolveMapId ( props , 'light' ) ) . toBe ( 'SAME_ID' )
579+ expect ( resolveMapId ( props , 'dark' ) ) . toBe ( 'SAME_ID' )
580+
581+ expect ( resolveColorScheme ( props , 'light' ) ) . toBe ( 'LIGHT' )
582+ expect ( resolveColorScheme ( props , 'dark' ) ) . toBe ( 'DARK' )
583+ } )
584+
585+ it ( 'does not emit a colorScheme when no color-mode props or @nuxtjs/color-mode are present' , ( ) => {
586+ // Avoid forcing a LIGHT scheme on existing maps that never opted in to
587+ // color-mode reactivity — would otherwise needlessly re-init on first
588+ // mount or accidentally override mapOptions.colorScheme.
589+ expect ( resolveColorScheme ( { } , 'light' ) ) . toBeUndefined ( )
590+ } )
591+
592+ it ( 'emits a colorScheme when @nuxtjs/color-mode is detected even without explicit mapIds' , ( ) => {
593+ expect ( resolveColorScheme ( { hasNuxtColorMode : true } , 'dark' ) ) . toBe ( 'DARK' )
594+ } )
595+
596+ it ( 'triggers re-init only when the resolved mapId or colorScheme actually changes' , ( ) => {
597+ // Mirrors the dedup guard in the recreate watcher.
598+ function shouldReinit (
599+ prev : { mapId : string | undefined , scheme : string | undefined } ,
600+ next : { mapId : string | undefined , scheme : string | undefined } ,
601+ ) {
602+ return prev . mapId !== next . mapId || prev . scheme !== next . scheme
603+ }
604+
605+ // Identical → no re-init (covers e.g. unrelated re-renders that re-evaluate the options computed).
606+ expect ( shouldReinit (
607+ { mapId : 'abc' , scheme : 'LIGHT' } ,
608+ { mapId : 'abc' , scheme : 'LIGHT' } ,
609+ ) ) . toBe ( false )
610+
611+ // mapId changes (two-id light/dark setup).
612+ expect ( shouldReinit (
613+ { mapId : 'LIGHT_ID' , scheme : 'LIGHT' } ,
614+ { mapId : 'DARK_ID' , scheme : 'DARK' } ,
615+ ) ) . toBe ( true )
616+
617+ // Single mapId, only colorScheme changes (cloud styling on one id).
618+ expect ( shouldReinit (
619+ { mapId : 'SAME_ID' , scheme : 'LIGHT' } ,
620+ { mapId : 'SAME_ID' , scheme : 'DARK' } ,
621+ ) ) . toBe ( true )
622+ } )
623+ } )
505624} )
0 commit comments