From 13c36b8c5f7b91ae8c44d48108a7504bcbecb9fe Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 13 Dec 2022 21:39:27 +0100 Subject: [PATCH 1/5] refactor: remove unused callsite parameter --- src/fireEvent.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index f5ec35f4f..652b7579c 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -69,7 +69,6 @@ const isEventEnabled = ( const findEventHandler = ( element: ReactTestInstance, eventName: string, - callsite?: any, nearestTouchResponder?: ReactTestInstance ): EventHandler | null => { const touchResponder = isTouchResponder(element) @@ -84,7 +83,7 @@ const findEventHandler = ( return null; } - return findEventHandler(element.parent, eventName, callsite, touchResponder); + return findEventHandler(element.parent, eventName, touchResponder); }; const getEventHandler = ( @@ -106,10 +105,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; @@ -128,19 +126,19 @@ const toEventHandlerName = (eventName: string) => `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; const pressHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'press', pressHandler, ...data); + invokeEvent(element, 'press', ...data); const changeTextHandler = ( element: ReactTestInstance, ...data: Array -): void => invokeEvent(element, 'changeText', changeTextHandler, ...data); +): void => invokeEvent(element, 'changeText', ...data); const scrollHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'scroll', scrollHandler, ...data); + invokeEvent(element, 'scroll', ...data); const fireEvent = ( element: ReactTestInstance, eventName: string, ...data: Array -): void => invokeEvent(element, eventName, fireEvent, ...data); +): void => invokeEvent(element, eventName, ...data); fireEvent.press = pressHandler; fireEvent.changeText = changeTextHandler; From 0c69846b546916432f9f0615955fa953b71dbc56 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 13 Dec 2022 21:45:25 +0100 Subject: [PATCH 2/5] refactor: re-organize explicit events --- src/fireEvent.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 652b7579c..063cc3630 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -125,23 +125,21 @@ const invokeEvent = ( const toEventHandlerName = (eventName: string) => `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; -const pressHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'press', ...data); -const changeTextHandler = ( - element: ReactTestInstance, - ...data: Array -): void => invokeEvent(element, 'changeText', ...data); -const scrollHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'scroll', ...data); - const fireEvent = ( element: ReactTestInstance, eventName: string, ...data: Array ): void => invokeEvent(element, eventName, ...data); -fireEvent.press = pressHandler; -fireEvent.changeText = changeTextHandler; -fireEvent.scroll = scrollHandler; +// 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): void => + invokeEvent(element, 'changeText', text); + +// Regular events: +fireEvent.press = (element: ReactTestInstance, ...data: Array): void => + invokeEvent(element, 'press', ...data); + +fireEvent.scroll = (element: ReactTestInstance, ...data: Array): void => + invokeEvent(element, 'scroll', ...data); export default fireEvent; From 0f48cc7188ee69f62f012950716ac555b79b9713 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 13 Dec 2022 22:10:44 +0100 Subject: [PATCH 3/5] feat: supply empty nativeEvent event --- src/__tests__/fireEvent.test.tsx | 19 ++++++++++++++++--- src/fireEvent.ts | 14 ++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/__tests__/fireEvent.test.tsx b/src/__tests__/fireEvent.test.tsx index 184ba6067..339cf5c9d 100644 --- a/src/__tests__/fireEvent.test.tsx +++ b/src/__tests__/fireEvent.test.tsx @@ -107,19 +107,30 @@ describe('fireEvent', () => { test('fireEvent.press', () => { const onPressMock = jest.fn(); const text = 'Fireevent press'; - const eventData = { + const event = { nativeEvent: { pageX: 20, pageY: 30, }, }; + const { getByText } = render( ); - fireEvent.press(getByText(text), eventData); + fireEvent.press(getByText(text), event); + expect(onPressMock).toHaveBeenCalledWith(event); - expect(onPressMock).toHaveBeenCalledWith(eventData); + fireEvent.press(getByText(text)); + expect(onPressMock).toHaveBeenCalledTimes(2); +}); + +test('fireEvent.press with default event', () => { + const onPressMock = jest.fn(); + const view = render(); + + fireEvent.press(view.getByTestId('pressable')); + expect(onPressMock).toHaveBeenCalledWith({ nativeEvent: {} }); }); test('fireEvent.scroll', () => { @@ -542,4 +553,6 @@ describe('native events', () => { fireEvent(getByTestId('test-id'), 'onMomentumScrollEnd'); expect(onMomentumScrollEndSpy).toHaveBeenCalled(); }); + + test('.press passed proper event object', () => {}); }); diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 063cc3630..8ac1b742b 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -136,10 +136,16 @@ fireEvent.changeText = (element: ReactTestInstance, text: string): void => invokeEvent(element, 'changeText', text); // Regular events: -fireEvent.press = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'press', ...data); +fireEvent.press = (element: ReactTestInstance, event?: any): void => + invokeEvent(element, 'press', event ?? buildReactEvent()); -fireEvent.scroll = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'scroll', ...data); +fireEvent.scroll = (element: ReactTestInstance, event?: any): void => + invokeEvent(element, 'scroll', event ?? buildReactEvent()); + +function buildReactEvent() { + return { + nativeEvent: {}, + }; +} export default fireEvent; From 5ca962425b992fb784bda8324da3da8c204cfe79 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 14 Dec 2022 13:00:19 +0100 Subject: [PATCH 4/5] feat: provide default event props for press and scroll events --- src/__tests__/fireEvent.test.tsx | 39 ++++++++++---------- src/fireEvent.ts | 62 ++++++++++++++++++++++++-------- src/helpers/create-event.ts | 35 ++++++++++++++++++ 3 files changed, 102 insertions(+), 34 deletions(-) create mode 100644 src/helpers/create-event.ts diff --git a/src/__tests__/fireEvent.test.tsx b/src/__tests__/fireEvent.test.tsx index 339cf5c9d..13dae4d75 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, pressEvent } from '../helpers/create-event'; type OnPressComponentProps = { onPress: () => void; @@ -104,33 +105,33 @@ describe('fireEvent', () => { }); }); -test('fireEvent.press', () => { +test('fireEvent.press with default event', () => { const onPressMock = jest.fn(); - const text = 'Fireevent press'; - const event = { - nativeEvent: { - pageX: 20, - pageY: 30, - }, - }; + const view = render(); - const { getByText } = render( - - ); + fireEvent.press(view.getByTestId('pressable')); + expect(onPressMock).toHaveBeenCalledWith({ nativeEvent: defaultPressEvent }); +}); - fireEvent.press(getByText(text), event); - expect(onPressMock).toHaveBeenCalledWith(event); +test('fireEvent.press with default event override', () => { + const onPressMock = jest.fn(); + const view = render(); - fireEvent.press(getByText(text)); - expect(onPressMock).toHaveBeenCalledTimes(2); + fireEvent.press(view.getByTestId('pressable'), { pageX: 10, pageY: 20 }); + expect(onPressMock).toHaveBeenCalledWith({ + nativeEvent: { ...defaultPressEvent, pageX: 10, pageY: 20 }, + }); }); -test('fireEvent.press with default event', () => { +test('fireEvent.press with explicit event', () => { const onPressMock = jest.fn(); const view = render(); - fireEvent.press(view.getByTestId('pressable')); - expect(onPressMock).toHaveBeenCalledWith({ nativeEvent: {} }); + const event = { + nativeEvent: { pageX: 20, pageY: 30 }, + }; + fireEvent.press(view.getByTestId('pressable'), event); + expect(onPressMock).toHaveBeenCalledWith(event); }); test('fireEvent.scroll', () => { @@ -553,6 +554,4 @@ describe('native events', () => { fireEvent(getByTestId('test-id'), 'onMomentumScrollEnd'); expect(onMomentumScrollEndSpy).toHaveBeenCalled(); }); - - test('.press passed proper event object', () => {}); }); diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 8ac1b742b..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; @@ -108,7 +109,6 @@ const invokeEvent = ( ...data: Array ) => { const handler = findEventHandler(element, eventName); - if (!handler) { return; } @@ -125,27 +125,61 @@ const invokeEvent = ( const toEventHandlerName = (eventName: string) => `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +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 + ...data: any[] ): void => invokeEvent(element, eventName, ...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): void => - invokeEvent(element, 'changeText', text); +function getEventData(eventName: string, ...data: any[]) { + // Legacy mode where user passes 2+ args + if (data.length > 1) { + return data; + } -// Regular events: -fireEvent.press = (element: ReactTestInstance, event?: any): void => - invokeEvent(element, 'press', event ?? buildReactEvent()); + // Legacy mode where user passes full event object + if (data[0]?.nativeEvent != null) { + return [data[0]]; + } -fireEvent.scroll = (element: ReactTestInstance, event?: any): void => - invokeEvent(element, 'scroll', event ?? buildReactEvent()); + // Mode where user passes optional event init data. + const name = getCoreEventName(eventName); + return [createEvent(name, data[0])]; +} -function buildReactEvent() { - return { - nativeEvent: {}, - }; +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, + }, + }; +} From eed9309ba3cc9361b992ca9537d66bd0a9b1bbd5 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 14 Dec 2022 13:57:54 +0100 Subject: [PATCH 5/5] fix: lint --- src/__tests__/fireEvent.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/fireEvent.test.tsx b/src/__tests__/fireEvent.test.tsx index 13dae4d75..faea59fd6 100644 --- a/src/__tests__/fireEvent.test.tsx +++ b/src/__tests__/fireEvent.test.tsx @@ -9,7 +9,7 @@ import { TextInput, } from 'react-native'; import { render, fireEvent } from '..'; -import { defaultPressEvent, pressEvent } from '../helpers/create-event'; +import { defaultPressEvent } from '../helpers/create-event'; type OnPressComponentProps = { onPress: () => void;