Skip to content

fix: press event order #1696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -33,7 +33,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
},
},
{
"name": "press",
"name": "pressOut",
"payload": {
"currentTarget": {
"measure": [Function],
Expand All @@ -52,7 +52,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
"pageX": 0,
"pageY": 0,
"target": 0,
"timestamp": 0,
"timestamp": 130,
"touches": [],
},
"persist": [Function],
Expand All @@ -63,7 +63,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
},
},
{
"name": "pressOut",
"name": "press",
"payload": {
"currentTarget": {
"measure": [Function],
Expand All @@ -82,7 +82,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
"pageX": 0,
"pageY": 0,
"target": 0,
"timestamp": 0,
"timestamp": 130,
"touches": [],
},
"persist": [Function],
Expand Down
4 changes: 2 additions & 2 deletions src/user-event/press/__tests__/press.real-timers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('userEvent.press with real timers', () => {
);
await user.press(screen.getByTestId('pressable'));

expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
});

test('does not trigger event when pressable is disabled', async () => {
Expand Down Expand Up @@ -128,7 +128,7 @@ describe('userEvent.press with real timers', () => {
);
await user.press(screen.getByTestId('pressable'));

expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
});

test('crawls up in the tree to find an element that responds to touch events', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/user-event/press/__tests__/press.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe('userEvent.press with fake timers', () => {
);
await user.press(screen.getByTestId('pressable'));

expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
});

test('crawls up in the tree to find an element that responds to touch events', async () => {
Expand Down
7 changes: 0 additions & 7 deletions src/user-event/press/constants.ts

This file was deleted.

29 changes: 15 additions & 14 deletions src/user-event/press/press.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { ReactTestInstance } from 'react-test-renderer';
import act from '../../act';
import { getHostParent } from '../../helpers/component-tree';
import { isTextInputEditable } from '../../helpers/text-input';
import { isPointerEventEnabled } from '../../helpers/pointer-events';
import { isHostText, isHostTextInput } from '../../helpers/host-component-names';
import { EventBuilder } from '../event-builder';
import { UserEventConfig, UserEventInstance } from '../setup';
import { dispatchEvent, wait } from '../utils';
import { DEFAULT_MIN_PRESS_DURATION } from './constants';

// These are constants defined in the React Native repo
export const DEFAULT_MIN_PRESS_DURATION = 130;
export const DEFAULT_LONG_PRESS_DELAY_MS = 500;

export interface PressOptions {
duration?: number;
Expand All @@ -27,7 +29,7 @@ export async function longPress(
): Promise<void> {
await basePress(this.config, element, {
type: 'longPress',
duration: options?.duration ?? 500,
duration: options?.duration ?? DEFAULT_LONG_PRESS_DELAY_MS,
});
}

Expand Down Expand Up @@ -73,18 +75,14 @@ const emitPressablePressEvents = async (

dispatchEvent(element, 'responderGrant', EventBuilder.Common.responderGrant());

await wait(config, options.duration);
// We apply minimum press duration here to ensure that `press` events are emitted after `pressOut`.
// Otherwise, pressables would emit them in the reverse order, which in reality happens only for
// very short presses (< 130ms) and contradicts the React Native docs.
// See: https://reactnative.dev/docs/pressable#onpress
let duration = Math.max(options.duration, DEFAULT_MIN_PRESS_DURATION);
await wait(config, duration);

dispatchEvent(element, 'responderRelease', EventBuilder.Common.responderRelease());

// React Native will wait for minimal delay of DEFAULT_MIN_PRESS_DURATION
// before emitting the `pressOut` event. We need to wait here, so that
// `press()` function does not return before that.
if (DEFAULT_MIN_PRESS_DURATION - options.duration > 0) {
await act(async () => {
await wait(config, DEFAULT_MIN_PRESS_DURATION - options.duration);
});
}
};

const isEnabledTouchResponder = (element: ReactTestInstance) => {
Expand Down Expand Up @@ -127,7 +125,10 @@ async function emitTextPressEvents(

dispatchEvent(element, 'pressOut', EventBuilder.Common.touch());

// Regular press events are emitted after `pressOut`.
// Regular press events are emitted after `pressOut` according to the React Native docs.
// See: https://reactnative.dev/docs/pressable#onpress
// Experimentally for very short presses (< 130ms) `press` events are actually emitted before `onPressOut`, but
// we will ignore that as in reality most pressed would be above the 130ms threshold.
if (options.type === 'press') {
dispatchEvent(element, 'press', EventBuilder.Common.touch());
}
Expand Down
Loading