@@ -8,6 +8,7 @@ import { act } from 'react-dom/test-utils';
88import type { MenuRef } from '../src' ;
99import Menu , { Divider , MenuItem , MenuItemGroup , SubMenu } from '../src' ;
1010import { isActive , last } from './util' ;
11+ import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook' ;
1112
1213jest . mock ( '@rc-component/trigger' , ( ) => {
1314 const react = require ( 'react' ) ;
@@ -790,5 +791,73 @@ describe('Menu', () => {
790791 expect ( catItem ) . toBe ( container . querySelectorAll ( 'li' ) [ 1 ] ) ;
791792 expect ( nonExistentItem ) . toBe ( null ) ;
792793 } ) ;
794+
795+ it ( 'should correctly handle invalid mergedActiveKey and fallback to focusable elements' , async ( ) => {
796+ // Mock to force make menu item visible
797+ spyElementPrototypes ( HTMLElement , {
798+ offsetParent : {
799+ get ( ) {
800+ return this . parentElement ;
801+ } ,
802+ } ,
803+ } ) ;
804+
805+ const menuRef = React . createRef < MenuRef > ( ) ;
806+ const focusSpy = jest . fn ( ) ;
807+
808+ const TestApp = ( ) => {
809+ const [ items , setItems ] = React . useState ( [
810+ { key : 'item1' , label : 'First Item' } ,
811+ { key : 'item2' , label : 'Second Item' } ,
812+ { key : 'item3' , label : 'Third Item' } ,
813+ ] ) ;
814+
815+ const removeSecondItem = ( ) => {
816+ setItems ( prevItems => prevItems . filter ( item => item . key !== 'item2' ) ) ;
817+ } ;
818+
819+ return (
820+ < div >
821+ < Menu data-testid = "menu" activeKey = "item2" tabIndex = { 0 } ref = { menuRef } >
822+ { items . map ( item => (
823+ < MenuItem key = { item . key } data-testid = { item . key } >
824+ { item . label }
825+ </ MenuItem >
826+ ) ) }
827+ </ Menu >
828+ < button data-testid = "remove-button" onClick = { removeSecondItem } tabIndex = { 0 } >
829+ Remove Second Item
830+ </ button >
831+ </ div >
832+ ) ;
833+ } ;
834+
835+ const { getByTestId } = await act ( async ( ) => render ( < TestApp /> ) ) ;
836+
837+ const removeButton = getByTestId ( 'remove-button' ) ;
838+
839+ // Verify initial state - item2 should be active
840+ expect ( getByTestId ( 'item2' ) ) . toHaveClass ( 'rc-menu-item-active' ) ;
841+
842+ // Remove the active item (item2)
843+ await act ( async ( ) => {
844+ fireEvent . click ( removeButton ) ;
845+ } ) ;
846+
847+ // Verify the item is removed
848+ expect ( ( ) => getByTestId ( 'item2' ) ) . toThrow ( ) ;
849+
850+ // mock focus on item 1 to make sure it gets focused
851+ const item1 = getByTestId ( 'item1' ) ;
852+ item1 . focus = focusSpy ;
853+
854+ // when we call focus(), it should properly handle the case where
855+ // mergedActiveKey ("item2") no longer exists
856+ await act ( async ( ) => {
857+ menuRef . current . focus ( ) ;
858+ } ) ;
859+
860+ expect ( focusSpy ) . toHaveBeenCalled ( ) ;
861+ } ) ;
793862} ) ;
794863/* eslint-enable */
0 commit comments