Add tests for non-React discrete events flushing in a microtask (#20772)
* Convert some old discrete tests to Hooks I'm planning to copy paste so why not update them anyway. * Copy paste discrete tests into another file These are still using React events. I'll change that next. * Convert the test to use native events
Dan Abramov committed
Feb 9, 2021 at 19:56 UTC
d919e2c41c71fc7bc4ae037b3a3c60a4600d0a54
2 files changed
+283
-73
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+55
-73
@@ -148,13 +148,6 @@ describe('ReactDOMFiberAsync', () => {
148
});
149
150
describe('concurrent mode', () => {
151
- beforeEach(() => {
152
- jest.resetModules();
153
-
154
- ReactDOM = require('react-dom');
155
- Scheduler = require('scheduler');
156
- });
157
-
151
// @gate experimental
152
it('does not perform deferred updates synchronously', () => {
153
const inputRef = React.createRef();
@@ -399,28 +392,26 @@ describe('ReactDOMFiberAsync', () => {
392
393
let formSubmitted = false;
394
402
- class Form extends React.Component {
403
- state = {active: true};
404
- disableForm = () => {
405
- this.setState({active: false});
406
- };
407
- submitForm = () => {
395
+ function Form() {
396
+ const [active, setActive] = React.useState(true);
397
+ function disableForm() {
398
+ setActive(false);
399
+ }
400
+ function submitForm() {
401
formSubmitted = true; // This should not get invoked
409
- };
410
- render() {
411
- return (
412
- <div>
413
- <button onClick={this.disableForm} ref={disableButtonRef}>
414
- Disable
415
- </button>
416
- {this.state.active ? (
417
- <button onClick={this.submitForm} ref={submitButtonRef}>
418
- Submit
419
- </button>
420
- ) : null}
421
- </div>
422
- );
402
}
403
+ return (
404
+ <div>
405
+ <button onClick={disableForm} ref={disableButtonRef}>
406
+ Disable
407
+ </button>
408
+ {active ? (
409
+ <button onClick={submitForm} ref={submitButtonRef}>
410
+ Submit
411
+ </button>
412
+ ) : null}
413
+ </div>
414
+ );
415
}
416
417
const root = ReactDOM.unstable_createRoot(container);
@@ -461,33 +452,29 @@ describe('ReactDOMFiberAsync', () => {
452
453
let formSubmitted = false;
454
464
- class Form extends React.Component {
465
- state = {active: true};
466
- disableForm = () => {
467
- this.setState({active: false});
468
- };
469
- submitForm = () => {
455
+ function Form() {
456
+ const [active, setActive] = React.useState(true);
457
+ function disableForm() {
458
+ setActive(false);
459
+ }
460
+ function submitForm() {
461
formSubmitted = true; // This should not get invoked
471
- };
472
- disabledSubmitForm = () => {
462
+ }
463
+ function disabledSubmitForm() {
464
// The form is disabled.
474
- };
475
- render() {
476
- return (
477
- <div>
478
- <button onClick={this.disableForm} ref={disableButtonRef}>
479
- Disable
480
- </button>
481
- <button
482
- onClick={
483
- this.state.active ? this.submitForm : this.disabledSubmitForm
484
- }
485
- ref={submitButtonRef}>
486
- Submit
487
- </button>
488
- </div>
489
- );
465
}
466
+ return (
467
+ <div>
468
+ <button onClick={disableForm} ref={disableButtonRef}>
469
+ Disable
470
+ </button>
471
+ <button
472
+ onClick={active ? submitForm : disabledSubmitForm}
473
+ ref={submitButtonRef}>
474
+ Submit
475
+ </button>
476
+ </div>
477
+ );
478
}
479
480
const root = ReactDOM.unstable_createRoot(container);
@@ -526,29 +513,24 @@ describe('ReactDOMFiberAsync', () => {
513
514
let formSubmitted = false;
515
529
- class Form extends React.Component {
530
- state = {active: false};
531
- enableForm = () => {
532
- this.setState({active: true});
533
- };
534
- submitForm = () => {
535
- formSubmitted = true; // This should happen
536
- };
537
- render() {
538
- return (
539
- <div>
540
- <button onClick={this.enableForm} ref={enableButtonRef}>
541
- Enable
542
- </button>
543
- <button
544
- onClick={this.state.active ? this.submitForm : null}
545
- ref={submitButtonRef}>
546
- Submit
547
- </button>{' '}
548
- : null}
549
- </div>
550
- );
516
+ function Form() {
517
+ const [active, setActive] = React.useState(false);
518
+ function enableForm() {
519
+ setActive(true);
520
+ }
521
+ function submitForm() {
522
+ formSubmitted = true; // This should not get invoked
523
}
524
+ return (
525
+ <div>
526
+ <button onClick={enableForm} ref={enableButtonRef}>
527
+ Enable
528
+ </button>
529
+ <button onClick={active ? submitForm : null} ref={submitButtonRef}>
530
+ Submit
531
+ </button>
532
+ </div>
533
+ );
534
}
535
536
const root = ReactDOM.unstable_createRoot(container);
packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
new
+228
@@ -0,0 +1,228 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+let React;
13
+
14
+let ReactDOM;
15
+let Scheduler;
16
+
17
+describe('ReactDOMNativeEventHeuristic-test', () => {
18
+ let container;
19
+
20
+ beforeEach(() => {
21
+ jest.resetModules();
22
+ container = document.createElement('div');
23
+ React = require('react');
24
+ ReactDOM = require('react-dom');
25
+ Scheduler = require('scheduler');
26
+
27
+ document.body.appendChild(container);
28
+ });
29
+
30
+ afterEach(() => {
31
+ document.body.removeChild(container);
32
+ });
33
+
34
+ function dispatchAndSetCurrentEvent(el, event) {
35
+ try {
36
+ window.event = event;
37
+ el.dispatchEvent(event);
38
+ } finally {
39
+ window.event = undefined;
40
+ }
41
+ }
42
+
43
+ // @gate experimental
44
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
45
+ it('ignores discrete events on a pending removed element', async () => {
46
+ const disableButtonRef = React.createRef();
47
+ const submitButtonRef = React.createRef();
48
+
49
+ function Form() {
50
+ const [active, setActive] = React.useState(true);
51
+
52
+ React.useLayoutEffect(() => {
53
+ disableButtonRef.current.onclick = disableForm;
54
+ });
55
+
56
+ function disableForm() {
57
+ setActive(false);
58
+ }
59
+
60
+ return (
61
+ <div>
62
+ <button ref={disableButtonRef}>Disable</button>
63
+ {active ? <button ref={submitButtonRef}>Submit</button> : null}
64
+ </div>
65
+ );
66
+ }
67
+
68
+ const root = ReactDOM.unstable_createRoot(container);
69
+ root.render(<Form />);
70
+ // Flush
71
+ Scheduler.unstable_flushAll();
72
+
73
+ const disableButton = disableButtonRef.current;
74
+ expect(disableButton.tagName).toBe('BUTTON');
75
+
76
+ // Dispatch a click event on the Disable-button.
77
+ const firstEvent = document.createEvent('Event');
78
+ firstEvent.initEvent('click', true, true);
79
+ expect(() =>
80
+ dispatchAndSetCurrentEvent(disableButton, firstEvent),
81
+ ).toErrorDev(['An update to Form inside a test was not wrapped in act']);
82
+
83
+ // There should now be a pending update to disable the form.
84
+ // This should not have flushed yet since it's in concurrent mode.
85
+ const submitButton = submitButtonRef.current;
86
+ expect(submitButton.tagName).toBe('BUTTON');
87
+
88
+ // Discrete events should be flushed in a microtask.
89
+ // Verify that the second button was removed.
90
+ await null;
91
+ expect(submitButtonRef.current).toBe(null);
92
+ // We'll assume that the browser won't let the user click it.
93
+ });
94
+
95
+ // @gate experimental
96
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
97
+ it('ignores discrete events on a pending removed event listener', async () => {
98
+ const disableButtonRef = React.createRef();
99
+ const submitButtonRef = React.createRef();
100
+
101
+ let formSubmitted = false;
102
+
103
+ function Form() {
104
+ const [active, setActive] = React.useState(true);
105
+
106
+ React.useLayoutEffect(() => {
107
+ disableButtonRef.current.onclick = disableForm;
108
+ submitButtonRef.current.onclick = active
109
+ ? submitForm
110
+ : disabledSubmitForm;
111
+ });
112
+
113
+ function disableForm() {
114
+ setActive(false);
115
+ }
116
+
117
+ function submitForm() {
118
+ formSubmitted = true; // This should not get invoked
119
+ }
120
+
121
+ function disabledSubmitForm() {
122
+ // The form is disabled.
123
+ }
124
+
125
+ return (
126
+ <div>
127
+ <button ref={disableButtonRef}>Disable</button>
128
+ <button ref={submitButtonRef}>Submit</button>
129
+ </div>
130
+ );
131
+ }
132
+
133
+ const root = ReactDOM.unstable_createRoot(container);
134
+ root.render(<Form />);
135
+ // Flush
136
+ Scheduler.unstable_flushAll();
137
+
138
+ const disableButton = disableButtonRef.current;
139
+ expect(disableButton.tagName).toBe('BUTTON');
140
+
141
+ // Dispatch a click event on the Disable-button.
142
+ const firstEvent = document.createEvent('Event');
143
+ firstEvent.initEvent('click', true, true);
144
+ expect(() => {
145
+ dispatchAndSetCurrentEvent(disableButton, firstEvent);
146
+ }).toErrorDev(['An update to Form inside a test was not wrapped in act']);
147
+
148
+ // There should now be a pending update to disable the form.
149
+ // This should not have flushed yet since it's in concurrent mode.
150
+ const submitButton = submitButtonRef.current;
151
+ expect(submitButton.tagName).toBe('BUTTON');
152
+
153
+ // Discrete events should be flushed in a microtask.
154
+ await null;
155
+
156
+ // Now let's dispatch an event on the submit button.
157
+ const secondEvent = document.createEvent('Event');
158
+ secondEvent.initEvent('click', true, true);
159
+ dispatchAndSetCurrentEvent(submitButton, secondEvent);
160
+
161
+ // Therefore the form should never have been submitted.
162
+ expect(formSubmitted).toBe(false);
163
+ });
164
+
165
+ // @gate experimental
166
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
167
+ it('uses the newest discrete events on a pending changed event listener', async () => {
168
+ const enableButtonRef = React.createRef();
169
+ const submitButtonRef = React.createRef();
170
+
171
+ let formSubmitted = false;
172
+
173
+ function Form() {
174
+ const [active, setActive] = React.useState(false);
175
+
176
+ React.useLayoutEffect(() => {
177
+ enableButtonRef.current.onclick = enableForm;
178
+ submitButtonRef.current.onclick = active ? submitForm : null;
179
+ });
180
+
181
+ function enableForm() {
182
+ setActive(true);
183
+ }
184
+
185
+ function submitForm() {
186
+ formSubmitted = true; // This should not get invoked
187
+ }
188
+
189
+ return (
190
+ <div>
191
+ <button ref={enableButtonRef}>Enable</button>
192
+ <button ref={submitButtonRef}>Submit</button>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ const root = ReactDOM.unstable_createRoot(container);
198
+ root.render(<Form />);
199
+ // Flush
200
+ Scheduler.unstable_flushAll();
201
+
202
+ const enableButton = enableButtonRef.current;
203
+ expect(enableButton.tagName).toBe('BUTTON');
204
+
205
+ // Dispatch a click event on the Enable-button.
206
+ const firstEvent = document.createEvent('Event');
207
+ firstEvent.initEvent('click', true, true);
208
+ expect(() => {
209
+ dispatchAndSetCurrentEvent(enableButton, firstEvent);
210
+ }).toErrorDev(['An update to Form inside a test was not wrapped in act']);
211
+
212
+ // There should now be a pending update to enable the form.
213
+ // This should not have flushed yet since it's in concurrent mode.
214
+ const submitButton = submitButtonRef.current;
215
+ expect(submitButton.tagName).toBe('BUTTON');
216
+
217
+ // Discrete events should be flushed in a microtask.
218
+ await null;
219
+
220
+ // Now let's dispatch an event on the submit button.
221
+ const secondEvent = document.createEvent('Event');
222
+ secondEvent.initEvent('click', true, true);
223
+ dispatchAndSetCurrentEvent(submitButton, secondEvent);
224
+
225
+ // Therefore the form should have been submitted.
226
+ expect(formSubmitted).toBe(true);
227
+ });
228
+});