Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/api/user-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()` \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
33 changes: 30 additions & 3 deletions src/user-event/scroll/__tests__/scroll-to-flat-list.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async function renderFlatListWithToolkit(props: ScrollViewProps = {}) {
const renderItem = (title: string) => <Text>{title}</Text>;
await render(
<FlatList
testID="flatList"
testID="flat-list"
onScroll={logEvent('scroll')}
onScrollBeginDrag={logEvent('scrollBeginDrag')}
onScrollEndDrag={logEvent('scrollEndDrag')}
Expand All @@ -46,12 +46,13 @@ describe('scrollTo() with FlatList', () => {
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 })');
Expand All @@ -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],
]);
});
Expand Down Expand Up @@ -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(
<FlatList
testID="flat-list"
data={data}
renderItem={(x) => <Item title={x.item} />}
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();
});
10 changes: 9 additions & 1 deletion src/user-event/scroll/__tests__/scroll-to.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 })');
Expand All @@ -64,6 +65,7 @@ describe('scrollTo()', () => {
['scroll', 0, 25],
['scroll', 0, 50],
['scroll', 0, 75],
['scroll', 0, 100],
['scrollEndDrag', 0, 100],
]);
});
Expand All @@ -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],
Expand All @@ -102,6 +105,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
});
Expand All @@ -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],
]);
});
Expand All @@ -140,6 +146,7 @@ describe('scrollTo()', () => {
['scroll', 125, 0],
['scroll', 150, 0],
['scroll', 175, 0],
['scroll', 200, 0],
['scrollEndDrag', 200, 0],
]);
});
Expand Down Expand Up @@ -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],
]);
});
Expand Down Expand Up @@ -254,6 +261,7 @@ describe('scrollTo()', () => {
['scroll', 25, 0],
['scroll', 50, 0],
['scroll', 75, 0],
['scroll', 100, 0],
['scrollEndDrag', 100, 0],
]);
});
Expand Down
10 changes: 3 additions & 7 deletions src/user-event/scroll/scroll-to.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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));
Expand Down
21 changes: 11 additions & 10 deletions src/user-event/scroll/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
Expand All @@ -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)));
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions website/docs/14.x/docs/api/events/user-event.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading