@@ -143,6 +143,82 @@ function TestInlineNestedNavigationMenu() {
143143 ) ;
144144}
145145
146+ function TestDeeplyNestedNavigationMenu ( ) {
147+ return (
148+ < NavigationMenu . Root >
149+ < NavigationMenu . List >
150+ < NavigationMenu . Item value = "item-1" >
151+ < NavigationMenu . Trigger data-testid = "trigger-1" > Item 1</ NavigationMenu . Trigger >
152+
153+ < NavigationMenu . Content data-testid = "content-1" >
154+ < NavigationMenu . Link href = "#link-1" data-testid = "link-1" >
155+ Link 1
156+ </ NavigationMenu . Link >
157+ { /* Level 2 */ }
158+ < NavigationMenu . Root defaultValue = "level2-item-1" >
159+ < NavigationMenu . List >
160+ < NavigationMenu . Item value = "level2-item-1" >
161+ < NavigationMenu . Trigger data-testid = "level2-trigger-1" >
162+ Level 2 Item 1
163+ </ NavigationMenu . Trigger >
164+ < NavigationMenu . Content data-testid = "level2-content-1" >
165+ < NavigationMenu . Link href = "#level2-link-1" data-testid = "level2-link-1" >
166+ Level 2 Link 1
167+ </ NavigationMenu . Link >
168+ { /* Level 3 */ }
169+ < NavigationMenu . Root defaultValue = "level3-item-1" >
170+ < NavigationMenu . List >
171+ < NavigationMenu . Item value = "level3-item-1" >
172+ < NavigationMenu . Trigger data-testid = "level3-trigger-1" >
173+ Level 3 Item 1
174+ </ NavigationMenu . Trigger >
175+ < NavigationMenu . Content data-testid = "level3-content-1" >
176+ < NavigationMenu . Link href = "#level3-link-1" >
177+ Level 3 Link 1
178+ </ NavigationMenu . Link >
179+ </ NavigationMenu . Content >
180+ </ NavigationMenu . Item >
181+ < NavigationMenu . Item value = "level3-item-2" >
182+ < NavigationMenu . Trigger data-testid = "level3-trigger-2" >
183+ Level 3 Item 2
184+ </ NavigationMenu . Trigger >
185+ < NavigationMenu . Content data-testid = "level3-content-2" >
186+ < NavigationMenu . Link href = "#level3-link-2" >
187+ Level 3 Link 2
188+ </ NavigationMenu . Link >
189+ </ NavigationMenu . Content >
190+ </ NavigationMenu . Item >
191+ </ NavigationMenu . List >
192+ < NavigationMenu . Viewport />
193+ </ NavigationMenu . Root >
194+ </ NavigationMenu . Content >
195+ </ NavigationMenu . Item >
196+ < NavigationMenu . Item value = "level2-item-2" >
197+ < NavigationMenu . Trigger data-testid = "level2-trigger-2" >
198+ Level 2 Item 2
199+ </ NavigationMenu . Trigger >
200+ < NavigationMenu . Content data-testid = "level2-content-2" >
201+ < NavigationMenu . Link href = "#level2-link-2" > Level 2 Link 2</ NavigationMenu . Link >
202+ </ NavigationMenu . Content >
203+ </ NavigationMenu . Item >
204+ </ NavigationMenu . List >
205+ < NavigationMenu . Viewport />
206+ </ NavigationMenu . Root >
207+ </ NavigationMenu . Content >
208+ </ NavigationMenu . Item >
209+ </ NavigationMenu . List >
210+
211+ < NavigationMenu . Portal >
212+ < NavigationMenu . Positioner >
213+ < NavigationMenu . Popup >
214+ < NavigationMenu . Viewport />
215+ </ NavigationMenu . Popup >
216+ </ NavigationMenu . Positioner >
217+ </ NavigationMenu . Portal >
218+ </ NavigationMenu . Root >
219+ ) ;
220+ }
221+
146222function TestNavigationMenuWithDialog ( ) {
147223 return (
148224 < NavigationMenu . Root >
@@ -928,6 +1004,90 @@ describe('<NavigationMenu.Root />', () => {
9281004 expect ( screen . queryByTestId ( 'nested-popup-2' ) ) . not . to . equal ( null ) ;
9291005 expect ( screen . queryByTestId ( 'nested-popup-1' ) ) . to . equal ( null ) ;
9301006 } ) ;
1007+
1008+ it ( 'allows arrow key navigation to submenu triggers' , async ( ) => {
1009+ const { user } = await render ( < TestInlineNestedNavigationMenu /> ) ;
1010+ const trigger1 = screen . getByTestId ( 'trigger-1' ) ;
1011+
1012+ fireEvent . click ( trigger1 ) ;
1013+ await flushMicrotasks ( ) ;
1014+
1015+ const popup1 = screen . getByTestId ( 'popup-1' ) ;
1016+ expect ( popup1 ) . not . to . equal ( null ) ;
1017+
1018+ const link1 = screen . getByText ( 'Link 1' ) ;
1019+ await act ( async ( ) => link1 . focus ( ) ) ;
1020+
1021+ // Arrow down should move to nested-trigger-1
1022+ await user . keyboard ( '{ArrowDown}' ) ;
1023+
1024+ const nestedTrigger1 = within ( popup1 ) . getByTestId ( 'nested-trigger-1' ) ;
1025+ expect ( nestedTrigger1 ) . toHaveFocus ( ) ;
1026+
1027+ // Arrow down should move to nested-trigger-2
1028+ await user . keyboard ( '{ArrowDown}' ) ;
1029+
1030+ const nestedTrigger2 = within ( popup1 ) . getByTestId ( 'nested-trigger-2' ) ;
1031+ expect ( nestedTrigger2 ) . toHaveFocus ( ) ;
1032+
1033+ // Arrow up should move back to nested-trigger-1
1034+ await user . keyboard ( '{ArrowUp}' ) ;
1035+ expect ( nestedTrigger1 ) . toHaveFocus ( ) ;
1036+
1037+ // Arrow up should move back to Link 1
1038+ await user . keyboard ( '{ArrowUp}' ) ;
1039+ expect ( link1 ) . toHaveFocus ( ) ;
1040+ } ) ;
1041+
1042+ it ( 'allows arrow key navigation with 3+ levels of nesting' , async ( ) => {
1043+ const { user } = await render ( < TestDeeplyNestedNavigationMenu /> ) ;
1044+ const trigger1 = screen . getByTestId ( 'trigger-1' ) ;
1045+
1046+ fireEvent . click ( trigger1 ) ;
1047+ await flushMicrotasks ( ) ;
1048+
1049+ const content1 = screen . getByTestId ( 'content-1' ) ;
1050+ expect ( content1 ) . not . to . equal ( null ) ;
1051+
1052+ // Level 1 content contains: Link 1, Level2-trigger-1, Level2-trigger-2
1053+ const link1 = screen . getByTestId ( 'link-1' ) ;
1054+ await act ( async ( ) => link1 . focus ( ) ) ;
1055+
1056+ // Navigate through Level 1 content items
1057+ await user . keyboard ( '{ArrowDown}' ) ;
1058+ const level2Trigger1 = screen . getByTestId ( 'level2-trigger-1' ) ;
1059+ expect ( level2Trigger1 ) . toHaveFocus ( ) ;
1060+
1061+ await user . keyboard ( '{ArrowDown}' ) ;
1062+ const level2Trigger2 = screen . getByTestId ( 'level2-trigger-2' ) ;
1063+ expect ( level2Trigger2 ) . toHaveFocus ( ) ;
1064+
1065+ await user . keyboard ( '{ArrowUp}' ) ;
1066+ expect ( level2Trigger1 ) . toHaveFocus ( ) ;
1067+
1068+ await user . keyboard ( '{ArrowUp}' ) ;
1069+ expect ( link1 ) . toHaveFocus ( ) ;
1070+
1071+ // Now navigate into Level 2 content (which contains Level 3 triggers)
1072+ const level2Content1 = screen . getByTestId ( 'level2-content-1' ) ;
1073+ const level2Link1 = within ( level2Content1 ) . getByTestId ( 'level2-link-1' ) ;
1074+ await act ( async ( ) => level2Link1 . focus ( ) ) ;
1075+
1076+ // Navigate through Level 2 content items (includes Level 3 triggers)
1077+ await user . keyboard ( '{ArrowDown}' ) ;
1078+ const level3Trigger1 = screen . getByTestId ( 'level3-trigger-1' ) ;
1079+ expect ( level3Trigger1 ) . toHaveFocus ( ) ;
1080+
1081+ await user . keyboard ( '{ArrowDown}' ) ;
1082+ const level3Trigger2 = screen . getByTestId ( 'level3-trigger-2' ) ;
1083+ expect ( level3Trigger2 ) . toHaveFocus ( ) ;
1084+
1085+ await user . keyboard ( '{ArrowUp}' ) ;
1086+ expect ( level3Trigger1 ) . toHaveFocus ( ) ;
1087+
1088+ await user . keyboard ( '{ArrowUp}' ) ;
1089+ expect ( level2Link1 ) . toHaveFocus ( ) ;
1090+ } ) ;
9311091 } ) ;
9321092 } ) ;
9331093} ) ;
0 commit comments