Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add testing-library compatibility props #3357

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
18 changes: 18 additions & 0 deletions src/components/GestureButtonsProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ export interface RawButtonProps
* Style object, use it to set additional styles.
*/
style?: StyleProp<ViewStyle>;

/**
* Used for testing-library compatibility, not passed to the native component.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
testOnly_onPress?: Function | null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of argument should be passed to testOnly_onPress?

Copy link
Contributor Author

@latekvo latekvo Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testOnly_onPress will currently always be of type ((event: PressableEvent) => void) | null, but this may change if more of our components will implement testOnly_onPress.

I've made it of type Function, because RawButtonProps usage is not specific to Pressable.

Link to type definitions: (link)
Extract from type definitions:

export type InnerPressableEvent = {
  changedTouches: InnerPressableEvent[];
  identifier: number;
  locationX: number;
  locationY: number;
  pageX: number;
  pageY: number;
  target: number;
  timestamp: number;
  touches: InnerPressableEvent[];
  force?: number;
};

export type PressableEvent = { nativeEvent: InnerPressableEvent };

export interface PressableProps
  extends AccessibilityProps,
    Omit<ViewProps, 'children' | 'style' | 'hitSlop'> {

  [...]

  onPress?: null | ((event: PressableEvent) => void);
  onPressIn?: null | ((event: PressableEvent) => void);
  onPressOut?: null | ((event: PressableEvent) => void);

  [...]
}


/**
* Used for testing-library compatibility, not passed to the native component.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
testOnly_onPressIn?: Function | null;

/**
* Used for testing-library compatibility, not passed to the native component.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
testOnly_onPressOut?: Function | null;
}
interface ButtonWithRefProps {
innerRef?: React.ForwardedRef<React.ComponentType<any>>;
Expand Down
7 changes: 5 additions & 2 deletions src/components/Pressable/Pressable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from './utils';
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon';
import { INT32_MAX } from '../../utils';
import { INT32_MAX, isTestEnv } from '../../utils';

const DEFAULT_LONG_PRESS_DURATION = 500;

Expand Down Expand Up @@ -390,7 +390,10 @@ export default function Pressable(props: PressableProps) {
touchSoundDisabled={android_disableSound ?? undefined}
rippleColor={processColor(android_ripple?.color ?? defaultRippleColor)}
rippleRadius={android_ripple?.radius ?? undefined}
style={[pointerStyle, styleProp]}>
style={[pointerStyle, styleProp]}
testOnly_onPress={isTestEnv() ? onPress : undefined}
testOnly_onPressIn={isTestEnv() ? onPressIn : undefined}
testOnly_onPressOut={isTestEnv() ? onPressOut : undefined}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do something similar to Reanimated, i.e. call this only once and store result in constant

const IS_TEST_ENV = isTestEnv()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though about that, but isTestEnv() is just a property read, and both notations are just as readable, so i don't think there's a point in caching the value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it calls hasProperty and then compares values, so it's more than just property read. While it's not crucial in test environment, I think it is better to memoize this value.

Copy link
Contributor Author

@latekvo latekvo Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, done in 7a31f5e and 4ec26c4

{childrenProp}
{__DEV__ ? (
<PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/createHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { filterConfig, scheduleFlushOperations } from './utils';
import findNodeHandle from '../findNodeHandle';
import { ValueOf } from '../typeUtils';
import { deepEqual, isFabric, isJestEnv, tagMessage } from '../utils';
import { deepEqual, isFabric, isTestEnv, tagMessage } from '../utils';
import { ActionType } from '../ActionType';
import { PressabilityDebugView } from './PressabilityDebugView';
import GestureHandlerRootViewContext from '../GestureHandlerRootViewContext';
Expand Down Expand Up @@ -428,7 +428,7 @@ export default function createHandler<
}

render() {
if (__DEV__ && !this.context && !isJestEnv() && Platform.OS !== 'web') {
if (__DEV__ && !this.context && !isTestEnv() && Platform.OS !== 'web') {
throw new Error(
name +
' must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.'
Expand Down Expand Up @@ -539,7 +539,7 @@ export default function createHandler<
{
ref: this.refHandler,
collapsable: false,
...(isJestEnv()
...(isTestEnv()
? {
handlerType: name,
handlerTag: this.handlerTag,
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/gestures/GestureDetector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import findNodeHandle from '../../../findNodeHandle';
import { GestureType } from '../gesture';
import { UserSelect, TouchAction } from '../../gestureHandlerCommon';
import { ComposedGesture } from '../gestureComposition';
import { isJestEnv } from '../../../utils';
import { isTestEnv } from '../../../utils';

import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext';
import { AttachedGestureState, GestureDetectorState } from './types';
Expand Down Expand Up @@ -94,7 +94,7 @@ interface GestureDetectorProps {
*/
export const GestureDetector = (props: GestureDetectorProps) => {
const rootViewContext = useContext(GestureHandlerRootViewContext);
if (__DEV__ && !rootViewContext && !isJestEnv() && Platform.OS !== 'web') {
if (__DEV__ && !rootViewContext && !isTestEnv() && Platform.OS !== 'web') {
throw new Error(
'GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.'
);
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/gestures/GestureDetector/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Platform } from 'react-native';

import { isJestEnv, tagMessage } from '../../../utils';
import { isTestEnv, tagMessage } from '../../../utils';
import { GestureRef, BaseGesture, GestureType } from '../gesture';

import { flingGestureHandlerProps } from '../../FlingGestureHandler';
Expand Down Expand Up @@ -100,7 +100,7 @@ export function checkGestureCallbacksForWorklets(gesture: GestureType) {
const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets;
// If none of the callbacks are worklets and the gesture is not explicitly marked with
// `.runOnJS(true)` show a warning
if (areAllNotWorklets && !isJestEnv()) {
if (areAllNotWorklets && !isTestEnv()) {
console.warn(
tagMessage(
`None of the callbacks in the gesture are worklets. If you wish to run them on the JS thread use '.runOnJS(true)' modifier on the gesture to make this explicit. Otherwise, mark the callbacks as 'worklet' to run them on the UI thread.`
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/handlersRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isJestEnv } from '../utils';
import { isTestEnv } from '../utils';
import { GestureType } from './gestures/gesture';
import { GestureEvent, HandlerStateChangeEvent } from './gestureHandlerCommon';

Expand All @@ -13,7 +13,7 @@ export function registerHandler(
testID?: string
) {
gestures.set(handlerTag, handler);
if (isJestEnv() && testID) {
if (isTestEnv() && testID) {
testIDs.set(testID, handlerTag);
}
}
Expand All @@ -27,7 +27,7 @@ export function registerOldGestureHandler(

export function unregisterHandler(handlerTag: number, testID?: string) {
gestures.delete(handlerTag);
if (isJestEnv() && testID) {
if (isTestEnv() && testID) {
testIDs.delete(testID);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export function hasProperty(object: object, key: string) {
return Object.prototype.hasOwnProperty.call(object, key);
}

export function isJestEnv(): boolean {
export function isTestEnv(): boolean {
// @ts-ignore Do not use `@types/node` because it will prioritise Node types over RN types which breaks the types (ex. setTimeout) in React Native projects.
return hasProperty(global, 'process') && !!process.env.JEST_WORKER_ID;
return hasProperty(global, 'process') && process.env.NODE_ENV === 'test';
}

export function tagMessage(msg: string) {
Expand Down
Loading