@@ -17,10 +17,13 @@ let Suspense;
1717let DiscreteEventPriority ;
1818let startTransition ;
1919let waitForMicrotasks ;
20+ let Scheduler ;
21+ let assertLog ;
2022
2123describe ( 'isomorphic act()' , ( ) => {
2224 beforeEach ( ( ) => {
2325 React = require ( 'react' ) ;
26+ Scheduler = require ( 'scheduler' ) ;
2427
2528 ReactNoop = require ( 'react-noop-renderer' ) ;
2629 DiscreteEventPriority =
@@ -31,6 +34,7 @@ describe('isomorphic act()', () => {
3134 startTransition = React . startTransition ;
3235
3336 waitForMicrotasks = require ( 'internal-test-utils' ) . waitForMicrotasks ;
37+ assertLog = require ( 'internal-test-utils' ) . assertLog ;
3438 } ) ;
3539
3640 beforeEach ( ( ) => {
@@ -41,6 +45,11 @@ describe('isomorphic act()', () => {
4145 jest . restoreAllMocks ( ) ;
4246 } ) ;
4347
48+ function Text ( { text} ) {
49+ Scheduler . log ( text ) ;
50+ return text ;
51+ }
52+
4453 // @gate __DEV__
4554 test ( 'bypasses queueMicrotask' , async ( ) => {
4655 const root = ReactNoop . createRoot ( ) ;
@@ -132,19 +141,67 @@ describe('isomorphic act()', () => {
132141 const root = ReactNoop . createLegacyRoot ( ) ;
133142
134143 await act ( async ( ) => {
135- // These updates are batched. This replicates the behavior of the original
136- // `act` implementation, for compatibility.
137- root . render ( 'A' ) ;
138- root . render ( 'B' ) ;
139- // Nothing has rendered yet.
140- expect ( root ) . toMatchRenderedOutput ( null ) ;
144+ queueMicrotask ( ( ) => {
145+ Scheduler . log ( 'Current tree in microtask: ' + root . getChildrenAsJSX ( ) ) ;
146+ root . render ( < Text text = "C" /> ) ;
147+ } ) ;
148+ root . render ( < Text text = "A" /> ) ;
149+ root . render ( < Text text = "B" /> ) ;
150+
141151 await null ;
142- // Updates are flushed after the first await.
143- expect ( root ) . toMatchRenderedOutput ( 'B' ) ;
152+ assertLog ( [
153+ // A and B should render in a single batch _before_ the microtask queue
154+ // has run. This replicates the behavior of the original `act`
155+ // implementation, for compatibility.
156+ 'B' ,
157+ 'Current tree in microtask: B' ,
158+
159+ // C isn't scheduled until a microtask, so it's rendered separately.
160+ 'C' ,
161+ ] ) ;
162+
163+ // Subsequent updates should also render in separate batches.
164+ root . render ( < Text text = "D" /> ) ;
165+ root . render ( < Text text = "E" /> ) ;
166+ assertLog ( [ 'D' , 'E' ] ) ;
167+ } ) ;
168+ } ) ;
144169
145- // Subsequent updates in the same scope aren't batched.
146- root . render ( 'C' ) ;
147- expect ( root ) . toMatchRenderedOutput ( 'C' ) ;
170+ // @gate __DEV__
171+ test ( 'in legacy mode, in an async scope, updates are batched until the first `await` (regression test: batchedUpdates)' , async ( ) => {
172+ const root = ReactNoop . createLegacyRoot ( ) ;
173+
174+ await act ( async ( ) => {
175+ queueMicrotask ( ( ) => {
176+ Scheduler . log ( 'Current tree in microtask: ' + root . getChildrenAsJSX ( ) ) ;
177+ root . render ( < Text text = "C" /> ) ;
178+ } ) ;
179+
180+ // This is a regression test. The presence of `batchedUpdates` would cause
181+ // these updates to not flush until a microtask. The correct behavior is
182+ // that they flush before the microtask queue, regardless of whether
183+ // they are wrapped with `batchedUpdates`.
184+ ReactNoop . batchedUpdates ( ( ) => {
185+ root . render ( < Text text = "A" /> ) ;
186+ root . render ( < Text text = "B" /> ) ;
187+ } ) ;
188+
189+ await null ;
190+ assertLog ( [
191+ // A and B should render in a single batch _before_ the microtask queue
192+ // has run. This replicates the behavior of the original `act`
193+ // implementation, for compatibility.
194+ 'B' ,
195+ 'Current tree in microtask: B' ,
196+
197+ // C isn't scheduled until a microtask, so it's rendered separately.
198+ 'C' ,
199+ ] ) ;
200+
201+ // Subsequent updates should also render in separate batches.
202+ root . render ( < Text text = "D" /> ) ;
203+ root . render ( < Text text = "E" /> ) ;
204+ assertLog ( [ 'D' , 'E' ] ) ;
148205 } ) ;
149206 } ) ;
150207
0 commit comments