@@ -116,4 +116,218 @@ describe('pull down to refresh', () => {
116116
117117 expect ( refresh ) . toHaveBeenCalled ( ) ;
118118 } ) ;
119+
120+ it ( 'does not start drag when scrollTop > 0' , ( ) => {
121+ const refresh = jest . fn ( ) ;
122+ const { container } = render (
123+ < InfiniteScroll
124+ dataLength = { 10 }
125+ loader = { 'Loading...' }
126+ hasMore = { true }
127+ next = { ( ) => { } }
128+ height = { 200 }
129+ pullDownToRefresh
130+ pullDownToRefreshThreshold = { 50 }
131+ refreshFunction = { refresh }
132+ pullDownToRefreshContent = { < div style = { { height : 100 } } > Pull</ div > }
133+ >
134+ < div />
135+ </ InfiniteScroll >
136+ ) ;
137+
138+ const node = container . querySelector (
139+ '.infinite-scroll-component'
140+ ) as HTMLElement ;
141+
142+ // Simulate container already scrolled down
143+ Object . defineProperty ( node , 'scrollTop' , { configurable : true , value : 50 } ) ;
144+
145+ const down = new MouseEvent ( 'mousedown' , { bubbles : true } as any ) ;
146+ Object . defineProperty ( down , 'pageY' , { value : 0 } ) ;
147+ act ( ( ) => {
148+ node . dispatchEvent ( down ) ;
149+ } ) ;
150+
151+ // Move should have no effect since drag was never started
152+ const move = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
153+ Object . defineProperty ( move , 'pageY' , { value : 60 } ) ;
154+ act ( ( ) => {
155+ node . dispatchEvent ( move ) ;
156+ } ) ;
157+
158+ expect ( node . style . transform ) . toBe ( '' ) ;
159+
160+ // Reset scrollTop
161+ Object . defineProperty ( node , 'scrollTop' , { configurable : true , value : 0 } ) ;
162+ } ) ;
163+
164+ it ( 'ignores upward pull (currentY < startY)' , ( ) => {
165+ const refresh = jest . fn ( ) ;
166+ const { container } = render (
167+ < InfiniteScroll
168+ dataLength = { 10 }
169+ loader = { 'Loading...' }
170+ hasMore = { true }
171+ next = { ( ) => { } }
172+ height = { 200 }
173+ pullDownToRefresh
174+ pullDownToRefreshThreshold = { 50 }
175+ refreshFunction = { refresh }
176+ pullDownToRefreshContent = { < div style = { { height : 100 } } > Pull</ div > }
177+ >
178+ < div />
179+ </ InfiniteScroll >
180+ ) ;
181+
182+ const node = container . querySelector (
183+ '.infinite-scroll-component'
184+ ) as HTMLElement ;
185+
186+ const down = new MouseEvent ( 'mousedown' , { bubbles : true } as any ) ;
187+ Object . defineProperty ( down , 'pageY' , { value : 100 } ) ;
188+ act ( ( ) => {
189+ node . dispatchEvent ( down ) ;
190+ } ) ;
191+
192+ // Move upward (pageY < startY)
193+ const move = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
194+ Object . defineProperty ( move , 'pageY' , { value : 40 } ) ;
195+ act ( ( ) => {
196+ node . dispatchEvent ( move ) ;
197+ } ) ;
198+
199+ // No transform applied for upward pull
200+ expect ( node . style . transform ) . toBe ( '' ) ;
201+ } ) ;
202+
203+ it ( 'caps transform at 1.5x maxPullDownDistance' , ( ) => {
204+ const refresh = jest . fn ( ) ;
205+ const { container } = render (
206+ < InfiniteScroll
207+ dataLength = { 10 }
208+ loader = { 'Loading...' }
209+ hasMore = { true }
210+ next = { ( ) => { } }
211+ height = { 200 }
212+ pullDownToRefresh
213+ pullDownToRefreshThreshold = { 50 }
214+ refreshFunction = { refresh }
215+ pullDownToRefreshContent = { < div style = { { height : 100 } } > Pull</ div > }
216+ >
217+ < div />
218+ </ InfiniteScroll >
219+ ) ;
220+
221+ const node = container . querySelector (
222+ '.infinite-scroll-component'
223+ ) as HTMLElement ;
224+
225+ const down = new MouseEvent ( 'mousedown' , { bubbles : true } as any ) ;
226+ Object . defineProperty ( down , 'pageY' , { value : 0 } ) ;
227+ act ( ( ) => {
228+ node . dispatchEvent ( down ) ;
229+ } ) ;
230+
231+ // Pull 60px — within cap (100 * 1.5 = 150), transform applied
232+ const move1 = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
233+ Object . defineProperty ( move1 , 'pageY' , { value : 60 } ) ;
234+ act ( ( ) => {
235+ node . dispatchEvent ( move1 ) ;
236+ } ) ;
237+ expect ( node . style . transform ) . toBe ( 'translate3d(0px, 60px, 0px)' ) ;
238+
239+ // Pull 200px — exceeds cap (150px), transform NOT updated
240+ const move2 = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
241+ Object . defineProperty ( move2 , 'pageY' , { value : 200 } ) ;
242+ act ( ( ) => {
243+ node . dispatchEvent ( move2 ) ;
244+ } ) ;
245+ // Transform stays at 60px because the 200px delta exceeds the 1.5x cap
246+ expect ( node . style . transform ) . toBe ( 'translate3d(0px, 60px, 0px)' ) ;
247+ } ) ;
248+
249+ it ( 'calls refreshFunction in window-scroll PTR mode (no height)' , ( ) => {
250+ // Exercises the scrollEl = window path (line 218 false branch) in PTR onStart
251+ const refresh = jest . fn ( ) ;
252+ const { container } = render (
253+ < InfiniteScroll
254+ dataLength = { 10 }
255+ loader = { 'Loading...' }
256+ hasMore = { true }
257+ next = { ( ) => { } }
258+ pullDownToRefresh
259+ pullDownToRefreshThreshold = { 50 }
260+ refreshFunction = { refresh }
261+ pullDownToRefreshContent = { < div style = { { height : 100 } } > Pull</ div > }
262+ >
263+ < div />
264+ </ InfiniteScroll >
265+ ) ;
266+
267+ const node = container . querySelector (
268+ '.infinite-scroll-component'
269+ ) as HTMLElement ;
270+
271+ // Listeners are attached to window when there is no height prop
272+ const down = new MouseEvent ( 'mousedown' , { bubbles : true } as any ) ;
273+ Object . defineProperty ( down , 'pageY' , { value : 0 } ) ;
274+ act ( ( ) => {
275+ window . dispatchEvent ( down ) ;
276+ } ) ;
277+
278+ const move = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
279+ Object . defineProperty ( move , 'pageY' , { value : 60 } ) ;
280+ act ( ( ) => {
281+ window . dispatchEvent ( move ) ;
282+ } ) ;
283+
284+ expect ( node . style . transform ) . toBe ( 'translate3d(0px, 60px, 0px)' ) ;
285+
286+ const up = new MouseEvent ( 'mouseup' , { bubbles : true } as any ) ;
287+ act ( ( ) => {
288+ window . dispatchEvent ( up ) ;
289+ } ) ;
290+
291+ expect ( refresh ) . toHaveBeenCalled ( ) ;
292+ } ) ;
293+
294+ it ( 'shows releaseToRefreshContent when threshold is breached' , ( ) => {
295+ const { container, getByText, queryByText } = render (
296+ < InfiniteScroll
297+ dataLength = { 10 }
298+ loader = { 'Loading...' }
299+ hasMore = { true }
300+ next = { ( ) => { } }
301+ height = { 200 }
302+ pullDownToRefresh
303+ pullDownToRefreshThreshold = { 50 }
304+ refreshFunction = { ( ) => { } }
305+ pullDownToRefreshContent = { < div > Pull down</ div > }
306+ releaseToRefreshContent = { < div > Release to refresh</ div > }
307+ >
308+ < div />
309+ </ InfiniteScroll >
310+ ) ;
311+
312+ const node = container . querySelector (
313+ '.infinite-scroll-component'
314+ ) as HTMLElement ;
315+
316+ expect ( queryByText ( 'Release to refresh' ) ) . toBeNull ( ) ;
317+
318+ const down = new MouseEvent ( 'mousedown' , { bubbles : true } as any ) ;
319+ Object . defineProperty ( down , 'pageY' , { value : 0 } ) ;
320+ act ( ( ) => {
321+ node . dispatchEvent ( down ) ;
322+ } ) ;
323+
324+ // Pull past threshold (60 > 50)
325+ const move = new MouseEvent ( 'mousemove' , { bubbles : true } as any ) ;
326+ Object . defineProperty ( move , 'pageY' , { value : 60 } ) ;
327+ act ( ( ) => {
328+ node . dispatchEvent ( move ) ;
329+ } ) ;
330+
331+ expect ( getByText ( 'Release to refresh' ) ) . toBeTruthy ( ) ;
332+ } ) ;
119333} ) ;
0 commit comments