Skip to content

Commit d919e2c

Browse files
authored
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
1 parent 97fce31 commit d919e2c

File tree

2 files changed

+283
-73
lines changed

2 files changed

+283
-73
lines changed

packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js

Lines changed: 55 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,6 @@ describe('ReactDOMFiberAsync', () => {
148148
});
149149

150150
describe('concurrent mode', () => {
151-
beforeEach(() => {
152-
jest.resetModules();
153-
154-
ReactDOM = require('react-dom');
155-
Scheduler = require('scheduler');
156-
});
157-
158151
// @gate experimental
159152
it('does not perform deferred updates synchronously', () => {
160153
const inputRef = React.createRef();
@@ -399,28 +392,26 @@ describe('ReactDOMFiberAsync', () => {
399392

400393
let formSubmitted = false;
401394

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() {
408401
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-
);
423402
}
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+
);
424415
}
425416

426417
const root = ReactDOM.unstable_createRoot(container);
@@ -461,33 +452,29 @@ describe('ReactDOMFiberAsync', () => {
461452

462453
let formSubmitted = false;
463454

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() {
470461
formSubmitted = true; // This should not get invoked
471-
};
472-
disabledSubmitForm = () => {
462+
}
463+
function disabledSubmitForm() {
473464
// 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-
);
490465
}
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+
);
491478
}
492479

493480
const root = ReactDOM.unstable_createRoot(container);
@@ -526,29 +513,24 @@ describe('ReactDOMFiberAsync', () => {
526513

527514
let formSubmitted = false;
528515

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

554536
const root = ReactDOM.unstable_createRoot(container);
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});

0 commit comments

Comments
 (0)