@@ -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+ const domSpy = spyElementPrototypes ( HTMLElement , {
798+ offsetParent : {
799+ get ( ) {
800+ return this . parentElement ;
801+ } ,
802+ } ,
803+ } ) ;
804+
805+ const menuRef = React . createRef < MenuRef > ( ) ;
806+
807+ const TestApp = ( ) => {
808+ const [ items , setItems ] = React . useState ( [
809+ { key : 'item1' , label : 'First Item' } ,
810+ { key : 'item2' , label : 'Second Item' } ,
811+ { key : 'item3' , label : 'Third Item' } ,
812+ ] ) ;
813+
814+ const removeSecondItem = ( ) => {
815+ setItems ( prevItems => prevItems . filter ( item => item . key !== 'item2' ) ) ;
816+ } ;
817+
818+ return (
819+ < div >
820+ < Menu data-testid = "menu" activeKey = "item2" tabIndex = { 0 } ref = { menuRef } >
821+ { items . map ( item => (
822+ < MenuItem key = { item . key } data-testid = { item . key } >
823+ { item . label }
824+ </ MenuItem >
825+ ) ) }
826+ </ Menu >
827+ < button data-testid = "remove-button" onClick = { removeSecondItem } tabIndex = { 0 } >
828+ Remove Second Item
829+ </ button >
830+ </ div >
831+ ) ;
832+ } ;
833+
834+ const { getByTestId } = render ( < TestApp /> ) ;
835+
836+ const removeButton = getByTestId ( 'remove-button' ) ;
837+
838+ // Verify initial state - item2 should be active
839+ expect ( getByTestId ( 'item2' ) ) . toHaveClass ( 'rc-menu-item-active' ) ;
840+
841+ // Remove the active item (item2)
842+ await act ( async ( ) => {
843+ fireEvent . click ( removeButton ) ;
844+ } ) ;
845+
846+ // Verify the item is removed
847+ expect ( ( ) => getByTestId ( 'item2' ) ) . toThrow ( ) ;
848+
849+ // mock focus on item 1 to make sure it gets focused
850+ const item1 = getByTestId ( 'item1' ) ;
851+ const focusSpy = jest . spyOn ( item1 , 'focus' ) . mockImplementation ( ( ) => { } ) ;
852+
853+ // when we call focus(), it should properly handle the case where
854+ // mergedActiveKey ("item2") no longer exists
855+ menuRef . current . focus ( ) ;
856+
857+ expect ( focusSpy ) . toHaveBeenCalled ( ) ;
858+ // cleanup
859+ focusSpy . mockRestore ( ) ;
860+ domSpy ?. mockRestore ?.( ) ;
861+ } ) ;
793862} ) ;
794863/* eslint-enable */
0 commit comments