From 5a4037531bfc58e3a839d85e99a50125c47c2040 Mon Sep 17 00:00:00 2001 From: Hugo Extrat Date: Thu, 13 Aug 2026 18:16:38 +0200 Subject: [PATCH] Forward press handlers as `testOnly_*` in `PressableWithTouchable` `Pressable` dispatches to `StatefulPressable` when a relation prop is passed and to `PressableWithTouchable` otherwise. Only the former sets `testOnly_onPress`/`onPressIn`/`onPressOut`/`onLongPress` on the button, so in the common case (no relation props) React Native Testing Library finds no handler and `fireEvent(element, 'press')` silently does nothing. The press state machine runs natively, so a test environment has no other way to reach the handlers. Forward them from `PressableWithTouchable` too, guarded by `isTestEnv()`, exactly as `StatefulPressable` does. Co-Authored-By: Claude Opus 5 --- .../src/__tests__/mocks.test.tsx | 33 ++++++++++++++++++- .../v3/components/PressableWithTouchable.tsx | 9 ++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/__tests__/mocks.test.tsx b/packages/react-native-gesture-handler/src/__tests__/mocks.test.tsx index b226005b10..2cc05f6073 100644 --- a/packages/react-native-gesture-handler/src/__tests__/mocks.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/mocks.test.tsx @@ -24,7 +24,7 @@ import { TouchableOpacity, TouchableWithoutFeedback, } from '../mocks/Touchables'; -import { Touchable } from '../v3/components'; +import { Pressable, Touchable } from '../v3/components'; describe('Jest mocks – legacy components render without crashing', () => { test('LegacyRawButton', () => { @@ -142,3 +142,34 @@ test('Trigger Touchable press', () => { expect(onPress).toHaveBeenCalled(); }); + +test('Pressable exposes its press handlers to testing-library', () => { + // `Pressable` renders `PressableWithTouchable` unless a relation prop is + // passed. Both engines have to forward the handlers as `testOnly_*` props on + // the button, since that is what `fireEvent(element, 'press')` picks up in a + // test environment — the press state machine itself runs natively. + const onPress = jest.fn(); + const onPressIn = jest.fn(); + const onPressOut = jest.fn(); + const onLongPress = jest.fn(); + + render( + + + Press Me + + + ); + + const button = screen.getByTestId('pressable'); + + expect(button.props.testOnly_onPress).toBe(onPress); + expect(button.props.testOnly_onPressIn).toBe(onPressIn); + expect(button.props.testOnly_onPressOut).toBe(onPressOut); + expect(button.props.testOnly_onLongPress).toBe(onLongPress); +}); diff --git a/packages/react-native-gesture-handler/src/v3/components/PressableWithTouchable.tsx b/packages/react-native-gesture-handler/src/v3/components/PressableWithTouchable.tsx index 923cc61ab6..03ac35b79f 100644 --- a/packages/react-native-gesture-handler/src/v3/components/PressableWithTouchable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/PressableWithTouchable.tsx @@ -13,9 +13,12 @@ import { numberAsInset, } from '../../components/Pressable/utils'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; +import { isTestEnv } from '../../utils'; import { pointerStyle } from './pointerStyle'; import { Touchable } from './Touchable/Touchable'; +const IS_TEST_ENV = isTestEnv(); + // RN's Pressable default. Touchable's own default is 600ms const DEFAULT_LONG_PRESS_DURATION = 500; @@ -314,7 +317,11 @@ const PressableWithTouchable = (props: PressableProps) => { onPress={handlePress} onLongPress={handleLongPress} onHoverIn={handleHoverIn} - onHoverOut={handleHoverOut}> + onHoverOut={handleHoverOut} + testOnly_onPress={IS_TEST_ENV ? onPress : undefined} + testOnly_onPressIn={IS_TEST_ENV ? onPressIn : undefined} + testOnly_onPressOut={IS_TEST_ENV ? onPressOut : undefined} + testOnly_onLongPress={IS_TEST_ENV ? onLongPress : undefined}> {resolvedChildren} {__DEV__ ? (