Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
dad2eaa
new file
m-bert Nov 3, 2025
6dbc10d
use native
m-bert Nov 3, 2025
4f1dcaa
Buttons
m-bert Nov 3, 2025
8ccf267
Remove Reanimated
m-bert Nov 3, 2025
df3f1fb
Components
m-bert Nov 4, 2025
a1da35a
Add proper values to relations
m-bert Nov 4, 2025
a680461
Bring back old components
m-bert Nov 4, 2025
a0d0240
Deprecate legacy buttons
m-bert Nov 4, 2025
ef1e736
Deprecate legacy components
m-bert Nov 4, 2025
563fab3
Export new components
m-bert Nov 4, 2025
0f40c58
Add web file
m-bert Nov 4, 2025
13951df
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 4, 2025
b77cc2b
Update import
m-bert Nov 4, 2025
c336453
use ref in check
m-bert Nov 4, 2025
c3f4320
Correct refs
m-bert Nov 4, 2025
c6c83ba
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 12, 2025
86c38f3
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 13, 2025
baf7eb1
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 14, 2025
c8f7c8c
Add examples
m-bert Nov 14, 2025
2a356f8
Fix buttons
m-bert Nov 14, 2025
73969da
Yet another fix
m-bert Nov 14, 2025
f4fa5f6
Remove Drawer Layout
m-bert Nov 14, 2025
ede51fe
Impoort hooks directly
m-bert Nov 15, 2025
d34ef86
remove import
m-bert Nov 15, 2025
fdf9533
Update examples
m-bert Nov 15, 2025
38f36fe
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 17, 2025
25bd430
Update scroll example
m-bert Nov 18, 2025
4c5eaf8
Correctly attach NativeViewGestureHandler
m-bert Nov 18, 2025
36d1afb
Fix relations in ScrollView
m-bert Nov 18, 2025
11eeef1
FlatList
m-bert Nov 19, 2025
753d43b
Merge branch 'next' into @mbert/components-new-api
m-bert Nov 24, 2025
9b4743a
Import hooks directly
m-bert Nov 24, 2025
f04d434
Fixes
m-bert Nov 24, 2025
1f77201
Update example
m-bert Nov 24, 2025
b491534
Update Scroll ref
m-bert Nov 24, 2025
0ea974e
Correct web initialization
m-bert Nov 24, 2025
03b8a0f
Remove comment
m-bert Nov 24, 2025
5a6dc60
Do not unpack rippleColor
m-bert Nov 24, 2025
0ce2be8
Remove testID
m-bert Nov 24, 2025
23403fa
Remove testID from cnw
m-bert Nov 24, 2025
b7c461d
Disbale reanimated for components
m-bert Nov 24, 2025
c31dba4
Add gestures to registry
m-bert Nov 25, 2025
cad5d35
Jest works
m-bert Nov 25, 2025
f165966
Change testId to testID
m-bert Nov 25, 2025
464699e
Update packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts
m-bert Dec 1, 2025
015e285
Merge branch 'next' into @mbert/test-ID
m-bert Dec 3, 2025
fbfa333
new line
m-bert Dec 3, 2025
57db777
Add tests to github workflow
m-bert Dec 4, 2025
6b7fff0
Store handlers only in jest
m-bert Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name: Test relations traversal algorithm
name: Test Gesture Handler 3 API

on:
pull_request:
paths:
- packages/react-native-gesture-handler/src/v3/**
- packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
- packages/react-native-gesture-handler/src/__tests__/API_V3.test.tsx
push:
branches:
- main
Expand Down Expand Up @@ -34,4 +35,4 @@ jobs:

- name: Run tests
working-directory: packages/react-native-gesture-handler
run: yarn test RelationsTraversal
run: yarn test RelationsTraversal API_V3
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { usePanGesture } from '../v3/hooks/gestures';
import { render, renderHook } from '@testing-library/react-native';
import { fireGestureHandler, getByGestureTestId } from '../jestUtils';
import { State } from '../State';
import GestureHandlerRootView from '../components/GestureHandlerRootView';
import { RectButton } from '../v3/components';
import { act } from 'react';

describe('[API v3] Hooks', () => {
test('Pan gesture', () => {
const onBegin = jest.fn();
const onStart = jest.fn();

const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),

Check warning on line 17 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unsafe return of an `any` typed value
onActivate: (e) => onStart(e),

Check warning on line 18 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unsafe return of an `any` typed value
})
).result.current;

fireGestureHandler(panGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);

expect(onBegin).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledTimes(1);
});
});

describe('[API v3] Components', () => {
test('Rect Button', () => {
const pressFn = jest.fn();

const RectButtonExample = () => {
return (
<GestureHandlerRootView>
<RectButton testID="btn" onPress={pressFn} />
</GestureHandlerRootView>
);
};

render(<RectButtonExample />);

const nativeGesture = getByGestureTestId('btn');

act(() => {
fireGestureHandler(nativeGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);
});

expect(pressFn).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import { isTestEnv } from '../utils';
import { GestureType } from './gestures/gesture';
import { GestureEvent, HandlerStateChangeEvent } from './gestureHandlerCommon';
import { SingleGesture } from '../v3/types';

export const handlerIDToTag: Record<string, number> = {};

// There were attempts to create types that merge possible HandlerData and Config,
// but ts was not able to infer them properly in many cases, so we use any here.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hookGestures = new Map<number, SingleGesture<any, any>>();
const gestures = new Map<number, GestureType>();
const oldHandlers = new Map<number, GestureHandlerCallbacks>();
const testIDs = new Map<string, number>();

export function registerGesture<THandlerData, TConfig>(
handlerTag: number,
gesture: SingleGesture<THandlerData, TConfig>
) {
if (isTestEnv() && gesture.config.testID) {
hookGestures.set(handlerTag, gesture);
testIDs.set(gesture.config.testID, handlerTag);
}
}

export function unregisterGesture(handlerTag: number) {
const gesture = hookGestures.get(handlerTag);

if (gesture && isTestEnv() && gesture.config.testID) {
testIDs.delete(gesture.config.testID);
hookGestures.delete(handlerTag);
}
}

export function registerHandler(
handlerTag: number,
handler: GestureType,
Expand Down Expand Up @@ -40,14 +65,18 @@ export function findHandler(handlerTag: number) {
return gestures.get(handlerTag);
}

export function findGesture(handlerTag: number) {
return hookGestures.get(handlerTag);
}

export function findOldGestureHandler(handlerTag: number) {
return oldHandlers.get(handlerTag);
}

export function findHandlerByTestID(testID: string) {
const handlerTag = testIDs.get(testID);
if (handlerTag !== undefined) {
return findHandler(handlerTag) ?? null;
return findHandler(handlerTag) ?? findGesture(handlerTag) ?? null;
}
return null;
}
Expand Down
41 changes: 38 additions & 3 deletions packages/react-native-gesture-handler/src/jestUtils/jestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {
} from '../handlers/TapGestureHandler';
import { State } from '../State';
import { hasProperty, withPrevAndCurrent } from '../utils';
import type { SingleGesture } from '../v3/types';
import { maybeUnpackValue } from '../v3/hooks/utils';

// Load fireEvent conditionally, so RNGH may be used in setups without testing-library
let fireEvent = (
Expand Down Expand Up @@ -164,11 +166,17 @@ const handlersDefaultEvents: DefaultEventsMapping = {
};

function isGesture(
componentOrGesture: ReactTestInstance | GestureType
componentOrGesture: ReactTestInstance | GestureType | SingleGesture<any, any>
): componentOrGesture is GestureType {
return componentOrGesture instanceof BaseGesture;
}

function isHookGesture(
componentOrGesture: ReactTestInstance | SingleGesture<any, any>
): componentOrGesture is SingleGesture<any, any> {
return 'detectorCallbacks' in componentOrGesture;
}

interface WrappedGestureHandlerTestEvent {
nativeEvent: GestureHandlerTestEvent;
}
Expand Down Expand Up @@ -408,7 +416,7 @@ interface HandlerData {
enabled: boolean | undefined;
}
function getHandlerData(
componentOrGesture: ReactTestInstance | GestureType
componentOrGesture: ReactTestInstance | GestureType | SingleGesture<any, any>
): HandlerData {
if (isGesture(componentOrGesture)) {
const gesture = componentOrGesture;
Expand All @@ -421,6 +429,33 @@ function getHandlerData(
enabled: gesture.config.enabled,
};
}

if (isHookGesture(componentOrGesture)) {
return {
handlerType: componentOrGesture.type as HandlerNames,
handlerTag: componentOrGesture.tag,
enabled: maybeUnpackValue(componentOrGesture.config.enabled),
emitEvent: (eventName, args) => {
const { state, oldState, handlerTag, ...rest } = args.nativeEvent;

const event = {
state,
handlerTag,
handlerData: { ...rest },
};

if (eventName === 'onGestureHandlerStateChange') {
componentOrGesture.detectorCallbacks.onGestureHandlerStateChange({
oldState: oldState as State,
...event,
});
} else if (eventName === 'onGestureHandlerEvent') {
componentOrGesture.detectorCallbacks.onGestureHandlerEvent?.(event);
}
},
};
}

const gestureHandlerComponent = componentOrGesture;
return {
emitEvent: (eventName, args) => {
Expand Down Expand Up @@ -465,7 +500,7 @@ type ExtractConfig<T> =
: Record<string, unknown>;

export function fireGestureHandler<THandler extends AllGestures | AllHandlers>(
componentOrGesture: ReactTestInstance | GestureType,
componentOrGesture: ReactTestInstance | GestureType | SingleGesture<any, any>,
eventList: Partial<GestureHandlerTestEvent<ExtractConfig<THandler>>>[] = []
): void {
const { emitEvent, handlerType, handlerTag, enabled } =
Expand Down
49 changes: 30 additions & 19 deletions packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import {
} from './utils';
import { tagMessage } from '../../utils';
import { BaseGestureConfig, SingleGesture, SingleGestureName } from '../types';
import { scheduleFlushOperations } from '../../handlers/utils';
import {
registerGesture,
unregisterGesture,
} from '../../handlers/handlersRegistry';
import { Platform } from 'react-native';
import { NativeProxy } from '../NativeProxy';

Expand Down Expand Up @@ -64,25 +69,8 @@ export function useGesture<THandlerData, TConfig>(
NativeProxy.createGestureHandler(type, tag, {});
}

useEffect(() => {
return () => {
NativeProxy.dropGestureHandler(tag);
};
}, [type, tag]);

useEffect(() => {
const preparedConfig = prepareConfigForNativeSide(type, config);
NativeProxy.setGestureHandlerConfig(tag, preparedConfig);

bindSharedValues(config, tag);

return () => {
unbindSharedValues(config, tag);
};
}, [tag, config, type]);

return useMemo(
(): SingleGesture<THandlerData, TConfig> => ({
const gesture = useMemo(
() => ({
tag,
type,
config,
Expand Down Expand Up @@ -121,4 +109,27 @@ export function useGesture<THandlerData, TConfig>(
gestureRelations,
]
);

useEffect(() => {
return () => {
NativeProxy.dropGestureHandler(tag);
scheduleFlushOperations();
};
}, [type, tag]);

useEffect(() => {
const preparedConfig = prepareConfigForNativeSide(type, config);
NativeProxy.setGestureHandlerConfig(tag, preparedConfig);
scheduleFlushOperations();

bindSharedValues(config, tag);
registerGesture(tag, gesture);

return () => {
unbindSharedValues(config, tag);
unregisterGesture(tag);
};
}, [tag, config, type, gesture]);

return gesture;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const CommonConfig = new Set<keyof CommonGestureConfig>([
'mouseButton',
'enableContextMenu',
'touchAction',
'testID',
]);

const ExternalRelationsConfig = new Set<keyof ExternalRelations>([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type InternalConfigProps<THandlerData> = {
export type CommonGestureConfig = {
disableReanimated?: boolean;
useAnimated?: boolean;
testID?: string;
} & WithSharedValue<
{
runOnJS?: boolean;
Expand Down
Loading