1010 * governing permissions and limitations under the License.
1111 */
1212
13+
1314import { AriaMenuProps , useMenu , useMenuItem } from '../' ;
1415import { Item } from '@react-stately/collections' ;
16+ import { Key } from '@react-types/shared' ;
1517import { pointerMap , render } from '@react-spectrum/test-utils-internal' ;
1618import React from 'react' ;
19+ import { TreeState , useTreeState } from '@react-stately/tree' ;
1720import userEvent from '@testing-library/user-event' ;
18- import { useTreeState } from '@react-stately/tree' ;
1921
2022function Menu < T extends object > ( props : AriaMenuProps < T > & { onSelect : ( ) => void } ) {
2123 // Create menu state based on the incoming props
@@ -51,6 +53,41 @@ function MenuItem({item, state, onAction}) {
5153 ) ;
5254}
5355
56+ interface VirtualizedMenuItemProps < T > {
57+ item : { key : Key , rendered : React . ReactNode , index ?: number } ,
58+ state : TreeState < T > ,
59+ onAction ?: ( key : Key ) => void
60+ }
61+
62+ function VirtualizedMenuItem < T > ( { item, state, onAction} : VirtualizedMenuItemProps < T > ) {
63+ let ref = React . useRef ( null ) ;
64+ let { menuItemProps} = useMenuItem (
65+ { key : item . key , onAction, isVirtualized : true } ,
66+ state ,
67+ ref
68+ ) ;
69+
70+ return (
71+ < li { ...menuItemProps } ref = { ref } >
72+ { item . rendered }
73+ </ li >
74+ ) ;
75+ }
76+
77+ function VirtualizedMenu < T extends object > ( props : AriaMenuProps < T > ) {
78+ let state = useTreeState ( props ) ;
79+ let ref = React . useRef ( null ) ;
80+ let { menuProps} = useMenu ( props , state , ref ) ;
81+
82+ return (
83+ < ul { ...menuProps } ref = { ref } >
84+ { [ ...state . collection ] . map ( ( item ) => (
85+ < VirtualizedMenuItem key = { item . key } item = { item } state = { state } />
86+ ) ) }
87+ </ ul >
88+ ) ;
89+ }
90+
5491describe ( 'useMenuTrigger' , function ( ) {
5592 let user ;
5693 beforeAll ( ( ) => {
@@ -75,3 +112,37 @@ describe('useMenuTrigger', function () {
75112 expect ( onSelect ) . toHaveBeenCalledTimes ( 1 ) ;
76113 } ) ;
77114} ) ;
115+
116+ describe ( 'useMenuItem with isVirtualized' , function ( ) {
117+ it ( 'sets correct aria-posinset (1-based) for virtualized menu items' , ( ) => {
118+ let { getAllByRole} = render (
119+ < VirtualizedMenu aria-label = "test menu" >
120+ < Item key = "1" > One</ Item >
121+ < Item key = "2" > Two</ Item >
122+ < Item key = "3" > Three</ Item >
123+ </ VirtualizedMenu >
124+ ) ;
125+
126+ let items = getAllByRole ( 'menuitem' ) ;
127+ // aria-posinset should be 1-based (1, 2, 3), not 0-based (0, 1, 2)
128+ expect ( items [ 0 ] ) . toHaveAttribute ( 'aria-posinset' , '1' ) ;
129+ expect ( items [ 1 ] ) . toHaveAttribute ( 'aria-posinset' , '2' ) ;
130+ expect ( items [ 2 ] ) . toHaveAttribute ( 'aria-posinset' , '3' ) ;
131+ } ) ;
132+
133+ it ( 'sets correct aria-setsize for virtualized menu items' , ( ) => {
134+ let { getAllByRole} = render (
135+ < VirtualizedMenu aria-label = "test menu" >
136+ < Item key = "1" > One</ Item >
137+ < Item key = "2" > Two</ Item >
138+ < Item key = "3" > Three</ Item >
139+ </ VirtualizedMenu >
140+ ) ;
141+
142+ let items = getAllByRole ( 'menuitem' ) ;
143+ // aria-setsize should match the total number of items
144+ expect ( items [ 0 ] ) . toHaveAttribute ( 'aria-setsize' , '3' ) ;
145+ expect ( items [ 1 ] ) . toHaveAttribute ( 'aria-setsize' , '3' ) ;
146+ expect ( items [ 2 ] ) . toHaveAttribute ( 'aria-setsize' , '3' ) ;
147+ } ) ;
148+ } ) ;
0 commit comments