From 758408d69924f2c26c25c1d81f6609fe062aeba2 Mon Sep 17 00:00:00 2001 From: Ngoc Le Date: Mon, 24 Aug 2026 22:50:19 +0700 Subject: [PATCH 1/3] Fix ReanimatedDrawerLayout animation speed after rerender --- ...nimatedDrawerLayoutAnimationSpeed.test.tsx | 105 ++++++++++++++++++ .../src/components/ReanimatedDrawerLayout.tsx | 1 + 2 files changed, 106 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx diff --git a/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx b/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx new file mode 100644 index 0000000000..de118d393b --- /dev/null +++ b/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx @@ -0,0 +1,105 @@ +import { act, render } from '@testing-library/react-native'; +import React from 'react'; +import { View } from 'react-native'; +import { withSpring } from 'react-native-reanimated'; + +import GestureHandlerRootView from '../components/GestureHandlerRootView'; +import ReanimatedDrawerLayout, { + type DrawerLayoutMethods, +} from '../components/ReanimatedDrawerLayout'; + +jest.mock('react-native-reanimated', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const ReactNative = jest.requireActual('react-native'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const ReactActual = jest.requireActual('react'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const AnimatedView = ReactNative.View; + + return { + __esModule: true, + default: { + View: AnimatedView, + createAnimatedComponent: (component: unknown) => component, + }, + View: AnimatedView, + createAnimatedComponent: (component: unknown) => component, + Extrapolation: { CLAMP: 'clamp' }, + interpolate: (value: number) => value, + isSharedValue: () => false, + useAnimatedProps: () => ({}), + useAnimatedStyle: () => ({}), + useDerivedValue: () => undefined, + useSharedValue: (initialValue: unknown) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + const ref = ReactActual.useRef({ value: initialValue }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + return ref.current; + }, + withSpring: jest.fn((toValue: unknown) => toValue), + }; +}); + +jest.mock('react-native-worklets', () => ({ + scheduleOnRN: jest.fn( + (callback: (...args: unknown[]) => void, ...args: unknown[]) => + callback(...args) + ), +})); + +jest.mock('../v3/detectors', () => ({ + InterceptingGestureDetector: ({ children }: { children: React.ReactNode }) => + children, + VirtualGestureDetector: ({ children }: { children: React.ReactNode }) => + children, +})); + +jest.mock('../v3/hooks/gestures', () => ({ + usePanGesture: (config: unknown) => config, + useTapGesture: (config: unknown) => config, +})); + +function Drawer({ + animationSpeed, + drawerRef, +}: { + animationSpeed: number; + drawerRef: React.Ref; +}) { + return ( + + }> + + + + ); +} + +describe('ReanimatedDrawerLayout animation speed', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('uses the latest animationSpeed prop after rerender', async () => { + const drawerRef = React.createRef(); + const { rerender } = render( + + ); + + await act(() => drawerRef.current?.openDrawer()); + expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( + expect.objectContaining({ mass: 0.5 }) + ); + + jest.mocked(withSpring).mockClear(); + rerender(); + + await act(() => drawerRef.current?.openDrawer()); + expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( + expect.objectContaining({ mass: 0.25 }) + ); + }); +}); diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx b/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx index 375d0bfb04..7532f67443 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx @@ -441,6 +441,7 @@ const DrawerLayout = function DrawerLayout( ); }, [ + animationSpeedProp, openValue, emitStateChanged, isDrawerOpen, From b858ef8583895eb15900cbdd376ce568185fbd98 Mon Sep 17 00:00:00 2001 From: Ngoc Le Date: Mon, 24 Aug 2026 22:59:26 +0700 Subject: [PATCH 2/3] test: use official Worklets mock --- ...nimatedDrawerLayoutAnimationSpeed.test.tsx | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx b/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx index de118d393b..26faf3f9f7 100644 --- a/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx @@ -40,12 +40,9 @@ jest.mock('react-native-reanimated', () => { }; }); -jest.mock('react-native-worklets', () => ({ - scheduleOnRN: jest.fn( - (callback: (...args: unknown[]) => void, ...args: unknown[]) => - callback(...args) - ), -})); +jest.mock('react-native-worklets', () => + jest.requireActual>('react-native-worklets/src/mock') +); jest.mock('../v3/detectors', () => ({ InterceptingGestureDetector: ({ children }: { children: React.ReactNode }) => @@ -78,6 +75,17 @@ function Drawer({ ); } +async function openDrawer( + drawerRef: React.RefObject +) { + await act(async () => { + drawerRef.current?.openDrawer(); + await new Promise((resolve) => { + queueMicrotask(resolve); + }); + }); +} + describe('ReanimatedDrawerLayout animation speed', () => { beforeEach(() => { jest.clearAllMocks(); @@ -89,7 +97,7 @@ describe('ReanimatedDrawerLayout animation speed', () => { ); - await act(() => drawerRef.current?.openDrawer()); + await openDrawer(drawerRef); expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( expect.objectContaining({ mass: 0.5 }) ); @@ -97,7 +105,7 @@ describe('ReanimatedDrawerLayout animation speed', () => { jest.mocked(withSpring).mockClear(); rerender(); - await act(() => drawerRef.current?.openDrawer()); + await openDrawer(drawerRef); expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( expect.objectContaining({ mass: 0.25 }) ); From 73f97c33bbffd12c36419e2de431fed8981faf83 Mon Sep 17 00:00:00 2001 From: Ngoc Le Date: Wed, 26 Aug 2026 21:35:08 +0700 Subject: [PATCH 3/3] test: remove drawer animation regression test --- ...nimatedDrawerLayoutAnimationSpeed.test.tsx | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx diff --git a/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx b/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx deleted file mode 100644 index 26faf3f9f7..0000000000 --- a/packages/react-native-gesture-handler/src/__tests__/reanimatedDrawerLayoutAnimationSpeed.test.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { act, render } from '@testing-library/react-native'; -import React from 'react'; -import { View } from 'react-native'; -import { withSpring } from 'react-native-reanimated'; - -import GestureHandlerRootView from '../components/GestureHandlerRootView'; -import ReanimatedDrawerLayout, { - type DrawerLayoutMethods, -} from '../components/ReanimatedDrawerLayout'; - -jest.mock('react-native-reanimated', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const ReactNative = jest.requireActual('react-native'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const ReactActual = jest.requireActual('react'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const AnimatedView = ReactNative.View; - - return { - __esModule: true, - default: { - View: AnimatedView, - createAnimatedComponent: (component: unknown) => component, - }, - View: AnimatedView, - createAnimatedComponent: (component: unknown) => component, - Extrapolation: { CLAMP: 'clamp' }, - interpolate: (value: number) => value, - isSharedValue: () => false, - useAnimatedProps: () => ({}), - useAnimatedStyle: () => ({}), - useDerivedValue: () => undefined, - useSharedValue: (initialValue: unknown) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - const ref = ReactActual.useRef({ value: initialValue }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access - return ref.current; - }, - withSpring: jest.fn((toValue: unknown) => toValue), - }; -}); - -jest.mock('react-native-worklets', () => - jest.requireActual>('react-native-worklets/src/mock') -); - -jest.mock('../v3/detectors', () => ({ - InterceptingGestureDetector: ({ children }: { children: React.ReactNode }) => - children, - VirtualGestureDetector: ({ children }: { children: React.ReactNode }) => - children, -})); - -jest.mock('../v3/hooks/gestures', () => ({ - usePanGesture: (config: unknown) => config, - useTapGesture: (config: unknown) => config, -})); - -function Drawer({ - animationSpeed, - drawerRef, -}: { - animationSpeed: number; - drawerRef: React.Ref; -}) { - return ( - - }> - - - - ); -} - -async function openDrawer( - drawerRef: React.RefObject -) { - await act(async () => { - drawerRef.current?.openDrawer(); - await new Promise((resolve) => { - queueMicrotask(resolve); - }); - }); -} - -describe('ReanimatedDrawerLayout animation speed', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('uses the latest animationSpeed prop after rerender', async () => { - const drawerRef = React.createRef(); - const { rerender } = render( - - ); - - await openDrawer(drawerRef); - expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( - expect.objectContaining({ mass: 0.5 }) - ); - - jest.mocked(withSpring).mockClear(); - rerender(); - - await openDrawer(drawerRef); - expect(jest.mocked(withSpring).mock.calls.at(-1)?.[1]).toEqual( - expect.objectContaining({ mass: 0.25 }) - ); - }); -});