diff --git a/src/__tests__/fireEvent.test.tsx b/src/__tests__/fireEvent.test.tsx index 6e9fb8f01..0da46994e 100644 --- a/src/__tests__/fireEvent.test.tsx +++ b/src/__tests__/fireEvent.test.tsx @@ -265,6 +265,7 @@ test('should not fire inside View with pointerEvents="none"', () => { ); fireEvent.press(screen.getByText('Trigger')); + fireEvent(screen.getByText('Trigger'), 'onPress'); expect(onPress).not.toHaveBeenCalled(); }); @@ -279,6 +280,7 @@ test('should not fire inside View with pointerEvents="box-only"', () => { ); fireEvent.press(screen.getByText('Trigger')); + fireEvent(screen.getByText('Trigger'), 'onPress'); expect(onPress).not.toHaveBeenCalled(); }); @@ -293,7 +295,8 @@ test('should fire inside View with pointerEvents="box-none"', () => { ); fireEvent.press(screen.getByText('Trigger')); - expect(onPress).toHaveBeenCalled(); + fireEvent(screen.getByText('Trigger'), 'onPress'); + expect(onPress).toHaveBeenCalledTimes(2); }); test('should fire inside View with pointerEvents="auto"', () => { @@ -307,7 +310,8 @@ test('should fire inside View with pointerEvents="auto"', () => { ); fireEvent.press(screen.getByText('Trigger')); - expect(onPress).toHaveBeenCalled(); + fireEvent(screen.getByText('Trigger'), 'onPress'); + expect(onPress).toHaveBeenCalledTimes(2); }); test('should not fire deeply inside View with pointerEvents="box-only"', () => { @@ -323,6 +327,7 @@ test('should not fire deeply inside View with pointerEvents="box-only"', () => { ); fireEvent.press(screen.getByText('Trigger')); + fireEvent(screen.getByText('Trigger'), 'onPress'); expect(onPress).not.toHaveBeenCalled(); }); diff --git a/src/fireEvent.ts b/src/fireEvent.ts index eb5ce8c28..f32ef6a37 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -5,13 +5,9 @@ import { getHostParent, isHostElement } from './helpers/component-tree'; import { filterNodeByType } from './helpers/filterNodeByType'; import { getHostComponentNames } from './helpers/host-component-names'; -type EventHandler = (...args: any) => unknown; - -const isTextInput = (element?: ReactTestInstance) => { - if (!element) { - return false; - } +type EventHandler = (...args: unknown[]) => unknown; +function isTextInput(element: ReactTestInstance) { // We have to test if the element type is either the `TextInput` component // (for composite component) or the string "TextInput" (for host component) // All queries return host components but since fireEvent bubbles up @@ -20,18 +16,22 @@ const isTextInput = (element?: ReactTestInstance) => { filterNodeByType(element, TextInput) || filterNodeByType(element, getHostComponentNames().textInput) ); -}; +} -const isTouchResponder = (element?: ReactTestInstance) => { - if (!isHostElement(element)) return false; +function isTouchResponder(element: ReactTestInstance) { + if (!isHostElement(element)) { + return false; + } - return !!element?.props.onStartShouldSetResponder || isTextInput(element); -}; + return ( + Boolean(element.props.onStartShouldSetResponder) || isTextInput(element) + ); +} -const isPointerEventEnabled = ( +function isPointerEventEnabled( element: ReactTestInstance, isParent?: boolean -): boolean => { +): boolean { const pointerEvents = element.props.pointerEvents; if (pointerEvents === 'none') { return false; @@ -47,54 +47,60 @@ const isPointerEventEnabled = ( } return isPointerEventEnabled(parent, true); -}; +} + +// Due to accepting both `press` and `onPress` for event names, we need to +// cover both forms. +const touchEventNames = ['press', 'onPress']; -const isTouchEvent = (eventName?: string) => { - return eventName === 'press'; -}; +function isTouchEvent(eventName: string) { + return touchEventNames.includes(eventName); +} -const isEventEnabled = ( +function isEventEnabled( element: ReactTestInstance, - touchResponder?: ReactTestInstance, - eventName?: string -) => { - if (isTextInput(element)) return element?.props.editable !== false; - if (!isPointerEventEnabled(element) && isTouchEvent(eventName)) return false; + eventName: string, + nearestTouchResponder?: ReactTestInstance +) { + if (isTextInput(element)) { + return element.props.editable !== false; + } - const touchStart = touchResponder?.props.onStartShouldSetResponder?.(); - const touchMove = touchResponder?.props.onMoveShouldSetResponder?.(); + if (isTouchEvent(eventName) && !isPointerEventEnabled(element)) { + return false; + } - if (touchStart || touchMove) return true; + const touchStart = nearestTouchResponder?.props.onStartShouldSetResponder?.(); + const touchMove = nearestTouchResponder?.props.onMoveShouldSetResponder?.(); + if (touchStart || touchMove) { + return true; + } return touchStart === undefined && touchMove === undefined; -}; +} -const findEventHandler = ( +function findEventHandler( element: ReactTestInstance, eventName: string, - callsite?: any, nearestTouchResponder?: ReactTestInstance -): EventHandler | null => { +): EventHandler | null { const touchResponder = isTouchResponder(element) ? element : nearestTouchResponder; const handler = getEventHandler(element, eventName); - if (handler && isEventEnabled(element, touchResponder, eventName)) + if (handler && isEventEnabled(element, eventName, touchResponder)) return handler; if (element.parent === null || element.parent.parent === null) { return null; } - return findEventHandler(element.parent, eventName, callsite, touchResponder); -}; + return findEventHandler(element.parent, eventName, touchResponder); +} -const getEventHandler = ( - element: ReactTestInstance, - eventName: string -): EventHandler | undefined => { - const eventHandlerName = toEventHandlerName(eventName); +function getEventHandler(element: ReactTestInstance, eventName: string) { + const eventHandlerName = getEventHandlerName(eventName); if (typeof element.props[eventHandlerName] === 'function') { return element.props[eventHandlerName]; } @@ -104,49 +110,37 @@ const getEventHandler = ( } return undefined; -}; +} + +function getEventHandlerName(eventName: string) { + return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +} -const invokeEvent = ( +function fireEvent( element: ReactTestInstance, eventName: string, - callsite?: any, - ...data: Array -) => { - const handler = findEventHandler(element, eventName, callsite); - + ...data: unknown[] +) { + const handler = findEventHandler(element, eventName); if (!handler) { return; } let returnValue; - act(() => { returnValue = handler(...data); }); return returnValue; -}; - -const toEventHandlerName = (eventName: string) => - `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +} -const pressHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'press', pressHandler, ...data); -const changeTextHandler = ( - element: ReactTestInstance, - ...data: Array -): void => invokeEvent(element, 'changeText', changeTextHandler, ...data); -const scrollHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'scroll', scrollHandler, ...data); +fireEvent.press = (element: ReactTestInstance, ...data: unknown[]) => + fireEvent(element, 'press', ...data); -const fireEvent = ( - element: ReactTestInstance, - eventName: string, - ...data: Array -): void => invokeEvent(element, eventName, fireEvent, ...data); +fireEvent.changeText = (element: ReactTestInstance, ...data: unknown[]) => + fireEvent(element, 'changeText', ...data); -fireEvent.press = pressHandler; -fireEvent.changeText = changeTextHandler; -fireEvent.scroll = scrollHandler; +fireEvent.scroll = (element: ReactTestInstance, ...data: unknown[]) => + fireEvent(element, 'scroll', ...data); export default fireEvent;