diff --git a/src/__tests__/fireEvent.test.tsx b/src/__tests__/fireEvent.test.tsx index 184ba6067..faea59fd6 100644 --- a/src/__tests__/fireEvent.test.tsx +++ b/src/__tests__/fireEvent.test.tsx @@ -9,6 +9,7 @@ import { TextInput, } from 'react-native'; import { render, fireEvent } from '..'; +import { defaultPressEvent } from '../helpers/create-event'; type OnPressComponentProps = { onPress: () => void; @@ -104,22 +105,33 @@ describe('fireEvent', () => { }); }); -test('fireEvent.press', () => { +test('fireEvent.press with default event', () => { const onPressMock = jest.fn(); - const text = 'Fireevent press'; - const eventData = { - nativeEvent: { - pageX: 20, - pageY: 30, - }, - }; - const { getByText } = render( - - ); + const view = render(); - fireEvent.press(getByText(text), eventData); + fireEvent.press(view.getByTestId('pressable')); + expect(onPressMock).toHaveBeenCalledWith({ nativeEvent: defaultPressEvent }); +}); - expect(onPressMock).toHaveBeenCalledWith(eventData); +test('fireEvent.press with default event override', () => { + const onPressMock = jest.fn(); + const view = render(); + + fireEvent.press(view.getByTestId('pressable'), { pageX: 10, pageY: 20 }); + expect(onPressMock).toHaveBeenCalledWith({ + nativeEvent: { ...defaultPressEvent, pageX: 10, pageY: 20 }, + }); +}); + +test('fireEvent.press with explicit event', () => { + const onPressMock = jest.fn(); + const view = render(); + + const event = { + nativeEvent: { pageX: 20, pageY: 30 }, + }; + fireEvent.press(view.getByTestId('pressable'), event); + expect(onPressMock).toHaveBeenCalledWith(event); }); test('fireEvent.scroll', () => { diff --git a/src/fireEvent.ts b/src/fireEvent.ts index f5ec35f4f..520e9ac36 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -3,6 +3,7 @@ import { TextInput } from 'react-native'; import act from './act'; import { isHostElement } from './helpers/component-tree'; import { filterNodeByType } from './helpers/filterNodeByType'; +import { createEvent } from './helpers/create-event'; type EventHandler = (...args: any) => unknown; @@ -69,7 +70,6 @@ const isEventEnabled = ( const findEventHandler = ( element: ReactTestInstance, eventName: string, - callsite?: any, nearestTouchResponder?: ReactTestInstance ): EventHandler | null => { const touchResponder = isTouchResponder(element) @@ -84,7 +84,7 @@ const findEventHandler = ( return null; } - return findEventHandler(element.parent, eventName, callsite, touchResponder); + return findEventHandler(element.parent, eventName, touchResponder); }; const getEventHandler = ( @@ -106,11 +106,9 @@ const getEventHandler = ( const invokeEvent = ( element: ReactTestInstance, eventName: string, - callsite?: any, ...data: Array ) => { - const handler = findEventHandler(element, eventName, callsite); - + const handler = findEventHandler(element, eventName); if (!handler) { return; } @@ -127,23 +125,61 @@ const invokeEvent = ( 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); +const getCoreEventName = (eventOrHandlerName: string) => { + if ( + eventOrHandlerName.startsWith('on') && + eventOrHandlerName[2] === eventOrHandlerName[2]?.toUpperCase() + ) { + const coreName = eventOrHandlerName.slice(2); + return coreName.charAt(0).toLowerCase() + coreName.slice(1); + } + + return eventOrHandlerName; +}; const fireEvent = ( element: ReactTestInstance, eventName: string, - ...data: Array -): void => invokeEvent(element, eventName, fireEvent, ...data); + ...data: any[] +): void => invokeEvent(element, eventName, ...data); -fireEvent.press = pressHandler; -fireEvent.changeText = changeTextHandler; -fireEvent.scroll = scrollHandler; +function getEventData(eventName: string, ...data: any[]) { + // Legacy mode where user passes 2+ args + if (data.length > 1) { + return data; + } + + // Legacy mode where user passes full event object + if (data[0]?.nativeEvent != null) { + return [data[0]]; + } + + // Mode where user passes optional event init data. + const name = getCoreEventName(eventName); + return [createEvent(name, data[0])]; +} + +function invokeEventWithDefaultData( + element: ReactTestInstance, + eventName: string, + ...data: any[] +) { + const eventData = getEventData(eventName, ...data); + return invokeEvent(element, eventName, ...eventData); +} + +// Regular events: +fireEvent.press = (element: ReactTestInstance, ...data: any[]) => { + return invokeEventWithDefaultData(element, 'press', ...data); +}; + +fireEvent.scroll = (element: ReactTestInstance, ...data: any[]) => { + return invokeEventWithDefaultData(element, 'scroll', ...data); +}; + +// changeText is not a regular event, as the callback args is just the changed not, and not an Event object +fireEvent.changeText = (element: ReactTestInstance, text: string) => { + return invokeEvent(element, 'changeText', text); +}; export default fireEvent; diff --git a/src/helpers/create-event.ts b/src/helpers/create-event.ts new file mode 100644 index 000000000..4fb8a0ef3 --- /dev/null +++ b/src/helpers/create-event.ts @@ -0,0 +1,35 @@ +// Based on: https://reactnative.dev/docs/pressevent#example +export const defaultPressEvent = { + changedTouches: [], + identifier: 0, + locationX: 0, + locationY: 0, + pageX: 0, + pageY: 0, + targe: 0, + timestamp: 0, + touches: [], +}; + +// Based on: https://reactnative.dev/docs/scrollview#onscroll +export const defaultScrollEvent = { + contentInset: { bottom: 0, left: 0, right: 0, top: 0 }, + contentOffset: { x: 0, y: 0 }, + contentSize: { height: 0, width: 0 }, + layoutMeasurement: { height: 0, width: 0 }, + zoomScale: 0, +}; + +const eventMap: Record = { + press: { ...defaultPressEvent }, + scroll: { ...defaultScrollEvent }, +}; + +export function createEvent(eventName: string, eventInit?: object) { + return { + nativeEvent: { + ...eventMap[eventName], + ...eventInit, + }, + }; +}