diff --git a/src/__tests__/fireEvent.test.js b/src/__tests__/fireEvent.test.js index 74d97aa9e..5fef1c806 100644 --- a/src/__tests__/fireEvent.test.js +++ b/src/__tests__/fireEvent.test.js @@ -282,3 +282,26 @@ test('is not fooled by non-native disabled prop', () => { fireEvent.press(screen.getByText('Trigger Test')); expect(handlePress).toHaveBeenCalledTimes(1); }); + +function TestChildTouchableComponent({ onPress, someProp }) { + return ( + + + Trigger + + + ); +} + +test('is not fooled by non-responder wrapping host elements', () => { + const handlePress = jest.fn(); + + const screen = render( + + + + ); + + fireEvent.press(screen.getByText('Trigger')); + expect(handlePress).not.toHaveBeenCalled(); +}); diff --git a/src/fireEvent.js b/src/fireEvent.js index 08b9a65ec..c9156dd13 100644 --- a/src/fireEvent.js +++ b/src/fireEvent.js @@ -2,32 +2,47 @@ import act from './act'; import { ErrorWithStack } from './helpers/errors'; -const isTextInputComponent = (element: ReactTestInstance) => { +const isHostElement = (element?: ReactTestInstance) => { + return typeof element?.type === 'string'; +}; + +const isTextInput = (element?: ReactTestInstance) => { // eslint-disable-next-line import/no-extraneous-dependencies const { TextInput } = require('react-native'); - return element.type === TextInput; + return element?.type === TextInput; +}; + +const isTouchResponder = (element?: ReactTestInstance) => { + if (!isHostElement(element)) return false; + + return !!element?.props.onStartShouldSetResponder || isTextInput(element); +}; + +const isEventEnabled = ( + element?: ReactTestInstance, + touchResponder?: ReactTestInstance +) => { + if (isTextInput(element)) return element?.props.editable !== false; + + return touchResponder?.props.onStartShouldSetResponder?.() !== false; }; const findEventHandler = ( element: ReactTestInstance, eventName: string, callsite?: any, - nearestHostDescendent?: ReactTestInstance, + nearestTouchResponder?: ReactTestInstance, hasDescendandHandler?: boolean ) => { - const handler = getEventHandler(element, eventName); - const hasHandler = handler != null || hasDescendandHandler; - - const isHostComponent = typeof element.type === 'string'; - const hostElement = isHostComponent ? element : nearestHostDescendent; + const touchResponder = isTouchResponder(element) + ? element + : nearestTouchResponder; - const isEventEnabled = isTextInputComponent(element) - ? element.props.editable !== false - : hostElement?.props.onStartShouldSetResponder?.() !== false; - - if (handler && isEventEnabled) return handler; + const handler = getEventHandler(element, eventName); + if (handler && isEventEnabled(element, touchResponder)) return handler; // Do not bubble event to the root element + const hasHandler = handler != null || hasDescendandHandler; if (element.parent === null || element.parent.parent === null) { if (hasHandler) { return null; @@ -43,7 +58,7 @@ const findEventHandler = ( element.parent, eventName, callsite, - hostElement, + touchResponder, hasHandler ); };