Skip to content

Commit a89dda7

Browse files
committed
test: close remaining coverage gaps — PTR edge cases, onScroll window mode, initialScrollY via scrollableTarget
Adds 7 tests covering: - PTR: scrollTop > 0 guard blocks drag start - PTR: upward pull (currentY < startY) ignored - PTR: delta capped at 1.5× maxPullDownDistance - PTR: releaseToRefreshContent shown when threshold breached - PTR: window-scroll mode (no height) — listeners on window, exercises document.documentElement.scrollTop branch - onScroll: window fallback when no height or scrollableTarget - initialScrollY: scrolls scrollableTarget element (no height prop path)
1 parent 85c3df7 commit a89dda7

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

src/__tests__/index.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,28 @@ describe('React Infinite Scroll Component', () => {
9090
jest.useRealTimers();
9191
});
9292

93+
it('calls scroll handler via window when no height or scrollableTarget', () => {
94+
jest.useFakeTimers();
95+
const onScrollMock = jest.fn();
96+
97+
render(
98+
<InfiniteScroll
99+
onScroll={onScrollMock}
100+
dataLength={4}
101+
loader={'Loading...'}
102+
hasMore={false}
103+
next={() => {}}
104+
>
105+
<div />
106+
</InfiniteScroll>
107+
);
108+
109+
window.dispatchEvent(new Event('scroll'));
110+
jest.runOnlyPendingTimers();
111+
expect(onScrollMock).toHaveBeenCalled();
112+
jest.useRealTimers();
113+
});
114+
93115
describe('When missing the dataLength prop', () => {
94116
it('throws an error', () => {
95117
const consoleSpy = jest
@@ -185,6 +207,34 @@ describe('React Infinite Scroll Component', () => {
185207

186208
HTMLElement.prototype.scrollTo = originalScrollTo;
187209
});
210+
211+
it('scrolls scrollableTarget to initialScrollY when scrollHeight is sufficient', () => {
212+
const target = document.createElement('div');
213+
const scrollToSpy = jest.fn();
214+
target.scrollTo = scrollToSpy as unknown as typeof target.scrollTo;
215+
Object.defineProperty(target, 'scrollHeight', {
216+
configurable: true,
217+
get: () => 500,
218+
});
219+
document.body.appendChild(target);
220+
221+
render(
222+
<InfiniteScroll
223+
dataLength={0}
224+
loader={'Loading...'}
225+
hasMore={false}
226+
next={() => {}}
227+
scrollableTarget={target}
228+
initialScrollY={200}
229+
>
230+
<div />
231+
</InfiniteScroll>
232+
);
233+
234+
expect(scrollToSpy).toHaveBeenCalledWith(0, 200);
235+
236+
document.body.removeChild(target);
237+
});
188238
});
189239

190240
describe('When user scrolls to the bottom', () => {

src/__tests__/pullDown.test.tsx

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)