Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -5,7 +5,7 @@
screen,
} from '@testing-library/react-native';
import { act } from 'react';
import { Keyboard, View } from 'react-native';
import { Keyboard, TextInput, View } from 'react-native';

import GestureHandlerRootView from '../components/GestureHandlerRootView';
import { fireGestureHandler, getByGestureTestId } from '../jestUtils';
Expand Down Expand Up @@ -55,8 +55,8 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),

Check warning on line 58 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 59 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;

Expand Down Expand Up @@ -84,8 +84,8 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onUpdate: (e) => onUpdate(e),

Check warning on line 87 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
onTouchesUp: (e) => onTouchesUp(e),

Check warning on line 88 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;

Expand All @@ -102,7 +102,7 @@

expect(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
jsEventHandler?.(malformedTouchEvent as any);

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

View workflow job for this annotation

GitHub Actions / check

Unsafe argument of type `any` assigned to a parameter of type `GestureHandlerEventWithHandlerData<PanHandlerData, PanExtendedHandlerData>`
}).not.toThrow();

expect(onUpdate).not.toHaveBeenCalled();
Expand All @@ -115,7 +115,7 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onUpdate: (e) => onUpdate(e),

Check warning on line 118 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;

Expand Down Expand Up @@ -454,8 +454,17 @@
keyboardShouldPersistTaps,
});

// The drop requires a focused RN TextInput to blur.
const focusInput = () =>
jest
.spyOn(TextInput.State, 'currentlyFocusedInput')
.mockReturnValue(
{} as ReturnType<typeof TextInput.State.currentlyFocusedInput>
);

test('isKeyboardDismissingTap is true only in never mode while the keyboard is visible', async () => {
const addListenerSpy = jest.spyOn(Keyboard, 'addListener');
const focusSpy = focusInput();

render(
<GestureHandlerRootView>
Expand All @@ -477,7 +486,38 @@
// Outside an RNGH ScrollView there is no context, so nothing is dropped.
expect(isKeyboardDismissingTap(null)).toBe(false);

// The verdict must survive the dismissal blurring the input mid-tap.
focusSpy.mockReturnValue(undefined);
expect(isKeyboardDismissingTap(makeContext('never'))).toBe(true);

addListenerSpy.mockRestore();
focusSpy.mockRestore();
});

test('isKeyboardDismissingTap is false when no RN TextInput is focused (native field keyboard)', async () => {
const addListenerSpy = jest.spyOn(Keyboard, 'addListener');

render(
<GestureHandlerRootView>
<ScrollView keyboardShouldPersistTaps="never" />
</GestureHandlerRootView>
);
await act(flushImmediate);

// Keyboard up for a native field (e.g. a native-stack search bar) -
// no RN TextInput to blur, so the tap must not be dropped.
showKeyboard(addListenerSpy);

expect(TextInput.State.currentlyFocusedInput()).toBeNull();
expect(isKeyboardDismissingTap(makeContext('never'))).toBe(false);

// Focus moving to an RN input while the keyboard stays up makes the
// tap dismissible again.
const focusSpy = focusInput();
expect(isKeyboardDismissingTap(makeContext('never'))).toBe(true);

addListenerSpy.mockRestore();
focusSpy.mockRestore();
});

Comment thread
m-bert marked this conversation as resolved.
test('isKeyboardDismissingTap is false for a detached (height 0) keyboard', async () => {
Expand All @@ -500,6 +540,7 @@

test('Touchable does NOT fire any press callback on the keyboard-dismissing tap (never)', async () => {
const addListenerSpy = jest.spyOn(Keyboard, 'addListener');
const focusSpy = focusInput();
const onPress = jest.fn();
const onPressIn = jest.fn();
const onPressOut = jest.fn();
Expand All @@ -519,6 +560,10 @@
await act(flushImmediate);
showKeyboard(addListenerSpy);

// The 'never' responder blurs the input at touch-down, before the
// press events arrive - mirror that ordering.
focusSpy.mockReturnValue(undefined);

// Includes a re-entry PressIn (finger dragged out and back in) so the
// capture-once verdict path is exercised too.
const button = screen.getByTestId('touchable');
Expand All @@ -535,6 +580,7 @@
expect(onPressIn).not.toHaveBeenCalled();
expect(onPressOut).not.toHaveBeenCalled();
addListenerSpy.mockRestore();
focusSpy.mockRestore();
});

test('Touchable fires onPress in never mode when the keyboard is not visible', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { TextInput } from 'react-native';

export type KeyboardShouldPersistTaps =
| boolean
Expand Down Expand Up @@ -27,9 +28,15 @@ export function updateResponderEventValue(
}

let isKeyboardVisible = false;
let keyboardOpenedForRNInput = false;

export function setKeyboardVisibility(visible: boolean) {
isKeyboardVisible = visible;

// Snapshotted at show-time: the dismissal blurs the input at touch-down,
// before the press events get checked
keyboardOpenedForRNInput =
visible && TextInput.State.currentlyFocusedInput?.() != null;
}

export function isKeyboardDismissingTap(
Expand All @@ -42,5 +49,14 @@ export function isKeyboardDismissingTap(
const mode = jsResponderContext.keyboardShouldPersistTaps;
const keyboardNeverPersistTaps = !mode || mode === 'never';

return keyboardNeverPersistTaps && isKeyboardVisible;
// Drop only taps that can dismiss the keyboard, i.e. an RN TextInput is (or
// was at show-time) focused - mirrors RN ScrollView's `_keyboardIsDismissible`.
// A native field's keyboard (e.g. a native-stack search bar) can't be
// blurred, so dropping there would leave presses permanently dead
return (
keyboardNeverPersistTaps &&
isKeyboardVisible &&
(keyboardOpenedForRNInput ||
TextInput.State.currentlyFocusedInput?.() != null)
);
}
Loading