diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index 877d9d7126..01aa09b278 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -30,6 +30,7 @@ open class GestureHandler { private var trackedPointersIDsCount = 0 private val windowOffset = IntArray(2) { 0 } var tag = 0 + var testID: String? = null var view: View? = null private set @@ -126,6 +127,7 @@ open class GestureHandler { } open fun resetConfig() { + testID = null needsPointerData = DEFAULT_NEEDS_POINTER_DATA manualActivation = DEFAULT_MANUAL_ACTIVATION shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE @@ -844,7 +846,7 @@ open class GestureHandler { open fun wantsToAttachDirectlyToView() = false override fun toString(): String { - val viewString = if (view == null) null else view!!.javaClass.simpleName + val viewString = testID ?: view?.javaClass?.simpleName return this.javaClass.simpleName + "@[" + tag + "]:" + viewString } @@ -896,6 +898,9 @@ open class GestureHandler { if (config.hasKey(KEY_MOUSE_BUTTON)) { handler.mouseButton = config.getInt(KEY_MOUSE_BUTTON) } + if (config.hasKey(KEY_TEST_ID)) { + handler.testID = config.getString(KEY_TEST_ID) + } } abstract fun createEventBuilder(handler: T): GestureHandlerEventDataBuilder @@ -917,6 +922,7 @@ open class GestureHandler { private const val KEY_HIT_SLOP_HORIZONTAL = "horizontal" private const val KEY_HIT_SLOP_WIDTH = "width" private const val KEY_HIT_SLOP_HEIGHT = "height" + private const val KEY_TEST_ID = "testID" private fun handleHitSlopProperty(handler: GestureHandler, config: ReadableMap) { if (config.getType(KEY_HIT_SLOP) == ReadableType.Number) { diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index 52a2ea5a66..597d061a31 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -74,6 +74,7 @@ @property (nonatomic, weak, nullable) id emitter; @property (nonatomic, readonly, nullable) UIGestureRecognizer *recognizer; @property (nonatomic, readonly, nullable) RNGestureHandlerPointerTracker *pointerTracker; +@property (nonatomic, nullable) NSString *testID; @property (nonatomic) BOOL enabled; @property (nonatomic) RNGestureHandlerActionType actionType; @property (nonatomic) BOOL shouldCancelWhenOutside; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 1580f304d4..f24b923edc 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -102,6 +102,7 @@ - (instancetype)initWithTag:(NSNumber *)tag - (void)resetConfig { self.enabled = YES; + self.testID = nil; self.manualActivation = NO; _shouldCancelWhenOutside = NO; _hitSlop = RNGHHitSlopEmpty; @@ -126,6 +127,11 @@ - (void)updateConfig:(NSDictionary *)config self.enabled = [RCTConvert BOOL:prop]; } + prop = config[@"testID"]; + if (prop != nil) { + self.testID = [RCTConvert NSString:prop]; + } + prop = config[@"shouldCancelWhenOutside"]; if (prop != nil) { _shouldCancelWhenOutside = [RCTConvert BOOL:prop]; diff --git a/packages/react-native-gesture-handler/src/v3/createNativeWrapper.tsx b/packages/react-native-gesture-handler/src/v3/createNativeWrapper.tsx index 5a014da419..d92d6e75a3 100644 --- a/packages/react-native-gesture-handler/src/v3/createNativeWrapper.tsx +++ b/packages/react-native-gesture-handler/src/v3/createNativeWrapper.tsx @@ -42,6 +42,7 @@ export default function createNativeWrapper

( childProps: { enabled: props.enabled, hitSlop: props.hitSlop, + testID: props.testID, } as P, } ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts index 943adfc1ce..12631bdf51 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts @@ -44,6 +44,11 @@ export const allowedNativeProps = new Set< 'needsPointerData', ]); +// Don't pass testID to the native side in production +if (!__DEV__) { + allowedNativeProps.delete('testID'); +} + export const HandlerCallbacks = new Set< keyof Required> >([ diff --git a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts index 8182337a5b..efd00bc9d2 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts @@ -48,6 +48,7 @@ export default abstract class GestureHandler implements IGestureHandler { private forAnimated: boolean = false; private forReanimated: boolean = false; private _handlerTag!: number; + private _testID?: string = undefined; private hitSlop?: HitSlop = undefined; private manualActivation: boolean = false; @@ -743,6 +744,10 @@ export default abstract class GestureHandler implements IGestureHandler { this.validateHitSlops(); } + if (config.testID !== undefined) { + this._testID = config.testID; + } + if (config.dispatchesAnimatedEvents !== undefined) { this.forAnimated = config.dispatchesAnimatedEvents; } @@ -936,6 +941,7 @@ export default abstract class GestureHandler implements IGestureHandler { } protected resetConfig(): void { + this._testID = undefined; this.manualActivation = false; this.shouldCancelWhenOutside = false; this.mouseButton = undefined; @@ -965,6 +971,10 @@ export default abstract class GestureHandler implements IGestureHandler { this._handlerTag = value; } + public get testID() { + return this._testID; + } + public get delegate() { return this._delegate; } diff --git a/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts index 5f85e89cc3..a7797352e4 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts @@ -17,6 +17,7 @@ export default interface IGestureHandler { activationIndex: number; awaiting: boolean; handlerTag: number; + readonly testID?: string; readonly delegate: GestureHandlerDelegate; readonly tracker: PointerTracker; readonly name: SingleGestureName; diff --git a/packages/react-native-gesture-handler/src/web/interfaces.ts b/packages/react-native-gesture-handler/src/web/interfaces.ts index b39b483099..7dcfc27e3d 100644 --- a/packages/react-native-gesture-handler/src/web/interfaces.ts +++ b/packages/react-native-gesture-handler/src/web/interfaces.ts @@ -32,6 +32,7 @@ export interface Handler { type ConfigArgs = | number | boolean + | string | HitSlop | UserSelect | TouchAction @@ -57,6 +58,7 @@ export interface Config extends Record { dispatchesAnimatedEvents?: false; dispatchesReanimatedEvents?: boolean; needsPointerData?: false; + testID?: string; activateAfterLongPress?: number; failOffsetXStart?: number;