Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 56d1b0f

Browse files
authored
[react-events] DOM event testing library (facebook#16433)
This patch formalizes the mock native events and event sequences used in unit tests. The `createEventTarget` function returns an object that can be used to dispatch native event sequences on the target without having to manually do so across all the scenarios we need to account for. Unit tests can be written as if we were only working with PointerEvent, but they will dispatch realistic native event sequences based on the execution environment (e.g., is PointerEvent supported?) and pointer type. ``` describe.each(environments)('Suite', (hasPointerEvents) => { beforeEach(() => { // setup }); test.each(pointerTypes)('Test', (pointerType) => { const target = createEventTarget(node); target.pointerdown({pointerType}); expect(callback).toBeCalled(); }); }); ``` Every native event that is dispatched now includes a complete object by default. The properties of the events can be customized. Properties that shouldn't be relied on in responder implementations are excluded from the mock native events to ensure tests will fail. Equivalent properties are normalized across different event types, e.g., 'pointerId' is converted to 'identifier' before a TouchEvent is dispatched.
1 parent e89c19d commit 56d1b0f

14 files changed

+1243
-1224
lines changed

packages/react-events/src/dom/Press.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ const pressResponderImpl = {
520520
const isPressed = state.isPressed;
521521

522522
handleStopPropagation(props, context, nativeEvent);
523+
523524
switch (type) {
524525
// START
525526
case 'pointerdown':
@@ -632,6 +633,7 @@ const pressResponderImpl = {
632633
const previousPointerType = state.pointerType;
633634

634635
handleStopPropagation(props, context, nativeEvent);
636+
635637
switch (type) {
636638
// MOVE
637639
case 'pointermove':

packages/react-events/src/dom/Scroll.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ type ScrollEvent = {|
6262
y: null | number,
6363
|};
6464

65-
const targetEventTypes = [
66-
'scroll',
67-
'pointerdown',
68-
'touchstart',
69-
'keyup',
70-
'wheel',
71-
];
72-
const rootEventTypes = ['touchcancel', 'touchend'];
65+
const hasPointerEvents =
66+
typeof window !== 'undefined' && window.PointerEvent !== undefined;
67+
68+
const targetEventTypes = hasPointerEvents
69+
? ['scroll', 'pointerdown', 'keyup', 'wheel']
70+
: ['scroll', 'mousedown', 'touchstart', 'keyup', 'wheel'];
71+
72+
const rootEventTypes = hasPointerEvents
73+
? ['pointercancel', 'pointerup']
74+
: ['touchcancel', 'touchend'];
7375

7476
function isFunction(obj): boolean {
7577
return typeof obj === 'function';
@@ -237,17 +239,23 @@ const scrollResponderImpl = {
237239
state.pointerType = pointerType;
238240
break;
239241
}
242+
case 'mousedown':
240243
case 'wheel': {
241244
state.pointerType = 'mouse';
242245
break;
243246
}
244247
case 'pointerdown': {
245248
state.pointerType = pointerType;
249+
if (pointerType === 'touch' && !state.isTouching) {
250+
state.isTouching = true;
251+
context.addRootEventTypes(rootEventTypes);
252+
}
246253
break;
247254
}
248255
case 'touchstart': {
249256
if (!state.isTouching) {
250257
state.isTouching = true;
258+
state.pointerType = 'touch';
251259
context.addRootEventTypes(rootEventTypes);
252260
}
253261
}
@@ -262,6 +270,8 @@ const scrollResponderImpl = {
262270
const {type} = event;
263271

264272
switch (type) {
273+
case 'pointercancel':
274+
case 'pointerup':
265275
case 'touchcancel':
266276
case 'touchend': {
267277
if (state.isTouching) {

packages/react-events/src/dom/__tests__/ContextMenu-test.internal.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@
99

1010
'use strict';
1111

12-
import {
13-
dispatchLongPressContextMenu,
14-
dispatchRightClickContextMenu,
15-
dispatchModifiedClickContextMenu,
16-
platform,
17-
setPointerEvent,
18-
} from '../test-utils';
12+
import {createEventTarget, platform, setPointerEvent} from '../testing-library';
1913

2014
let React;
2115
let ReactFeatureFlags;
@@ -62,7 +56,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
6256
};
6357
ReactDOM.render(<Component />, container);
6458

65-
dispatchRightClickContextMenu(ref.current, {preventDefault});
59+
const target = createEventTarget(ref.current);
60+
target.contextmenu({preventDefault});
6661
expect(preventDefault).toHaveBeenCalledTimes(1);
6762
expect(onContextMenu).toHaveBeenCalledTimes(1);
6863
expect(onContextMenu).toHaveBeenCalledWith(
@@ -80,7 +75,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
8075
};
8176
ReactDOM.render(<Component />, container);
8277

83-
dispatchLongPressContextMenu(ref.current, {preventDefault});
78+
const target = createEventTarget(ref.current);
79+
target.contextmenu({preventDefault}, {pointerType: 'touch'});
8480
expect(preventDefault).toHaveBeenCalledTimes(1);
8581
expect(onContextMenu).toHaveBeenCalledTimes(1);
8682
expect(onContextMenu).toHaveBeenCalledWith(
@@ -100,7 +96,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
10096
};
10197
ReactDOM.render(<Component />, container);
10298

103-
dispatchRightClickContextMenu(ref.current);
99+
const target = createEventTarget(ref.current);
100+
target.contextmenu();
104101
expect(onContextMenu).toHaveBeenCalledTimes(0);
105102
});
106103

@@ -117,7 +114,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
117114
};
118115
ReactDOM.render(<Component />, container);
119116

120-
dispatchRightClickContextMenu(ref.current, {preventDefault});
117+
const target = createEventTarget(ref.current);
118+
target.contextmenu({preventDefault});
121119
expect(preventDefault).toHaveBeenCalledTimes(0);
122120
expect(onContextMenu).toHaveBeenCalledTimes(1);
123121
});
@@ -142,7 +140,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
142140
};
143141
ReactDOM.render(<Component />, container);
144142

145-
dispatchModifiedClickContextMenu(ref.current);
143+
const target = createEventTarget(ref.current);
144+
target.contextmenu({}, {modified: true});
146145
expect(onContextMenu).toHaveBeenCalledTimes(1);
147146
expect(onContextMenu).toHaveBeenCalledWith(
148147
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
@@ -169,7 +168,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
169168
};
170169
ReactDOM.render(<Component />, container);
171170

172-
dispatchModifiedClickContextMenu(ref.current);
171+
const target = createEventTarget(ref.current);
172+
target.contextmenu({}, {modified: true});
173173
expect(onContextMenu).toHaveBeenCalledTimes(0);
174174
});
175175
});

packages/react-events/src/dom/__tests__/Focus-test.internal.js

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@
99

1010
'use strict';
1111

12-
import {
13-
blur,
14-
focus,
15-
keydown,
16-
setPointerEvent,
17-
platform,
18-
dispatchPointerDown,
19-
dispatchPointerUp,
20-
} from '../test-utils';
12+
import {createEventTarget, setPointerEvent, platform} from '../testing-library';
2113

2214
let React;
2315
let ReactFeatureFlags;
@@ -73,9 +65,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
7365
});
7466

7567
it('does not call callbacks', () => {
76-
const dispatch = arg => ref.current.dispatchEvent(arg);
77-
dispatch(focus());
78-
dispatch(blur());
68+
const target = createEventTarget(ref.current);
69+
target.focus();
70+
target.blur();
7971
expect(onFocus).not.toBeCalled();
8072
expect(onBlur).not.toBeCalled();
8173
});
@@ -97,9 +89,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
9789
});
9890

9991
it('is called after "blur" event', () => {
100-
const dispatch = arg => ref.current.dispatchEvent(arg);
101-
dispatch(focus());
102-
dispatch(blur());
92+
const target = createEventTarget(ref.current);
93+
target.focus();
94+
target.blur();
10395
expect(onBlur).toHaveBeenCalledTimes(1);
10496
});
10597
});
@@ -127,29 +119,32 @@ describe.each(table)('Focus responder', hasPointerEvents => {
127119
beforeEach(componentInit);
128120

129121
it('is called after "focus" event', () => {
130-
ref.current.dispatchEvent(focus());
122+
const target = createEventTarget(ref.current);
123+
target.focus();
131124
expect(onFocus).toHaveBeenCalledTimes(1);
132125
});
133126

134127
it('is not called if descendants of target receive focus', () => {
135-
innerRef.current.dispatchEvent(focus());
128+
const target = createEventTarget(innerRef.current);
129+
target.focus();
136130
expect(onFocus).not.toBeCalled();
137131
});
138132

139133
it('is called with the correct pointerType: mouse', () => {
140-
const target = ref.current;
141-
dispatchPointerDown(target, {pointerType: 'mouse'});
142-
dispatchPointerUp(target, {pointerType: 'mouse'});
134+
const target = createEventTarget(ref.current);
135+
target.pointerdown();
136+
target.pointerup();
143137
expect(onFocus).toHaveBeenCalledTimes(1);
144138
expect(onFocus).toHaveBeenCalledWith(
145139
expect.objectContaining({pointerType: 'mouse'}),
146140
);
147141
});
148142

149143
it('is called with the correct pointerType: touch', () => {
150-
const target = ref.current;
151-
dispatchPointerDown(target, {pointerType: 'touch'});
152-
dispatchPointerUp(target, {pointerType: 'touch'});
144+
const target = createEventTarget(ref.current);
145+
const pointerType = 'touch';
146+
target.pointerdown({pointerType});
147+
target.pointerup({pointerType});
153148
expect(onFocus).toHaveBeenCalledTimes(1);
154149
expect(onFocus).toHaveBeenCalledWith(
155150
expect.objectContaining({pointerType: 'touch'}),
@@ -158,9 +153,10 @@ describe.each(table)('Focus responder', hasPointerEvents => {
158153

159154
if (hasPointerEvents) {
160155
it('is called with the correct pointerType: pen', () => {
161-
const target = ref.current;
162-
dispatchPointerDown(target, {pointerType: 'pen'});
163-
dispatchPointerUp(target, {pointerType: 'pen'});
156+
const target = createEventTarget(ref.current);
157+
const pointerType = 'pen';
158+
target.pointerdown({pointerType});
159+
target.pointerup({pointerType});
164160
expect(onFocus).toHaveBeenCalledTimes(1);
165161
expect(onFocus).toHaveBeenCalledWith(
166162
expect.objectContaining({pointerType: 'pen'}),
@@ -169,10 +165,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
169165
}
170166

171167
it('is called with the correct pointerType using a keyboard', () => {
172-
const target = ref.current;
173-
// Keyboard tab
174-
target.dispatchEvent(keydown({key: 'Tab'}));
175-
target.dispatchEvent(focus());
168+
const target = createEventTarget(ref.current);
169+
target.keydown({key: 'Tab'});
170+
target.focus();
176171
expect(onFocus).toHaveBeenCalledTimes(1);
177172
expect(onFocus).toHaveBeenCalledWith(
178173
expect.objectContaining({pointerType: 'keyboard'}),
@@ -184,10 +179,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
184179
jest.resetModules();
185180
initializeModules();
186181
componentInit();
187-
const target = ref.current;
188182

189-
target.dispatchEvent(keydown({key: 'Tab', altKey: true}));
190-
target.dispatchEvent(focus());
183+
const target = createEventTarget(ref.current);
184+
target.keydown({key: 'Tab', altKey: true});
185+
target.focus();
186+
191187
expect(onFocus).toHaveBeenCalledTimes(1);
192188
expect(onFocus).toHaveBeenCalledWith(
193189
expect.objectContaining({
@@ -220,20 +216,20 @@ describe.each(table)('Focus responder', hasPointerEvents => {
220216
});
221217

222218
it('is called after "blur" and "focus" events', () => {
223-
const target = ref.current;
224-
target.dispatchEvent(focus());
219+
const target = createEventTarget(ref.current);
220+
target.focus();
225221
expect(onFocusChange).toHaveBeenCalledTimes(1);
226222
expect(onFocusChange).toHaveBeenCalledWith(true);
227-
target.dispatchEvent(blur());
223+
target.blur();
228224
expect(onFocusChange).toHaveBeenCalledTimes(2);
229225
expect(onFocusChange).toHaveBeenCalledWith(false);
230226
});
231227

232228
it('is not called after "blur" and "focus" events on descendants', () => {
233-
const target = innerRef.current;
234-
target.dispatchEvent(focus());
229+
const target = createEventTarget(innerRef.current);
230+
target.focus();
235231
expect(onFocusChange).toHaveBeenCalledTimes(0);
236-
target.dispatchEvent(blur());
232+
target.blur();
237233
expect(onFocusChange).toHaveBeenCalledTimes(0);
238234
});
239235
});
@@ -259,48 +255,52 @@ describe.each(table)('Focus responder', hasPointerEvents => {
259255
});
260256

261257
it('is called after "focus" and "blur" if keyboard navigation is active', () => {
262-
const target = ref.current;
258+
const target = createEventTarget(ref.current);
259+
const containerTarget = createEventTarget(container);
263260
// use keyboard first
264-
container.dispatchEvent(keydown({key: 'Tab'}));
265-
target.dispatchEvent(focus());
261+
containerTarget.keydown({key: 'Tab'});
262+
target.focus();
266263
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
267264
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
268-
target.dispatchEvent(blur({relatedTarget: container}));
265+
target.blur({relatedTarget: container});
269266
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
270267
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
271268
});
272269

273270
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
274-
const target = ref.current;
271+
const target = createEventTarget(ref.current);
272+
const containerTarget = createEventTarget(container);
275273
// use keyboard first
276-
container.dispatchEvent(keydown({key: 'Tab'}));
277-
target.dispatchEvent(focus());
274+
containerTarget.keydown({key: 'Tab'});
275+
target.focus();
278276
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
279277
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
280278
// then use pointer on the target, focus should no longer be visible
281-
dispatchPointerDown(target);
279+
target.pointerdown();
282280
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
283281
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
284282
// onFocusVisibleChange should not be called again
285-
target.dispatchEvent(blur({relatedTarget: container}));
283+
target.blur({relatedTarget: container});
286284
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
287285
});
288286

289287
it('is not called after "focus" and "blur" events without keyboard', () => {
290-
const target = ref.current;
291-
dispatchPointerDown(target);
292-
dispatchPointerUp(target);
293-
dispatchPointerDown(container);
294-
target.dispatchEvent(blur({relatedTarget: container}));
288+
const target = createEventTarget(ref.current);
289+
const containerTarget = createEventTarget(container);
290+
target.pointerdown();
291+
target.pointerup();
292+
containerTarget.pointerdown();
293+
target.blur({relatedTarget: container});
295294
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
296295
});
297296

298297
it('is not called after "blur" and "focus" events on descendants', () => {
299-
const target = innerRef.current;
300-
container.dispatchEvent(keydown({key: 'Tab'}));
301-
target.dispatchEvent(focus());
298+
const innerTarget = createEventTarget(innerRef.current);
299+
const containerTarget = createEventTarget(container);
300+
containerTarget.keydown({key: 'Tab'});
301+
innerTarget.focus();
302302
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
303-
target.dispatchEvent(blur({relatedTarget: container}));
303+
innerTarget.blur({relatedTarget: container});
304304
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
305305
});
306306
});
@@ -338,10 +338,13 @@ describe.each(table)('Focus responder', hasPointerEvents => {
338338

339339
ReactDOM.render(<Outer />, container);
340340

341-
outerRef.current.dispatchEvent(focus());
342-
outerRef.current.dispatchEvent(blur());
343-
innerRef.current.dispatchEvent(focus());
344-
innerRef.current.dispatchEvent(blur());
341+
const innerTarget = createEventTarget(innerRef.current);
342+
const outerTarget = createEventTarget(outerRef.current);
343+
344+
outerTarget.focus();
345+
outerTarget.blur();
346+
innerTarget.focus();
347+
innerTarget.blur();
345348
expect(events).toEqual([
346349
'outer: onFocus',
347350
'outer: onFocusChange',

0 commit comments

Comments
 (0)