@testing-library/react-native
Version:
Simple and complete React Native testing utilities that encourage good testing practices.
160 lines (132 loc) • 4.17 kB
text/typescript
import { ReactTestInstance } from 'react-test-renderer';
import {
ViewProps,
TextProps,
TextInputProps,
PressableProps,
ScrollViewProps,
} from 'react-native';
import act from './act';
import { isHostElement } from './helpers/component-tree';
import { isHostTextInput } from './helpers/host-component-names';
import { isPointerEventEnabled } from './helpers/pointer-events';
type EventHandler = (...args: unknown[]) => unknown;
export function isTouchResponder(element: ReactTestInstance) {
if (!isHostElement(element)) {
return false;
}
return (
Boolean(element.props.onStartShouldSetResponder) || isHostTextInput(element)
);
}
/**
* List of events affected by `pointerEvents` prop.
*
* Note: `fireEvent` is accepting both `press` and `onPress` for event names,
* so we need cover both forms.
*/
const eventsAffectedByPointerEventsProp = new Set(['press', 'onPress']);
/**
* List of `TextInput` events not affected by `editable` prop.
*
* Note: `fireEvent` is accepting both `press` and `onPress` for event names,
* so we need cover both forms.
*/
const textInputEventsIgnoringEditableProp = new Set([
'contentSizeChange',
'onContentSizeChange',
'layout',
'onLayout',
'scroll',
'onScroll',
]);
export function isEventEnabled(
element: ReactTestInstance,
eventName: string,
nearestTouchResponder?: ReactTestInstance
) {
if (isHostTextInput(nearestTouchResponder)) {
return (
nearestTouchResponder?.props.editable !== false ||
textInputEventsIgnoringEditableProp.has(eventName)
);
}
if (
eventsAffectedByPointerEventsProp.has(eventName) &&
!isPointerEventEnabled(element)
) {
return false;
}
const touchStart = nearestTouchResponder?.props.onStartShouldSetResponder?.();
const touchMove = nearestTouchResponder?.props.onMoveShouldSetResponder?.();
if (touchStart || touchMove) {
return true;
}
return touchStart === undefined && touchMove === undefined;
}
function findEventHandler(
element: ReactTestInstance,
eventName: string,
nearestTouchResponder?: ReactTestInstance
): EventHandler | null {
const touchResponder = isTouchResponder(element)
? element
: nearestTouchResponder;
const handler = getEventHandler(element, eventName);
if (handler && isEventEnabled(element, eventName, touchResponder))
return handler;
if (element.parent === null || element.parent.parent === null) {
return null;
}
return findEventHandler(element.parent, eventName, touchResponder);
}
function getEventHandler(element: ReactTestInstance, eventName: string) {
const eventHandlerName = getEventHandlerName(eventName);
if (typeof element.props[eventHandlerName] === 'function') {
return element.props[eventHandlerName];
}
if (typeof element.props[eventName] === 'function') {
return element.props[eventName];
}
return undefined;
}
function getEventHandlerName(eventName: string) {
return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
}
// Allows any string but will provide autocomplete for type T
type StringWithAutoComplete<T> = T | (string & Record<never, never>);
// String union type of keys of T that start with on, stripped from on
type OnKeys<T> = keyof {
[K in keyof T as K extends `on${infer Rest}`
? Uncapitalize<Rest>
: never]: T[K];
};
type EventName = StringWithAutoComplete<
| OnKeys<ViewProps>
| OnKeys<TextProps>
| OnKeys<TextInputProps>
| OnKeys<PressableProps>
| OnKeys<ScrollViewProps>
>;
function fireEvent(
element: ReactTestInstance,
eventName: EventName,
...data: unknown[]
) {
const handler = findEventHandler(element, eventName);
if (!handler) {
return;
}
let returnValue;
act(() => {
returnValue = handler(...data);
});
return returnValue;
}
fireEvent.press = (element: ReactTestInstance, ...data: unknown[]) =>
fireEvent(element, 'press', ...data);
fireEvent.changeText = (element: ReactTestInstance, ...data: unknown[]) =>
fireEvent(element, 'changeText', ...data);
fireEvent.scroll = (element: ReactTestInstance, ...data: unknown[]) =>
fireEvent(element, 'scroll', ...data);
export default fireEvent;