@@ -611,6 +611,99 @@ describe( 'Menu', () => {
611611 ) . toBeChecked ( ) ;
612612 } ) ;
613613
614+ it ( 'should allow resetting checked radio items from outside the menu' , async ( ) => {
615+ const ControlledRadioMenus = ( ) => {
616+ const [ selectedOption , setSelectedOption ] = useState <
617+ 'menu-a-option' | 'menu-b-option'
618+ > ( 'menu-a-option' ) ;
619+
620+ return (
621+ < >
622+ < Menu >
623+ < Menu . TriggerButton > Open menu A</ Menu . TriggerButton >
624+ < Menu . Popover >
625+ < Menu . Group >
626+ < Menu . RadioItem
627+ name = "menu-a"
628+ value = "menu-a-option"
629+ checked = {
630+ selectedOption === 'menu-a-option'
631+ }
632+ onChange = { ( ) =>
633+ setSelectedOption ( 'menu-a-option' )
634+ }
635+ >
636+ Menu A option
637+ </ Menu . RadioItem >
638+ </ Menu . Group >
639+ </ Menu . Popover >
640+ </ Menu >
641+
642+ < Menu >
643+ < Menu . TriggerButton > Open menu B</ Menu . TriggerButton >
644+ < Menu . Popover >
645+ < Menu . Group >
646+ < Menu . RadioItem
647+ name = "menu-b"
648+ value = "menu-b-option"
649+ checked = {
650+ selectedOption === 'menu-b-option'
651+ }
652+ onChange = { ( ) =>
653+ setSelectedOption ( 'menu-b-option' )
654+ }
655+ >
656+ Menu B option
657+ </ Menu . RadioItem >
658+ </ Menu . Group >
659+ </ Menu . Popover >
660+ </ Menu >
661+ </ >
662+ ) ;
663+ } ;
664+
665+ render ( < ControlledRadioMenus /> ) ;
666+
667+ const openMenuAMenu = screen . getByRole ( 'button' , {
668+ name : 'Open menu A' ,
669+ } ) ;
670+ const openMenuBMenu = screen . getByRole ( 'button' , {
671+ name : 'Open menu B' ,
672+ } ) ;
673+
674+ // Open the first menu and verify its checked state.
675+ await click ( openMenuAMenu ) ;
676+ const menuAOptionInitial = screen . getByRole ( 'menuitemradio' , {
677+ name : 'Menu A option' ,
678+ } ) ;
679+ expect ( menuAOptionInitial ) . toBeChecked ( ) ;
680+
681+ // Close the first menu.
682+ await click ( document . body ) ;
683+
684+ // Open the second menu, change the selection there.
685+ await click ( openMenuBMenu ) ;
686+ const menuBOption = screen . getByRole ( 'menuitemradio' , {
687+ name : 'Menu B option' ,
688+ } ) ;
689+ expect ( menuBOption ) . not . toBeChecked ( ) ;
690+
691+ await click ( menuBOption ) ;
692+ expect ( menuBOption ) . toBeChecked ( ) ;
693+
694+ // Close the second menu.
695+ await click ( document . body ) ;
696+
697+ // Reopen the first menu: its radio item should now reflect the
698+ // updated external state and be unchecked.
699+ await click ( openMenuAMenu ) ;
700+
701+ const menuAOption = screen . getByRole ( 'menuitemradio' , {
702+ name : 'Menu A option' ,
703+ } ) ;
704+ expect ( menuAOption ) . not . toBeChecked ( ) ;
705+ } ) ;
706+
614707 it ( 'should check checkbox items and keep the menu open when clicking (controlled)' , async ( ) => {
615708 const onCheckboxValueChangeSpy = jest . fn ( ) ;
616709
0 commit comments