diff --git a/src/user-event/index.ts b/src/user-event/index.ts
index a94f54be..87c93465 100644
--- a/src/user-event/index.ts
+++ b/src/user-event/index.ts
@@ -20,4 +20,5 @@ export const userEvent = {
paste: (element: ReactTestInstance, text: string) => setup().paste(element, text),
scrollTo: (element: ReactTestInstance, options: ScrollToOptions) =>
setup().scrollTo(element, options),
+ pullToRefresh: (element: ReactTestInstance) => setup().pullToRefresh(element),
};
diff --git a/src/user-event/pull-to-refresh.ts b/src/user-event/pull-to-refresh.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/user-event/scroll/__tests__/pull-to-refresh.test.tsx b/src/user-event/scroll/__tests__/pull-to-refresh.test.tsx
new file mode 100644
index 00000000..2d2451b8
--- /dev/null
+++ b/src/user-event/scroll/__tests__/pull-to-refresh.test.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import { FlatList, RefreshControl, ScrollView, SectionList, Text } from 'react-native';
+
+import { render, screen, userEvent } from '../../..';
+
+describe('pullToRefresh()', () => {
+ it('supports ScrollView', async () => {
+ const onRefreshMock = jest.fn();
+ render(
+ }
+ />,
+ );
+ const user = userEvent.setup();
+
+ await user.pullToRefresh(screen.getByTestId('view'));
+ expect(onRefreshMock).toHaveBeenCalled();
+ });
+
+ it('supports FlatList', async () => {
+ const onRefreshMock = jest.fn();
+ render(
+ {item}}
+ refreshControl={}
+ />,
+ );
+ const user = userEvent.setup();
+
+ await user.pullToRefresh(screen.getByTestId('view'));
+ expect(onRefreshMock).toHaveBeenCalled();
+ });
+
+ it('supports SectionList', async () => {
+ const onRefreshMock = jest.fn();
+ render(
+ {item}}
+ refreshControl={}
+ />,
+ );
+ const user = userEvent.setup();
+
+ await user.pullToRefresh(screen.getByTestId('view'));
+ expect(onRefreshMock).toHaveBeenCalled();
+ });
+
+ it('does not throw when RefreshControl is not set', async () => {
+ render();
+ const user = userEvent.setup();
+
+ await expect(() => user.pullToRefresh(screen.getByTestId('view'))).not.toThrow();
+ });
+
+ it('does not throw when RefreshControl onRefresh is not set', async () => {
+ render(} />);
+ const user = userEvent.setup();
+
+ await expect(() => user.pullToRefresh(screen.getByTestId('view'))).not.toThrow();
+ });
+});
diff --git a/src/user-event/scroll/pull-to-refresh.ts b/src/user-event/scroll/pull-to-refresh.ts
new file mode 100644
index 00000000..3189b32c
--- /dev/null
+++ b/src/user-event/scroll/pull-to-refresh.ts
@@ -0,0 +1,28 @@
+import type { ReactTestInstance } from 'react-test-renderer';
+
+import act from '../../act';
+import { ErrorWithStack } from '../../helpers/errors';
+import { isHostScrollView } from '../../helpers/host-component-names';
+import type { UserEventInstance } from '../setup';
+
+export async function pullToRefresh(
+ this: UserEventInstance,
+ element: ReactTestInstance,
+): Promise {
+ if (!isHostScrollView(element)) {
+ throw new ErrorWithStack(
+ `pullToRefresh() works only with host "ScrollView" elements. Passed element has type "${element.type}".`,
+ pullToRefresh,
+ );
+ }
+
+ const refreshControl = element.props.refreshControl;
+ if (refreshControl == null || typeof refreshControl.props.onRefresh !== 'function') {
+ return;
+ }
+
+ // eslint-disable-next-line require-await
+ await act(async () => {
+ refreshControl.props.onRefresh();
+ });
+}
diff --git a/src/user-event/setup/setup.ts b/src/user-event/setup/setup.ts
index e6d164e5..f8105cb5 100644
--- a/src/user-event/setup/setup.ts
+++ b/src/user-event/setup/setup.ts
@@ -8,6 +8,7 @@ import type { PressOptions } from '../press';
import { longPress, press } from '../press';
import type { ScrollToOptions } from '../scroll';
import { scrollTo } from '../scroll';
+import { pullToRefresh } from '../scroll/pull-to-refresh';
import type { TypeOptions } from '../type';
import { type } from '../type';
import { wait } from '../utils';
@@ -138,12 +139,24 @@ export interface UserEventInstance {
paste: (element: ReactTestInstance, text: string) => Promise;
/**
- * Simlate user scorlling a ScrollView element.
+ * Simlate user scorlling a given `ScrollView`-like element.
*
- * @param element ScrollView element
+ * Supported components: ScrollView, FlatList, SectionList
+ *
+ * @param element ScrollView-like element
* @returns
*/
scrollTo: (element: ReactTestInstance, options: ScrollToOptions) => Promise;
+
+ /**
+ * Simulate using pull-to-refresh gesture on a given `ScrollView`-like element.
+ *
+ * Supported components: ScrollView, FlatList, SectionList
+ *
+ * @param element ScrollView-like element
+ * @returns
+ */
+ pullToRefresh: (element: ReactTestInstance) => Promise;
}
function createInstance(config: UserEventConfig): UserEventInstance {
@@ -159,6 +172,7 @@ function createInstance(config: UserEventConfig): UserEventInstance {
clear: wrapAndBindImpl(instance, clear),
paste: wrapAndBindImpl(instance, paste),
scrollTo: wrapAndBindImpl(instance, scrollTo),
+ pullToRefresh: wrapAndBindImpl(instance, pullToRefresh),
};
Object.assign(instance, api);