Skip to content

Commit 7dc6e22

Browse files
committed
chore: fix Pressable implementation
1 parent 3eee37f commit 7dc6e22

File tree

5 files changed

+22
-28
lines changed

5 files changed

+22
-28
lines changed

src/user-event/press/__tests__/__snapshots__/press.test.tsx.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
3333
},
3434
},
3535
{
36-
"name": "press",
36+
"name": "pressOut",
3737
"payload": {
3838
"currentTarget": {
3939
"measure": [Function],
@@ -52,7 +52,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
5252
"pageX": 0,
5353
"pageY": 0,
5454
"target": 0,
55-
"timestamp": 0,
55+
"timestamp": 130,
5656
"touches": [],
5757
},
5858
"persist": [Function],
@@ -63,7 +63,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
6363
},
6464
},
6565
{
66-
"name": "pressOut",
66+
"name": "press",
6767
"payload": {
6868
"currentTarget": {
6969
"measure": [Function],
@@ -82,7 +82,7 @@ exports[`userEvent.press with fake timers calls onPressIn, onPress and onPressOu
8282
"pageX": 0,
8383
"pageY": 0,
8484
"target": 0,
85-
"timestamp": 0,
85+
"timestamp": 130,
8686
"touches": [],
8787
},
8888
"persist": [Function],

src/user-event/press/__tests__/press.real-timers.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('userEvent.press with real timers', () => {
3232
);
3333
await user.press(screen.getByTestId('pressable'));
3434

35-
expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
35+
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
3636
});
3737

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

131-
expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
131+
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
132132
});
133133

134134
test('crawls up in the tree to find an element that responds to touch events', async () => {

src/user-event/press/__tests__/press.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ describe('userEvent.press with fake timers', () => {
129129
);
130130
await user.press(screen.getByTestId('pressable'));
131131

132-
expect(getEventsNames(events)).toEqual(['pressIn', 'press', 'pressOut']);
132+
expect(getEventsNames(events)).toEqual(['pressIn', 'pressOut', 'press']);
133133
});
134134

135135
test('crawls up in the tree to find an element that responds to touch events', async () => {

src/user-event/press/constants.ts

-7
This file was deleted.

src/user-event/press/press.ts

+15-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { ReactTestInstance } from 'react-test-renderer';
2-
import act from '../../act';
32
import { getHostParent } from '../../helpers/component-tree';
43
import { isTextInputEditable } from '../../helpers/text-input';
54
import { isPointerEventEnabled } from '../../helpers/pointer-events';
65
import { isHostText, isHostTextInput } from '../../helpers/host-component-names';
76
import { EventBuilder } from '../event-builder';
87
import { UserEventConfig, UserEventInstance } from '../setup';
98
import { dispatchEvent, wait } from '../utils';
10-
import { DEFAULT_MIN_PRESS_DURATION } from './constants';
9+
10+
// These are constants defined in the React Native repo
11+
export const DEFAULT_MIN_PRESS_DURATION = 130;
12+
export const DEFAULT_LONG_PRESS_DELAY_MS = 500;
1113

1214
export interface PressOptions {
1315
duration?: number;
@@ -27,7 +29,7 @@ export async function longPress(
2729
): Promise<void> {
2830
await basePress(this.config, element, {
2931
type: 'longPress',
30-
duration: options?.duration ?? 500,
32+
duration: options?.duration ?? DEFAULT_LONG_PRESS_DELAY_MS,
3133
});
3234
}
3335

@@ -73,18 +75,14 @@ const emitPressablePressEvents = async (
7375

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

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

7885
dispatchEvent(element, 'responderRelease', EventBuilder.Common.responderRelease());
79-
80-
// React Native will wait for minimal delay of DEFAULT_MIN_PRESS_DURATION
81-
// before emitting the `pressOut` event. We need to wait here, so that
82-
// `press()` function does not return before that.
83-
if (DEFAULT_MIN_PRESS_DURATION - options.duration > 0) {
84-
await act(async () => {
85-
await wait(config, DEFAULT_MIN_PRESS_DURATION - options.duration);
86-
});
87-
}
8886
};
8987

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

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

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

0 commit comments

Comments
 (0)