Skip to content

Commit 5f0f0d8

Browse files
huextratclaude
authored andcommitted
Forward press handlers as testOnly_* in PressableWithTouchable (#4416)
## Description Fixes #4417. Since 3.2.0, `Pressable` dispatches between two engines: `StatefulPressable` when one of the relation props (`simultaneousWith` / `requireToFail` / `block`) is passed, and `PressableWithTouchable` otherwise — the common case. Only `StatefulPressable` sets `testOnly_onPress` / `testOnly_onPressIn` / `testOnly_onPressOut` / `testOnly_onLongPress` on the button. `PressableWithTouchable` doesn't, so for a plain `<Pressable onPress={...}>` React Native Testing Library finds no handler and `fireEvent(element, 'press')` silently does nothing. The press state machine runs natively, so in a test environment those props are the only way to reach the handlers. This makes the regression silent and fairly wide-reaching: no error is thrown, tests just stop observing presses. On 3.1.0 the single v3 `Pressable` always forwarded them, so this is a 3.1.0 → 3.2.0 regression for any app whose Jest suite drives a `Pressable`. The fix forwards the four handlers from `PressableWithTouchable` too, guarded by `isTestEnv()`, exactly as `StatefulPressable` does. `Touchable` spreads its remaining props onto `GestureHandlerButton`, which already declares them in `ButtonProps`, so nothing else needed to change and the props stay stripped outside a test environment. Note this is unrelated to #4414, which fixed the `testOnly_pressed` display state. ## Test plan Added a regression test in `src/__tests__/mocks.test.tsx` asserting the four `testOnly_*` props are wired on the button for a relation-free `Pressable`. It fails on `main` and passes with the fix. The test asserts the props rather than calling `fireEvent(element, 'press')` because this repo is on `@testing-library/react-native@12.9`, which predates the `testOnly_*` handler lookup (added in v13). Asserting the props keeps the test meaningful on the pinned version and independent of the RNTL version. In `packages/react-native-gesture-handler`: - `yarn test` — 16 suites, 135 tests passing - `yarn ts-check` — clean - `yarn lint:js` — 0 errors - `yarn format:js` — clean Also verified end-to-end in a real app (Expo 57 / RN 0.86, RNTL 14.0.1) that had ~3 suites broken by the 3.1.0 → 3.2.0 bump: with this change applied as a patch, the full suite is green again (136 suites, 1663 tests, 73 snapshots). Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 44046a6 commit 5f0f0d8

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

‎packages/react-native-gesture-handler/src/__tests__/mocks.test.tsx‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
TouchableOpacity,
2525
TouchableWithoutFeedback,
2626
} from '../mocks/Touchables';
27-
import { Touchable } from '../v3/components';
27+
import { Pressable, Touchable } from '../v3/components';
2828

2929
describe('Jest mocks – legacy components render without crashing', () => {
3030
test('LegacyRawButton', () => {
@@ -142,3 +142,34 @@ test('Trigger Touchable press', () => {
142142

143143
expect(onPress).toHaveBeenCalled();
144144
});
145+
146+
test('Pressable exposes its press handlers to testing-library', () => {
147+
// `Pressable` renders `PressableWithTouchable` unless a relation prop is
148+
// passed. Both engines have to forward the handlers as `testOnly_*` props on
149+
// the button, since that is what `fireEvent(element, 'press')` picks up in a
150+
// test environment — the press state machine itself runs natively.
151+
const onPress = jest.fn();
152+
const onPressIn = jest.fn();
153+
const onPressOut = jest.fn();
154+
const onLongPress = jest.fn();
155+
156+
render(
157+
<GestureHandlerRootView>
158+
<Pressable
159+
testID="pressable"
160+
onPress={onPress}
161+
onPressIn={onPressIn}
162+
onPressOut={onPressOut}
163+
onLongPress={onLongPress}>
164+
<Text>Press Me</Text>
165+
</Pressable>
166+
</GestureHandlerRootView>
167+
);
168+
169+
const button = screen.getByTestId('pressable');
170+
171+
expect(button.props.testOnly_onPress).toBe(onPress);
172+
expect(button.props.testOnly_onPressIn).toBe(onPressIn);
173+
expect(button.props.testOnly_onPressOut).toBe(onPressOut);
174+
expect(button.props.testOnly_onLongPress).toBe(onLongPress);
175+
});

‎packages/react-native-gesture-handler/src/v3/components/PressableWithTouchable.tsx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import {
1313
numberAsInset,
1414
} from '../../components/Pressable/utils';
1515
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
16+
import { isTestEnv } from '../../utils';
1617
import { pointerStyle } from './pointerStyle';
1718
import { Touchable } from './Touchable/Touchable';
1819

20+
const IS_TEST_ENV = isTestEnv();
21+
1922
// RN's Pressable default. Touchable's own default is 600ms
2023
const DEFAULT_LONG_PRESS_DURATION = 500;
2124

@@ -314,7 +317,11 @@ const PressableWithTouchable = (props: PressableProps) => {
314317
onPress={handlePress}
315318
onLongPress={handleLongPress}
316319
onHoverIn={handleHoverIn}
317-
onHoverOut={handleHoverOut}>
320+
onHoverOut={handleHoverOut}
321+
testOnly_onPress={IS_TEST_ENV ? onPress : undefined}
322+
testOnly_onPressIn={IS_TEST_ENV ? onPressIn : undefined}
323+
testOnly_onPressOut={IS_TEST_ENV ? onPressOut : undefined}
324+
testOnly_onLongPress={IS_TEST_ENV ? onLongPress : undefined}>
318325
{resolvedChildren}
319326
{__DEV__ ? (
320327
<PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />

0 commit comments

Comments
 (0)