@@ -77,6 +77,119 @@ describe('<ScrollArea.Scrollbar />', () => {
7777 } ) ;
7878 } ) ;
7979
80+ describe ( 'data-hovering attribute' , ( ) => {
81+ it ( 'adds [data-hovering] when the synthetic pointer target differs from the native path' , async ( ) => {
82+ await render (
83+ < ScrollArea . Root data-testid = "root" style = { { width : 200 , height : 200 } } >
84+ < ScrollArea . Viewport data-testid = "viewport" style = { { width : '100%' , height : '100%' } } >
85+ < div style = { { width : 1000 , height : 1000 } } />
86+ </ ScrollArea . Viewport >
87+ < ScrollArea . Scrollbar orientation = "vertical" data-testid = "vertical" keepMounted />
88+ </ ScrollArea . Root > ,
89+ ) ;
90+
91+ const viewport = screen . getByTestId ( 'viewport' ) ;
92+ const verticalScrollbar = screen . getByTestId ( 'vertical' ) ;
93+
94+ // Real browser runs can start with the viewport already hovered because
95+ // ScrollAreaViewport syncs `:hover` on mount.
96+ fireEvent . pointerLeave ( viewport , { pointerType : 'mouse' } ) ;
97+ expect ( verticalScrollbar ) . not . toHaveAttribute ( 'data-hovering' ) ;
98+
99+ const PointerEventCtor = window . PointerEvent ?? window . Event ;
100+ const event = new PointerEventCtor ( 'pointerover' , {
101+ bubbles : true ,
102+ } ) ;
103+
104+ Object . defineProperties ( event , {
105+ composedPath : {
106+ configurable : true ,
107+ value : ( ) => [ document . body , viewport ] ,
108+ } ,
109+ pointerType : {
110+ configurable : true ,
111+ value : 'mouse' ,
112+ } ,
113+ } ) ;
114+
115+ fireEvent ( viewport , event ) ;
116+
117+ expect ( verticalScrollbar ) . toHaveAttribute ( 'data-hovering' , '' ) ;
118+
119+ fireEvent . pointerLeave ( viewport , { pointerType : 'mouse' } ) ;
120+
121+ expect ( verticalScrollbar ) . not . toHaveAttribute ( 'data-hovering' ) ;
122+ } ) ;
123+ } ) ;
124+
125+ describe ( 'track pointer down' , ( ) => {
126+ it ( 'ignores thumb clicks when the native path differs from the synthetic target' , async ( ) => {
127+ await render (
128+ < ScrollArea . Root style = { { width : 200 , height : 200 } } >
129+ < ScrollArea . Viewport data-testid = "viewport" style = { { width : '100%' , height : '100%' } } >
130+ < div style = { { width : 1000 , height : 1000 } } />
131+ </ ScrollArea . Viewport >
132+ < ScrollArea . Scrollbar orientation = "vertical" data-testid = "vertical" keepMounted >
133+ < ScrollArea . Thumb data-testid = "thumb" />
134+ </ ScrollArea . Scrollbar >
135+ </ ScrollArea . Root > ,
136+ ) ;
137+
138+ const viewport = screen . getByTestId ( 'viewport' ) as HTMLDivElement ;
139+ const verticalScrollbar = screen . getByTestId ( 'vertical' ) ;
140+ const thumb = screen . getByTestId ( 'thumb' ) ;
141+
142+ Object . defineProperties ( viewport , {
143+ clientHeight : {
144+ configurable : true ,
145+ value : 200 ,
146+ } ,
147+ scrollHeight : {
148+ configurable : true ,
149+ value : 1000 ,
150+ } ,
151+ scrollTop : {
152+ configurable : true ,
153+ writable : true ,
154+ value : 0 ,
155+ } ,
156+ } ) ;
157+
158+ Object . defineProperties ( verticalScrollbar , {
159+ offsetHeight : {
160+ configurable : true ,
161+ value : 200 ,
162+ } ,
163+ getBoundingClientRect : {
164+ configurable : true ,
165+ value : ( ) => ( {
166+ top : 0 ,
167+ } ) ,
168+ } ,
169+ } ) ;
170+
171+ Object . defineProperty ( thumb , 'offsetHeight' , {
172+ configurable : true ,
173+ value : 40 ,
174+ } ) ;
175+
176+ const event = new MouseEvent ( 'pointerdown' , {
177+ bubbles : true ,
178+ button : 0 ,
179+ clientY : 160 ,
180+ } ) ;
181+
182+ Object . defineProperty ( event , 'composedPath' , {
183+ configurable : true ,
184+ value : ( ) => [ thumb , verticalScrollbar ] ,
185+ } ) ;
186+
187+ fireEvent ( verticalScrollbar , event ) ;
188+
189+ expect ( viewport . scrollTop ) . toBe ( 0 ) ;
190+ } ) ;
191+ } ) ;
192+
80193 describe . skipIf ( isJSDOM ) ( 'data overflow attributes (scrollbars)' , ( ) => {
81194 const VIEWPORT_SIZE = 200 ;
82195 const SCROLLABLE_CONTENT_SIZE = 1000 ;
0 commit comments