Improve test harness of submit events (#13463)
This PR includes a test that I've enabled in #13358 and another test that we've discussed in #13462 as well as some random cleanup while I'm at it.
Philipp Spieß committed
Aug 22, 2018 at 22:46 UTC
3661616c282d50d3f90cfd1fb9f8ac1bf43eaab8
3 files changed
+210
-147
fixtures/dom/yarn.lock
+1
-13
@@ -2153,14 +2153,6 @@ dotenv@4.0.0:
2153
version "4.0.0"
2154
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
2155
2156
-draft-js@^0.10.5:
2157
- version "0.10.5"
2158
- resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742"
2159
- dependencies:
2160
- fbjs "^0.8.15"
2161
- immutable "~3.7.4"
2162
- object-assign "^4.1.0"
2163
-
2156
duplexer2@^0.1.4:
2157
version "0.1.4"
2158
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@@ -2680,7 +2672,7 @@ fbjs@^0.8.1, fbjs@^0.8.4:
2672
setimmediate "^1.0.5"
2673
ua-parser-js "^0.7.9"
2674
2683
-fbjs@^0.8.15, fbjs@^0.8.16:
2675
+fbjs@^0.8.16:
2676
version "0.8.16"
2677
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
2678
dependencies:
@@ -3310,10 +3302,6 @@ ignore@^3.3.3:
3302
version "3.3.3"
3303
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
3304
3313
-immutable@~3.7.4:
3314
- version "3.7.6"
3315
- resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
3316
-
3305
imurmurhash@^0.1.4:
3306
version "0.1.4"
3307
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
packages/react-dom/src/__tests__/ReactDOM-test.js
+33
-26
@@ -23,34 +23,41 @@ describe('ReactDOM', () => {
23
ReactTestUtils = require('react-dom/test-utils');
24
});
25
26
- // TODO: uncomment this test once we can run in phantom, which
27
- // supports real submit events.
28
- /*
26
it('should bubble onSubmit', function() {
30
- const count = 0;
31
- const form;
32
- const Parent = React.createClass({
33
- handleSubmit: function() {
34
- count++;
35
- return false;
36
- },
37
- render: function() {
38
- return <Child />;
39
- }
40
- });
41
- const Child = React.createClass({
42
- render: function() {
43
- return <form><input type="submit" value="Submit" /></form>;
44
- },
45
- componentDidMount: function() {
46
- form = ReactDOM.findDOMNode(this);
47
- }
48
- });
49
- const instance = ReactTestUtils.renderIntoDocument(<Parent />);
50
- form.submit();
51
- expect(count).toEqual(1);
27
+ const container = document.createElement('div');
28
+
29
+ let count = 0;
30
+ let buttonRef;
31
+
32
+ function Parent() {
33
+ return (
34
+ <div
35
+ onSubmit={event => {
36
+ event.preventDefault();
37
+ count++;
38
+ }}>
39
+ <Child />
40
+ </div>
41
+ );
42
+ }
43
+
44
+ function Child() {
45
+ return (
46
+ <form>
47
+ <input type="submit" ref={button => (buttonRef = button)} />
48
+ </form>
49
+ );
50
+ }
51
+
52
+ document.body.appendChild(container);
53
+ try {
54
+ ReactDOM.render(<Parent />, container);
55
+ buttonRef.click();
56
+ expect(count).toBe(1);
57
+ } finally {
58
+ document.body.removeChild(container);
59
+ }
60
});
53
- */
61
62
it('allows a DOM element to be used with a string', () => {
63
const element = React.createElement('div', {className: 'foo'});
packages/react-dom/src/__tests__/ReactDOMEventListener-test.js
+176
-108
@@ -28,14 +28,19 @@ describe('ReactDOMEventListener', () => {
28
document.body.appendChild(container);
29
document.body.appendChild(otherNode);
30
31
- otherNode.dispatchEvent(
32
- new MouseEvent('mouseout', {
33
- bubbles: true,
34
- cancelable: true,
35
- relatedTarget: node,
36
- }),
37
- );
38
- expect(mock).toBeCalled();
31
+ try {
32
+ otherNode.dispatchEvent(
33
+ new MouseEvent('mouseout', {
34
+ bubbles: true,
35
+ cancelable: true,
36
+ relatedTarget: node,
37
+ }),
38
+ );
39
+ expect(mock).toBeCalled();
40
+ } finally {
41
+ document.body.removeChild(container);
42
+ document.body.removeChild(otherNode);
43
+ }
44
});
45
46
describe('Propagation', () => {
@@ -56,16 +61,18 @@ describe('ReactDOMEventListener', () => {
61
parentNode.appendChild(childContainer);
62
document.body.appendChild(parentContainer);
63
59
- const nativeEvent = document.createEvent('Event');
60
- nativeEvent.initEvent('mouseout', true, true);
61
- childNode.dispatchEvent(nativeEvent);
62
-
63
- expect(mouseOut).toBeCalled();
64
- expect(mouseOut).toHaveBeenCalledTimes(2);
65
- expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
66
- expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
67
-
68
- document.body.removeChild(parentContainer);
64
+ try {
65
+ const nativeEvent = document.createEvent('Event');
66
+ nativeEvent.initEvent('mouseout', true, true);
67
+ childNode.dispatchEvent(nativeEvent);
68
+
69
+ expect(mouseOut).toBeCalled();
70
+ expect(mouseOut).toHaveBeenCalledTimes(2);
71
+ expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
72
+ expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
73
+ } finally {
74
+ document.body.removeChild(parentContainer);
75
+ }
76
});
77
78
it('should propagate events two levels down', () => {
@@ -92,50 +99,58 @@ describe('ReactDOMEventListener', () => {
99
100
document.body.appendChild(grandParentContainer);
101
95
- const nativeEvent = document.createEvent('Event');
96
- nativeEvent.initEvent('mouseout', true, true);
97
- childNode.dispatchEvent(nativeEvent);
98
-
99
- expect(mouseOut).toBeCalled();
100
- expect(mouseOut).toHaveBeenCalledTimes(3);
101
- expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
102
- expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
103
- expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode);
104
-
105
- document.body.removeChild(grandParentContainer);
102
+ try {
103
+ const nativeEvent = document.createEvent('Event');
104
+ nativeEvent.initEvent('mouseout', true, true);
105
+ childNode.dispatchEvent(nativeEvent);
106
+
107
+ expect(mouseOut).toBeCalled();
108
+ expect(mouseOut).toHaveBeenCalledTimes(3);
109
+ expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
110
+ expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
111
+ expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode);
112
+ } finally {
113
+ document.body.removeChild(grandParentContainer);
114
+ }
115
});
116
117
// Regression test for https://github.com/facebook/react/issues/1105
118
it('should not get confused by disappearing elements', () => {
119
const container = document.createElement('div');
120
document.body.appendChild(container);
112
- class MyComponent extends React.Component {
113
- state = {clicked: false};
114
- handleClick = () => {
115
- this.setState({clicked: true});
116
- };
117
- componentDidMount() {
118
- expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild);
119
- }
120
- componentDidUpdate() {
121
- expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild);
122
- }
123
- render() {
124
- if (this.state.clicked) {
125
- return <span>clicked!</span>;
126
- } else {
127
- return <button onClick={this.handleClick}>not yet clicked</button>;
121
+
122
+ try {
123
+ class MyComponent extends React.Component {
124
+ state = {clicked: false};
125
+ handleClick = () => {
126
+ this.setState({clicked: true});
127
+ };
128
+ componentDidMount() {
129
+ expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild);
130
+ }
131
+ componentDidUpdate() {
132
+ expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild);
133
+ }
134
+ render() {
135
+ if (this.state.clicked) {
136
+ return <span>clicked!</span>;
137
+ } else {
138
+ return (
139
+ <button onClick={this.handleClick}>not yet clicked</button>
140
+ );
141
+ }
142
}
143
}
144
+ ReactDOM.render(<MyComponent />, container);
145
+ container.firstChild.dispatchEvent(
146
+ new MouseEvent('click', {
147
+ bubbles: true,
148
+ }),
149
+ );
150
+ expect(container.firstChild.textContent).toBe('clicked!');
151
+ } finally {
152
+ document.body.removeChild(container);
153
}
131
- ReactDOM.render(<MyComponent />, container);
132
- container.firstChild.dispatchEvent(
133
- new MouseEvent('click', {
134
- bubbles: true,
135
- }),
136
- );
137
- expect(container.firstChild.textContent).toBe('clicked!');
138
- document.body.removeChild(container);
154
});
155
156
it('should batch between handlers from different roots', () => {
@@ -164,21 +179,24 @@ describe('ReactDOMEventListener', () => {
179
parentNode.appendChild(childContainer);
180
document.body.appendChild(parentContainer);
181
167
- const nativeEvent = document.createEvent('Event');
168
- nativeEvent.initEvent('mouseout', true, true);
169
- childNode.dispatchEvent(nativeEvent);
170
-
171
- // Child and parent should both call from event handlers.
172
- expect(mock).toHaveBeenCalledTimes(2);
173
- // The first call schedules a render of '1' into the 'Child'.
174
- // However, we're batching so it isn't flushed yet.
175
- expect(mock.mock.calls[0][0]).toBe('Child');
176
- // The first call schedules a render of '2' into the 'Child'.
177
- // We're still batching so it isn't flushed yet either.
178
- expect(mock.mock.calls[1][0]).toBe('Child');
179
- // By the time we leave the handler, the second update is flushed.
180
- expect(childNode.textContent).toBe('2');
181
- document.body.removeChild(parentContainer);
182
+ try {
183
+ const nativeEvent = document.createEvent('Event');
184
+ nativeEvent.initEvent('mouseout', true, true);
185
+ childNode.dispatchEvent(nativeEvent);
186
+
187
+ // Child and parent should both call from event handlers.
188
+ expect(mock).toHaveBeenCalledTimes(2);
189
+ // The first call schedules a render of '1' into the 'Child'.
190
+ // However, we're batching so it isn't flushed yet.
191
+ expect(mock.mock.calls[0][0]).toBe('Child');
192
+ // The first call schedules a render of '2' into the 'Child'.
193
+ // We're still batching so it isn't flushed yet either.
194
+ expect(mock.mock.calls[1][0]).toBe('Child');
195
+ // By the time we leave the handler, the second update is flushed.
196
+ expect(childNode.textContent).toBe('2');
197
+ } finally {
198
+ document.body.removeChild(parentContainer);
199
+ }
200
});
201
});
202
@@ -208,14 +226,17 @@ describe('ReactDOMEventListener', () => {
226
227
document.body.appendChild(container);
228
211
- const nativeEvent = document.createEvent('Event');
212
- nativeEvent.initEvent('mouseout', true, true);
213
- instance.getInner().dispatchEvent(nativeEvent);
229
+ try {
230
+ const nativeEvent = document.createEvent('Event');
231
+ nativeEvent.initEvent('mouseout', true, true);
232
+ instance.getInner().dispatchEvent(nativeEvent);
233
215
- expect(mouseOut).toBeCalled();
216
- expect(mouseOut).toHaveBeenCalledTimes(1);
217
- expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
218
- document.body.removeChild(container);
234
+ expect(mouseOut).toBeCalled();
235
+ expect(mouseOut).toHaveBeenCalledTimes(1);
236
+ expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
237
+ } finally {
238
+ document.body.removeChild(container);
239
+ }
240
});
241
242
// Regression test for https://github.com/facebook/react/pull/12877
@@ -271,46 +292,93 @@ describe('ReactDOMEventListener', () => {
292
document.body.removeChild(container);
293
});
294
295
+ // This is a special case for submit and reset events as they are listened on
296
+ // at the element level and not the document.
297
+ // @see https://github.com/facebook/react/pull/13462
298
+ it('should not receive submit events if native, interim DOM handler prevents it', () => {
299
+ const container = document.createElement('div');
300
+ document.body.appendChild(container);
301
+
302
+ try {
303
+ const formRef = React.createRef();
304
+ const interimRef = React.createRef();
305
+
306
+ const handleSubmit = jest.fn();
307
+ const handleReset = jest.fn();
308
+ ReactDOM.render(
309
+ <div ref={interimRef}>
310
+ <form ref={formRef} onSubmit={handleSubmit} onReset={handleReset} />
311
+ </div>,
312
+ container,
313
+ );
314
+
315
+ interimRef.current.onsubmit = nativeEvent =>
316
+ nativeEvent.stopPropagation();
317
+ interimRef.current.onreset = nativeEvent => nativeEvent.stopPropagation();
318
+
319
+ formRef.current.dispatchEvent(
320
+ new Event('submit', {
321
+ // https://developer.mozilla.org/en-US/docs/Web/Events/submit
322
+ bubbles: true,
323
+ }),
324
+ );
325
+
326
+ formRef.current.dispatchEvent(
327
+ new Event('reset', {
328
+ // https://developer.mozilla.org/en-US/docs/Web/Events/reset
329
+ bubbles: true,
330
+ }),
331
+ );
332
+
333
+ expect(handleSubmit).toHaveBeenCalled();
334
+ expect(handleReset).toHaveBeenCalled();
335
+ } finally {
336
+ document.body.removeChild(container);
337
+ }
338
+ });
339
+
340
it('should dispatch loadstart only for media elements', () => {
341
const container = document.createElement('div');
342
document.body.appendChild(container);
343
278
- const imgRef = React.createRef();
279
- const videoRef = React.createRef();
344
+ try {
345
+ const imgRef = React.createRef();
346
+ const videoRef = React.createRef();
347
281
- const handleImgLoadStart = jest.fn();
282
- const handleVideoLoadStart = jest.fn();
283
- ReactDOM.render(
284
- <div>
285
- <img ref={imgRef} onLoadStart={handleImgLoadStart} />
286
- <video ref={videoRef} onLoadStart={handleVideoLoadStart} />
287
- </div>,
288
- container,
289
- );
348
+ const handleImgLoadStart = jest.fn();
349
+ const handleVideoLoadStart = jest.fn();
350
+ ReactDOM.render(
351
+ <div>
352
+ <img ref={imgRef} onLoadStart={handleImgLoadStart} />
353
+ <video ref={videoRef} onLoadStart={handleVideoLoadStart} />
354
+ </div>,
355
+ container,
356
+ );
357
291
- // Note for debugging: loadstart currently doesn't fire in Chrome.
292
- // https://bugs.chromium.org/p/chromium/issues/detail?id=458851
293
- imgRef.current.dispatchEvent(
294
- new ProgressEvent('loadstart', {
295
- bubbles: false,
296
- }),
297
- );
298
- // Historically, we happened to not support onLoadStart
299
- // on <img>, and this test documents that lack of support.
300
- // If we decide to support it in the future, we should change
301
- // this line to expect 1 call. Note that fixing this would
302
- // be simple but would require attaching a handler to each
303
- // <img>. So far nobody asked us for it.
304
- expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
305
-
306
- videoRef.current.dispatchEvent(
307
- new ProgressEvent('loadstart', {
308
- bubbles: false,
309
- }),
310
- );
311
- expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
358
+ // Note for debugging: loadstart currently doesn't fire in Chrome.
359
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=458851
360
+ imgRef.current.dispatchEvent(
361
+ new ProgressEvent('loadstart', {
362
+ bubbles: false,
363
+ }),
364
+ );
365
+ // Historically, we happened to not support onLoadStart
366
+ // on <img>, and this test documents that lack of support.
367
+ // If we decide to support it in the future, we should change
368
+ // this line to expect 1 call. Note that fixing this would
369
+ // be simple but would require attaching a handler to each
370
+ // <img>. So far nobody asked us for it.
371
+ expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
372
313
- document.body.removeChild(container);
373
+ videoRef.current.dispatchEvent(
374
+ new ProgressEvent('loadstart', {
375
+ bubbles: false,
376
+ }),
377
+ );
378
+ expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
379
+ } finally {
380
+ document.body.removeChild(container);
381
+ }
382
});
383
384
it('should not attempt to listen to unnecessary events on the top level', () => {