@samitouri / QOS-React-2 / commits / d919e2c41c

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 +});