From c42623972adcf010c763d41468d4e946412f1579 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:30:08 +0200 Subject: [PATCH 01/12] refactor: remove unused callsite param --- src/fireEvent.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index eb5ce8c28..c02062a71 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -72,7 +72,6 @@ const isEventEnabled = ( const findEventHandler = ( element: ReactTestInstance, eventName: string, - callsite?: any, nearestTouchResponder?: ReactTestInstance ): EventHandler | null => { const touchResponder = isTouchResponder(element) @@ -87,7 +86,7 @@ const findEventHandler = ( return null; } - return findEventHandler(element.parent, eventName, callsite, touchResponder); + return findEventHandler(element.parent, eventName, touchResponder); }; const getEventHandler = ( @@ -109,10 +108,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; @@ -131,19 +129,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 582883651f002ea8b7952b02e3143fd05ccc8af5 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:32:09 +0200 Subject: [PATCH 02/12] refactor: simplify fireEvent setup --- src/fireEvent.ts | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index c02062a71..116953cfb 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -93,7 +93,7 @@ const getEventHandler = ( element: ReactTestInstance, eventName: string ): EventHandler | undefined => { - const eventHandlerName = toEventHandlerName(eventName); + const eventHandlerName = getEventHandlerName(eventName); if (typeof element.props[eventHandlerName] === 'function') { return element.props[eventHandlerName]; } @@ -105,19 +105,20 @@ const getEventHandler = ( return undefined; }; -const invokeEvent = ( +const getEventHandlerName = (eventName: string) => + `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; + +const fireEvent = ( element: ReactTestInstance, eventName: string, ...data: Array ) => { const handler = findEventHandler(element, eventName); - if (!handler) { return; } let returnValue; - act(() => { returnValue = handler(...data); }); @@ -125,26 +126,15 @@ const invokeEvent = ( return returnValue; }; -const toEventHandlerName = (eventName: string) => - `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +fireEvent.press = (element: ReactTestInstance, ...data: Array): void => + fireEvent(element, 'press', ...data); -const pressHandler = (element: ReactTestInstance, ...data: Array): void => - invokeEvent(element, 'press', ...data); -const changeTextHandler = ( +fireEvent.changeText = ( 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); +): void => fireEvent(element, 'changeText', ...data); -fireEvent.press = pressHandler; -fireEvent.changeText = changeTextHandler; -fireEvent.scroll = scrollHandler; +fireEvent.scroll = (element: ReactTestInstance, ...data: Array): void => + fireEvent(element, 'scroll', ...data); export default fireEvent; From bab3c44b90397e532660a63f4fe70d2cee7634f3 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:33:59 +0200 Subject: [PATCH 03/12] refactor: use function declarations --- src/fireEvent.ts | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 116953cfb..0f15116d8 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -7,7 +7,7 @@ import { getHostComponentNames } from './helpers/host-component-names'; type EventHandler = (...args: any) => unknown; -const isTextInput = (element?: ReactTestInstance) => { +function isTextInput(element?: ReactTestInstance) { if (!element) { return false; } @@ -20,18 +20,18 @@ const isTextInput = (element?: ReactTestInstance) => { filterNodeByType(element, TextInput) || filterNodeByType(element, getHostComponentNames().textInput) ); -}; +} -const isTouchResponder = (element?: ReactTestInstance) => { +function isTouchResponder(element?: ReactTestInstance) { if (!isHostElement(element)) return false; return !!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,17 +47,17 @@ const isPointerEventEnabled = ( } return isPointerEventEnabled(parent, true); -}; +} -const isTouchEvent = (eventName?: string) => { +function isTouchEvent(eventName?: string) { return eventName === 'press'; -}; +} -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; @@ -67,13 +67,13 @@ const isEventEnabled = ( if (touchStart || touchMove) return true; return touchStart === undefined && touchMove === undefined; -}; +} -const findEventHandler = ( +function findEventHandler( element: ReactTestInstance, eventName: string, nearestTouchResponder?: ReactTestInstance -): EventHandler | null => { +): EventHandler | null { const touchResponder = isTouchResponder(element) ? element : nearestTouchResponder; @@ -87,12 +87,9 @@ const findEventHandler = ( } return findEventHandler(element.parent, eventName, touchResponder); -}; +} -const getEventHandler = ( - element: ReactTestInstance, - eventName: string -): EventHandler | undefined => { +function getEventHandler(element: ReactTestInstance, eventName: string) { const eventHandlerName = getEventHandlerName(eventName); if (typeof element.props[eventHandlerName] === 'function') { return element.props[eventHandlerName]; @@ -103,16 +100,17 @@ const getEventHandler = ( } return undefined; -}; +} -const getEventHandlerName = (eventName: string) => - `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +function getEventHandlerName(eventName: string) { + return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; +} -const fireEvent = ( +function fireEvent( element: ReactTestInstance, eventName: string, ...data: Array -) => { +) { const handler = findEventHandler(element, eventName); if (!handler) { return; @@ -124,7 +122,7 @@ const fireEvent = ( }); return returnValue; -}; +} fireEvent.press = (element: ReactTestInstance, ...data: Array): void => fireEvent(element, 'press', ...data); From 2feb6bf42d28dbe1a3670227122c3df1786790aa Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:38:07 +0200 Subject: [PATCH 04/12] refactor: improve typing --- src/fireEvent.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 0f15116d8..7f0784f5c 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -55,16 +55,17 @@ function isTouchEvent(eventName?: string) { function isEventEnabled( element: ReactTestInstance, - touchResponder?: ReactTestInstance, - eventName?: string + eventName: string, + nearestTouchResponder?: ReactTestInstance ) { if (isTextInput(element)) return element?.props.editable !== false; if (!isPointerEventEnabled(element) && isTouchEvent(eventName)) return false; - const touchStart = touchResponder?.props.onStartShouldSetResponder?.(); - const touchMove = touchResponder?.props.onMoveShouldSetResponder?.(); - - 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; } @@ -79,7 +80,7 @@ function findEventHandler( : 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) { @@ -109,7 +110,7 @@ function getEventHandlerName(eventName: string) { function fireEvent( element: ReactTestInstance, eventName: string, - ...data: Array + ...data: unknown[] ) { const handler = findEventHandler(element, eventName); if (!handler) { @@ -124,15 +125,13 @@ function fireEvent( return returnValue; } -fireEvent.press = (element: ReactTestInstance, ...data: Array): void => +fireEvent.press = (element: ReactTestInstance, ...data: unknown[]) => fireEvent(element, 'press', ...data); -fireEvent.changeText = ( - element: ReactTestInstance, - ...data: Array -): void => fireEvent(element, 'changeText', ...data); +fireEvent.changeText = (element: ReactTestInstance, ...data: unknown[]) => + fireEvent(element, 'changeText', ...data); -fireEvent.scroll = (element: ReactTestInstance, ...data: Array): void => +fireEvent.scroll = (element: ReactTestInstance, ...data: unknown[]) => fireEvent(element, 'scroll', ...data); export default fireEvent; From 65dc20ed34172e4b0808749ea62623f2910d53c4 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:43:23 +0200 Subject: [PATCH 05/12] fix: `onPress` event skipped pointer event checks --- src/__tests__/fireEvent.test.tsx | 9 +++++++-- src/fireEvent.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) 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 7f0784f5c..9e0240fba 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -49,8 +49,12 @@ function isPointerEventEnabled( return isPointerEventEnabled(parent, true); } -function isTouchEvent(eventName?: string) { - return eventName === 'press'; +// Due to accepting both `press` and `onPress` for event names, we need to +// cover both forms. +const touchEventNames = ['press', 'onPress']; + +function isTouchEvent(eventName: string) { + return touchEventNames.includes(eventName); } function isEventEnabled( From 79fd3985cd1f76c3b72b10da40658a10e87b485b Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:50:34 +0200 Subject: [PATCH 06/12] refactor: improve null checks --- src/fireEvent.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 9e0240fba..3f2596d8f 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -7,7 +7,7 @@ import { getHostComponentNames } from './helpers/host-component-names'; type EventHandler = (...args: any) => unknown; -function isTextInput(element?: ReactTestInstance) { +function isTextInput(element: ReactTestInstance) { if (!element) { return false; } @@ -22,10 +22,12 @@ function isTextInput(element?: ReactTestInstance) { ); } -function isTouchResponder(element?: ReactTestInstance) { - if (!isHostElement(element)) return false; +function isTouchResponder(element: ReactTestInstance) { + if (!isHostElement(element)) { + return false; + } - return !!element?.props.onStartShouldSetResponder || isTextInput(element); + return !!element.props.onStartShouldSetResponder || isTextInput(element); } function isPointerEventEnabled( From 08c395267b90a369e53b664d5cbba74c46ca038f Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:50:47 +0200 Subject: [PATCH 07/12] chore: reformat code --- src/fireEvent.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 3f2596d8f..be9ac61be 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -64,8 +64,13 @@ function isEventEnabled( eventName: string, nearestTouchResponder?: ReactTestInstance ) { - if (isTextInput(element)) return element?.props.editable !== false; - if (!isPointerEventEnabled(element) && isTouchEvent(eventName)) return false; + if (isTextInput(element)) { + return element.props.editable !== false; + } + + if (isTouchEvent(eventName) && !isPointerEventEnabled(element)) { + return false; + } const touchStart = nearestTouchResponder?.props.onStartShouldSetResponder?.(); const touchMove = nearestTouchResponder?.props.onMoveShouldSetResponder?.(); From 569344ed06f7471c34a128ac8956d0e40492a3dd Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:58:49 +0200 Subject: [PATCH 08/12] chore: remove dead check --- src/fireEvent.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index be9ac61be..bfad782ad 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -8,10 +8,6 @@ import { getHostComponentNames } from './helpers/host-component-names'; type EventHandler = (...args: any) => unknown; function isTextInput(element: ReactTestInstance) { - if (!element) { - return false; - } - // 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 From cf2038424a09046f89482376217e13f82eaa53f2 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 22:59:26 +0200 Subject: [PATCH 09/12] refactor: improve typing --- src/fireEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index bfad782ad..31cd38cc1 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -5,7 +5,7 @@ import { getHostParent, isHostElement } from './helpers/component-tree'; import { filterNodeByType } from './helpers/filterNodeByType'; import { getHostComponentNames } from './helpers/host-component-names'; -type EventHandler = (...args: any) => unknown; +type EventHandler = (...args: unknown[]) => unknown; function isTextInput(element: ReactTestInstance) { // We have to test if the element type is either the `TextInput` component From 5d15a8fa920a0ea116d3335442dd43bca0f7b326 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 2 May 2023 23:28:51 +0200 Subject: [PATCH 10/12] chore: revert unintended changes --- src/helpers/component-tree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/component-tree.ts b/src/helpers/component-tree.ts index 3d3ea03eb..340472d72 100644 --- a/src/helpers/component-tree.ts +++ b/src/helpers/component-tree.ts @@ -4,7 +4,7 @@ import { ReactTestInstance } from 'react-test-renderer'; * Checks if the given element is a host element. * @param element The element to check. */ -export function isHostElement(element?: ReactTestInstance | null) { +export function isHostElement(element?: ReactTestInstance | null): boolean { return typeof element?.type === 'string'; } From 0d089c2d266ce9794ae8b651736855e88057ddfd Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 3 May 2023 09:59:58 +0200 Subject: [PATCH 11/12] refactor: code review changes --- src/fireEvent.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fireEvent.ts b/src/fireEvent.ts index 31cd38cc1..f32ef6a37 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -23,7 +23,9 @@ function isTouchResponder(element: ReactTestInstance) { return false; } - return !!element.props.onStartShouldSetResponder || isTextInput(element); + return ( + Boolean(element.props.onStartShouldSetResponder) || isTextInput(element) + ); } function isPointerEventEnabled( From b9cc5ee2d7a866f4e432bd74e7c9d33ade274fa8 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 3 May 2023 10:07:06 +0200 Subject: [PATCH 12/12] refactor: tweaks --- src/helpers/component-tree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/component-tree.ts b/src/helpers/component-tree.ts index 340472d72..3d3ea03eb 100644 --- a/src/helpers/component-tree.ts +++ b/src/helpers/component-tree.ts @@ -4,7 +4,7 @@ import { ReactTestInstance } from 'react-test-renderer'; * Checks if the given element is a host element. * @param element The element to check. */ -export function isHostElement(element?: ReactTestInstance | null): boolean { +export function isHostElement(element?: ReactTestInstance | null) { return typeof element?.type === 'string'; }