Skip to content

Commit 440668e

Browse files
feat: fireEvent.press/scroll default event object (#1884)
1 parent 4c71a67 commit 440668e

File tree

26 files changed

+631
-282
lines changed

26 files changed

+631
-282
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

src/__tests__/fire-event.test.tsx

Lines changed: 193 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const verticalScrollEvent = { nativeEvent: { contentOffset: { y: 200 } } };
2121
const horizontalScrollEvent = { nativeEvent: { contentOffset: { x: 50 } } };
2222
const pressEventData = { nativeEvent: { pageX: 20, pageY: 30 } };
2323

24+
beforeEach(() => {
25+
jest.spyOn(Date, 'now').mockImplementation(() => 100100100100);
26+
});
27+
2428
test('fireEvent accepts event name with or without "on" prefix', async () => {
2529
const onPress = jest.fn();
2630
await render(<Pressable testID="btn" onPress={onPress} />);
@@ -36,7 +40,7 @@ test('fireEvent passes event data to handler', async () => {
3640
const onPress = jest.fn();
3741
await render(<Pressable testID="btn" onPress={onPress} />);
3842
await fireEvent.press(screen.getByTestId('btn'), pressEventData);
39-
expect(onPress).toHaveBeenCalledWith(pressEventData);
43+
expect(onPress.mock.calls[0][0]).toMatchObject(pressEventData);
4044
});
4145

4246
test('fireEvent passes multiple parameters to handler', async () => {
@@ -46,11 +50,11 @@ test('fireEvent passes multiple parameters to handler', async () => {
4650
expect(handlePress).toHaveBeenCalledWith('param1', 'param2', 'param3');
4751
});
4852

49-
test('fireEvent returns handler return value', async () => {
53+
test('fireEvent.press returns undefined when event handler returns a value', async () => {
5054
const handler = jest.fn().mockReturnValue('result');
5155
await render(<Pressable testID="btn" onPress={handler} />);
5256
const result = await fireEvent.press(screen.getByTestId('btn'));
53-
expect(result).toBe('result');
57+
expect(result).toBe(undefined);
5458
});
5559

5660
test('fireEvent bubbles event to parent handler', async () => {
@@ -65,6 +69,71 @@ test('fireEvent bubbles event to parent handler', async () => {
6569
});
6670

6771
describe('fireEvent.press', () => {
72+
test('passes default press event object to handler', async () => {
73+
const onPress = jest.fn();
74+
await render(<Pressable testID="btn" onPress={onPress} />);
75+
await fireEvent.press(screen.getByTestId('btn'));
76+
expect(onPress.mock.calls[0][0]).toMatchInlineSnapshot(`
77+
{
78+
"currentTarget": {
79+
"measure": [Function],
80+
},
81+
"isDefaultPrevented": [Function],
82+
"isPersistent": [Function],
83+
"isPropagationStopped": [Function],
84+
"nativeEvent": {
85+
"changedTouches": [],
86+
"identifier": 0,
87+
"locationX": 0,
88+
"locationY": 0,
89+
"pageX": 0,
90+
"pageY": 0,
91+
"target": 0,
92+
"timestamp": 100100100100,
93+
"touches": [],
94+
},
95+
"persist": [Function],
96+
"preventDefault": [Function],
97+
"stopPropagation": [Function],
98+
"target": {},
99+
"timeStamp": 0,
100+
}
101+
`);
102+
});
103+
104+
test('overrides default event properties with passed event props', async () => {
105+
const onPress = jest.fn();
106+
await render(<Pressable testID="btn" onPress={onPress} />);
107+
const customEventData = { nativeEvent: { pageX: 20, pageY: 30 } };
108+
await fireEvent.press(screen.getByTestId('btn'), customEventData);
109+
expect(onPress.mock.calls[0][0]).toMatchInlineSnapshot(`
110+
{
111+
"currentTarget": {
112+
"measure": [Function],
113+
},
114+
"isDefaultPrevented": [Function],
115+
"isPersistent": [Function],
116+
"isPropagationStopped": [Function],
117+
"nativeEvent": {
118+
"changedTouches": [],
119+
"identifier": 0,
120+
"locationX": 0,
121+
"locationY": 0,
122+
"pageX": 20,
123+
"pageY": 30,
124+
"target": 0,
125+
"timestamp": 100100100100,
126+
"touches": [],
127+
},
128+
"persist": [Function],
129+
"preventDefault": [Function],
130+
"stopPropagation": [Function],
131+
"target": {},
132+
"timeStamp": 0,
133+
}
134+
`);
135+
});
136+
68137
test.each([
69138
['Pressable', Pressable],
70139
['TouchableOpacity', TouchableOpacity],
@@ -106,6 +175,107 @@ describe('fireEvent.changeText', () => {
106175
});
107176

108177
describe('fireEvent.scroll', () => {
178+
test('passes default scroll event object to handler', async () => {
179+
const onScroll = jest.fn();
180+
await render(
181+
<ScrollView testID="scroll" onScroll={onScroll}>
182+
<Text>Content</Text>
183+
</ScrollView>,
184+
);
185+
const scrollView = screen.getByTestId('scroll');
186+
await fireEvent.scroll(scrollView);
187+
expect(onScroll.mock.calls[0][0]).toMatchInlineSnapshot(`
188+
{
189+
"currentTarget": {},
190+
"isDefaultPrevented": [Function],
191+
"isPersistent": [Function],
192+
"isPropagationStopped": [Function],
193+
"nativeEvent": {
194+
"contentInset": {
195+
"bottom": 0,
196+
"left": 0,
197+
"right": 0,
198+
"top": 0,
199+
},
200+
"contentOffset": {
201+
"x": 0,
202+
"y": 0,
203+
},
204+
"contentSize": {
205+
"height": 0,
206+
"width": 0,
207+
},
208+
"layoutMeasurement": {
209+
"height": 0,
210+
"width": 0,
211+
},
212+
"responderIgnoreScroll": true,
213+
"target": 0,
214+
"velocity": {
215+
"x": 0,
216+
"y": 0,
217+
},
218+
},
219+
"persist": [Function],
220+
"preventDefault": [Function],
221+
"stopPropagation": [Function],
222+
"target": {},
223+
"timeStamp": 0,
224+
}
225+
`);
226+
});
227+
228+
test('overrides default event properties with passed event props', async () => {
229+
const onScroll = jest.fn();
230+
await render(
231+
<ScrollView testID="scroll" onScroll={onScroll}>
232+
<Text>Content</Text>
233+
</ScrollView>,
234+
);
235+
const scrollView = screen.getByTestId('scroll');
236+
const customEventData = { nativeEvent: { contentOffset: { x: 50, y: 200 } } };
237+
await fireEvent.scroll(scrollView, customEventData);
238+
expect(onScroll.mock.calls[0][0]).toMatchInlineSnapshot(`
239+
{
240+
"currentTarget": {},
241+
"isDefaultPrevented": [Function],
242+
"isPersistent": [Function],
243+
"isPropagationStopped": [Function],
244+
"nativeEvent": {
245+
"contentInset": {
246+
"bottom": 0,
247+
"left": 0,
248+
"right": 0,
249+
"top": 0,
250+
},
251+
"contentOffset": {
252+
"x": 50,
253+
"y": 200,
254+
},
255+
"contentSize": {
256+
"height": 0,
257+
"width": 0,
258+
},
259+
"layoutMeasurement": {
260+
"height": 0,
261+
"width": 0,
262+
},
263+
"responderIgnoreScroll": true,
264+
"target": 0,
265+
"velocity": {
266+
"x": 0,
267+
"y": 0,
268+
},
269+
},
270+
"persist": [Function],
271+
"preventDefault": [Function],
272+
"stopPropagation": [Function],
273+
"target": {},
274+
"timeStamp": 0,
275+
}
276+
`);
277+
});
278+
109279
test('works on ScrollView', async () => {
110280
const onScroll = jest.fn();
111281
await render(
@@ -115,7 +285,7 @@ describe('fireEvent.scroll', () => {
115285
);
116286
const scrollView = screen.getByTestId('scroll');
117287
await fireEvent.scroll(scrollView, verticalScrollEvent);
118-
expect(onScroll).toHaveBeenCalledWith(verticalScrollEvent);
288+
expect(onScroll.mock.calls[0][0]).toMatchObject(verticalScrollEvent);
119289
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 200 });
120290
});
121291

@@ -134,7 +304,7 @@ describe('fireEvent.scroll', () => {
134304
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 200 });
135305
});
136306

137-
test('without contentOffset does not update native state', async () => {
307+
test('without contentOffset scrolls to (0, 0)', async () => {
138308
const onScroll = jest.fn();
139309
await render(
140310
<ScrollView testID="scroll" onScroll={onScroll}>
@@ -143,8 +313,10 @@ describe('fireEvent.scroll', () => {
143313
);
144314
const scrollView = screen.getByTestId('scroll');
145315
await fireEvent.scroll(scrollView, {});
146-
expect(onScroll).toHaveBeenCalled();
147-
expect(nativeState.contentOffsetForElement.get(scrollView)).toBeUndefined();
316+
expect(onScroll.mock.calls[0][0]).toMatchObject({
317+
nativeEvent: { contentOffset: { x: 0, y: 0 } },
318+
});
319+
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 0 });
148320
});
149321

150322
test('with non-finite contentOffset values uses 0', async () => {
@@ -171,10 +343,23 @@ describe('fireEvent.scroll', () => {
171343
);
172344
const scrollView = screen.getByTestId('scroll');
173345
await fireEvent.scroll(scrollView, horizontalScrollEvent);
174-
expect(onScroll).toHaveBeenCalledWith(horizontalScrollEvent);
346+
expect(onScroll.mock.calls[0][0]).toMatchObject(horizontalScrollEvent);
175347
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 50, y: 0 });
176348
});
177349

350+
test('without contentOffset via fireEvent() does not update native state', async () => {
351+
const onScroll = jest.fn();
352+
await render(
353+
<ScrollView testID="scroll" onScroll={onScroll}>
354+
<Text>Content</Text>
355+
</ScrollView>,
356+
);
357+
const scrollView = screen.getByTestId('scroll');
358+
await fireEvent(scrollView, 'scroll', { nativeEvent: {} });
359+
expect(onScroll).toHaveBeenCalled();
360+
expect(nativeState.contentOffsetForElement.get(scrollView)).toBeUndefined();
361+
});
362+
178363
test('with non-finite x contentOffset value uses 0', async () => {
179364
const onScroll = jest.fn();
180365
await render(
File renamed without changes.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
buildBlurEvent,
3+
buildFocusEvent,
4+
buildResponderGrantEvent,
5+
buildResponderReleaseEvent,
6+
buildTouchEvent,
7+
} from '../common';
8+
9+
test('buildTouchEvent returns event with touch nativeEvent', () => {
10+
const event = buildTouchEvent();
11+
12+
expect(event.nativeEvent).toEqual({
13+
changedTouches: [],
14+
identifier: 0,
15+
locationX: 0,
16+
locationY: 0,
17+
pageX: 0,
18+
pageY: 0,
19+
target: 0,
20+
timestamp: expect.any(Number),
21+
touches: [],
22+
});
23+
expect(event.currentTarget).toHaveProperty('measure');
24+
expect(event).toHaveProperty('preventDefault');
25+
});
26+
27+
test('buildResponderGrantEvent returns touch event with dispatchConfig', () => {
28+
const event = buildResponderGrantEvent();
29+
30+
expect(event.dispatchConfig).toEqual({
31+
registrationName: 'onResponderGrant',
32+
});
33+
expect(event.nativeEvent).toHaveProperty('touches');
34+
});
35+
36+
test('buildResponderReleaseEvent returns touch event with dispatchConfig', () => {
37+
const event = buildResponderReleaseEvent();
38+
39+
expect(event.dispatchConfig).toEqual({
40+
registrationName: 'onResponderRelease',
41+
});
42+
expect(event.nativeEvent).toHaveProperty('touches');
43+
});
44+
45+
test('buildFocusEvent returns event with target', () => {
46+
const event = buildFocusEvent();
47+
48+
expect(event.nativeEvent).toEqual({ target: 0 });
49+
expect(event).toHaveProperty('preventDefault');
50+
});
51+
52+
test('buildBlurEvent returns event with target', () => {
53+
const event = buildBlurEvent();
54+
55+
expect(event.nativeEvent).toEqual({ target: 0 });
56+
expect(event).toHaveProperty('preventDefault');
57+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as eventBuilder from '..';
2+
3+
test('re-exports all event builders', () => {
4+
expect(eventBuilder.buildTouchEvent).toBeInstanceOf(Function);
5+
expect(eventBuilder.buildResponderGrantEvent).toBeInstanceOf(Function);
6+
expect(eventBuilder.buildResponderReleaseEvent).toBeInstanceOf(Function);
7+
expect(eventBuilder.buildFocusEvent).toBeInstanceOf(Function);
8+
expect(eventBuilder.buildBlurEvent).toBeInstanceOf(Function);
9+
expect(eventBuilder.buildScrollEvent).toBeInstanceOf(Function);
10+
expect(eventBuilder.buildTextChangeEvent).toBeInstanceOf(Function);
11+
expect(eventBuilder.buildKeyPressEvent).toBeInstanceOf(Function);
12+
expect(eventBuilder.buildSubmitEditingEvent).toBeInstanceOf(Function);
13+
expect(eventBuilder.buildEndEditingEvent).toBeInstanceOf(Function);
14+
expect(eventBuilder.buildTextSelectionChangeEvent).toBeInstanceOf(Function);
15+
expect(eventBuilder.buildContentSizeChangeEvent).toBeInstanceOf(Function);
16+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { buildScrollEvent } from '../scroll';
2+
3+
test('buildScrollEvent returns default scroll event', () => {
4+
const event = buildScrollEvent();
5+
6+
expect(event.nativeEvent).toEqual({
7+
contentInset: { bottom: 0, left: 0, right: 0, top: 0 },
8+
contentOffset: { y: 0, x: 0 },
9+
contentSize: { height: 0, width: 0 },
10+
layoutMeasurement: { height: 0, width: 0 },
11+
responderIgnoreScroll: true,
12+
target: 0,
13+
velocity: { y: 0, x: 0 },
14+
});
15+
});
16+
17+
test('buildScrollEvent uses provided offset', () => {
18+
const event = buildScrollEvent({ y: 100, x: 50 });
19+
20+
expect(event.nativeEvent.contentOffset).toEqual({ y: 100, x: 50 });
21+
});
22+
23+
test('buildScrollEvent uses provided options', () => {
24+
const event = buildScrollEvent(
25+
{ y: 0, x: 0 },
26+
{
27+
contentSize: { height: 1000, width: 400 },
28+
layoutMeasurement: { height: 800, width: 400 },
29+
},
30+
);
31+
32+
expect(event.nativeEvent.contentSize).toEqual({ height: 1000, width: 400 });
33+
expect(event.nativeEvent.layoutMeasurement).toEqual({ height: 800, width: 400 });
34+
});

0 commit comments

Comments
 (0)