From 8f13c5ed0548efb9a61801103c7f252058c851f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 1 Dec 2025 14:04:49 +0100 Subject: [PATCH 1/9] update mocks --- .../react-native-gesture-handler/jestSetup.js | 4 ++-- .../src/mocks/GestureButtons.tsx | 10 ++++++++++ .../src/mocks/mocks.tsx | 17 +++++++---------- 3 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/mocks/GestureButtons.tsx diff --git a/packages/react-native-gesture-handler/jestSetup.js b/packages/react-native-gesture-handler/jestSetup.js index ebc6cd1a56..5ed8cc24fd 100644 --- a/packages/react-native-gesture-handler/jestSetup.js +++ b/packages/react-native-gesture-handler/jestSetup.js @@ -7,7 +7,7 @@ jest.mock('./lib/commonjs/RNGestureHandlerModule', () => require('./lib/commonjs/mocks/mocks') ); jest.mock('./lib/commonjs/components/GestureButtons', () => - require('./lib/commonjs/mocks/mocks') + require('./lib/commonjs/mocks/GestureButtons') ); jest.mock('./lib/commonjs/components/Pressable', () => require('./lib/commonjs/mocks/Pressable') @@ -18,7 +18,7 @@ jest.mock('./lib/module/RNGestureHandlerModule', () => require('./lib/module/mocks/mocks') ); jest.mock('./lib/module/components/GestureButtons', () => - require('./lib/module/mocks/mocks') + require('./lib/module/mocks/GestureButtons') ); jest.mock('./lib/module/components/Pressable', () => require('./lib/module/mocks/Pressable') diff --git a/packages/react-native-gesture-handler/src/mocks/GestureButtons.tsx b/packages/react-native-gesture-handler/src/mocks/GestureButtons.tsx new file mode 100644 index 0000000000..a4ca02ae26 --- /dev/null +++ b/packages/react-native-gesture-handler/src/mocks/GestureButtons.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { TouchableNativeFeedback, View } from 'react-native'; +export const RawButton = ({ enabled, ...rest }: any) => ( + + + +); +export const BaseButton = RawButton; +export const RectButton = RawButton; +export const BorderlessButton = TouchableNativeFeedback; diff --git a/packages/react-native-gesture-handler/src/mocks/mocks.tsx b/packages/react-native-gesture-handler/src/mocks/mocks.tsx index 03d42f34fc..8d429919c4 100644 --- a/packages/react-native-gesture-handler/src/mocks/mocks.tsx +++ b/packages/react-native-gesture-handler/src/mocks/mocks.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { TouchableHighlight, TouchableNativeFeedback, @@ -17,7 +16,7 @@ import { Directions } from '../Directions'; const NOOP = () => { // Do nothing }; -const PanGestureHandler = View; + const attachGestureHandler = NOOP; const createGestureHandler = NOOP; const dropGestureHandler = NOOP; @@ -26,6 +25,8 @@ const updateGestureHandlerConfig = NOOP; const flushOperations = NOOP; const configureRelations = NOOP; const install = NOOP; + +const PanGestureHandler = View; const NativeViewGestureHandler = View; const TapGestureHandler = View; const ForceTouchGestureHandler = View; @@ -33,14 +34,8 @@ const LongPressGestureHandler = View; const PinchGestureHandler = View; const RotationGestureHandler = View; const FlingGestureHandler = View; -export const RawButton = ({ enabled, ...rest }: any) => ( - - - -); -export const BaseButton = RawButton; -export const RectButton = RawButton; -export const BorderlessButton = TouchableNativeFeedback; +const NativeDetector = View; +const InterceptingDetector = View; export default { TouchableHighlight, @@ -68,6 +63,8 @@ export default { configureRelations, flushOperations, install, + NativeDetector, + InterceptingDetector, // Probably can be removed Directions, State, From 694761e2c221dfc6ec9a2318eee4f989088083a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 1 Dec 2025 14:20:07 +0100 Subject: [PATCH 2/9] update hook --- .../src/__tests__/RelationsTraversal.test.tsx | 137 ++++++++---------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx index 99238234f4..23c6f5c5bf 100644 --- a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx @@ -4,26 +4,25 @@ import { useCompetingGestures, useSimultaneousGestures, } from '../v3/hooks/composition'; -import { useGesture } from '../v3/hooks/useGesture'; import { configureRelations } from '../v3/detectors/utils'; -import { SingleGesture, SingleGestureName } from '../v3/types'; import { renderHook } from '@testing-library/react-native'; +import { + PanGesture, + TapGesture, + usePanGesture, + useTapGesture, +} from '../v3/hooks/gestures'; describe('Ensure only one leaf node', () => { - let pan1: SingleGesture, - pan2: SingleGesture, - pan3: SingleGesture; + let pan1: PanGesture, pan2: PanGesture, pan3: PanGesture; beforeEach(() => { - pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; - pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; - pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; + pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; + pan3 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; }); const errorMessage = tagMessage( @@ -55,16 +54,13 @@ describe('Ensure only one leaf node', () => { }); describe('Simple relations', () => { - let pan1: SingleGesture, - pan2: SingleGesture; + let pan1: PanGesture, pan2: PanGesture; beforeEach(() => { - pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; - pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; + pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; }); test('useSimultaneous', () => { @@ -110,17 +106,16 @@ describe('Simple relations', () => { describe('External relations', () => { test('simultaneousWith', () => { - const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) + .result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: pan1, }) ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: [pan1, pan2], }) @@ -142,17 +137,16 @@ describe('External relations', () => { }); test('requireToFail', () => { - const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) + .result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, requireToFail: pan1, }) ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, requireToFail: [pan1, pan2], }) @@ -168,17 +162,16 @@ describe('External relations', () => { }); test('blocks', () => { - const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) + .result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, block: pan1, }) ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, block: [pan1, pan2], }) @@ -198,33 +191,23 @@ describe('External relations', () => { }); describe('Complex relations', () => { - let pan1: SingleGesture, - pan2: SingleGesture, - pan3: SingleGesture; - let tap1: SingleGesture, - tap2: SingleGesture, - tap3: SingleGesture; + let pan1: PanGesture, pan2: PanGesture, pan3: PanGesture; + let tap1: TapGesture, tap2: TapGesture, tap3: TapGesture; beforeEach(() => { - tap1 = renderHook(() => - useGesture(SingleGestureName.Tap, { disableReanimated: true }) - ).result.current; - tap2 = renderHook(() => - useGesture(SingleGestureName.Tap, { disableReanimated: true }) - ).result.current; - tap3 = renderHook(() => - useGesture(SingleGestureName.Tap, { disableReanimated: true }) - ).result.current; + tap1 = renderHook(() => useTapGesture({ disableReanimated: true })).result + .current; + tap2 = renderHook(() => useTapGesture({ disableReanimated: true })).result + .current; + tap3 = renderHook(() => useTapGesture({ disableReanimated: true })).result + .current; - pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; - pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; - pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { disableReanimated: true }) - ).result.current; + pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; + pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; + pan3 = renderHook(() => usePanGesture({ disableReanimated: true })).result + .current; }); // Test case from description of https://github.com/software-mansion/react-native-gesture-handler/pull/3693 @@ -315,31 +298,31 @@ describe('Complex relations', () => { describe('Complex relations with external gestures', () => { test('Case 1', () => { const pan5 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: pan5, }) ).result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: pan5, }) ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, requireToFail: pan5, }) ).result.current; const pan4 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, requireToFail: pan5, }) @@ -389,33 +372,33 @@ describe('Complex relations with external gestures', () => { test('Case 2', () => { const pan4 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; const pan5 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: [pan4, pan5], }) ).result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, requireToFail: [pan4, pan5], }) ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; @@ -461,13 +444,13 @@ describe('Complex relations with external gestures', () => { describe('External relations with composed gestures', () => { test('Case 1', () => { const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; @@ -477,7 +460,7 @@ describe('External relations with composed gestures', () => { ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: composedGesture, }) @@ -499,13 +482,13 @@ describe('External relations with composed gestures', () => { test('Case 1 - reversed order of configuring relations', () => { const pan1 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; const pan2 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, }) ).result.current; @@ -515,7 +498,7 @@ describe('External relations with composed gestures', () => { ).result.current; const pan3 = renderHook(() => - useGesture(SingleGestureName.Pan, { + usePanGesture({ disableReanimated: true, simultaneousWith: composedGesture, }) From 87eea003a32c0920152338d1f0edb3573a4e86ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 1 Dec 2025 16:09:18 +0100 Subject: [PATCH 3/9] virtual detector --- .../src/__tests__/V3.test.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/__tests__/V3.test.tsx diff --git a/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx b/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx new file mode 100644 index 0000000000..7c22c28173 --- /dev/null +++ b/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx @@ -0,0 +1,53 @@ +import React, { useRef } from 'react'; +import { render, cleanup } from '@testing-library/react-native'; +import { + GestureHandlerRootView, + InterceptingGestureDetector, + usePanGesture, + useTapGesture, +} from '../index'; +import { findNodeHandle, View } from 'react-native'; +import { VirtualDetector } from '../v3/detectors/VirtualDetector/VirtualDetector'; + +beforeEach(() => cleanup()); +jest.mock('react-native/Libraries/ReactNative/RendererProxy', () => ({ + findNodeHandle: jest.fn(), +})); + +describe('VirtualDetector', () => { + test('virtual detector must be under InterceptingGestureDetector', () => { + function VirtualDetectorWithNoBoundary() { + const tap = useTapGesture({}); + return ( + + + + + + ); + } + expect(() => render()).toThrow( + 'VirtualGestureDetector must be a descendant of an InterceptingGestureDetector' + ); + }); + test('virtual detector does not handle animated events', () => { + (findNodeHandle as jest.Mock).mockReturnValue(123); + + function VirtualDetectorAnimated() { + const tap = usePanGesture({ useAnimated: true }); + const ref = useRef(null); + return ( + + + + + + + + ); + } + expect(() => render()).toThrow( + '[react-native-gesture-handler] VirtualGestureDetector cannot handle Animated events with native driver when used inside InterceptingGestureDetector. Use Reanimated or Animated events without native driver instead.' + ); + }); +}); From 4b2fa1882248fa64836fe7bbd5716c8bbdd186ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 1 Dec 2025 16:53:56 +0100 Subject: [PATCH 4/9] rest --- .../src/__tests__/V3.test.tsx | 75 +++++++++++++++++-- .../useEnsureGestureHandlerRootView.ts | 3 +- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx b/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx index 7c22c28173..04e91d9ca8 100644 --- a/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx @@ -1,9 +1,10 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { render, cleanup } from '@testing-library/react-native'; import { + Gesture, + GestureDetector, GestureHandlerRootView, InterceptingGestureDetector, - usePanGesture, useTapGesture, } from '../index'; import { findNodeHandle, View } from 'react-native'; @@ -34,20 +35,82 @@ describe('VirtualDetector', () => { (findNodeHandle as jest.Mock).mockReturnValue(123); function VirtualDetectorAnimated() { - const tap = usePanGesture({ useAnimated: true }); - const ref = useRef(null); + const tap = useTapGesture({ useAnimated: true }); return ( - + ); } expect(() => render()).toThrow( - '[react-native-gesture-handler] VirtualGestureDetector cannot handle Animated events with native driver when used inside InterceptingGestureDetector. Use Reanimated or Animated events without native driver instead.' + 'VirtualGestureDetector cannot handle Animated events with native driver when used inside InterceptingGestureDetector. Use Reanimated or Animated events without native driver instead.' + ); + }); + test('intercepting detector cant handle multiple types of events', () => { + (findNodeHandle as jest.Mock).mockReturnValue(123); + const mockWorklet = () => undefined; + mockWorklet.__workletHash = 123; + function InterceptingDetectorMultipleTypes() { + const tap = useTapGesture({ useAnimated: true }); + const tap2 = useTapGesture({ onActivate: mockWorklet }); + return ( + + + + + + + + ); + } + expect(() => render()).toThrow( + 'InterceptingGestureDetector can only handle either Reanimated or Animated events.' + ); + }); +}); + +describe('Check if descendant of root view', () => { + test('gesture detector', () => { + function GestureDetectorNoRootView() { + const tap = useTapGesture({}); + return ( + + + + ); + } + expect(() => render()).toThrow( + '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/fundamentals/installation for more details.' + ); + }); + test('intercepting detector', () => { + function GestureDetectorNoRootView() { + const tap = useTapGesture({}); + return ( + + + + ); + } + expect(() => render()).toThrow( + '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/fundamentals/installation for more details.' + ); + }); + test('legacy detector', () => { + function GestureDetectorNoRootView() { + const tap = Gesture.Tap(); + return ( + + + + ); + } + expect(() => render()).toThrow( + '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/fundamentals/installation for more details.' ); }); }); diff --git a/packages/react-native-gesture-handler/src/v3/detectors/useEnsureGestureHandlerRootView.ts b/packages/react-native-gesture-handler/src/v3/detectors/useEnsureGestureHandlerRootView.ts index 2cb5949bbd..41fdf97698 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/useEnsureGestureHandlerRootView.ts +++ b/packages/react-native-gesture-handler/src/v3/detectors/useEnsureGestureHandlerRootView.ts @@ -1,12 +1,11 @@ import { use } from 'react'; -import { isTestEnv } from '../../utils'; import { Platform } from 'react-native'; import GestureHandlerRootViewContext from '../../GestureHandlerRootViewContext'; export function useEnsureGestureHandlerRootView() { const rootViewContext = use(GestureHandlerRootViewContext); - if (__DEV__ && !rootViewContext && !isTestEnv() && Platform.OS !== 'web') { + if (__DEV__ && !rootViewContext && 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/fundamentals/installation for more details.' ); From 08abfcca8ea03fad4943934dfa73e84b3d55d7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 2 Dec 2025 08:28:05 +0100 Subject: [PATCH 5/9] rename to errors --- .../src/__tests__/{V3.test.tsx => errors.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react-native-gesture-handler/src/__tests__/{V3.test.tsx => errors.test.tsx} (100%) diff --git a/packages/react-native-gesture-handler/src/__tests__/V3.test.tsx b/packages/react-native-gesture-handler/src/__tests__/errors.test.tsx similarity index 100% rename from packages/react-native-gesture-handler/src/__tests__/V3.test.tsx rename to packages/react-native-gesture-handler/src/__tests__/errors.test.tsx From bc8a4bcfe59590a190c2b9862bb99ba6e04760e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 2 Dec 2025 08:36:39 +0100 Subject: [PATCH 6/9] capitalise test suite name --- .../src/__tests__/{errors.test.tsx => Errors.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react-native-gesture-handler/src/__tests__/{errors.test.tsx => Errors.test.tsx} (100%) diff --git a/packages/react-native-gesture-handler/src/__tests__/errors.test.tsx b/packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx similarity index 100% rename from packages/react-native-gesture-handler/src/__tests__/errors.test.tsx rename to packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx From 2b5ac66dda5b78122ccd94dc3939b791a5511f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 2 Dec 2025 08:38:35 +0100 Subject: [PATCH 7/9] mocking HostGestureDetector directly --- packages/react-native-gesture-handler/src/mocks/mocks.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/mocks/mocks.tsx b/packages/react-native-gesture-handler/src/mocks/mocks.tsx index 8d429919c4..fff6f2ca24 100644 --- a/packages/react-native-gesture-handler/src/mocks/mocks.tsx +++ b/packages/react-native-gesture-handler/src/mocks/mocks.tsx @@ -34,8 +34,7 @@ const LongPressGestureHandler = View; const PinchGestureHandler = View; const RotationGestureHandler = View; const FlingGestureHandler = View; -const NativeDetector = View; -const InterceptingDetector = View; +const HostGestureDetector = View; export default { TouchableHighlight, @@ -63,8 +62,7 @@ export default { configureRelations, flushOperations, install, - NativeDetector, - InterceptingDetector, + HostGestureDetector, // Probably can be removed Directions, State, From 40eed712e5decb4d8977cee7ca2cd44fbb89e9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 2 Dec 2025 08:45:13 +0100 Subject: [PATCH 8/9] restores relation traversal --- .../src/__tests__/RelationsTraversal.test.tsx | 137 ++++++++++-------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx index 23c6f5c5bf..99238234f4 100644 --- a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx @@ -4,25 +4,26 @@ import { useCompetingGestures, useSimultaneousGestures, } from '../v3/hooks/composition'; +import { useGesture } from '../v3/hooks/useGesture'; import { configureRelations } from '../v3/detectors/utils'; +import { SingleGesture, SingleGestureName } from '../v3/types'; import { renderHook } from '@testing-library/react-native'; -import { - PanGesture, - TapGesture, - usePanGesture, - useTapGesture, -} from '../v3/hooks/gestures'; describe('Ensure only one leaf node', () => { - let pan1: PanGesture, pan2: PanGesture, pan3: PanGesture; + let pan1: SingleGesture, + pan2: SingleGesture, + pan3: SingleGesture; beforeEach(() => { - pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; - pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; - pan3 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; + pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; + pan2 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; + pan3 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; }); const errorMessage = tagMessage( @@ -54,13 +55,16 @@ describe('Ensure only one leaf node', () => { }); describe('Simple relations', () => { - let pan1: PanGesture, pan2: PanGesture; + let pan1: SingleGesture, + pan2: SingleGesture; beforeEach(() => { - pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; - pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; + pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; + pan2 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; }); test('useSimultaneous', () => { @@ -106,16 +110,17 @@ describe('Simple relations', () => { describe('External relations', () => { test('simultaneousWith', () => { - const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) - .result.current; + const pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: pan1, }) ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: [pan1, pan2], }) @@ -137,16 +142,17 @@ describe('External relations', () => { }); test('requireToFail', () => { - const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) - .result.current; + const pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, requireToFail: pan1, }) ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, requireToFail: [pan1, pan2], }) @@ -162,16 +168,17 @@ describe('External relations', () => { }); test('blocks', () => { - const pan1 = renderHook(() => usePanGesture({ disableReanimated: true })) - .result.current; + const pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, block: pan1, }) ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, block: [pan1, pan2], }) @@ -191,23 +198,33 @@ describe('External relations', () => { }); describe('Complex relations', () => { - let pan1: PanGesture, pan2: PanGesture, pan3: PanGesture; - let tap1: TapGesture, tap2: TapGesture, tap3: TapGesture; + let pan1: SingleGesture, + pan2: SingleGesture, + pan3: SingleGesture; + let tap1: SingleGesture, + tap2: SingleGesture, + tap3: SingleGesture; beforeEach(() => { - tap1 = renderHook(() => useTapGesture({ disableReanimated: true })).result - .current; - tap2 = renderHook(() => useTapGesture({ disableReanimated: true })).result - .current; - tap3 = renderHook(() => useTapGesture({ disableReanimated: true })).result - .current; + tap1 = renderHook(() => + useGesture(SingleGestureName.Tap, { disableReanimated: true }) + ).result.current; + tap2 = renderHook(() => + useGesture(SingleGestureName.Tap, { disableReanimated: true }) + ).result.current; + tap3 = renderHook(() => + useGesture(SingleGestureName.Tap, { disableReanimated: true }) + ).result.current; - pan1 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; - pan2 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; - pan3 = renderHook(() => usePanGesture({ disableReanimated: true })).result - .current; + pan1 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; + pan2 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; + pan3 = renderHook(() => + useGesture(SingleGestureName.Pan, { disableReanimated: true }) + ).result.current; }); // Test case from description of https://github.com/software-mansion/react-native-gesture-handler/pull/3693 @@ -298,31 +315,31 @@ describe('Complex relations', () => { describe('Complex relations with external gestures', () => { test('Case 1', () => { const pan5 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; const pan1 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: pan5, }) ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: pan5, }) ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, requireToFail: pan5, }) ).result.current; const pan4 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, requireToFail: pan5, }) @@ -372,33 +389,33 @@ describe('Complex relations with external gestures', () => { test('Case 2', () => { const pan4 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; const pan5 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; const pan1 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: [pan4, pan5], }) ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, requireToFail: [pan4, pan5], }) ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; @@ -444,13 +461,13 @@ describe('Complex relations with external gestures', () => { describe('External relations with composed gestures', () => { test('Case 1', () => { const pan1 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; @@ -460,7 +477,7 @@ describe('External relations with composed gestures', () => { ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: composedGesture, }) @@ -482,13 +499,13 @@ describe('External relations with composed gestures', () => { test('Case 1 - reversed order of configuring relations', () => { const pan1 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; const pan2 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, }) ).result.current; @@ -498,7 +515,7 @@ describe('External relations with composed gestures', () => { ).result.current; const pan3 = renderHook(() => - usePanGesture({ + useGesture(SingleGestureName.Pan, { disableReanimated: true, simultaneousWith: composedGesture, }) From 390de3fa1716dfbf66b4ee1bf7e36e47f38a7d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 2 Dec 2025 09:33:18 +0100 Subject: [PATCH 9/9] styling --- .../src/__tests__/Errors.test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx b/packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx index 04e91d9ca8..e494f2cf7a 100644 --- a/packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/Errors.test.tsx @@ -27,6 +27,7 @@ describe('VirtualDetector', () => { ); } + expect(() => render()).toThrow( 'VirtualGestureDetector must be a descendant of an InterceptingGestureDetector' ); @@ -46,14 +47,17 @@ describe('VirtualDetector', () => { ); } + expect(() => render()).toThrow( 'VirtualGestureDetector cannot handle Animated events with native driver when used inside InterceptingGestureDetector. Use Reanimated or Animated events without native driver instead.' ); }); + test('intercepting detector cant handle multiple types of events', () => { (findNodeHandle as jest.Mock).mockReturnValue(123); const mockWorklet = () => undefined; mockWorklet.__workletHash = 123; + function InterceptingDetectorMultipleTypes() { const tap = useTapGesture({ useAnimated: true }); const tap2 = useTapGesture({ onActivate: mockWorklet }); @@ -67,6 +71,7 @@ describe('VirtualDetector', () => { ); } + expect(() => render()).toThrow( 'InterceptingGestureDetector can only handle either Reanimated or Animated events.' ); @@ -87,6 +92,7 @@ describe('Check if descendant of root view', () => { '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/fundamentals/installation for more details.' ); }); + test('intercepting detector', () => { function GestureDetectorNoRootView() { const tap = useTapGesture({}); @@ -96,10 +102,12 @@ describe('Check if descendant of root view', () => { ); } + expect(() => render()).toThrow( '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/fundamentals/installation for more details.' ); }); + test('legacy detector', () => { function GestureDetectorNoRootView() { const tap = Gesture.Tap(); @@ -109,6 +117,7 @@ describe('Check if descendant of root view', () => { ); } + expect(() => render()).toThrow( '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/fundamentals/installation for more details.' );