@@ -31,31 +31,36 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
31
31
document . body . removeChild ( container ) ;
32
32
} ) ;
33
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
+
34
43
// @gate experimental
35
- it ( 'ignores discrete events on a pending removed element' , ( ) => {
44
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
45
+ it ( 'ignores discrete events on a pending removed element' , async ( ) => {
36
46
const disableButtonRef = React . createRef ( ) ;
37
47
const submitButtonRef = React . createRef ( ) ;
38
48
39
- let formSubmitted = false ;
40
-
41
49
function Form ( ) {
42
50
const [ active , setActive ] = React . useState ( true ) ;
51
+
52
+ React . useLayoutEffect ( ( ) => {
53
+ disableButtonRef . current . onclick = disableForm ;
54
+ } ) ;
55
+
43
56
function disableForm ( ) {
44
57
setActive ( false ) ;
45
58
}
46
- function submitForm ( ) {
47
- formSubmitted = true ; // This should not get invoked
48
- }
59
+
49
60
return (
50
61
< div >
51
- < button onClick = { disableForm } ref = { disableButtonRef } >
52
- Disable
53
- </ button >
54
- { active ? (
55
- < button onClick = { submitForm } ref = { submitButtonRef } >
56
- Submit
57
- </ button >
58
- ) : null }
62
+ < button ref = { disableButtonRef } > Disable</ button >
63
+ { active ? < button ref = { submitButtonRef } > Submit</ button > : null }
59
64
</ div >
60
65
) ;
61
66
}
@@ -71,54 +76,56 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
71
76
// Dispatch a click event on the Disable-button.
72
77
const firstEvent = document . createEvent ( 'Event' ) ;
73
78
firstEvent . initEvent ( 'click' , true , true ) ;
74
- disableButton . dispatchEvent ( firstEvent ) ;
79
+ expect ( ( ) =>
80
+ dispatchAndSetCurrentEvent ( disableButton , firstEvent ) ,
81
+ ) . toErrorDev ( [ 'An update to Form inside a test was not wrapped in act' ] ) ;
75
82
76
83
// There should now be a pending update to disable the form.
77
-
78
84
// This should not have flushed yet since it's in concurrent mode.
79
85
const submitButton = submitButtonRef . current ;
80
86
expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
81
87
82
- // In the meantime, we can dispatch a new client event on the submit button.
83
- const secondEvent = document . createEvent ( 'Event' ) ;
84
- secondEvent . initEvent ( 'click' , true , true ) ;
85
- // This should force the pending update to flush which disables the submit button before the event is invoked.
86
- submitButton . dispatchEvent ( secondEvent ) ;
87
-
88
- // Therefore the form should never have been submitted.
89
- expect ( formSubmitted ) . toBe ( false ) ;
90
-
88
+ // Discrete events should be flushed in a microtask.
89
+ // Verify that the second button was removed.
90
+ await null ;
91
91
expect ( submitButtonRef . current ) . toBe ( null ) ;
92
+ // We'll assume that the browser won't let the user click it.
92
93
} ) ;
93
94
94
95
// @gate experimental
95
- it ( 'ignores discrete events on a pending removed event listener' , ( ) => {
96
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
97
+ it ( 'ignores discrete events on a pending removed event listener' , async ( ) => {
96
98
const disableButtonRef = React . createRef ( ) ;
97
99
const submitButtonRef = React . createRef ( ) ;
98
100
99
101
let formSubmitted = false ;
100
102
101
103
function Form ( ) {
102
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
+
103
113
function disableForm ( ) {
104
114
setActive ( false ) ;
105
115
}
116
+
106
117
function submitForm ( ) {
107
118
formSubmitted = true ; // This should not get invoked
108
119
}
120
+
109
121
function disabledSubmitForm ( ) {
110
122
// The form is disabled.
111
123
}
124
+
112
125
return (
113
126
< div >
114
- < button onClick = { disableForm } ref = { disableButtonRef } >
115
- Disable
116
- </ button >
117
- < button
118
- onClick = { active ? submitForm : disabledSubmitForm }
119
- ref = { submitButtonRef } >
120
- Submit
121
- </ button >
127
+ < button ref = { disableButtonRef } > Disable</ button >
128
+ < button ref = { submitButtonRef } > Submit</ button >
122
129
</ div >
123
130
) ;
124
131
}
@@ -134,47 +141,55 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
134
141
// Dispatch a click event on the Disable-button.
135
142
const firstEvent = document . createEvent ( 'Event' ) ;
136
143
firstEvent . initEvent ( 'click' , true , true ) ;
137
- disableButton . dispatchEvent ( firstEvent ) ;
144
+ expect ( ( ) => {
145
+ dispatchAndSetCurrentEvent ( disableButton , firstEvent ) ;
146
+ } ) . toErrorDev ( [ 'An update to Form inside a test was not wrapped in act' ] ) ;
138
147
139
148
// There should now be a pending update to disable the form.
140
-
141
149
// This should not have flushed yet since it's in concurrent mode.
142
150
const submitButton = submitButtonRef . current ;
143
151
expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
144
152
145
- // In the meantime, we can dispatch a new client event on the submit button.
153
+ // Discrete events should be flushed in a microtask.
154
+ await null ;
155
+
156
+ // Now let's dispatch an event on the submit button.
146
157
const secondEvent = document . createEvent ( 'Event' ) ;
147
158
secondEvent . initEvent ( 'click' , true , true ) ;
148
- // This should force the pending update to flush which disables the submit button before the event is invoked.
149
- submitButton . dispatchEvent ( secondEvent ) ;
159
+ dispatchAndSetCurrentEvent ( submitButton , secondEvent ) ;
150
160
151
161
// Therefore the form should never have been submitted.
152
162
expect ( formSubmitted ) . toBe ( false ) ;
153
163
} ) ;
154
164
155
165
// @gate experimental
156
- it ( 'uses the newest discrete events on a pending changed event listener' , ( ) => {
166
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
167
+ it ( 'uses the newest discrete events on a pending changed event listener' , async ( ) => {
157
168
const enableButtonRef = React . createRef ( ) ;
158
169
const submitButtonRef = React . createRef ( ) ;
159
170
160
171
let formSubmitted = false ;
161
172
162
173
function Form ( ) {
163
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
+
164
181
function enableForm ( ) {
165
182
setActive ( true ) ;
166
183
}
184
+
167
185
function submitForm ( ) {
168
186
formSubmitted = true ; // This should not get invoked
169
187
}
188
+
170
189
return (
171
190
< div >
172
- < button onClick = { enableForm } ref = { enableButtonRef } >
173
- Enable
174
- </ button >
175
- < button onClick = { active ? submitForm : null } ref = { submitButtonRef } >
176
- Submit
177
- </ button >
191
+ < button ref = { enableButtonRef } > Enable</ button >
192
+ < button ref = { submitButtonRef } > Submit</ button >
178
193
</ div >
179
194
) ;
180
195
}
@@ -190,19 +205,22 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
190
205
// Dispatch a click event on the Enable-button.
191
206
const firstEvent = document . createEvent ( 'Event' ) ;
192
207
firstEvent . initEvent ( 'click' , true , true ) ;
193
- enableButton . dispatchEvent ( firstEvent ) ;
208
+ expect ( ( ) => {
209
+ dispatchAndSetCurrentEvent ( enableButton , firstEvent ) ;
210
+ } ) . toErrorDev ( [ 'An update to Form inside a test was not wrapped in act' ] ) ;
194
211
195
212
// There should now be a pending update to enable the form.
196
-
197
213
// This should not have flushed yet since it's in concurrent mode.
198
214
const submitButton = submitButtonRef . current ;
199
215
expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
200
216
201
- // In the meantime, we can dispatch a new client event on the submit button.
217
+ // Discrete events should be flushed in a microtask.
218
+ await null ;
219
+
220
+ // Now let's dispatch an event on the submit button.
202
221
const secondEvent = document . createEvent ( 'Event' ) ;
203
222
secondEvent . initEvent ( 'click' , true , true ) ;
204
- // This should force the pending update to flush which enables the submit button before the event is invoked.
205
- submitButton . dispatchEvent ( secondEvent ) ;
223
+ dispatchAndSetCurrentEvent ( submitButton , secondEvent ) ;
206
224
207
225
// Therefore the form should have been submitted.
208
226
expect ( formSubmitted ) . toBe ( true ) ;
0 commit comments