diff --git a/docs/api/user-event.md b/docs/api/user-event.md
index bfee6e2c3..b3b9e8797 100644
--- a/docs/api/user-event.md
+++ b/docs/api/user-event.md
@@ -285,13 +285,13 @@ The sequence of events depends on whether the scroll includes an optional moment
- `contentSizeChange`
- `scrollBeginDrag`
-- `scroll` (multiple events)
+- `scroll` (zero or more events; none if the scroll offset does not change)
- `scrollEndDrag`
**Momentum scroll (optional)**:
- `momentumScrollBegin`
-- `scroll` (multiple events)
+- `scroll` (zero or more events; none if the scroll offset does not change)
- `momentumScrollEnd`
## `pullToRefresh()` \
diff --git a/src/user-event/scroll/__tests__/__snapshots__/scroll-to-flat-list.test.tsx.snap b/src/user-event/scroll/__tests__/__snapshots__/scroll-to-flat-list.test.tsx.snap
index e71333cf6..ba7a21e4d 100644
--- a/src/user-event/scroll/__tests__/__snapshots__/scroll-to-flat-list.test.tsx.snap
+++ b/src/user-event/scroll/__tests__/__snapshots__/scroll-to-flat-list.test.tsx.snap
@@ -162,6 +162,46 @@ exports[`scrollTo() with FlatList supports vertical drag scroll: scrollTo({ y: 1
"timeStamp": 0,
},
},
+ {
+ "name": "scroll",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "contentInset": {
+ "bottom": 0,
+ "left": 0,
+ "right": 0,
+ "top": 0,
+ },
+ "contentOffset": {
+ "x": 0,
+ "y": 100,
+ },
+ "contentSize": {
+ "height": 0,
+ "width": 0,
+ },
+ "layoutMeasurement": {
+ "height": 0,
+ "width": 0,
+ },
+ "responderIgnoreScroll": true,
+ "target": 0,
+ "velocity": {
+ "x": 0,
+ "y": 0,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
{
"name": "scrollEndDrag",
"payload": {
diff --git a/src/user-event/scroll/__tests__/__snapshots__/scroll-to.test.tsx.snap b/src/user-event/scroll/__tests__/__snapshots__/scroll-to.test.tsx.snap
index ef252e6df..b12ed6bae 100644
--- a/src/user-event/scroll/__tests__/__snapshots__/scroll-to.test.tsx.snap
+++ b/src/user-event/scroll/__tests__/__snapshots__/scroll-to.test.tsx.snap
@@ -162,6 +162,46 @@ exports[`scrollTo() supports vertical drag scroll: scrollTo({ y: 100 }) 1`] = `
"timeStamp": 0,
},
},
+ {
+ "name": "scroll",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "contentInset": {
+ "bottom": 0,
+ "left": 0,
+ "right": 0,
+ "top": 0,
+ },
+ "contentOffset": {
+ "x": 0,
+ "y": 100,
+ },
+ "contentSize": {
+ "height": 0,
+ "width": 0,
+ },
+ "layoutMeasurement": {
+ "height": 0,
+ "width": 0,
+ },
+ "responderIgnoreScroll": true,
+ "target": 0,
+ "velocity": {
+ "x": 0,
+ "y": 0,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
{
"name": "scrollEndDrag",
"payload": {
diff --git a/src/user-event/scroll/__tests__/scroll-to-flat-list.test.tsx b/src/user-event/scroll/__tests__/scroll-to-flat-list.test.tsx
index 80141ee77..92080a3f4 100644
--- a/src/user-event/scroll/__tests__/scroll-to-flat-list.test.tsx
+++ b/src/user-event/scroll/__tests__/scroll-to-flat-list.test.tsx
@@ -23,7 +23,7 @@ async function renderFlatListWithToolkit(props: ScrollViewProps = {}) {
const renderItem = (title: string) => {title};
await render(
{
const { events } = await renderFlatListWithToolkit();
const user = userEvent.setup();
- await user.scrollTo(screen.getByTestId('flatList'), { y: 100 });
+ await user.scrollTo(screen.getByTestId('flat-list'), { y: 100 });
expect(mapEventsToShortForm(events)).toEqual([
['scrollBeginDrag', 0, 0],
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
expect(events).toMatchSnapshot('scrollTo({ y: 100 })');
@@ -61,12 +62,13 @@ describe('scrollTo() with FlatList', () => {
const { events } = await renderFlatListWithToolkit({ horizontal: true });
const user = userEvent.setup();
- await user.scrollTo(screen.getByTestId('flatList'), { x: 100 });
+ await user.scrollTo(screen.getByTestId('flat-list'), { x: 100 });
expect(mapEventsToShortForm(events)).toEqual([
['scrollBeginDrag', 0, 0],
['scroll', 0, 25],
['scroll', 0, 50],
['scroll', 0, 75],
+ ['scroll', 0, 100],
['scrollEndDrag', 0, 100],
]);
});
@@ -117,3 +119,28 @@ test('scrollTo with contentSize and layoutMeasurement update FlatList content',
// Prevent act warning by unmounting the component
await screen.unmount();
});
+
+test('drag scroll delivers final offset to FlatList', async () => {
+ const onEndReached = jest.fn();
+ await render(
+ }
+ onEndReached={onEndReached}
+ onEndReachedThreshold={0}
+ />,
+ );
+ const user = userEvent.setup();
+
+ await user.scrollTo(screen.getByTestId('flat-list'), {
+ y: 520,
+ contentSize: { width: 240, height: 1000 },
+ layoutMeasurement: { width: 240, height: 480 },
+ });
+
+ expect(onEndReached).toHaveBeenCalledTimes(1);
+
+ // Prevent act warning by unmounting the component
+ await screen.unmount();
+});
diff --git a/src/user-event/scroll/__tests__/scroll-to.test.tsx b/src/user-event/scroll/__tests__/scroll-to.test.tsx
index 3deb234fa..e2190ad3d 100644
--- a/src/user-event/scroll/__tests__/scroll-to.test.tsx
+++ b/src/user-event/scroll/__tests__/scroll-to.test.tsx
@@ -49,6 +49,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
expect(events).toMatchSnapshot('scrollTo({ y: 100 })');
@@ -64,6 +65,7 @@ describe('scrollTo()', () => {
['scroll', 0, 25],
['scroll', 0, 50],
['scroll', 0, 75],
+ ['scroll', 0, 100],
['scrollEndDrag', 0, 100],
]);
});
@@ -81,6 +83,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
['momentumScrollBegin', 100, 0],
['scroll', 110, 0],
@@ -102,6 +105,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
});
@@ -117,11 +121,13 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
['scrollBeginDrag', 100, 0],
['scroll', 125, 0],
['scroll', 150, 0],
['scroll', 175, 0],
+ ['scroll', 200, 0],
['scrollEndDrag', 200, 0],
]);
});
@@ -140,6 +146,7 @@ describe('scrollTo()', () => {
['scroll', 125, 0],
['scroll', 150, 0],
['scroll', 175, 0],
+ ['scroll', 200, 0],
['scrollEndDrag', 200, 0],
]);
});
@@ -190,9 +197,9 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
['momentumScrollBegin', 100, 0],
- ['scroll', 100, 0],
['momentumScrollEnd', 100, 0],
]);
});
@@ -254,6 +261,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
+ ['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
});
diff --git a/src/user-event/scroll/scroll-to.ts b/src/user-event/scroll/scroll-to.ts
index 19f29d06b..6d581d6ef 100644
--- a/src/user-event/scroll/scroll-to.ts
+++ b/src/user-event/scroll/scroll-to.ts
@@ -103,10 +103,8 @@ async function emitDragScrollEvents(
buildScrollEvent(scrollSteps[0], scrollOptions),
);
- // Note: experimentally, in case of drag scroll the last scroll step
- // will not trigger `scroll` event.
- // See: https://github.com/callstack/react-native-testing-library/wiki/ScrollView-Events
- for (let i = 1; i < scrollSteps.length - 1; i += 1) {
+ // Native emits `scroll` for every drag step, including the last; `scrollEndDrag` repeats its offset.
+ for (let i = 1; i < scrollSteps.length; i += 1) {
await wait(config);
await interaction.dispatchEvent('scroll', buildScrollEvent(scrollSteps[i], scrollOptions));
}
@@ -132,9 +130,7 @@ async function emitMomentumScrollEvents(
buildScrollEvent(scrollSteps[0], scrollOptions),
);
- // Note: experimentally, in case of momentum scroll the last scroll step
- // will trigger `scroll` event.
- // See: https://github.com/callstack/react-native-testing-library/wiki/ScrollView-Events
+ // Native emits `scroll` for every momentum step, including the last; `momentumScrollEnd` repeats its offset.
for (let i = 1; i < scrollSteps.length; i += 1) {
await wait(config);
await interaction.dispatchEvent('scroll', buildScrollEvent(scrollSteps[i], scrollOptions));
diff --git a/src/user-event/scroll/utils.ts b/src/user-event/scroll/utils.ts
index 8fd043edd..4bb28483a 100644
--- a/src/user-event/scroll/utils.ts
+++ b/src/user-event/scroll/utils.ts
@@ -10,14 +10,14 @@ export function createScrollSteps(
interpolator: InterpolatorFn,
): Point[] {
if (target.y != null) {
- return interpolator(target.y, initialOffset.y, DEFAULT_STEPS_COUNT).map((y) => ({
+ return interpolate(target.y, initialOffset.y, interpolator).map((y) => ({
y,
x: initialOffset.x,
}));
}
if (target.x != null) {
- return interpolator(target.x, initialOffset.x, DEFAULT_STEPS_COUNT).map((x) => ({
+ return interpolate(target.x, initialOffset.x, interpolator).map((x) => ({
x,
y: initialOffset.y,
}));
@@ -26,14 +26,19 @@ export function createScrollSteps(
return [];
}
+function interpolate(end: number, start: number, interpolator: InterpolatorFn): number[] {
+ // No movement: a single step, so no `scroll` event is emitted for an unchanged offset.
+ if (end === start) {
+ return [start];
+ }
+
+ return interpolator(end, start, DEFAULT_STEPS_COUNT);
+}
+
/**
* Generate linear scroll values (with equal steps).
*/
export function linearInterpolator(end: number, start: number, steps: number): number[] {
- if (end === start) {
- return [end, start];
- }
-
const result = [];
for (let i = 0; i < steps; i += 1) {
result.push(lerp(start, end, i / (steps - 1)));
@@ -46,10 +51,6 @@ export function linearInterpolator(end: number, start: number, steps: number): n
* Generate inertial scroll values (exponentially slowing down).
*/
export function inertialInterpolator(end: number, start: number, steps: number): number[] {
- if (end === start) {
- return [end, start];
- }
-
const result = [];
let factor = 1;
for (let i = 0; i < steps - 1; i += 1) {
diff --git a/website/docs/14.x/docs/api/events/user-event.mdx b/website/docs/14.x/docs/api/events/user-event.mdx
index 2c7f5fcd6..984f74015 100644
--- a/website/docs/14.x/docs/api/events/user-event.mdx
+++ b/website/docs/14.x/docs/api/events/user-event.mdx
@@ -286,13 +286,13 @@ The sequence of events depends on whether the scroll includes an optional moment
- `contentSizeChange`
- `scrollBeginDrag`
-- `scroll` (multiple events)
+- `scroll` (zero or more events; none if the scroll offset does not change)
- `scrollEndDrag`
**Momentum scroll (optional)**:
- `momentumScrollBegin`
-- `scroll` (multiple events)
+- `scroll` (zero or more events; none if the scroll offset does not change)
- `momentumScrollEnd`
## `pullToRefresh()` \{#pull-to-refresh}