From 9456b4824b7cc5f241a740cbc3101bd8d1011411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 14:01:37 +0300 Subject: [PATCH 01/12] Added panning control with GestureContext. Toast control fix. --- src/Toast.tsx | 6 ++++-- src/components/AnimatedContainer.tsx | 15 ++++++++++++++- src/contexts/__tests__/LoggerContext.test.tsx | 8 ++------ src/contexts/index.ts | 1 + src/hooks/usePanResponder.ts | 18 ++++++++++-------- src/useToast.ts | 15 ++++++++++----- 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/Toast.tsx b/src/Toast.tsx index a151a550d..38fa98c2e 100644 --- a/src/Toast.tsx +++ b/src/Toast.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { LoggerProvider } from './contexts'; +import { LoggerProvider, GestureProvider } from './contexts'; import { ToastUI } from './ToastUI'; import { ToastHideParams, @@ -88,7 +88,9 @@ export function Toast(props: ToastProps) { return ( - + + + ); } diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index e12f2c415..e84ecd053 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Animated, Dimensions, PanResponderGestureState } from 'react-native'; -import { useLogger } from '../contexts'; +import { useLogger, useGesture } from '../contexts'; import { usePanResponder, useSlideAnimation, @@ -81,6 +81,7 @@ export function AnimatedContainer({ swipeable }: AnimatedContainerProps) { const { log } = useLogger(); + const { panning } = useGesture(); const { computeViewDimensions, height } = useViewDimensions(); @@ -93,6 +94,16 @@ export function AnimatedContainer({ avoidKeyboard }); + const onPanStart = React.useCallback(() => { + log('Swipe, pan start'); + panning.current = true; + }, [log, panning]); + + const onPanEnd = React.useCallback(() => { + log('Swipe, pan end'); + panning.current = false; + }, [log, panning]); + const onDismiss = React.useCallback(() => { log('Swipe, dismissing'); animate(0); @@ -118,6 +129,8 @@ export function AnimatedContainer({ computeNewAnimatedValueForGesture, onDismiss, onRestore, + onPanStart, + onPanEnd, disable: !swipeable }); diff --git a/src/contexts/__tests__/LoggerContext.test.tsx b/src/contexts/__tests__/LoggerContext.test.tsx index 5170f9c65..4cef88b71 100644 --- a/src/contexts/__tests__/LoggerContext.test.tsx +++ b/src/contexts/__tests__/LoggerContext.test.tsx @@ -11,12 +11,8 @@ const setup = (props?: Omit) => { const wrapper = ({ children }: { children: ReactChildren }) => ( {children} ); - const utils = renderHook(useLogger, { - wrapper - }); - return { - ...utils - }; + const utils = renderHook(useLogger, { wrapper }); + return { ...utils }; }; describe('test Logger context', () => { diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 0b66bf77c..115567318 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -1 +1,2 @@ export * from './LoggerContext'; +export * from './GestureContext'; diff --git a/src/hooks/usePanResponder.ts b/src/hooks/usePanResponder.ts index 3e0260a14..35eb42db6 100644 --- a/src/hooks/usePanResponder.ts +++ b/src/hooks/usePanResponder.ts @@ -36,6 +36,8 @@ export type UsePanResponderParams = { ) => number; onDismiss: () => void; onRestore: () => void; + onPanStart: () => void; + onPanEnd: () => void; disable?: boolean; }; @@ -44,34 +46,34 @@ export function usePanResponder({ computeNewAnimatedValueForGesture, onDismiss, onRestore, + onPanStart, + onPanEnd, disable }: UsePanResponderParams) { const onMove = React.useCallback( (_event: GestureResponderEvent, gesture: PanResponderGestureState) => { - if (disable) { - return; - } + if (disable) return; const newAnimatedValue = computeNewAnimatedValueForGesture(gesture); + onPanStart(); animatedValue.current?.setValue(newAnimatedValue); }, - [animatedValue, computeNewAnimatedValueForGesture, disable] + [animatedValue, computeNewAnimatedValueForGesture, onPanStart, disable] ); const onRelease = React.useCallback( (_event: GestureResponderEvent, gesture: PanResponderGestureState) => { - if (disable) { - return; - } + if (disable) return; const newAnimatedValue = computeNewAnimatedValueForGesture(gesture); + onPanEnd(); if (shouldDismissView(newAnimatedValue, gesture)) { onDismiss(); } else { onRestore(); } }, - [computeNewAnimatedValueForGesture, onDismiss, onRestore, disable] + [computeNewAnimatedValueForGesture, onPanEnd, onDismiss, onRestore, disable] ); const panResponder = React.useMemo( diff --git a/src/useToast.ts b/src/useToast.ts index f738a33c8..99a239f68 100644 --- a/src/useToast.ts +++ b/src/useToast.ts @@ -1,6 +1,6 @@ import React from 'react'; -import { useLogger } from './contexts'; +import { useLogger, useGesture } from './contexts'; import { useTimeout } from './hooks'; import { ToastData, ToastOptions, ToastProps, ToastShowParams } from './types'; import { noop } from './utils/func'; @@ -35,6 +35,7 @@ export type UseToastParams = { export function useToast({ defaultOptions }: UseToastParams) { const { log } = useLogger(); + const { panning } = useGesture(); const [isVisible, setIsVisible] = React.useState(false); const [data, setData] = React.useState(DEFAULT_DATA); @@ -47,10 +48,14 @@ export function useToast({ defaultOptions }: UseToastParams) { React.useState>(initialOptions); const onAutoHide = React.useCallback(() => { - log('Auto hiding'); - setIsVisible(false); - options.onHide(); - }, [log, options]); + if (panning.current) { + log('Auto hiding was blocked due to panning'); + } else { + log('Auto hiding'); + setIsVisible(false); + options.onHide(); + } + }, [log, options, panning]); const { startTimer, clearTimer } = useTimeout( onAutoHide, options.visibilityTime From 1fd586d719f502689e48e79ad36a6115520fb241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 14:05:27 +0300 Subject: [PATCH 02/12] Yarn v2+ files added gitignore. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 453d0e1d0..2a4cd9736 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ node_modules .idea coverage lib +.yarn +.yarnrc.yml From 4d212d30b89ef15b83997a031da5aa0afd835bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 14:45:15 +0300 Subject: [PATCH 03/12] Add missing GestureContext components. --- src/contexts/GestureContext.tsx | 30 +++++++++++++++++++ .../__tests__/GestureContext.test.tsx | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/contexts/GestureContext.tsx create mode 100644 src/contexts/__tests__/GestureContext.test.tsx diff --git a/src/contexts/GestureContext.tsx b/src/contexts/GestureContext.tsx new file mode 100644 index 000000000..8e2ef3913 --- /dev/null +++ b/src/contexts/GestureContext.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { ReactChildren } from '../types'; + +export type GestureContextType = { + panning: React.MutableRefObject; +}; + +export type GestureProviderProps = { + children: ReactChildren; +}; + +const GestureContext = React.createContext({ + panning: { current: false } +}); + +function GestureProvider({ children }: GestureProviderProps) { + const panning = React.useRef(false); + const value = { panning }; + return ( + {children} + ); +} + +function useGesture() { + const ctx = React.useContext(GestureContext); + return ctx; +} + +export { GestureProvider, useGesture }; diff --git a/src/contexts/__tests__/GestureContext.test.tsx b/src/contexts/__tests__/GestureContext.test.tsx new file mode 100644 index 000000000..c4da9efcd --- /dev/null +++ b/src/contexts/__tests__/GestureContext.test.tsx @@ -0,0 +1,30 @@ +/* eslint-env jest */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import React from 'react'; + +import { ReactChildren } from '../../types'; +import { GestureProvider, useGesture } from '../GestureContext'; +import { GestureProviderProps } from '..'; + +const setup = (props?: Omit) => { + const wrapper = ({ children }: { children: ReactChildren }) => ( + {children} + ); + const utils = renderHook(() => useGesture(), { wrapper }); + return { ...utils }; +}; + +describe('GestureContext', () => { + it('provides a panning ref with current defaulting to false', () => { + const { result } = setup(); + expect(result.current.panning).toBeDefined(); + expect(result.current.panning.current).toBe(false); + }); + + it('allows updating the panning ref value', () => { + const { result } = setup(); + act(() => (result.current.panning.current = true)); + expect(result.current.panning.current).toBe(true); + }); +}); From 8e85123a558c17aa9d3da8157eddd2597d1ae877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 17:15:29 +0300 Subject: [PATCH 04/12] Panning event start-end fix. --- src/components/AnimatedContainer.tsx | 8 ++++---- src/hooks/usePanResponder.ts | 30 ++++++++++++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index e84ecd053..358a44dd6 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -94,12 +94,12 @@ export function AnimatedContainer({ avoidKeyboard }); - const onPanStart = React.useCallback(() => { + const onStart = React.useCallback(() => { log('Swipe, pan start'); panning.current = true; }, [log, panning]); - const onPanEnd = React.useCallback(() => { + const onEnd = React.useCallback(() => { log('Swipe, pan end'); panning.current = false; }, [log, panning]); @@ -129,8 +129,8 @@ export function AnimatedContainer({ computeNewAnimatedValueForGesture, onDismiss, onRestore, - onPanStart, - onPanEnd, + onStart, + onEnd, disable: !swipeable }); diff --git a/src/hooks/usePanResponder.ts b/src/hooks/usePanResponder.ts index 35eb42db6..dc23d9ce7 100644 --- a/src/hooks/usePanResponder.ts +++ b/src/hooks/usePanResponder.ts @@ -36,8 +36,8 @@ export type UsePanResponderParams = { ) => number; onDismiss: () => void; onRestore: () => void; - onPanStart: () => void; - onPanEnd: () => void; + onStart: () => void; + onEnd: () => void; disable?: boolean; }; @@ -46,19 +46,26 @@ export function usePanResponder({ computeNewAnimatedValueForGesture, onDismiss, onRestore, - onPanStart, - onPanEnd, + onStart, + onEnd, disable }: UsePanResponderParams) { + const onGrant = React.useCallback(() => { + if (disable) return; + onStart(); + }, + [onStart, disable] + ); + const onMove = React.useCallback( (_event: GestureResponderEvent, gesture: PanResponderGestureState) => { if (disable) return; const newAnimatedValue = computeNewAnimatedValueForGesture(gesture); - onPanStart(); + animatedValue.current?.setValue(newAnimatedValue); }, - [animatedValue, computeNewAnimatedValueForGesture, onPanStart, disable] + [animatedValue, computeNewAnimatedValueForGesture, disable] ); const onRelease = React.useCallback( @@ -66,30 +73,33 @@ export function usePanResponder({ if (disable) return; const newAnimatedValue = computeNewAnimatedValueForGesture(gesture); - onPanEnd(); + onEnd(); if (shouldDismissView(newAnimatedValue, gesture)) { onDismiss(); } else { onRestore(); } }, - [computeNewAnimatedValueForGesture, onPanEnd, onDismiss, onRestore, disable] + [computeNewAnimatedValueForGesture, onEnd, onDismiss, onRestore, disable] ); const panResponder = React.useMemo( () => PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onPanResponderGrant: onGrant, onMoveShouldSetPanResponder: shouldSetPanResponder, onMoveShouldSetPanResponderCapture: shouldSetPanResponder, onPanResponderMove: onMove, onPanResponderRelease: onRelease }), - [onMove, onRelease] + [onMove, onRelease, onGrant] ); return { panResponder, onMove, - onRelease + onRelease, + onGrant, }; } From 14697f08c58781de7e439a6f424c566ca3d2f96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 17:19:27 +0300 Subject: [PATCH 05/12] Return handler object reordered. --- src/hooks/usePanResponder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePanResponder.ts b/src/hooks/usePanResponder.ts index dc23d9ce7..47f40a864 100644 --- a/src/hooks/usePanResponder.ts +++ b/src/hooks/usePanResponder.ts @@ -98,8 +98,8 @@ export function usePanResponder({ return { panResponder, + onGrant, onMove, onRelease, - onGrant, }; } From 8f1ef8494740eabe8d36563741f64e0bce67df9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Tue, 24 Jun 2025 19:40:18 +0300 Subject: [PATCH 06/12] Safeguard against unmanageable child behavior. --- src/components/AnimatedContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index 358a44dd6..026afa195 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -131,7 +131,7 @@ export function AnimatedContainer({ onRestore, onStart, onEnd, - disable: !swipeable + disable: !swipeable && !isVisible, }); React.useLayoutEffect(() => { From 93164220d85e1a20f9b9fac34e51be7428ef0ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Wed, 25 Jun 2025 12:19:00 +0300 Subject: [PATCH 07/12] Disable condition fix. --- src/components/AnimatedContainer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index 026afa195..3e45050e1 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -94,6 +94,8 @@ export function AnimatedContainer({ avoidKeyboard }); + const disable = React.useMemo(() => !swipeable || !isVisible, [swipeable, isVisible]); + const onStart = React.useCallback(() => { log('Swipe, pan start'); panning.current = true; @@ -131,7 +133,7 @@ export function AnimatedContainer({ onRestore, onStart, onEnd, - disable: !swipeable && !isVisible, + disable, }); React.useLayoutEffect(() => { From b285467cd891c6fb2348980ed7074df18320c3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Wed, 25 Jun 2025 14:10:16 +0300 Subject: [PATCH 08/12] Return to safer use of pointerEvents. --- src/components/AnimatedContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index 3e45050e1..e5053db6c 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -148,7 +148,7 @@ export function AnimatedContainer({ style={[styles.base, styles[position], animationStyles]} // This container View is never the target of touch events but its subviews can be. // By doing this, tapping buttons behind the Toast is allowed - pointerEvents={isVisible ? 'box-none' : 'none'} + pointerEvents='box-none' {...panResponder.panHandlers}> {children} From 251b3a66fdc4675900607b4669f424558fe48ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Wed, 25 Jun 2025 17:52:26 +0300 Subject: [PATCH 09/12] Condition simplify. --- src/components/AnimatedContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnimatedContainer.tsx b/src/components/AnimatedContainer.tsx index e5053db6c..3f7bac4a0 100644 --- a/src/components/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer.tsx @@ -94,7 +94,7 @@ export function AnimatedContainer({ avoidKeyboard }); - const disable = React.useMemo(() => !swipeable || !isVisible, [swipeable, isVisible]); + const disable = !swipeable || !isVisible; const onStart = React.useCallback(() => { log('Swipe, pan start'); From 939b535646ddd6f9a5650509b45d058ca06e3ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Wed, 25 Jun 2025 17:54:00 +0300 Subject: [PATCH 10/12] Update some tests. --- src/__tests__/Toast.test.tsx | 4 ++-- src/components/__tests__/AnimatedContainer.test.tsx | 1 + src/contexts/__tests__/GestureContext.test.tsx | 4 +++- src/hooks/__tests__/usePanResponder.test.ts | 5 +++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/__tests__/Toast.test.tsx b/src/__tests__/Toast.test.tsx index f91b1dd62..a9584b42e 100644 --- a/src/__tests__/Toast.test.tsx +++ b/src/__tests__/Toast.test.tsx @@ -7,14 +7,14 @@ import { Button, Modal, Text } from 'react-native'; import { Toast } from '../Toast'; /* - The Modal component is automatically mocked by RN and apparently contains a bug which makes the Modal + The Modal component is automatically mocked by RN and apparently contains a bug which makes the Modal (and its children) to always be visible in the test tree. This fixes the issue: */ jest.mock('react-native/Libraries/Modal/Modal', () => { const ActualModal = jest.requireActual('react-native/Libraries/Modal/Modal'); - return (props) => ; + return (props: any) => ; }); jest.mock('react-native/Libraries/LogBox/LogBox'); diff --git a/src/components/__tests__/AnimatedContainer.test.tsx b/src/components/__tests__/AnimatedContainer.test.tsx index 5bca48dc1..59783c7c4 100644 --- a/src/components/__tests__/AnimatedContainer.test.tsx +++ b/src/components/__tests__/AnimatedContainer.test.tsx @@ -30,6 +30,7 @@ const setup = (props?: Omit, 'children'>) => { topOffset: 40, bottomOffset: 40, keyboardOffset: 10, + avoidKeyboard: true, onHide }; diff --git a/src/contexts/__tests__/GestureContext.test.tsx b/src/contexts/__tests__/GestureContext.test.tsx index c4da9efcd..5d9630749 100644 --- a/src/contexts/__tests__/GestureContext.test.tsx +++ b/src/contexts/__tests__/GestureContext.test.tsx @@ -24,7 +24,9 @@ describe('GestureContext', () => { it('allows updating the panning ref value', () => { const { result } = setup(); - act(() => (result.current.panning.current = true)); + act(() => { + result.current.panning.current = true + }); expect(result.current.panning.current).toBe(true); }); }); diff --git a/src/hooks/__tests__/usePanResponder.test.ts b/src/hooks/__tests__/usePanResponder.test.ts index 6ed6ddddc..ba9216114 100644 --- a/src/hooks/__tests__/usePanResponder.test.ts +++ b/src/hooks/__tests__/usePanResponder.test.ts @@ -16,6 +16,8 @@ const setup = ({ newAnimatedValueForGesture = 0, disable = false } = {}) => { ); const onDismiss = jest.fn(); const onRestore = jest.fn(); + const onStart = jest.fn(); + const onEnd = jest.fn(); const utils = renderHook(() => usePanResponder({ @@ -23,6 +25,8 @@ const setup = ({ newAnimatedValueForGesture = 0, disable = false } = {}) => { computeNewAnimatedValueForGesture, onDismiss, onRestore, + onStart, + onEnd, disable }) ); @@ -39,6 +43,7 @@ describe('test usePanResponder hook', () => { it('returns defaults', () => { const { result } = setup(); expect(result.current.panResponder.panHandlers).toBeDefined(); + expect(result.current.onGrant).toBeDefined(); expect(result.current.onMove).toBeDefined(); expect(result.current.onRelease).toBeDefined(); }); From 12b6ab8f3c3f31c9c2795ae15dbebabb2e60fd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Thu, 26 Jun 2025 17:58:46 +0300 Subject: [PATCH 11/12] Tests revised, test support for new features. --- src/__helpers__/PanResponder.ts | 4 ++ src/__tests__/Toast.test.tsx | 4 +- .../{useToast.test.ts => useToast.test.tsx} | 40 +++++++++++++++++-- .../__tests__/AnimatedContainer.test.tsx | 2 + src/contexts/GestureContext.tsx | 7 ++-- src/hooks/__tests__/usePanResponder.test.ts | 16 +++++--- src/hooks/__tests__/useViewDimensions.test.ts | 2 +- src/hooks/usePanResponder.ts | 12 ++++-- 8 files changed, 68 insertions(+), 19 deletions(-) rename src/__tests__/{useToast.test.ts => useToast.test.tsx} (81%) diff --git a/src/__helpers__/PanResponder.ts b/src/__helpers__/PanResponder.ts index fcb6b68d1..a9b7d918b 100644 --- a/src/__helpers__/PanResponder.ts +++ b/src/__helpers__/PanResponder.ts @@ -24,12 +24,16 @@ export function mockPanResponder() { .spyOn(PanResponder, 'create') .mockImplementation( ({ + onStartShouldSetPanResponder, + onPanResponderGrant, onMoveShouldSetPanResponder, onMoveShouldSetPanResponderCapture, onPanResponderMove, onPanResponderRelease }: PanResponderCallbacks) => ({ panHandlers: { + onStartShouldSetResponder: onStartShouldSetPanResponder, + onResponderGrant: onPanResponderGrant, onMoveShouldSetResponder: onMoveShouldSetPanResponder, onMoveShouldSetResponderCapture: onMoveShouldSetPanResponderCapture, onResponderMove: onPanResponderMove, diff --git a/src/__tests__/Toast.test.tsx b/src/__tests__/Toast.test.tsx index a9584b42e..9113a105d 100644 --- a/src/__tests__/Toast.test.tsx +++ b/src/__tests__/Toast.test.tsx @@ -84,7 +84,7 @@ describe('test Toast component', () => { // Show the Modal const showModalButton = utils.queryByText('Show modal'); expect(showModalButton).toBeTruthy(); - fireEvent.press(showModalButton); + fireEvent.press(showModalButton as any); await waitFor(() => { expect(utils.queryByText('Inside modal')).toBeTruthy(); }); @@ -104,7 +104,7 @@ describe('test Toast component', () => { // Hide modal const hideModalButton = utils.queryByText('Hide modal'); expect(hideModalButton).toBeTruthy(); - fireEvent.press(hideModalButton); + fireEvent.press(hideModalButton as any); await waitFor(() => { expect(utils.queryByText('Inside modal')).toBeFalsy(); }); diff --git a/src/__tests__/useToast.test.ts b/src/__tests__/useToast.test.tsx similarity index 81% rename from src/__tests__/useToast.test.ts rename to src/__tests__/useToast.test.tsx index 4227189b3..df2148d03 100644 --- a/src/__tests__/useToast.test.ts +++ b/src/__tests__/useToast.test.tsx @@ -1,21 +1,30 @@ /* eslint-env jest */ import { act, renderHook } from '@testing-library/react-hooks'; +import React from 'react'; import { ToastOptions } from '../types'; import { DEFAULT_DATA, DEFAULT_OPTIONS, useToast } from '../useToast'; +import { GestureProvider } from '../contexts'; -const setup = () => { +const setupGestureWrapper = (panning: boolean) => { + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +const setup = (panning = false) => { + const wrapper = setupGestureWrapper(panning) const utils = renderHook(() => - useToast({ - defaultOptions: DEFAULT_OPTIONS - }) + useToast({ defaultOptions: DEFAULT_OPTIONS }), + { wrapper } ); return { ...utils }; }; + describe('test useToast hook', () => { it('returns defaults', () => { const { result } = setup(); @@ -167,6 +176,29 @@ describe('test useToast hook', () => { expect(onHide).toHaveBeenCalled(); }); + it('automatically hides when autoHide: true and panning.current: true', () => { + jest.useFakeTimers(); + const { result } = setup(true); + const onHide = jest.fn(); + act(() => { + result.current.show({ + text1: 'test', + autoHide: true, + onHide + }); + }); + + expect(result.current.isVisible).toBe(true); + + act(() => { + jest.runAllTimers(); + }); + + expect(result.current.isVisible).toBe(true); + expect(onHide).not.toHaveBeenCalled(); + }); + + it('shows using only text2', () => { const { result } = setup(); diff --git a/src/components/__tests__/AnimatedContainer.test.tsx b/src/components/__tests__/AnimatedContainer.test.tsx index 59783c7c4..e956682ee 100644 --- a/src/components/__tests__/AnimatedContainer.test.tsx +++ b/src/components/__tests__/AnimatedContainer.test.tsx @@ -96,6 +96,7 @@ describe('test AnimatedContainer component', () => { moveY: 100, dy: 10 }; + panHandler?.props.onResponderGrant(); panHandler?.props.onResponderMove(undefined, gesture); panHandler?.props.onResponderRelease(undefined, gesture); expect(onRestorePosition).toHaveBeenCalled(); @@ -120,6 +121,7 @@ describe('test AnimatedContainer component', () => { moveY: 5, dy: -78 }; + panHandler?.props.onResponderGrant(); panHandler?.props.onResponderMove(undefined, gesture); panHandler?.props.onResponderRelease(undefined, gesture); expect(onHide).toHaveBeenCalled(); diff --git a/src/contexts/GestureContext.tsx b/src/contexts/GestureContext.tsx index 8e2ef3913..0172dad81 100644 --- a/src/contexts/GestureContext.tsx +++ b/src/contexts/GestureContext.tsx @@ -8,15 +8,16 @@ export type GestureContextType = { export type GestureProviderProps = { children: ReactChildren; + panning?: boolean; }; const GestureContext = React.createContext({ panning: { current: false } }); -function GestureProvider({ children }: GestureProviderProps) { - const panning = React.useRef(false); - const value = { panning }; +function GestureProvider({ children, panning = false }: GestureProviderProps) { + const panningRef = React.useRef(panning); + const value = { panning: panningRef }; return ( {children} ); diff --git a/src/hooks/__tests__/usePanResponder.test.ts b/src/hooks/__tests__/usePanResponder.test.ts index ba9216114..169651d46 100644 --- a/src/hooks/__tests__/usePanResponder.test.ts +++ b/src/hooks/__tests__/usePanResponder.test.ts @@ -5,7 +5,7 @@ import { Animated, GestureResponderEvent } from 'react-native'; import { mockGestureValues } from '../../__helpers__/PanResponder'; import { usePanResponder } from '../usePanResponder'; -import { shouldSetPanResponder } from '..'; +import { moveShouldSetPanResponder, startShouldSetPanResponder } from '..'; const setup = ({ newAnimatedValueForGesture = 0, disable = false } = {}) => { const animatedValue = { @@ -52,6 +52,7 @@ describe('test usePanResponder hook', () => { const { result, computeNewAnimatedValueForGesture } = setup({ newAnimatedValueForGesture: 1 }); + result.current.onGrant(); result.current.onMove({} as GestureResponderEvent, mockGestureValues); expect(computeNewAnimatedValueForGesture).toBeCalledWith(mockGestureValues); }); @@ -70,6 +71,7 @@ describe('test usePanResponder hook', () => { disable: true }); + result.current.onGrant(); result.current.onMove({} as GestureResponderEvent, mockGestureValues); expect(computeNewAnimatedValueForGesture).not.toBeCalledWith( mockGestureValues @@ -115,13 +117,17 @@ describe('test usePanResponder hook', () => { }); describe('test shouldSetPanResponder function', () => { + it('is set pan start always true', () => { + expect(startShouldSetPanResponder()).toBe(true); + }); + it('is set when dx > offset', () => { const gesture = { ...mockGestureValues, dx: 2.1, dy: 0 }; - expect(shouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( + expect(moveShouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( true ); }); @@ -132,7 +138,7 @@ describe('test shouldSetPanResponder function', () => { dx: 0, dy: 2.1 }; - expect(shouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( + expect(moveShouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( true ); }); @@ -143,7 +149,7 @@ describe('test shouldSetPanResponder function', () => { dx: 2, dy: 0 }; - expect(shouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( + expect(moveShouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( false ); }); @@ -154,7 +160,7 @@ describe('test shouldSetPanResponder function', () => { dx: 0, dy: 2 }; - expect(shouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( + expect(moveShouldSetPanResponder({} as GestureResponderEvent, gesture)).toBe( false ); }); diff --git a/src/hooks/__tests__/useViewDimensions.test.ts b/src/hooks/__tests__/useViewDimensions.test.ts index b08ba3afb..953164ce8 100644 --- a/src/hooks/__tests__/useViewDimensions.test.ts +++ b/src/hooks/__tests__/useViewDimensions.test.ts @@ -7,7 +7,7 @@ import { act } from 'react-test-renderer'; import { useViewDimensions } from '../useViewDimensions'; import { UseViewDimensionsParams } from '..'; -const setup = (offsets: UseViewDimensionsParams) => { +const setup = (offsets?: UseViewDimensionsParams) => { const layoutChangeEventMock = { nativeEvent: { layout: { diff --git a/src/hooks/usePanResponder.ts b/src/hooks/usePanResponder.ts index 47f40a864..7fdc11838 100644 --- a/src/hooks/usePanResponder.ts +++ b/src/hooks/usePanResponder.ts @@ -6,7 +6,11 @@ import { PanResponderGestureState } from 'react-native'; -export function shouldSetPanResponder( +export function startShouldSetPanResponder() { + return true; +} + +export function moveShouldSetPanResponder( _event: GestureResponderEvent, gesture: PanResponderGestureState ) { @@ -86,10 +90,10 @@ export function usePanResponder({ const panResponder = React.useMemo( () => PanResponder.create({ - onStartShouldSetPanResponder: () => true, + onStartShouldSetPanResponder: startShouldSetPanResponder, onPanResponderGrant: onGrant, - onMoveShouldSetPanResponder: shouldSetPanResponder, - onMoveShouldSetPanResponderCapture: shouldSetPanResponder, + onMoveShouldSetPanResponder: moveShouldSetPanResponder, + onMoveShouldSetPanResponderCapture: moveShouldSetPanResponder, onPanResponderMove: onMove, onPanResponderRelease: onRelease }), From 7ce8e39fc80e71bc0fbe14a0741d67e28fc0f9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Thu, 26 Jun 2025 18:30:34 +0300 Subject: [PATCH 12/12] useToast test description change. --- src/__tests__/useToast.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/useToast.test.tsx b/src/__tests__/useToast.test.tsx index df2148d03..5f47270af 100644 --- a/src/__tests__/useToast.test.tsx +++ b/src/__tests__/useToast.test.tsx @@ -176,7 +176,7 @@ describe('test useToast hook', () => { expect(onHide).toHaveBeenCalled(); }); - it('automatically hides when autoHide: true and panning.current: true', () => { + it('does not hide when autoHide is true but user is panning', () => { jest.useFakeTimers(); const { result } = setup(true); const onHide = jest.fn();