Skip to content

fix: RN Gesture Handler Pressable support #1746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 25, 2025
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 examples/basic/components/__tests__/AnimatedView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('AnimatedView', () => {
);
expect(screen.root).toHaveStyle({ opacity: 0 });

await act(() => jest.advanceTimersByTime(250));
act(() => jest.advanceTimersByTime(250));
expect(screen.root).toHaveStyle({ opacity: 1 });
});

Expand All @@ -32,7 +32,7 @@ describe('AnimatedView', () => {
);
expect(screen.root).toHaveStyle({ opacity: 0 });

await act(() => jest.advanceTimersByTime(250));
act(() => jest.advanceTimersByTime(250));
expect(screen.root).toHaveStyle({ opacity: 1 });
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"chalk": "^4.1.2",
"jest-matcher-utils": "^29.7.0",
"pretty-format": "^29.7.0",
"react-native-gesture-handler": "^2.23.1",
"redent": "^3.0.0"
},
"peerDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/react-native-animated.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,28 @@ describe('AnimatedView', () => {
jest.useRealTimers();
});

it('should use native driver when useNativeDriver is true', async () => {
it('should use native driver when useNativeDriver is true', () => {
render(
<AnimatedView fadeInDuration={250} useNativeDriver={true}>
Test
</AnimatedView>,
);
expect(screen.root).toHaveStyle({ opacity: 0 });

await act(() => jest.advanceTimersByTime(250));
act(() => jest.advanceTimersByTime(250));
// This stopped working in tests in RN 0.77
// expect(screen.root).toHaveStyle({ opacity: 0 });
});

it('should not use native driver when useNativeDriver is false', async () => {
it('should not use native driver when useNativeDriver is false', () => {
render(
<AnimatedView fadeInDuration={250} useNativeDriver={false}>
Test
</AnimatedView>,
);
expect(screen.root).toHaveStyle({ opacity: 0 });

await act(() => jest.advanceTimersByTime(250));
act(() => jest.advanceTimersByTime(250));
expect(screen.root).toHaveStyle({ opacity: 1 });
});
});
61 changes: 61 additions & 0 deletions src/__tests__/react-native-gesture-handler.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'react-native-gesture-handler/jestSetup.js';
import React from 'react';
import { View } from 'react-native';
import { Pressable } from 'react-native-gesture-handler';

import { fireEvent, render, screen, userEvent } from '..';
import { createEventLogger, getEventsNames } from '../test-utils';

test('fireEvent can invoke press events for RNGH Pressable', () => {
const onPress = jest.fn();
const onPressIn = jest.fn();
const onPressOut = jest.fn();
const onLongPress = jest.fn();

render(
<View>
<Pressable
testID="pressable"
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onLongPress={onLongPress}
/>
</View>,
);

const pressable = screen.getByTestId('pressable');

fireEvent.press(pressable);
expect(onPress).toHaveBeenCalled();

fireEvent(pressable, 'pressIn');
expect(onPressIn).toHaveBeenCalled();

fireEvent(pressable, 'pressOut');
expect(onPressOut).toHaveBeenCalled();

fireEvent(pressable, 'longPress');
expect(onLongPress).toHaveBeenCalled();
});

test('userEvent can invoke press events for RNGH Pressable', async () => {
const { events, logEvent } = createEventLogger();
const user = userEvent.setup();

render(
<View>
<Pressable
testID="pressable"
onPress={logEvent('press')}
onPressIn={logEvent('pressIn')}
onPressOut={logEvent('pressOut')}
onLongPress={logEvent('longPress')}
/>
</View>,
);

const pressable = screen.getByTestId('pressable');
await user.press(pressable);
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
});
34 changes: 33 additions & 1 deletion src/user-event/press/__tests__/longPress.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { Pressable, Text, TouchableHighlight, TouchableOpacity } from 'react-native';
import { Pressable, Text, TouchableHighlight, TouchableOpacity, View } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';

import { render, screen } from '../../..';
import { createEventLogger, getEventsNames } from '../../../test-utils';
Expand Down Expand Up @@ -152,4 +153,35 @@ describe('userEvent.longPress with fake timers', () => {

expect(mockOnLongPress).toHaveBeenCalled();
});

test('longPress accepts custom duration', async () => {
const { events, logEvent } = createEventLogger();
const user = userEvent.setup();

render(
<Pressable
onPress={logEvent('press')}
onPressIn={logEvent('pressIn')}
onPressOut={logEvent('pressOut')}
onLongPress={logEvent('longPress')}
testID="pressable"
/>,
);

await user.longPress(screen.getByTestId('pressable'), { duration: 50 });
expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
});

it('longPress throws on composite components', async () => {
render(<View testID="view" />);
const user = userEvent.setup();

const compositeView = screen.getByTestId('view').parent as ReactTestInstance;
await expect(user.longPress(compositeView)).rejects.toThrowErrorMatchingInlineSnapshot(`
"longPress() works only with host elements. Passed element has type "function Component() {
(0, _classCallCheck2.default)(this, Component);
return _callSuper(this, Component, arguments);
}"."
`);
});
});
6 changes: 4 additions & 2 deletions src/user-event/press/__tests__/press.real-timers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ describe('userEvent.press with real timers', () => {
);

await user.press(screen.getByTestId('pressable'));
// Typical event order is pressIn, pressOut, press
// But sometimes due to a race condition, the order is pressIn, press, pressOut.
const eventSequence = getEventsNames(events).join(', ');
expect(
eventSequence === 'pressIn, pressOut, press' || eventSequence === 'pressIn, press, pressOut',
Expand Down Expand Up @@ -201,11 +203,11 @@ describe('userEvent.press with real timers', () => {
);
await user.press(screen.getByTestId('pressable'));

const eventsNames = getEventsNames(events).join(', ');
const eventSequence = getEventsNames(events).join(', ');
// Typical event order is pressIn, pressOut, press
// But sometimes due to a race condition, the order is pressIn, press, pressOut.
expect(
eventsNames === 'pressIn, pressOut, press' || eventsNames === 'pressIn, press, pressOut',
eventSequence === 'pressIn, pressOut, press' || eventSequence === 'pressIn, press, pressOut',
).toBe(true);
});

Expand Down
14 changes: 14 additions & 0 deletions src/user-event/press/__tests__/press.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';

import { render, screen } from '../../..';
import { createEventLogger, getEventsNames } from '../../../test-utils';
Expand Down Expand Up @@ -331,6 +332,19 @@ describe('userEvent.press with fake timers', () => {
expect(mockOnPress).toHaveBeenCalled();
});

it('press throws on composite components', async () => {
render(<View testID="view" />);
const user = userEvent.setup();

const compositeView = screen.getByTestId('view').parent as ReactTestInstance;
await expect(user.press(compositeView)).rejects.toThrowErrorMatchingInlineSnapshot(`
"press() works only with host elements. Passed element has type "function Component() {
(0, _classCallCheck2.default)(this, Component);
return _callSuper(this, Component, arguments);
}"."
`);
});

test('disables act environmennt', async () => {
// In this test there is state update during await when typing
// Since wait is not wrapped by act there would be a warning
Expand Down
Loading
Loading