11let React ;
2- let ReactNoop ;
2+ let ReactDOM ;
3+ let ReactDOMClient ;
34let Scheduler ;
45let act ;
56let useState ;
@@ -15,7 +16,8 @@ describe('ReactFlushSync', () => {
1516 jest . resetModules ( ) ;
1617
1718 React = require ( 'react' ) ;
18- ReactNoop = require ( 'react-noop-renderer' ) ;
19+ ReactDOM = require ( 'react-dom' ) ;
20+ ReactDOMClient = require ( 'react-dom/client' ) ;
1921 Scheduler = require ( 'scheduler' ) ;
2022 act = require ( 'internal-test-utils' ) . act ;
2123 useState = React . useState ;
@@ -32,7 +34,49 @@ describe('ReactFlushSync', () => {
3234 return text ;
3335 }
3436
35- test ( 'changes priority of updates in useEffect' , async ( ) => {
37+ function getVisibleChildren ( element : Element ) : React$Node {
38+ const children = [ ] ;
39+ let node : any = element . firstChild ;
40+ while ( node ) {
41+ if ( node . nodeType === 1 ) {
42+ if (
43+ ( ( node . tagName !== 'SCRIPT' && node . tagName !== 'script' ) ||
44+ node . hasAttribute ( 'data-meaningful' ) ) &&
45+ node . tagName !== 'TEMPLATE' &&
46+ node . tagName !== 'template' &&
47+ ! node . hasAttribute ( 'hidden' ) &&
48+ ! node . hasAttribute ( 'aria-hidden' )
49+ ) {
50+ const props : any = { } ;
51+ const attributes = node . attributes ;
52+ for ( let i = 0 ; i < attributes . length ; i ++ ) {
53+ if (
54+ attributes [ i ] . name === 'id' &&
55+ attributes [ i ] . value . includes ( ':' )
56+ ) {
57+ // We assume this is a React added ID that's a non-visual implementation detail.
58+ continue ;
59+ }
60+ props [ attributes [ i ] . name ] = attributes [ i ] . value ;
61+ }
62+ props . children = getVisibleChildren ( node ) ;
63+ children . push (
64+ require ( 'react' ) . createElement ( node . tagName . toLowerCase ( ) , props ) ,
65+ ) ;
66+ }
67+ } else if ( node . nodeType === 3 ) {
68+ children . push ( node . data ) ;
69+ }
70+ node = node . nextSibling ;
71+ }
72+ return children . length === 0
73+ ? undefined
74+ : children . length === 1
75+ ? children [ 0 ]
76+ : children ;
77+ }
78+
79+ it ( 'changes priority of updates in useEffect' , async ( ) => {
3680 spyOnDev ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
3781
3882 function App ( ) {
@@ -41,13 +85,14 @@ describe('ReactFlushSync', () => {
4185 useEffect ( ( ) => {
4286 if ( syncState !== 1 ) {
4387 setState ( 1 ) ;
44- ReactNoop . flushSync ( ( ) => setSyncState ( 1 ) ) ;
88+ ReactDOM . flushSync ( ( ) => setSyncState ( 1 ) ) ;
4589 }
4690 } , [ syncState , state ] ) ;
4791 return < Text text = { `${ syncState } , ${ state } ` } /> ;
4892 }
4993
50- const root = ReactNoop . createRoot ( ) ;
94+ const container = document . createElement ( 'div' ) ;
95+ const root = ReactDOMClient . createRoot ( container ) ;
5196 await act ( async ( ) => {
5297 React . startTransition ( ( ) => {
5398 root . render ( < App /> ) ;
@@ -62,7 +107,7 @@ describe('ReactFlushSync', () => {
62107 ) ;
63108
64109 // The remaining update is not sync
65- ReactNoop . flushSync ( ) ;
110+ ReactDOM . flushSync ( ) ;
66111 assertLog ( [ ] ) ;
67112
68113 if ( gate ( flags => flags . enableUnifiedSyncLane ) ) {
@@ -72,7 +117,7 @@ describe('ReactFlushSync', () => {
72117 await waitForPaint ( [ '1, 1' ] ) ;
73118 }
74119 } ) ;
75- expect ( root ) . toMatchRenderedOutput ( '1, 1' ) ;
120+ expect ( getVisibleChildren ( container ) ) . toEqual ( '1, 1' ) ;
76121
77122 if ( __DEV__ ) {
78123 expect ( console . error . mock . calls [ 0 ] [ 0 ] ) . toContain (
@@ -83,7 +128,7 @@ describe('ReactFlushSync', () => {
83128 }
84129 } ) ;
85130
86- test ( ' nested with startTransition', async ( ) => {
131+ it ( 'supports nested flushSync with startTransition', async ( ) => {
87132 let setSyncState ;
88133 let setState ;
89134 function App ( ) {
@@ -94,20 +139,21 @@ describe('ReactFlushSync', () => {
94139 return < Text text = { `${ syncState } , ${ state } ` } /> ;
95140 }
96141
97- const root = ReactNoop . createRoot ( ) ;
142+ const container = document . createElement ( 'div' ) ;
143+ const root = ReactDOMClient . createRoot ( container ) ;
98144 await act ( ( ) => {
99145 root . render ( < App /> ) ;
100146 } ) ;
101147 assertLog ( [ '0, 0' ] ) ;
102- expect ( root ) . toMatchRenderedOutput ( '0, 0' ) ;
148+ expect ( getVisibleChildren ( container ) ) . toEqual ( '0, 0' ) ;
103149
104150 await act ( ( ) => {
105- ReactNoop . flushSync ( ( ) => {
151+ ReactDOM . flushSync ( ( ) => {
106152 startTransition ( ( ) => {
107153 // This should be async even though flushSync is on the stack, because
108154 // startTransition is closer.
109155 setState ( 1 ) ;
110- ReactNoop . flushSync ( ( ) => {
156+ ReactDOM . flushSync ( ( ) => {
111157 // This should be async even though startTransition is on the stack,
112158 // because flushSync is closer.
113159 setSyncState ( 1 ) ;
@@ -116,24 +162,25 @@ describe('ReactFlushSync', () => {
116162 } ) ;
117163 // Only the sync update should have flushed
118164 assertLog ( [ '1, 0' ] ) ;
119- expect ( root ) . toMatchRenderedOutput ( '1, 0' ) ;
165+ expect ( getVisibleChildren ( container ) ) . toEqual ( '1, 0' ) ;
120166 } ) ;
121167 // Now the async update has flushed, too.
122168 assertLog ( [ '1, 1' ] ) ;
123- expect ( root ) . toMatchRenderedOutput ( '1, 1' ) ;
169+ expect ( getVisibleChildren ( container ) ) . toEqual ( '1, 1' ) ;
124170 } ) ;
125171
126- test ( 'flushes passive effects synchronously when they are the result of a sync render' , async ( ) => {
172+ it ( 'flushes passive effects synchronously when they are the result of a sync render' , async ( ) => {
127173 function App ( ) {
128174 useEffect ( ( ) => {
129175 Scheduler . log ( 'Effect' ) ;
130176 } , [ ] ) ;
131177 return < Text text = "Child" /> ;
132178 }
133179
134- const root = ReactNoop . createRoot ( ) ;
180+ const container = document . createElement ( 'div' ) ;
181+ const root = ReactDOMClient . createRoot ( container ) ;
135182 await act ( ( ) => {
136- ReactNoop . flushSync ( ( ) => {
183+ ReactDOM . flushSync ( ( ) => {
137184 root . render ( < App /> ) ;
138185 } ) ;
139186 assertLog ( [
@@ -142,35 +189,37 @@ describe('ReactFlushSync', () => {
142189 // flushSync should flush it.
143190 'Effect' ,
144191 ] ) ;
145- expect ( root ) . toMatchRenderedOutput ( 'Child' ) ;
192+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Child' ) ;
146193 } ) ;
147194 } ) ;
148195
149- test ( 'do not flush passive effects synchronously after render in legacy mode' , async ( ) => {
196+ // @gate !disableLegacyMode
197+ it ( 'does not flush passive effects synchronously after render in legacy mode' , async ( ) => {
150198 function App ( ) {
151199 useEffect ( ( ) => {
152200 Scheduler . log ( 'Effect' ) ;
153201 } , [ ] ) ;
154202 return < Text text = "Child" /> ;
155203 }
156204
157- const root = ReactNoop . createLegacyRoot ( ) ;
205+ const container = document . createElement ( 'div' ) ;
158206 await act ( ( ) => {
159- ReactNoop . flushSync ( ( ) => {
160- root . render ( < App /> ) ;
207+ ReactDOM . flushSync ( ( ) => {
208+ ReactDOM . render ( < App /> , container ) ;
161209 } ) ;
162210 assertLog ( [
163211 'Child' ,
164212 // Because we're in legacy mode, we shouldn't have flushed the passive
165213 // effects yet.
166214 ] ) ;
167- expect ( root ) . toMatchRenderedOutput ( 'Child' ) ;
215+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Child' ) ;
168216 } ) ;
169217 // Effect flushes after paint.
170218 assertLog ( [ 'Effect' ] ) ;
171219 } ) ;
172220
173- test ( 'flush pending passive effects before scope is called in legacy mode' , async ( ) => {
221+ // @gate !disableLegacyMode
222+ it ( 'flushes pending passive effects before scope is called in legacy mode' , async ( ) => {
174223 let currentStep = 0 ;
175224
176225 function App ( { step} ) {
@@ -181,82 +230,89 @@ describe('ReactFlushSync', () => {
181230 return < Text text = { step } /> ;
182231 }
183232
184- const root = ReactNoop . createLegacyRoot ( ) ;
233+ const container = document . createElement ( 'div' ) ;
185234 await act ( ( ) => {
186- ReactNoop . flushSync ( ( ) => {
187- root . render ( < App step = { 1 } /> ) ;
235+ ReactDOM . flushSync ( ( ) => {
236+ ReactDOM . render ( < App step = { 1 } /> , container ) ;
188237 } ) ;
189238 assertLog ( [
190239 1 ,
191240 // Because we're in legacy mode, we shouldn't have flushed the passive
192241 // effects yet.
193242 ] ) ;
194- expect ( root ) . toMatchRenderedOutput ( '1' ) ;
243+ expect ( getVisibleChildren ( container ) ) . toEqual ( '1' ) ;
195244
196- ReactNoop . flushSync ( ( ) => {
245+ ReactDOM . flushSync ( ( ) => {
197246 // This should render step 2 because the passive effect has already
198247 // fired, before the scope function is called.
199- root . render ( < App step = { currentStep + 1 } /> ) ;
248+ ReactDOM . render ( < App step = { currentStep + 1 } /> , container ) ;
200249 } ) ;
201250 assertLog ( [ 'Effect: 1' , 2 ] ) ;
202- expect ( root ) . toMatchRenderedOutput ( '2' ) ;
251+ expect ( getVisibleChildren ( container ) ) . toEqual ( '2' ) ;
203252 } ) ;
204253 assertLog ( [ 'Effect: 2' ] ) ;
205254 } ) ;
206255
207- test ( "do not flush passive effects synchronously when they aren't the result of a sync render", async ( ) => {
256+ it ( "does not flush passive effects synchronously when they aren't the result of a sync render", async ( ) => {
208257 function App ( ) {
209258 useEffect ( ( ) => {
210259 Scheduler . log ( 'Effect' ) ;
211260 } , [ ] ) ;
212261 return < Text text = "Child" /> ;
213262 }
214263
215- const root = ReactNoop . createRoot ( ) ;
264+ const container = document . createElement ( 'div' ) ;
265+ const root = ReactDOMClient . createRoot ( container ) ;
216266 await act ( async ( ) => {
217267 root . render ( < App /> ) ;
218268 await waitForPaint ( [
219269 'Child' ,
220270 // Because the passive effect was not the result of a sync update, it
221271 // should not flush before paint.
222272 ] ) ;
223- expect ( root ) . toMatchRenderedOutput ( 'Child' ) ;
273+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Child' ) ;
224274 } ) ;
225275 // Effect flushes after paint.
226276 assertLog ( [ 'Effect' ] ) ;
227277 } ) ;
228278
229- test ( 'does not flush pending passive effects' , async ( ) => {
279+ it ( 'does not flush pending passive effects' , async ( ) => {
230280 function App ( ) {
231281 useEffect ( ( ) => {
232282 Scheduler . log ( 'Effect' ) ;
233283 } , [ ] ) ;
234284 return < Text text = "Child" /> ;
235285 }
236286
237- const root = ReactNoop . createRoot ( ) ;
287+ const container = document . createElement ( 'div' ) ;
288+ const root = ReactDOMClient . createRoot ( container ) ;
238289 await act ( async ( ) => {
239290 root . render ( < App /> ) ;
240291 await waitForPaint ( [ 'Child' ] ) ;
241- expect ( root ) . toMatchRenderedOutput ( 'Child' ) ;
292+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Child' ) ;
242293
243294 // Passive effects are pending. Calling flushSync should not affect them.
244- ReactNoop . flushSync ( ) ;
295+ ReactDOM . flushSync ( ) ;
245296 // Effects still haven't fired.
246297 assertLog ( [ ] ) ;
247298 } ) ;
248299 // Now the effects have fired.
249300 assertLog ( [ 'Effect' ] ) ;
250301 } ) ;
251302
252- test ( 'completely exhausts synchronous work queue even if something throws' , async ( ) => {
303+ it ( 'completely exhausts synchronous work queue even if something throws' , async ( ) => {
253304 function Throws ( { error} ) {
254305 throw error ;
255306 }
256307
257- const root1 = ReactNoop . createRoot ( ) ;
258- const root2 = ReactNoop . createRoot ( ) ;
259- const root3 = ReactNoop . createRoot ( ) ;
308+ const container1 = document . createElement ( 'div' ) ;
309+ const root1 = ReactDOMClient . createRoot ( container1 ) ;
310+
311+ const container2 = document . createElement ( 'div' ) ;
312+ const root2 = ReactDOMClient . createRoot ( container2 ) ;
313+
314+ const container3 = document . createElement ( 'div' ) ;
315+ const root3 = ReactDOMClient . createRoot ( container3 ) ;
260316
261317 await act ( async ( ) => {
262318 root1 . render ( < Text text = "Hi" /> ) ;
@@ -270,7 +326,7 @@ describe('ReactFlushSync', () => {
270326
271327 let error ;
272328 try {
273- ReactNoop . flushSync ( ( ) => {
329+ ReactDOM . flushSync ( ( ) => {
274330 root1 . render ( < Throws error = { aahh } /> ) ;
275331 root2 . render ( < Throws error = { nooo } /> ) ;
276332 root3 . render ( < Text text = "aww" /> ) ;
@@ -283,9 +339,9 @@ describe('ReactFlushSync', () => {
283339 // earlier updates errored.
284340 assertLog ( [ 'aww' ] ) ;
285341 // Roots 1 and 2 were unmounted.
286- expect ( root1 ) . toMatchRenderedOutput ( null ) ;
287- expect ( root2 ) . toMatchRenderedOutput ( null ) ;
288- expect ( root3 ) . toMatchRenderedOutput ( 'aww' ) ;
342+ expect ( getVisibleChildren ( container1 ) ) . toEqual ( undefined ) ;
343+ expect ( getVisibleChildren ( container2 ) ) . toEqual ( undefined ) ;
344+ expect ( getVisibleChildren ( container3 ) ) . toEqual ( 'aww' ) ;
289345
290346 // Because there were multiple errors, React threw an AggregateError.
291347 // eslint-disable-next-line no-undef
0 commit comments