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

Codemod tests to waitFor pattern (4/?) (#26302)

This converts some of our test suite to use the `waitFor` test pattern, instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of these changes are automated with jscodeshift, with some slight manual cleanup in certain cases. See #26285 for full context.

Andrew Clark committed Mar 3, 2023 at 21:24 UTC faacefb4d0167f8d5973672754fe87afc607397b
13 files changed +1095 -1274
packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+19 -9
@@ -18,6 +18,8 @@ describe('SimpleEventPlugin', function () {
18
19 let onClick;
20 let container;
21 + let assertLog;
22 + let waitForAll;
23
24 function expectClickThru(element) {
25 element.click();
@@ -43,6 +45,10 @@ describe('SimpleEventPlugin', function () {
45 ReactDOMClient = require('react-dom/client');
46 Scheduler = require('scheduler');
47
48 + const InternalTestUtils = require('internal-test-utils');
49 + assertLog = InternalTestUtils.assertLog;
50 + waitForAll = InternalTestUtils.waitForAll;
51 +
52 onClick = jest.fn();
53 });
54
@@ -222,12 +228,12 @@ describe('SimpleEventPlugin', function () {
228
229 ReactDOM.render(<Button />, container);
230 expect(button.textContent).toEqual('Count: 0');
225 - expect(Scheduler).toHaveYielded([]);
231 + assertLog([]);
232
233 click();
234
235 // There should be exactly one update.
230 - expect(Scheduler).toHaveYielded(['didUpdate - Count: 3']);
236 + assertLog(['didUpdate - Count: 3']);
237 expect(button.textContent).toEqual('Count: 3');
238 });
239
@@ -240,6 +246,10 @@ describe('SimpleEventPlugin', function () {
246 ReactDOMClient = require('react-dom/client');
247 Scheduler = require('scheduler');
248
249 + const InternalTestUtils = require('internal-test-utils');
250 + assertLog = InternalTestUtils.assertLog;
251 + waitForAll = InternalTestUtils.waitForAll;
252 +
253 act = require('jest-react').act;
254 });
255
@@ -274,10 +284,10 @@ describe('SimpleEventPlugin', function () {
284 // Initial mount
285 root.render(<Button />);
286 // Should not have flushed yet because it's async
277 - expect(Scheduler).toHaveYielded([]);
287 + assertLog([]);
288 expect(button).toBe(undefined);
289 // Flush async work
280 - expect(Scheduler).toFlushAndYield(['render button: enabled']);
290 + await waitForAll(['render button: enabled']);
291
292 function click() {
293 const event = new MouseEvent('click', {
@@ -292,7 +302,7 @@ describe('SimpleEventPlugin', function () {
302
303 // Click the button to trigger the side-effect
304 await act(async () => click());
295 - expect(Scheduler).toHaveYielded([
305 + assertLog([
306 // The handler fired
307 'Side-effect',
308 // The component re-rendered synchronously, even in concurrent mode.
@@ -301,7 +311,7 @@ describe('SimpleEventPlugin', function () {
311
312 // Click the button again
313 click();
304 - expect(Scheduler).toHaveYielded([
314 + assertLog([
315 // The event handler was removed from the button, so there's no effect.
316 ]);
317
@@ -312,7 +322,7 @@ describe('SimpleEventPlugin', function () {
322 click();
323 click();
324 click();
315 - expect(Scheduler).toFlushAndYield([]);
325 + await waitForAll([]);
326 });
327
328 // NOTE: This test was written for the old behavior of discrete updates,
@@ -345,7 +355,7 @@ describe('SimpleEventPlugin', function () {
355 // Should not have flushed yet because it's async
356 expect(button).toBe(undefined);
357 // Flush async work
348 - Scheduler.unstable_flushAll();
358 + await waitForAll([]);
359 expect(button.textContent).toEqual('Count: 0');
360
361 function click() {
@@ -373,7 +383,7 @@ describe('SimpleEventPlugin', function () {
383 await act(async () => click());
384
385 // Flush the remaining work
376 - Scheduler.unstable_flushAll();
386 + await waitForAll([]);
387 // The counter should equal the total number of clicks
388 expect(button.textContent).toEqual('Count: 7');
389 });
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+58 -88
@@ -18,6 +18,8 @@ let ReactTestRenderer;
18 let Scheduler;
19 let ReactDOMServer;
20 let act;
21 +let assertLog;
22 +let waitForAll;
23
24 describe('ReactHooks', () => {
25 beforeEach(() => {
@@ -30,6 +32,10 @@ describe('ReactHooks', () => {
32 Scheduler = require('scheduler');
33 ReactDOMServer = require('react-dom/server');
34 act = require('jest-react').act;
35 +
36 + const InternalTestUtils = require('internal-test-utils');
37 + assertLog = InternalTestUtils.assertLog;
38 + waitForAll = InternalTestUtils.waitForAll;
39 });
40
41 if (__DEV__) {
@@ -54,7 +60,7 @@ describe('ReactHooks', () => {
60 });
61 }
62
57 - it('bails out in the render phase if all of the state is the same', () => {
63 + it('bails out in the render phase if all of the state is the same', async () => {
64 const {useState, useLayoutEffect} = React;
65
66 function Child({text}) {
@@ -80,11 +86,7 @@ describe('ReactHooks', () => {
86
87 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
88 root.update(<Parent />);
83 - expect(Scheduler).toFlushAndYield([
84 - 'Parent: 0, 0',
85 - 'Child: 0, 0',
86 - 'Effect: 0, 0',
87 - ]);
89 + await waitForAll(['Parent: 0, 0', 'Child: 0, 0', 'Effect: 0, 0']);
90 expect(root).toMatchRenderedOutput('0, 0');
91
92 // Normal update
@@ -93,15 +95,11 @@ describe('ReactHooks', () => {
95 setCounter2(1);
96 });
97
96 - expect(Scheduler).toHaveYielded([
97 - 'Parent: 1, 1',
98 - 'Child: 1, 1',
99 - 'Effect: 1, 1',
100 - ]);
98 + assertLog(['Parent: 1, 1', 'Child: 1, 1', 'Effect: 1, 1']);
99
100 // Update that bails out.
101 act(() => setCounter1(1));
104 - expect(Scheduler).toHaveYielded(['Parent: 1, 1']);
102 + assertLog(['Parent: 1, 1']);
103
104 // This time, one of the state updates but the other one doesn't. So we
105 // can't bail out.
@@ -110,11 +108,7 @@ describe('ReactHooks', () => {
108 setCounter2(2);
109 });
110
113 - expect(Scheduler).toHaveYielded([
114 - 'Parent: 1, 2',
115 - 'Child: 1, 2',
116 - 'Effect: 1, 2',
117 - ]);
111 + assertLog(['Parent: 1, 2', 'Child: 1, 2', 'Effect: 1, 2']);
112
113 // Lots of updates that eventually resolve to the current values.
114 act(() => {
@@ -128,7 +122,7 @@ describe('ReactHooks', () => {
122
123 // Because the final values are the same as the current values, the
124 // component bails out.
131 - expect(Scheduler).toHaveYielded(['Parent: 1, 2']);
125 + assertLog(['Parent: 1, 2']);
126
127 // prepare to check SameValue
128 act(() => {
@@ -136,11 +130,7 @@ describe('ReactHooks', () => {
130 setCounter2(NaN);
131 });
132
139 - expect(Scheduler).toHaveYielded([
140 - 'Parent: 0, NaN',
141 - 'Child: 0, NaN',
142 - 'Effect: 0, NaN',
143 - ]);
133 + assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
134
135 // check if re-setting to negative 0 / NaN still bails out
136 act(() => {
@@ -150,20 +140,16 @@ describe('ReactHooks', () => {
140 setCounter2(NaN);
141 });
142
153 - expect(Scheduler).toHaveYielded(['Parent: 0, NaN']);
143 + assertLog(['Parent: 0, NaN']);
144
145 // check if changing negative 0 to positive 0 does not bail out
146 act(() => {
147 setCounter1(0);
148 });
159 - expect(Scheduler).toHaveYielded([
160 - 'Parent: 0, NaN',
161 - 'Child: 0, NaN',
162 - 'Effect: 0, NaN',
163 - ]);
149 + assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
150 });
151
166 - it('bails out in render phase if all the state is the same and props bail out with memo', () => {
152 + it('bails out in render phase if all the state is the same and props bail out with memo', async () => {
153 const {useState, memo} = React;
154
155 function Child({text}) {
@@ -188,10 +174,7 @@ describe('ReactHooks', () => {
174
175 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
176 root.update(<Parent theme="light" />);
191 - expect(Scheduler).toFlushAndYield([
192 - 'Parent: 0, 0 (light)',
193 - 'Child: 0, 0 (light)',
194 - ]);
177 + await waitForAll(['Parent: 0, 0 (light)', 'Child: 0, 0 (light)']);
178 expect(root).toMatchRenderedOutput('0, 0 (light)');
179
180 // Normal update
@@ -200,14 +183,11 @@ describe('ReactHooks', () => {
183 setCounter2(1);
184 });
185
203 - expect(Scheduler).toHaveYielded([
204 - 'Parent: 1, 1 (light)',
205 - 'Child: 1, 1 (light)',
206 - ]);
186 + assertLog(['Parent: 1, 1 (light)', 'Child: 1, 1 (light)']);
187
188 // Update that bails out.
189 act(() => setCounter1(1));
210 - expect(Scheduler).toHaveYielded(['Parent: 1, 1 (light)']);
190 + assertLog(['Parent: 1, 1 (light)']);
191
192 // This time, one of the state updates but the other one doesn't. So we
193 // can't bail out.
@@ -216,10 +196,7 @@ describe('ReactHooks', () => {
196 setCounter2(2);
197 });
198
219 - expect(Scheduler).toHaveYielded([
220 - 'Parent: 1, 2 (light)',
221 - 'Child: 1, 2 (light)',
222 - ]);
199 + assertLog(['Parent: 1, 2 (light)', 'Child: 1, 2 (light)']);
200
201 // Updates bail out, but component still renders because props
202 // have changed
@@ -229,10 +206,7 @@ describe('ReactHooks', () => {
206 root.update(<Parent theme="dark" />);
207 });
208
232 - expect(Scheduler).toHaveYielded([
233 - 'Parent: 1, 2 (dark)',
234 - 'Child: 1, 2 (dark)',
235 - ]);
209 + assertLog(['Parent: 1, 2 (dark)', 'Child: 1, 2 (dark)']);
210
211 // Both props and state bail out
212 act(() => {
@@ -241,10 +215,10 @@ describe('ReactHooks', () => {
215 root.update(<Parent theme="dark" />);
216 });
217
244 - expect(Scheduler).toHaveYielded(['Parent: 1, 2 (dark)']);
218 + assertLog(['Parent: 1, 2 (dark)']);
219 });
220
247 - it('warns about setState second argument', () => {
221 + it('warns about setState second argument', async () => {
222 const {useState} = React;
223
224 let setCounter;
@@ -258,7 +232,7 @@ describe('ReactHooks', () => {
232
233 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
234 root.update(<Counter />);
261 - expect(Scheduler).toFlushAndYield(['Count: 0']);
235 + await waitForAll(['Count: 0']);
236 expect(root).toMatchRenderedOutput('0');
237
238 expect(() => {
@@ -274,11 +248,11 @@ describe('ReactHooks', () => {
248 'declare it in the component body with useEffect().',
249 {withoutStack: true},
250 );
277 - expect(Scheduler).toHaveYielded(['Count: 1']);
251 + assertLog(['Count: 1']);
252 expect(root).toMatchRenderedOutput('1');
253 });
254
281 - it('warns about dispatch second argument', () => {
255 + it('warns about dispatch second argument', async () => {
256 const {useReducer} = React;
257
258 let dispatch;
@@ -292,7 +266,7 @@ describe('ReactHooks', () => {
266
267 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
268 root.update(<Counter />);
295 - expect(Scheduler).toFlushAndYield(['Count: 0']);
269 + await waitForAll(['Count: 0']);
270 expect(root).toMatchRenderedOutput('0');
271
272 expect(() => {
@@ -308,11 +282,11 @@ describe('ReactHooks', () => {
282 'declare it in the component body with useEffect().',
283 {withoutStack: true},
284 );
311 - expect(Scheduler).toHaveYielded(['Count: 1']);
285 + assertLog(['Count: 1']);
286 expect(root).toMatchRenderedOutput('1');
287 });
288
315 - it('never bails out if context has changed', () => {
289 + it('never bails out if context has changed', async () => {
290 const {useState, useLayoutEffect, useContext} = React;
291
292 const ThemeContext = React.createContext('light');
@@ -355,7 +329,7 @@ describe('ReactHooks', () => {
329 );
330 });
331
358 - expect(Scheduler).toHaveYielded([
332 + assertLog([
333 'Theme: light',
334 'Parent: 0 (light)',
335 'Child: 0 (light)',
@@ -366,21 +340,17 @@ describe('ReactHooks', () => {
340 // Updating the theme to the same value doesn't cause the consumers
341 // to re-render.
342 setTheme('light');
369 - expect(Scheduler).toFlushAndYield([]);
343 + await waitForAll([]);
344 expect(root).toMatchRenderedOutput('0 (light)');
345
346 // Normal update
347 act(() => setCounter(1));
374 - expect(Scheduler).toHaveYielded([
375 - 'Parent: 1 (light)',
376 - 'Child: 1 (light)',
377 - 'Effect: 1 (light)',
378 - ]);
348 + assertLog(['Parent: 1 (light)', 'Child: 1 (light)', 'Effect: 1 (light)']);
349 expect(root).toMatchRenderedOutput('1 (light)');
350
351 // Update that doesn't change state, so it bails out
352 act(() => setCounter(1));
383 - expect(Scheduler).toHaveYielded(['Parent: 1 (light)']);
353 + assertLog(['Parent: 1 (light)']);
354 expect(root).toMatchRenderedOutput('1 (light)');
355
356 // Update that doesn't change state, but the context changes, too, so it
@@ -390,7 +360,7 @@ describe('ReactHooks', () => {
360 setTheme('dark');
361 });
362
393 - expect(Scheduler).toHaveYielded([
363 + assertLog([
364 'Theme: dark',
365 'Parent: 1 (dark)',
366 'Child: 1 (dark)',
@@ -399,7 +369,7 @@ describe('ReactHooks', () => {
369 expect(root).toMatchRenderedOutput('1 (dark)');
370 });
371
402 - it('can bail out without calling render phase (as an optimization) if queue is known to be empty', () => {
372 + it('can bail out without calling render phase (as an optimization) if queue is known to be empty', async () => {
373 const {useState, useLayoutEffect} = React;
374
375 function Child({text}) {
@@ -420,12 +390,12 @@ describe('ReactHooks', () => {
390
391 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
392 root.update(<Parent />);
423 - expect(Scheduler).toFlushAndYield(['Parent: 0', 'Child: 0', 'Effect: 0']);
393 + await waitForAll(['Parent: 0', 'Child: 0', 'Effect: 0']);
394 expect(root).toMatchRenderedOutput('0');
395
396 // Normal update
397 act(() => setCounter(1));
428 - expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 1', 'Effect: 1']);
398 + assertLog(['Parent: 1', 'Child: 1', 'Effect: 1']);
399 expect(root).toMatchRenderedOutput('1');
400
401 // Update to the same state. React doesn't know if the queue is empty
@@ -433,25 +403,25 @@ describe('ReactHooks', () => {
403 // enter the render phase before we can bail out. But we bail out before
404 // rendering the child, and we don't fire any effects.
405 act(() => setCounter(1));
436 - expect(Scheduler).toHaveYielded(['Parent: 1']);
406 + assertLog(['Parent: 1']);
407 expect(root).toMatchRenderedOutput('1');
408
409 // Update to the same state again. This times, neither fiber has pending
410 // update priority, so we can bail out before even entering the render phase.
411 act(() => setCounter(1));
442 - expect(Scheduler).toFlushAndYield([]);
412 + await waitForAll([]);
413 expect(root).toMatchRenderedOutput('1');
414
415 // This changes the state to something different so it renders normally.
416 act(() => setCounter(2));
447 - expect(Scheduler).toHaveYielded(['Parent: 2', 'Child: 2', 'Effect: 2']);
417 + assertLog(['Parent: 2', 'Child: 2', 'Effect: 2']);
418 expect(root).toMatchRenderedOutput('2');
419
420 // prepare to check SameValue
421 act(() => {
422 setCounter(0);
423 });
454 - expect(Scheduler).toHaveYielded(['Parent: 0', 'Child: 0', 'Effect: 0']);
424 + assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
425 expect(root).toMatchRenderedOutput('0');
426
427 // Update to the same state for the first time to flush the queue
@@ -459,25 +429,25 @@ describe('ReactHooks', () => {
429 setCounter(0);
430 });
431
462 - expect(Scheduler).toHaveYielded(['Parent: 0']);
432 + assertLog(['Parent: 0']);
433 expect(root).toMatchRenderedOutput('0');
434
435 // Update again to the same state. Should bail out.
436 act(() => {
437 setCounter(0);
438 });
469 - expect(Scheduler).toFlushAndYield([]);
439 + await waitForAll([]);
440 expect(root).toMatchRenderedOutput('0');
441
442 // Update to a different state (positive 0 to negative 0)
443 act(() => {
444 setCounter(0 / -1);
445 });
476 - expect(Scheduler).toHaveYielded(['Parent: 0', 'Child: 0', 'Effect: 0']);
446 + assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
447 expect(root).toMatchRenderedOutput('0');
448 });
449
480 - it('bails out multiple times in a row without entering render phase', () => {
450 + it('bails out multiple times in a row without entering render phase', async () => {
451 const {useState} = React;
452
453 function Child({text}) {
@@ -495,7 +465,7 @@ describe('ReactHooks', () => {
465
466 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
467 root.update(<Parent />);
498 - expect(Scheduler).toFlushAndYield(['Parent: 0', 'Child: 0']);
468 + await waitForAll(['Parent: 0', 'Child: 0']);
469 expect(root).toMatchRenderedOutput('0');
470
471 const update = value => {
@@ -515,7 +485,7 @@ describe('ReactHooks', () => {
485 update(3);
486 });
487
518 - expect(Scheduler).toHaveYielded([
488 + assertLog([
489 // The first four updates were eagerly computed, because the queue is
490 // empty before each one.
491 'Compute state (0 -> 0)',
@@ -527,7 +497,7 @@ describe('ReactHooks', () => {
497 ]);
498
499 // Now let's enter the render phase
530 - expect(Scheduler).toFlushAndYield([
500 + await waitForAll([
501 // We don't need to re-compute the first four updates. Only the final two.
502 'Compute state (1 -> 2)',
503 'Compute state (2 -> 3)',
@@ -537,7 +507,7 @@ describe('ReactHooks', () => {
507 expect(root).toMatchRenderedOutput('3');
508 });
509
540 - it('can rebase on top of a previously skipped update', () => {
510 + it('can rebase on top of a previously skipped update', async () => {
511 const {useState} = React;
512
513 function Child({text}) {
@@ -555,7 +525,7 @@ describe('ReactHooks', () => {
525
526 const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
527 root.update(<Parent />);
558 - expect(Scheduler).toFlushAndYield(['Parent: 1', 'Child: 1']);
528 + await waitForAll(['Parent: 1', 'Child: 1']);
529 expect(root).toMatchRenderedOutput('1');
530
531 const update = compute => {
@@ -576,13 +546,13 @@ describe('ReactHooks', () => {
546 ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
547 }
548 // The new state is eagerly computed.
579 - expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']);
549 + assertLog(['Compute state (1 -> 100)']);
550
551 // but before it's flushed, a higher priority update interrupts it.
552 root.unstable_flushSync(() => {
553 update(n => n + 5);
554 });
585 - expect(Scheduler).toHaveYielded([
555 + assertLog([
556 // The eagerly computed state was completely skipped
557 'Compute state (1 -> 6)',
558 'Parent: 6',
@@ -593,7 +563,7 @@ describe('ReactHooks', () => {
563 // Now when we finish the first update, the second update is rebased on top.
564 // Notice we didn't have to recompute the first update even though it was
565 // skipped in the previous render.
596 - expect(Scheduler).toFlushAndYield([
566 + await waitForAll([
567 'Compute state (100 -> 105)',
568 'Parent: 105',
569 'Child: 105',
@@ -612,7 +582,7 @@ describe('ReactHooks', () => {
582 return props.dependencies;
583 }
584 const root = ReactTestRenderer.create(<App dependencies={['A']} />);
615 - expect(Scheduler).toHaveYielded(['Did commit: A']);
585 + assertLog(['Did commit: A']);
586 expect(() => {
587 root.update(<App dependencies={['A', 'B']} />);
588 }).toErrorDev([
@@ -639,7 +609,7 @@ describe('ReactHooks', () => {
609
610 const root = ReactTestRenderer.create(null);
611 root.update(<App text="Hello" hasDeps={true} />);
642 - expect(Scheduler).toHaveYielded(['Compute']);
612 + assertLog(['Compute']);
613 expect(root).toMatchRenderedOutput('HELLO');
614
615 expect(() => {
@@ -1841,7 +1811,7 @@ describe('ReactHooks', () => {
1811 );
1812 expect(root).toMatchRenderedOutput('loading');
1813 await Promise.resolve();
1844 - Scheduler.unstable_flushAll();
1814 + await waitForAll([]);
1815 expect(root).toMatchRenderedOutput('hello');
1816 });
1817
@@ -1873,7 +1843,7 @@ describe('ReactHooks', () => {
1843 );
1844 expect(root).toMatchRenderedOutput('loading');
1845 await Promise.resolve();
1876 - Scheduler.unstable_flushAll();
1846 + await waitForAll([]);
1847 expect(root).toMatchRenderedOutput('hello');
1848 });
1849
@@ -1905,7 +1875,7 @@ describe('ReactHooks', () => {
1875 );
1876 expect(root).toMatchRenderedOutput('loading');
1877 await Promise.resolve();
1908 - Scheduler.unstable_flushAll();
1878 + await waitForAll([]);
1879 expect(root).toMatchRenderedOutput('hello');
1880 });
1881
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+433 -594
@@ -35,6 +35,11 @@ let memo;
35 let act;
36 let ContinuousEventPriority;
37 let SuspenseList;
38 +let waitForAll;
39 +let waitFor;
40 +let waitForThrow;
41 +let waitForPaint;
42 +let assertLog;
43
44 describe('ReactHooksWithNoopRenderer', () => {
45 beforeEach(() => {
@@ -65,6 +70,13 @@ describe('ReactHooksWithNoopRenderer', () => {
70 SuspenseList = React.SuspenseList;
71 }
72
73 + const InternalTestUtils = require('internal-test-utils');
74 + waitForAll = InternalTestUtils.waitForAll;
75 + waitFor = InternalTestUtils.waitFor;
76 + waitForThrow = InternalTestUtils.waitForThrow;
77 + waitForPaint = InternalTestUtils.waitForPaint;
78 + assertLog = InternalTestUtils.assertLog;
79 +
80 textCache = new Map();
81
82 readText = text => {
@@ -151,7 +163,7 @@ describe('ReactHooksWithNoopRenderer', () => {
163 return Promise.resolve().then(() => {});
164 }
165
154 - it('resumes after an interruption', () => {
166 + it('resumes after an interruption', async () => {
167 function Counter(props, ref) {
168 const [count, updateCount] = useState(0);
169 useImperativeHandle(ref, () => ({updateCount}));
@@ -162,33 +174,33 @@ describe('ReactHooksWithNoopRenderer', () => {
174 // Initial mount
175 const counter = React.createRef(null);
176 ReactNoop.render(<Counter label="Count" ref={counter} />);
165 - expect(Scheduler).toFlushAndYield(['Count: 0']);
177 + await waitForAll(['Count: 0']);
178 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
179
180 // Schedule some updates
169 - act(() => {
181 + await act(async () => {
182 React.startTransition(() => {
183 counter.current.updateCount(1);
184 counter.current.updateCount(count => count + 10);
185 });
186
187 // Partially flush without committing
176 - expect(Scheduler).toFlushAndYieldThrough(['Count: 11']);
188 + await waitFor(['Count: 11']);
189 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
190
191 // Interrupt with a high priority update
192 ReactNoop.flushSync(() => {
193 ReactNoop.render(<Counter label="Total" />);
194 });
183 - expect(Scheduler).toHaveYielded(['Total: 0']);
195 + assertLog(['Total: 0']);
196
197 // Resume rendering
186 - expect(Scheduler).toFlushAndYield(['Total: 11']);
198 + await waitForAll(['Total: 11']);
199 expect(ReactNoop).toMatchRenderedOutput(<span prop="Total: 11" />);
200 });
201 });
202
191 - it('throws inside class components', () => {
203 + it('throws inside class components', async () => {
204 class BadCounter extends React.Component {
205 render() {
206 const [count] = useState(0);
@@ -212,11 +224,11 @@ describe('ReactHooksWithNoopRenderer', () => {
224 return <Text text={count} />;
225 }
226 ReactNoop.render(<GoodCounter initialCount={10} />);
215 - expect(Scheduler).toFlushAndYield([10]);
227 + await waitForAll([10]);
228 });
229
230 if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
219 - it('throws inside module-style components', () => {
231 + it('throws inside module-style components', async () => {
232 function Counter() {
233 return {
234 render() {
@@ -249,11 +261,11 @@ describe('ReactHooksWithNoopRenderer', () => {
261 return <Text text={count} />;
262 }
263 ReactNoop.render(<GoodCounter initialCount={10} />);
252 - expect(Scheduler).toFlushAndYield([10]);
264 + await waitForAll([10]);
265 });
266 }
267
256 - it('throws when called outside the render phase', () => {
268 + it('throws when called outside the render phase', async () => {
269 expect(() => {
270 expect(() => useState(0)).toThrow(
271 "Cannot read property 'useState' of null",
@@ -270,7 +282,7 @@ describe('ReactHooksWithNoopRenderer', () => {
282 });
283
284 describe('useState', () => {
273 - it('simple mount and update', () => {
285 + it('simple mount and update', async () => {
286 function Counter(props, ref) {
287 const [count, updateCount] = useState(0);
288 useImperativeHandle(ref, () => ({updateCount}));
@@ -279,19 +291,19 @@ describe('ReactHooksWithNoopRenderer', () => {
291 Counter = forwardRef(Counter);
292 const counter = React.createRef(null);
293 ReactNoop.render(<Counter ref={counter} />);
282 - expect(Scheduler).toFlushAndYield(['Count: 0']);
294 + await waitForAll(['Count: 0']);
295 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
296
297 act(() => counter.current.updateCount(1));
286 - expect(Scheduler).toHaveYielded(['Count: 1']);
298 + assertLog(['Count: 1']);
299 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
300
301 act(() => counter.current.updateCount(count => count + 10));
290 - expect(Scheduler).toHaveYielded(['Count: 11']);
302 + assertLog(['Count: 11']);
303 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
304 });
305
294 - it('lazy state initializer', () => {
306 + it('lazy state initializer', async () => {
307 function Counter(props, ref) {
308 const [count, updateCount] = useState(() => {
309 Scheduler.unstable_yieldValue('getInitialState');
@@ -303,15 +315,15 @@ describe('ReactHooksWithNoopRenderer', () => {
315 Counter = forwardRef(Counter);
316 const counter = React.createRef(null);
317 ReactNoop.render(<Counter initialState={42} ref={counter} />);
306 - expect(Scheduler).toFlushAndYield(['getInitialState', 'Count: 42']);
318 + await waitForAll(['getInitialState', 'Count: 42']);
319 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 42" />);
320
321 act(() => counter.current.updateCount(7));
310 - expect(Scheduler).toHaveYielded(['Count: 7']);
322 + assertLog(['Count: 7']);
323 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 7" />);
324 });
325
314 - it('multiple states', () => {
326 + it('multiple states', async () => {
327 function Counter(props, ref) {
328 const [count, updateCount] = useState(0);
329 const [label, updateLabel] = useState('Count');
@@ -321,17 +333,17 @@ describe('ReactHooksWithNoopRenderer', () => {
333 Counter = forwardRef(Counter);
334 const counter = React.createRef(null);
335 ReactNoop.render(<Counter ref={counter} />);
324 - expect(Scheduler).toFlushAndYield(['Count: 0']);
336 + await waitForAll(['Count: 0']);
337 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
338
339 act(() => counter.current.updateCount(7));
328 - expect(Scheduler).toHaveYielded(['Count: 7']);
340 + assertLog(['Count: 7']);
341
342 act(() => counter.current.updateLabel('Total'));
331 - expect(Scheduler).toHaveYielded(['Total: 7']);
343 + assertLog(['Total: 7']);
344 });
345
334 - it('returns the same updater function every time', () => {
346 + it('returns the same updater function every time', async () => {
347 let updater = null;
348 function Counter() {
349 const [count, updateCount] = useState(0);
@@ -339,25 +351,25 @@ describe('ReactHooksWithNoopRenderer', () => {
351 return <Text text={'Count: ' + count} />;
352 }
353 ReactNoop.render(<Counter />);
342 - expect(Scheduler).toFlushAndYield(['Count: 0']);
354 + await waitForAll(['Count: 0']);
355 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
356
357 const firstUpdater = updater;
358
359 act(() => firstUpdater(1));
348 - expect(Scheduler).toHaveYielded(['Count: 1']);
360 + assertLog(['Count: 1']);
361 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
362
363 const secondUpdater = updater;
364
365 act(() => firstUpdater(count => count + 10));
354 - expect(Scheduler).toHaveYielded(['Count: 11']);
366 + assertLog(['Count: 11']);
367 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
368
369 expect(firstUpdater).toBe(secondUpdater);
370 });
371
360 - it('does not warn on set after unmount', () => {
372 + it('does not warn on set after unmount', async () => {
373 let _updateCount;
374 function Counter(props, ref) {
375 const [, updateCount] = useState(0);
@@ -366,13 +378,13 @@ describe('ReactHooksWithNoopRenderer', () => {
378 }
379
380 ReactNoop.render(<Counter />);
369 - expect(Scheduler).toFlushWithoutYielding();
381 + await waitForAll([]);
382 ReactNoop.render(null);
371 - expect(Scheduler).toFlushWithoutYielding();
383 + await waitForAll([]);
384 act(() => _updateCount(1));
385 });
386
375 - it('works with memo', () => {
387 + it('works with memo', async () => {
388 let _updateCount;
389 function Counter(props) {
390 const [count, updateCount] = useState(0);
@@ -382,21 +394,21 @@ describe('ReactHooksWithNoopRenderer', () => {
394 Counter = memo(Counter);
395
396 ReactNoop.render(<Counter />);
385 - expect(Scheduler).toFlushAndYield(['Count: 0']);
397 + await waitForAll(['Count: 0']);
398 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
399
400 ReactNoop.render(<Counter />);
389 - expect(Scheduler).toFlushAndYield([]);
401 + await waitForAll([]);
402 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
403
404 act(() => _updateCount(1));
393 - expect(Scheduler).toHaveYielded(['Count: 1']);
405 + assertLog(['Count: 1']);
406 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
407 });
408 });
409
410 describe('updates during the render phase', () => {
399 - it('restarts the render function and applies the new updates on top', () => {
411 + it('restarts the render function and applies the new updates on top', async () => {
412 function ScrollView({row: newRow}) {
413 const [isScrollingDown, setIsScrollingDown] = useState(false);
414 const [row, setRow] = useState(null);
@@ -411,37 +423,37 @@ describe('ReactHooksWithNoopRenderer', () => {
423 }
424
425 ReactNoop.render(<ScrollView row={1} />);
414 - expect(Scheduler).toFlushAndYield(['Scrolling down: false']);
426 + await waitForAll(['Scrolling down: false']);
427 expect(ReactNoop).toMatchRenderedOutput(
428 <span prop="Scrolling down: false" />,
429 );
430
431 ReactNoop.render(<ScrollView row={5} />);
420 - expect(Scheduler).toFlushAndYield(['Scrolling down: true']);
432 + await waitForAll(['Scrolling down: true']);
433 expect(ReactNoop).toMatchRenderedOutput(
434 <span prop="Scrolling down: true" />,
435 );
436
437 ReactNoop.render(<ScrollView row={5} />);
426 - expect(Scheduler).toFlushAndYield(['Scrolling down: true']);
438 + await waitForAll(['Scrolling down: true']);
439 expect(ReactNoop).toMatchRenderedOutput(
440 <span prop="Scrolling down: true" />,
441 );
442
443 ReactNoop.render(<ScrollView row={10} />);
432 - expect(Scheduler).toFlushAndYield(['Scrolling down: true']);
444 + await waitForAll(['Scrolling down: true']);
445 expect(ReactNoop).toMatchRenderedOutput(
446 <span prop="Scrolling down: true" />,
447 );
448
449 ReactNoop.render(<ScrollView row={2} />);
438 - expect(Scheduler).toFlushAndYield(['Scrolling down: false']);
450 + await waitForAll(['Scrolling down: false']);
451 expect(ReactNoop).toMatchRenderedOutput(
452 <span prop="Scrolling down: false" />,
453 );
454
455 ReactNoop.render(<ScrollView row={2} />);
444 - expect(Scheduler).toFlushAndYield(['Scrolling down: false']);
456 + await waitForAll(['Scrolling down: false']);
457 expect(ReactNoop).toMatchRenderedOutput(
458 <span prop="Scrolling down: false" />,
459 );
@@ -472,7 +484,7 @@ describe('ReactHooksWithNoopRenderer', () => {
484 </>,
485 );
486 });
475 - expect(Scheduler).toHaveYielded(['Foo [0]', 'Bar']);
487 + assertLog(['Foo [0]', 'Bar']);
488
489 // Bar will update Foo during its render phase. React should warn.
490 await act(async () => {
@@ -498,11 +510,11 @@ describe('ReactHooksWithNoopRenderer', () => {
510 <Bar triggerUpdate={true} />
511 </>,
512 );
501 - expect(Scheduler).toFlushAndYield(['Foo [1]', 'Bar', 'Foo [2]']);
513 + await waitForAll(['Foo [1]', 'Bar', 'Foo [2]']);
514 });
515 });
516
505 - it('keeps restarting until there are no more new updates', () => {
517 + it('keeps restarting until there are no more new updates', async () => {
518 function Counter({row: newRow}) {
519 const [count, setCount] = useState(0);
520 if (count < 3) {
@@ -513,17 +525,11 @@ describe('ReactHooksWithNoopRenderer', () => {
525 }
526
527 ReactNoop.render(<Counter />);
516 - expect(Scheduler).toFlushAndYield([
517 - 'Render: 0',
518 - 'Render: 1',
519 - 'Render: 2',
520 - 'Render: 3',
521 - 3,
522 - ]);
528 + await waitForAll(['Render: 0', 'Render: 1', 'Render: 2', 'Render: 3', 3]);
529 expect(ReactNoop).toMatchRenderedOutput(<span prop={3} />);
530 });
531
526 - it('updates multiple times within same render function', () => {
532 + it('updates multiple times within same render function', async () => {
533 function Counter({row: newRow}) {
534 const [count, setCount] = useState(0);
535 if (count < 12) {
@@ -536,7 +542,7 @@ describe('ReactHooksWithNoopRenderer', () => {
542 }
543
544 ReactNoop.render(<Counter />);
539 - expect(Scheduler).toFlushAndYield([
545 + await waitForAll([
546 // Should increase by three each time
547 'Render: 0',
548 'Render: 3',
@@ -548,7 +554,7 @@ describe('ReactHooksWithNoopRenderer', () => {
554 expect(ReactNoop).toMatchRenderedOutput(<span prop={12} />);
555 });
556
551 - it('throws after too many iterations', () => {
557 + it('throws after too many iterations', async () => {
558 function Counter({row: newRow}) {
559 const [count, setCount] = useState(0);
560 setCount(count + 1);
@@ -562,7 +568,7 @@ describe('ReactHooksWithNoopRenderer', () => {
568 );
569 });
570
565 - it('works with useReducer', () => {
571 + it('works with useReducer', async () => {
572 function reducer(state, action) {
573 return action === 'increment' ? state + 1 : state;
574 }
@@ -576,17 +582,11 @@ describe('ReactHooksWithNoopRenderer', () => {
582 }
583
584 ReactNoop.render(<Counter />);
579 - expect(Scheduler).toFlushAndYield([
580 - 'Render: 0',
581 - 'Render: 1',
582 - 'Render: 2',
583 - 'Render: 3',
584 - 3,
585 - ]);
585 + await waitForAll(['Render: 0', 'Render: 1', 'Render: 2', 'Render: 3', 3]);
586 expect(ReactNoop).toMatchRenderedOutput(<span prop={3} />);
587 });
588
589 - it('uses reducer passed at time of render, not time of dispatch', () => {
589 + it('uses reducer passed at time of render, not time of dispatch', async () => {
590 // This test is a bit contrived but it demonstrates a subtle edge case.
591
592 // Reducer A increments by 1. Reducer B increments by 10.
@@ -626,7 +626,7 @@ describe('ReactHooksWithNoopRenderer', () => {
626 Counter = forwardRef(Counter);
627 const counter = React.createRef(null);
628 ReactNoop.render(<Counter ref={counter} />);
629 - expect(Scheduler).toFlushAndYield([
629 + await waitForAll([
630 // The count should increase by alternating amounts of 10 and 1
631 // until we reach 21.
632 'Render: 0',
@@ -643,7 +643,7 @@ describe('ReactHooksWithNoopRenderer', () => {
643 counter.current.dispatch('reset');
644 });
645 ReactNoop.render(<Counter ref={counter} />);
646 - expect(Scheduler).toHaveYielded([
646 + assertLog([
647 'Render: 0',
648 'Render: 1',
649 'Render: 11',
@@ -686,20 +686,20 @@ describe('ReactHooksWithNoopRenderer', () => {
686 const root = ReactNoop.createRoot();
687 root.render(<Foo signal={true} />);
688
689 - expect(Scheduler).toFlushAndYield([0]);
689 + await waitForAll([0]);
690 expect(root).toMatchRenderedOutput(<span prop={0} />);
691
692 React.startTransition(() => {
693 root.render(<Foo signal={false} />);
694 });
695 - expect(Scheduler).toFlushAndYield(['Suspend!']);
695 + await waitForAll(['Suspend!']);
696 expect(root).toMatchRenderedOutput(<span prop={0} />);
697
698 // Rendering again should suspend again.
699 React.startTransition(() => {
700 root.render(<Foo signal={false} />);
701 });
702 - expect(Scheduler).toFlushAndYield(['Suspend!']);
702 + await waitForAll(['Suspend!']);
703 });
704
705 it('discards render phase updates if something suspends, but not other updates in the same component', async () => {
@@ -740,7 +740,7 @@ describe('ReactHooksWithNoopRenderer', () => {
740 const root = ReactNoop.createRoot();
741 root.render(<Foo signal={true} />);
742
743 - expect(Scheduler).toFlushAndYield(['A:0']);
743 + await waitForAll(['A:0']);
744 expect(root).toMatchRenderedOutput(<span prop="A:0" />);
745
746 await act(async () => {
@@ -749,21 +749,21 @@ describe('ReactHooksWithNoopRenderer', () => {
749 setLabel('B');
750 });
751
752 - expect(Scheduler).toFlushAndYield(['Suspend!']);
752 + await waitForAll(['Suspend!']);
753 expect(root).toMatchRenderedOutput(<span prop="A:0" />);
754
755 // Rendering again should suspend again.
756 React.startTransition(() => {
757 root.render(<Foo signal={false} />);
758 });
759 - expect(Scheduler).toFlushAndYield(['Suspend!']);
759 + await waitForAll(['Suspend!']);
760
761 // Flip the signal back to "cancel" the update. However, the update to
762 // label should still proceed. It shouldn't have been dropped.
763 React.startTransition(() => {
764 root.render(<Foo signal={true} />);
765 });
766 - expect(Scheduler).toFlushAndYield(['B:0']);
766 + await waitForAll(['B:0']);
767 expect(root).toMatchRenderedOutput(<span prop="B:0" />);
768 });
769 });
@@ -790,7 +790,7 @@ describe('ReactHooksWithNoopRenderer', () => {
790 await act(async () => {
791 root.render(<ScrollView row={10} />);
792 });
793 - expect(Scheduler).toHaveYielded(['Up']);
793 + assertLog(['Up']);
794 expect(root).toMatchRenderedOutput(<span prop="Up" />);
795
796 await act(async () => {
@@ -801,7 +801,7 @@ describe('ReactHooksWithNoopRenderer', () => {
801 setRow(20);
802 });
803 });
804 - expect(Scheduler).toHaveYielded(['Up', 'Down']);
804 + assertLog(['Up', 'Down']);
805 expect(root).toMatchRenderedOutput(<span prop="Down" />);
806 });
807
@@ -821,13 +821,13 @@ describe('ReactHooksWithNoopRenderer', () => {
821
822 const root = ReactNoop.createRoot();
823 root.render(<App />);
824 - expect(Scheduler).toFlushAndYield([1]);
824 + await waitForAll([1]);
825 expect(root).toMatchRenderedOutput(<span prop={1} />);
826 });
827 });
828
829 describe('useReducer', () => {
830 - it('simple mount and update', () => {
830 + it('simple mount and update', async () => {
831 const INCREMENT = 'INCREMENT';
832 const DECREMENT = 'DECREMENT';
833
@@ -850,11 +850,11 @@ describe('ReactHooksWithNoopRenderer', () => {
850 Counter = forwardRef(Counter);
851 const counter = React.createRef(null);
852 ReactNoop.render(<Counter ref={counter} />);
853 - expect(Scheduler).toFlushAndYield(['Count: 0']);
853 + await waitForAll(['Count: 0']);
854 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
855
856 act(() => counter.current.dispatch(INCREMENT));
857 - expect(Scheduler).toHaveYielded(['Count: 1']);
857 + assertLog(['Count: 1']);
858 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
859 act(() => {
860 counter.current.dispatch(DECREMENT);
@@ -862,11 +862,11 @@ describe('ReactHooksWithNoopRenderer', () => {
862 counter.current.dispatch(DECREMENT);
863 });
864
865 - expect(Scheduler).toHaveYielded(['Count: -2']);
865 + assertLog(['Count: -2']);
866 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: -2" />);
867 });
868
869 - it('lazy init', () => {
869 + it('lazy init', async () => {
870 const INCREMENT = 'INCREMENT';
871 const DECREMENT = 'DECREMENT';
872
@@ -892,11 +892,11 @@ describe('ReactHooksWithNoopRenderer', () => {
892 Counter = forwardRef(Counter);
893 const counter = React.createRef(null);
894 ReactNoop.render(<Counter initialCount={10} ref={counter} />);
895 - expect(Scheduler).toFlushAndYield(['Init', 'Count: 10']);
895 + await waitForAll(['Init', 'Count: 10']);
896 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 10" />);
897
898 act(() => counter.current.dispatch(INCREMENT));
899 - expect(Scheduler).toHaveYielded(['Count: 11']);
899 + assertLog(['Count: 11']);
900 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
901
902 act(() => {
@@ -905,12 +905,12 @@ describe('ReactHooksWithNoopRenderer', () => {
905 counter.current.dispatch(DECREMENT);
906 });
907
908 - expect(Scheduler).toHaveYielded(['Count: 8']);
908 + assertLog(['Count: 8']);
909 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 8" />);
910 });
911
912 // Regression test for https://github.com/facebook/react/issues/14360
913 - it('handles dispatches with mixed priorities', () => {
913 + it('handles dispatches with mixed priorities', async () => {
914 const INCREMENT = 'INCREMENT';
915
916 function reducer(state, action) {
@@ -927,7 +927,7 @@ describe('ReactHooksWithNoopRenderer', () => {
927 const counter = React.createRef(null);
928 ReactNoop.render(<Counter ref={counter} />);
929
930 - expect(Scheduler).toFlushAndYield(['Count: 0']);
930 + await waitForAll(['Count: 0']);
931 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
932
933 ReactNoop.batchedUpdates(() => {
@@ -940,47 +940,47 @@ describe('ReactHooksWithNoopRenderer', () => {
940 counter.current.dispatch(INCREMENT);
941 });
942 if (gate(flags => flags.enableUnifiedSyncLane)) {
943 - expect(Scheduler).toHaveYielded(['Count: 4']);
943 + assertLog(['Count: 4']);
944 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 4" />);
945 } else {
946 - expect(Scheduler).toHaveYielded(['Count: 1']);
946 + assertLog(['Count: 1']);
947 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
948 - expect(Scheduler).toFlushAndYield(['Count: 4']);
948 + await waitForAll(['Count: 4']);
949 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 4" />);
950 }
951 });
952 });
953
954 describe('useEffect', () => {
955 - it('simple mount and update', () => {
955 + it('simple mount and update', async () => {
956 function Counter(props) {
957 useEffect(() => {
958 Scheduler.unstable_yieldValue(`Passive effect [${props.count}]`);
959 });
960 return <Text text={'Count: ' + props.count} />;
961 }
962 - act(() => {
962 + await act(async () => {
963 ReactNoop.render(<Counter count={0} />, () =>
964 Scheduler.unstable_yieldValue('Sync effect'),
965 );
966 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
966 + await waitFor(['Count: 0', 'Sync effect']);
967 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
968 // Effects are deferred until after the commit
969 - expect(Scheduler).toFlushAndYield(['Passive effect [0]']);
969 + await waitForAll(['Passive effect [0]']);
970 });
971
972 - act(() => {
972 + await act(async () => {
973 ReactNoop.render(<Counter count={1} />, () =>
974 Scheduler.unstable_yieldValue('Sync effect'),
975 );
976 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
976 + await waitFor(['Count: 1', 'Sync effect']);
977 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
978 // Effects are deferred until after the commit
979 - expect(Scheduler).toFlushAndYield(['Passive effect [1]']);
979 + await waitForAll(['Passive effect [1]']);
980 });
981 });
982
983 - it('flushes passive effects even with sibling deletions', () => {
983 + it('flushes passive effects even with sibling deletions', async () => {
984 function LayoutEffect(props) {
985 useLayoutEffect(() => {
986 Scheduler.unstable_yieldValue(`Layout effect`);
@@ -994,13 +994,9 @@ describe('ReactHooksWithNoopRenderer', () => {
994 return <Text text="Passive" />;
995 }
996 const passive = <PassiveEffect key="p" />;
997 - act(() => {
997 + await act(async () => {
998 ReactNoop.render([<LayoutEffect key="l" />, passive]);
999 - expect(Scheduler).toFlushAndYieldThrough([
1000 - 'Layout',
1001 - 'Passive',
1002 - 'Layout effect',
1003 - ]);
999 + await waitFor(['Layout', 'Passive', 'Layout effect']);
1000 expect(ReactNoop).toMatchRenderedOutput(
1001 <>
1002 <span prop="Layout" />
@@ -1010,14 +1006,14 @@ describe('ReactHooksWithNoopRenderer', () => {
1006 // Destroying the first child shouldn't prevent the passive effect from
1007 // being executed
1008 ReactNoop.render([passive]);
1013 - expect(Scheduler).toFlushAndYield(['Passive effect']);
1009 + await waitForAll(['Passive effect']);
1010 expect(ReactNoop).toMatchRenderedOutput(<span prop="Passive" />);
1011 });
1012 // exiting act calls flushPassiveEffects(), but there are none left to flush.
1017 - expect(Scheduler).toHaveYielded([]);
1013 + assertLog([]);
1014 });
1015
1020 - it('flushes passive effects even if siblings schedule an update', () => {
1016 + it('flushes passive effects even if siblings schedule an update', async () => {
1017 function PassiveEffect(props) {
1018 useEffect(() => {
1019 Scheduler.unstable_yieldValue('Passive effect');
@@ -1038,8 +1034,8 @@ describe('ReactHooksWithNoopRenderer', () => {
1034
1035 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);
1036
1041 - act(() => {
1042 - expect(Scheduler).toFlushAndYield([
1037 + await act(async () => {
1038 + await waitForAll([
1039 'Passive',
1040 'Layout',
1041 'Layout effect 0',
@@ -1057,7 +1053,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1053 );
1054 });
1055
1060 - it('flushes passive effects even if siblings schedule a new root', () => {
1056 + it('flushes passive effects even if siblings schedule a new root', async () => {
1057 function PassiveEffect(props) {
1058 useEffect(() => {
1059 Scheduler.unstable_yieldValue('Passive effect');
@@ -1072,9 +1068,9 @@ describe('ReactHooksWithNoopRenderer', () => {
1068 });
1069 return <Text text="Layout" />;
1070 }
1075 - act(() => {
1071 + await act(async () => {
1072 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);
1077 - expect(Scheduler).toFlushAndYield([
1073 + await waitForAll([
1074 'Passive',
1075 'Layout',
1076 'Layout effect',
@@ -1093,7 +1089,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1089 it(
1090 'flushes effects serially by flushing old effects before flushing ' +
1091 "new ones, if they haven't already fired",
1096 - () => {
1092 + async () => {
1093 function getCommittedText() {
1094 const children = ReactNoop.getChildrenAsJSX();
1095 if (children === null) {
@@ -1110,17 +1106,17 @@ describe('ReactHooksWithNoopRenderer', () => {
1106 });
1107 return <Text text={props.count} />;
1108 }
1113 - act(() => {
1109 + await act(async () => {
1110 ReactNoop.render(<Counter count={0} />, () =>
1111 Scheduler.unstable_yieldValue('Sync effect'),
1112 );
1117 - expect(Scheduler).toFlushAndYieldThrough([0, 'Sync effect']);
1113 + await waitFor([0, 'Sync effect']);
1114 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
1115 // Before the effects have a chance to flush, schedule another update
1116 ReactNoop.render(<Counter count={1} />, () =>
1117 Scheduler.unstable_yieldValue('Sync effect'),
1118 );
1123 - expect(Scheduler).toFlushAndYieldThrough([
1119 + await waitFor([
1120 // The previous effect flushes before the reconciliation
1121 'Committed state when effect was fired: 0',
1122 1,
@@ -1129,13 +1125,11 @@ describe('ReactHooksWithNoopRenderer', () => {
1125 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
1126 });
1127
1132 - expect(Scheduler).toHaveYielded([
1133 - 'Committed state when effect was fired: 1',
1134 - ]);
1128 + assertLog(['Committed state when effect was fired: 1']);
1129 },
1130 );
1131
1138 - it('defers passive effect destroy functions during unmount', () => {
1132 + it('defers passive effect destroy functions during unmount', async () => {
1133 function Child({bar, foo}) {
1134 React.useEffect(() => {
1135 Scheduler.unstable_yieldValue('passive bar create');
@@ -1165,61 +1159,52 @@ describe('ReactHooksWithNoopRenderer', () => {
1159 return null;
1160 }
1161
1168 - act(() => {
1162 + await act(async () => {
1163 ReactNoop.render(<Child bar={1} foo={1} />, () =>
1164 Scheduler.unstable_yieldValue('Sync effect'),
1165 );
1172 - expect(Scheduler).toFlushAndYieldThrough([
1166 + await waitFor([
1167 'render',
1168 'layout bar create',
1169 'layout foo create',
1170 'Sync effect',
1171 ]);
1172 // Effects are deferred until after the commit
1179 - expect(Scheduler).toFlushAndYield([
1180 - 'passive bar create',
1181 - 'passive foo create',
1182 - ]);
1173 + await waitForAll(['passive bar create', 'passive foo create']);
1174 });
1175
1176 // This update exists to test an internal implementation detail:
1177 // Effects without updating dependencies lose their layout/passive tag during an update.
1187 - act(() => {
1178 + await act(async () => {
1179 ReactNoop.render(<Child bar={1} foo={2} />, () =>
1180 Scheduler.unstable_yieldValue('Sync effect'),
1181 );
1191 - expect(Scheduler).toFlushAndYieldThrough([
1182 + await waitFor([
1183 'render',
1184 'layout foo destroy',
1185 'layout foo create',
1186 'Sync effect',
1187 ]);
1188 // Effects are deferred until after the commit
1198 - expect(Scheduler).toFlushAndYield([
1199 - 'passive foo destroy',
1200 - 'passive foo create',
1201 - ]);
1189 + await waitForAll(['passive foo destroy', 'passive foo create']);
1190 });
1191
1192 // Unmount the component and verify that passive destroy functions are deferred until post-commit.
1205 - act(() => {
1193 + await act(async () => {
1194 ReactNoop.render(null, () =>
1195 Scheduler.unstable_yieldValue('Sync effect'),
1196 );
1209 - expect(Scheduler).toFlushAndYieldThrough([
1197 + await waitFor([
1198 'layout bar destroy',
1199 'layout foo destroy',
1200 'Sync effect',
1201 ]);
1202 // Effects are deferred until after the commit
1215 - expect(Scheduler).toFlushAndYield([
1216 - 'passive bar destroy',
1217 - 'passive foo destroy',
1218 - ]);
1203 + await waitForAll(['passive bar destroy', 'passive foo destroy']);
1204 });
1205 });
1206
1222 - it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
1207 + it('does not warn about state updates for unmounted components with pending passive unmounts', async () => {
1208 let completePendingRequest = null;
1209 function Component() {
1210 Scheduler.unstable_yieldValue('Component');
@@ -1241,32 +1226,28 @@ describe('ReactHooksWithNoopRenderer', () => {
1226 return didLoad;
1227 }
1228
1244 - act(() => {
1229 + await act(async () => {
1230 ReactNoop.renderToRootWithID(<Component />, 'root', () =>
1231 Scheduler.unstable_yieldValue('Sync effect'),
1232 );
1248 - expect(Scheduler).toFlushAndYieldThrough([
1249 - 'Component',
1250 - 'layout create',
1251 - 'Sync effect',
1252 - ]);
1233 + await waitFor(['Component', 'layout create', 'Sync effect']);
1234 ReactNoop.flushPassiveEffects();
1254 - expect(Scheduler).toHaveYielded(['passive create']);
1235 + assertLog(['passive create']);
1236
1237 // Unmount but don't process pending passive destroy function
1238 ReactNoop.unmountRootWithID('root');
1258 - expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
1239 + await waitFor(['layout destroy']);
1240
1241 // Simulate an XHR completing, which will cause a state update-
1242 // but should not log a warning.
1243 completePendingRequest();
1244
1245 ReactNoop.flushPassiveEffects();
1265 - expect(Scheduler).toHaveYielded(['passive destroy']);
1246 + assertLog(['passive destroy']);
1247 });
1248 });
1249
1269 - it('does not warn about state updates for unmounted components with pending passive unmounts for alternates', () => {
1250 + it('does not warn about state updates for unmounted components with pending passive unmounts for alternates', async () => {
1251 let setParentState = null;
1252 const setChildStates = [];
1253
@@ -1309,9 +1290,9 @@ describe('ReactHooksWithNoopRenderer', () => {
1290 // later tick: schedule unmount for parent
1291 // start process unmount (but don't flush passive effectS)
1292 // State update on child
1312 - act(() => {
1293 + await act(async () => {
1294 ReactNoop.render(<Parent />);
1314 - expect(Scheduler).toFlushAndYieldThrough([
1295 + await waitFor([
1296 'Parent true render',
1297 'Child one render',
1298 'Child two render',
@@ -1324,7 +1305,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1305
1306 // Update children.
1307 setChildStates.forEach(setChildState => setChildState(1));
1327 - expect(Scheduler).toFlushAndYieldThrough([
1308 + await waitFor([
1309 'Child one render',
1310 'Child two render',
1311 'Child one commit',
@@ -1335,27 +1316,24 @@ describe('ReactHooksWithNoopRenderer', () => {
1316 React.startTransition(() => {
1317 setChildStates.forEach(setChildState => setChildState(2));
1318 });
1338 - expect(Scheduler).toFlushAndYieldThrough(['Child one render']);
1319 + await waitFor(['Child one render']);
1320
1321 // Schedule unmount for the parent that unmounts children with pending update.
1322 ReactNoop.unstable_runWithPriority(ContinuousEventPriority, () => {
1323 setParentState(false);
1324 });
1344 - expect(Scheduler).toFlushUntilNextPaint([
1345 - 'Parent false render',
1346 - 'Parent false commit',
1347 - ]);
1325 + await waitForPaint(['Parent false render', 'Parent false commit']);
1326
1327 // Schedule updates for children too (which should be ignored)
1328 setChildStates.forEach(setChildState => setChildState(2));
1351 - expect(Scheduler).toFlushAndYield([
1329 + await waitForAll([
1330 'Child one passive destroy',
1331 'Child two passive destroy',
1332 ]);
1333 });
1334 });
1335
1358 - it('does not warn about state updates for unmounted components with no pending passive unmounts', () => {
1336 + it('does not warn about state updates for unmounted components with no pending passive unmounts', async () => {
1337 let completePendingRequest = null;
1338 function Component() {
1339 Scheduler.unstable_yieldValue('Component');
@@ -1371,26 +1349,22 @@ describe('ReactHooksWithNoopRenderer', () => {
1349 return didLoad;
1350 }
1351
1374 - act(() => {
1352 + await act(async () => {
1353 ReactNoop.renderToRootWithID(<Component />, 'root', () =>
1354 Scheduler.unstable_yieldValue('Sync effect'),
1355 );
1378 - expect(Scheduler).toFlushAndYieldThrough([
1379 - 'Component',
1380 - 'layout create',
1381 - 'Sync effect',
1382 - ]);
1356 + await waitFor(['Component', 'layout create', 'Sync effect']);
1357
1358 // Unmount but don't process pending passive destroy function
1359 ReactNoop.unmountRootWithID('root');
1386 - expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
1360 + await waitFor(['layout destroy']);
1361
1362 // Simulate an XHR completing.
1363 completePendingRequest();
1364 });
1365 });
1366
1393 - it('does not warn if there are pending passive unmount effects but not for the current fiber', () => {
1367 + it('does not warn if there are pending passive unmount effects but not for the current fiber', async () => {
1368 let completePendingRequest = null;
1369 function ComponentWithXHR() {
1370 Scheduler.unstable_yieldValue('Component');
@@ -1419,7 +1393,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1393 return null;
1394 }
1395
1422 - act(() => {
1396 + await act(async () => {
1397 ReactNoop.renderToRootWithID(
1398 <>
1399 <ComponentWithXHR />
@@ -1428,27 +1402,20 @@ describe('ReactHooksWithNoopRenderer', () => {
1402 'root',
1403 () => Scheduler.unstable_yieldValue('Sync effect'),
1404 );
1431 - expect(Scheduler).toFlushAndYieldThrough([
1432 - 'Component',
1433 - 'a:layout create',
1434 - 'Sync effect',
1435 - ]);
1405 + await waitFor(['Component', 'a:layout create', 'Sync effect']);
1406 ReactNoop.flushPassiveEffects();
1437 - expect(Scheduler).toHaveYielded([
1438 - 'a:passive create',
1439 - 'b:passive create',
1440 - ]);
1407 + assertLog(['a:passive create', 'b:passive create']);
1408
1409 // Unmount but don't process pending passive destroy function
1410 ReactNoop.unmountRootWithID('root');
1444 - expect(Scheduler).toFlushAndYieldThrough(['a:layout destroy']);
1411 + await waitFor(['a:layout destroy']);
1412
1413 // Simulate an XHR completing in the component without a pending passive effect..
1414 completePendingRequest();
1415 });
1416 });
1417
1451 - it('does not warn if there are updates after pending passive unmount effects have been flushed', () => {
1418 + it('does not warn if there are updates after pending passive unmount effects have been flushed', async () => {
1419 let updaterFunction;
1420
1421 function Component() {
@@ -1469,21 +1436,17 @@ describe('ReactHooksWithNoopRenderer', () => {
1436 Scheduler.unstable_yieldValue('Sync effect'),
1437 );
1438 });
1472 - expect(Scheduler).toHaveYielded([
1473 - 'Component',
1474 - 'Sync effect',
1475 - 'passive create',
1476 - ]);
1439 + assertLog(['Component', 'Sync effect', 'passive create']);
1440
1441 ReactNoop.unmountRootWithID('root');
1479 - expect(Scheduler).toFlushAndYield(['passive destroy']);
1442 + await waitForAll(['passive destroy']);
1443
1444 act(() => {
1445 updaterFunction(true);
1446 });
1447 });
1448
1486 - it('does not show a warning when a component updates its own state from within passive unmount function', () => {
1449 + it('does not show a warning when a component updates its own state from within passive unmount function', async () => {
1450 function Component() {
1451 Scheduler.unstable_yieldValue('Component');
1452 const [didLoad, setDidLoad] = React.useState(false);
@@ -1497,23 +1460,19 @@ describe('ReactHooksWithNoopRenderer', () => {
1460 return didLoad;
1461 }
1462
1500 - act(() => {
1463 + await act(async () => {
1464 ReactNoop.renderToRootWithID(<Component />, 'root', () =>
1465 Scheduler.unstable_yieldValue('Sync effect'),
1466 );
1504 - expect(Scheduler).toFlushAndYieldThrough([
1505 - 'Component',
1506 - 'Sync effect',
1507 - 'passive create',
1508 - ]);
1467 + await waitFor(['Component', 'Sync effect', 'passive create']);
1468
1469 // Unmount but don't process pending passive destroy function
1470 ReactNoop.unmountRootWithID('root');
1512 - expect(Scheduler).toFlushAndYield(['passive destroy']);
1471 + await waitForAll(['passive destroy']);
1472 });
1473 });
1474
1516 - it('does not show a warning when a component updates a child state from within passive unmount function', () => {
1475 + it('does not show a warning when a component updates a child state from within passive unmount function', async () => {
1476 function Parent() {
1477 Scheduler.unstable_yieldValue('Parent');
1478 const updaterRef = useRef(null);
@@ -1537,9 +1496,9 @@ describe('ReactHooksWithNoopRenderer', () => {
1496 return state;
1497 }
1498
1540 - act(() => {
1499 + await act(async () => {
1500 ReactNoop.renderToRootWithID(<Parent />, 'root');
1542 - expect(Scheduler).toFlushAndYieldThrough([
1501 + await waitFor([
1502 'Parent',
1503 'Child',
1504 'Child passive create',
@@ -1548,11 +1507,11 @@ describe('ReactHooksWithNoopRenderer', () => {
1507
1508 // Unmount but don't process pending passive destroy function
1509 ReactNoop.unmountRootWithID('root');
1551 - expect(Scheduler).toFlushAndYield(['Parent passive destroy']);
1510 + await waitForAll(['Parent passive destroy']);
1511 });
1512 });
1513
1555 - it('does not show a warning when a component updates a parents state from within passive unmount function', () => {
1514 + it('does not show a warning when a component updates a parents state from within passive unmount function', async () => {
1515 function Parent() {
1516 const [state, setState] = React.useState(false);
1517 Scheduler.unstable_yieldValue('Parent');
@@ -1571,21 +1530,17 @@ describe('ReactHooksWithNoopRenderer', () => {
1530 return state;
1531 }
1532
1574 - act(() => {
1533 + await act(async () => {
1534 ReactNoop.renderToRootWithID(<Parent />, 'root');
1576 - expect(Scheduler).toFlushAndYieldThrough([
1577 - 'Parent',
1578 - 'Child',
1579 - 'Child passive create',
1580 - ]);
1535 + await waitFor(['Parent', 'Child', 'Child passive create']);
1536
1537 // Unmount but don't process pending passive destroy function
1538 ReactNoop.unmountRootWithID('root');
1584 - expect(Scheduler).toFlushAndYield(['Child passive destroy']);
1539 + await waitForAll(['Child passive destroy']);
1540 });
1541 });
1542
1588 - it('updates have async priority', () => {
1543 + it('updates have async priority', async () => {
1544 function Counter(props) {
1545 const [count, updateCount] = useState('(empty)');
1546 useEffect(() => {
@@ -1594,33 +1549,30 @@ describe('ReactHooksWithNoopRenderer', () => {
1549 }, [props.count]);
1550 return <Text text={'Count: ' + count} />;
1551 }
1597 - act(() => {
1552 + await act(async () => {
1553 ReactNoop.render(<Counter count={0} />, () =>
1554 Scheduler.unstable_yieldValue('Sync effect'),
1555 );
1601 - expect(Scheduler).toFlushAndYieldThrough([
1602 - 'Count: (empty)',
1603 - 'Sync effect',
1604 - ]);
1556 + await waitFor(['Count: (empty)', 'Sync effect']);
1557 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: (empty)" />);
1558 ReactNoop.flushPassiveEffects();
1607 - expect(Scheduler).toHaveYielded(['Schedule update [0]']);
1608 - expect(Scheduler).toFlushAndYield(['Count: 0']);
1559 + assertLog(['Schedule update [0]']);
1560 + await waitForAll(['Count: 0']);
1561 });
1562
1611 - act(() => {
1563 + await act(async () => {
1564 ReactNoop.render(<Counter count={1} />, () =>
1565 Scheduler.unstable_yieldValue('Sync effect'),
1566 );
1615 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1567 + await waitFor(['Count: 0', 'Sync effect']);
1568 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1569 ReactNoop.flushPassiveEffects();
1618 - expect(Scheduler).toHaveYielded(['Schedule update [1]']);
1619 - expect(Scheduler).toFlushAndYield(['Count: 1']);
1570 + assertLog(['Schedule update [1]']);
1571 + await waitForAll(['Count: 1']);
1572 });
1573 });
1574
1623 - it('updates have async priority even if effects are flushed early', () => {
1575 + it('updates have async priority even if effects are flushed early', async () => {
1576 function Counter(props) {
1577 const [count, updateCount] = useState('(empty)');
1578 useEffect(() => {
@@ -1629,14 +1581,11 @@ describe('ReactHooksWithNoopRenderer', () => {
1581 }, [props.count]);
1582 return <Text text={'Count: ' + count} />;
1583 }
1632 - act(() => {
1584 + await act(async () => {
1585 ReactNoop.render(<Counter count={0} />, () =>
1586 Scheduler.unstable_yieldValue('Sync effect'),
1587 );
1636 - expect(Scheduler).toFlushAndYieldThrough([
1637 - 'Count: (empty)',
1638 - 'Sync effect',
1639 - ]);
1588 + await waitFor(['Count: (empty)', 'Sync effect']);
1589 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: (empty)" />);
1590
1591 // Rendering again should flush the previous commit's effects
@@ -1646,13 +1595,10 @@ describe('ReactHooksWithNoopRenderer', () => {
1595 );
1596 });
1597
1649 - expect(Scheduler).toFlushAndYieldThrough([
1650 - 'Schedule update [0]',
1651 - 'Count: 0',
1652 - ]);
1598 + await waitFor(['Schedule update [0]', 'Count: 0']);
1599
1600 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1655 - expect(Scheduler).toFlushAndYieldThrough([
1601 + await waitFor([
1602 'Count: 0',
1603 'Sync effect',
1604 'Schedule update [1]',
@@ -1663,7 +1609,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1609 });
1610 });
1611
1666 - it('does not flush non-discrete passive effects when flushing sync', () => {
1612 + it('does not flush non-discrete passive effects when flushing sync', async () => {
1613 let _updateCount;
1614 function Counter(props) {
1615 const [count, updateCount] = useState(0);
@@ -1678,7 +1624,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1624 ReactNoop.render(<Counter count={0} />, () =>
1625 Scheduler.unstable_yieldValue('Sync effect'),
1626 );
1681 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1627 + await waitFor(['Count: 0', 'Sync effect']);
1628 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1629 // A flush sync doesn't cause the passive effects to fire.
1630 // So we haven't added the other update yet.
@@ -1692,13 +1638,9 @@ describe('ReactHooksWithNoopRenderer', () => {
1638 // This should be fine because any non-discrete set of work doesn't guarantee order
1639 // and easily could've happened slightly later too.
1640 if (gate(flags => flags.enableUnifiedSyncLane)) {
1695 - expect(Scheduler).toHaveYielded(['Will set count to 1', 'Count: 1']);
1641 + assertLog(['Will set count to 1', 'Count: 1']);
1642 } else {
1697 - expect(Scheduler).toHaveYielded([
1698 - 'Will set count to 1',
1699 - 'Count: 2',
1700 - 'Count: 1',
1701 - ]);
1643 + assertLog(['Will set count to 1', 'Count: 2', 'Count: 1']);
1644 }
1645
1646 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
@@ -1728,7 +1670,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1670 });
1671
1672 // Even in legacy mode, effects are deferred until after paint
1731 - expect(Scheduler).toHaveYielded(['Count: (empty)']);
1673 + assertLog(['Count: (empty)']);
1674 expect(ReactNoop).toMatchRenderedOutput(
1675 <span prop="Count: (empty)" />,
1676 );
@@ -1737,12 +1679,12 @@ describe('ReactHooksWithNoopRenderer', () => {
1679 // effects get forced on exiting act()
1680 // There were multiple updates, but there should only be a
1681 // single render
1740 - expect(Scheduler).toHaveYielded(['Count: 0']);
1682 + assertLog(['Count: 0']);
1683 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1684 },
1685 );
1686
1745 - it('flushSync is not allowed', () => {
1687 + it('flushSync is not allowed', async () => {
1688 function Counter(props) {
1689 const [count, updateCount] = useState('(empty)');
1690 useEffect(() => {
@@ -1750,7 +1692,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1692 ReactNoop.flushSync(() => {
1693 updateCount(props.count);
1694 });
1753 - expect(Scheduler).toHaveYielded([`Schedule update [${props.count}]`]);
1695 + assertLog([`Schedule update [${props.count}]`]);
1696 // This shouldn't flush synchronously.
1697 expect(ReactNoop).not.toMatchRenderedOutput(
1698 <span prop={`Count: ${props.count}`} />,
@@ -1758,25 +1700,22 @@ describe('ReactHooksWithNoopRenderer', () => {
1700 }, [props.count]);
1701 return <Text text={'Count: ' + count} />;
1702 }
1761 - expect(() => {
1762 - act(() => {
1703 + await expect(async () => {
1704 + await act(async () => {
1705 ReactNoop.render(<Counter count={0} />, () =>
1706 Scheduler.unstable_yieldValue('Sync effect'),
1707 );
1766 - expect(Scheduler).toFlushAndYieldThrough([
1767 - 'Count: (empty)',
1768 - 'Sync effect',
1769 - ]);
1708 + await waitFor(['Count: (empty)', 'Sync effect']);
1709 expect(ReactNoop).toMatchRenderedOutput(
1710 <span prop="Count: (empty)" />,
1711 );
1712 });
1713 }).toErrorDev('flushSync was called from inside a lifecycle method');
1775 - expect(Scheduler).toHaveYielded([`Count: 0`]);
1714 + assertLog([`Count: 0`]);
1715 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1716 });
1717
1779 - it('unmounts previous effect', () => {
1718 + it('unmounts previous effect', async () => {
1719 function Counter(props) {
1720 useEffect(() => {
1721 Scheduler.unstable_yieldValue(`Did create [${props.count}]`);
@@ -1786,28 +1725,28 @@ describe('ReactHooksWithNoopRenderer', () => {
1725 });
1726 return <Text text={'Count: ' + props.count} />;
1727 }
1789 - act(() => {
1728 + await act(async () => {
1729 ReactNoop.render(<Counter count={0} />, () =>
1730 Scheduler.unstable_yieldValue('Sync effect'),
1731 );
1793 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1732 + await waitFor(['Count: 0', 'Sync effect']);
1733 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1734 });
1735
1797 - expect(Scheduler).toHaveYielded(['Did create [0]']);
1736 + assertLog(['Did create [0]']);
1737
1799 - act(() => {
1738 + await act(async () => {
1739 ReactNoop.render(<Counter count={1} />, () =>
1740 Scheduler.unstable_yieldValue('Sync effect'),
1741 );
1803 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1742 + await waitFor(['Count: 1', 'Sync effect']);
1743 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1744 });
1745
1807 - expect(Scheduler).toHaveYielded(['Did destroy [0]', 'Did create [1]']);
1746 + assertLog(['Did destroy [0]', 'Did create [1]']);
1747 });
1748
1810 - it('unmounts on deletion', () => {
1749 + it('unmounts on deletion', async () => {
1750 function Counter(props) {
1751 useEffect(() => {
1752 Scheduler.unstable_yieldValue(`Did create [${props.count}]`);
@@ -1817,22 +1756,22 @@ describe('ReactHooksWithNoopRenderer', () => {
1756 });
1757 return <Text text={'Count: ' + props.count} />;
1758 }
1820 - act(() => {
1759 + await act(async () => {
1760 ReactNoop.render(<Counter count={0} />, () =>
1761 Scheduler.unstable_yieldValue('Sync effect'),
1762 );
1824 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1763 + await waitFor(['Count: 0', 'Sync effect']);
1764 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1765 });
1766
1828 - expect(Scheduler).toHaveYielded(['Did create [0]']);
1767 + assertLog(['Did create [0]']);
1768
1769 ReactNoop.render(null);
1831 - expect(Scheduler).toFlushAndYield(['Did destroy [0]']);
1770 + await waitForAll(['Did destroy [0]']);
1771 expect(ReactNoop).toMatchRenderedOutput(null);
1772 });
1773
1835 - it('unmounts on deletion after skipped effect', () => {
1774 + it('unmounts on deletion after skipped effect', async () => {
1775 function Counter(props) {
1776 useEffect(() => {
1777 Scheduler.unstable_yieldValue(`Did create [${props.count}]`);
@@ -1842,32 +1781,32 @@ describe('ReactHooksWithNoopRenderer', () => {
1781 }, []);
1782 return <Text text={'Count: ' + props.count} />;
1783 }
1845 - act(() => {
1784 + await act(async () => {
1785 ReactNoop.render(<Counter count={0} />, () =>
1786 Scheduler.unstable_yieldValue('Sync effect'),
1787 );
1849 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1788 + await waitFor(['Count: 0', 'Sync effect']);
1789 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1790 });
1791
1853 - expect(Scheduler).toHaveYielded(['Did create [0]']);
1792 + assertLog(['Did create [0]']);
1793
1855 - act(() => {
1794 + await act(async () => {
1795 ReactNoop.render(<Counter count={1} />, () =>
1796 Scheduler.unstable_yieldValue('Sync effect'),
1797 );
1859 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1798 + await waitFor(['Count: 1', 'Sync effect']);
1799 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1800 });
1801
1863 - expect(Scheduler).toHaveYielded([]);
1802 + assertLog([]);
1803
1804 ReactNoop.render(null);
1866 - expect(Scheduler).toFlushAndYield(['Did destroy [0]']);
1805 + await waitForAll(['Did destroy [0]']);
1806 expect(ReactNoop).toMatchRenderedOutput(null);
1807 });
1808
1870 - it('always fires effects if no dependencies are provided', () => {
1809 + it('always fires effects if no dependencies are provided', async () => {
1810 function effect() {
1811 Scheduler.unstable_yieldValue(`Did create`);
1812 return () => {
@@ -1878,32 +1817,32 @@ describe('ReactHooksWithNoopRenderer', () => {
1817 useEffect(effect);
1818 return <Text text={'Count: ' + props.count} />;
1819 }
1881 - act(() => {
1820 + await act(async () => {
1821 ReactNoop.render(<Counter count={0} />, () =>
1822 Scheduler.unstable_yieldValue('Sync effect'),
1823 );
1885 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1824 + await waitFor(['Count: 0', 'Sync effect']);
1825 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1826 });
1827
1889 - expect(Scheduler).toHaveYielded(['Did create']);
1828 + assertLog(['Did create']);
1829
1891 - act(() => {
1830 + await act(async () => {
1831 ReactNoop.render(<Counter count={1} />, () =>
1832 Scheduler.unstable_yieldValue('Sync effect'),
1833 );
1895 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1834 + await waitFor(['Count: 1', 'Sync effect']);
1835 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1836 });
1837
1899 - expect(Scheduler).toHaveYielded(['Did destroy', 'Did create']);
1838 + assertLog(['Did destroy', 'Did create']);
1839
1840 ReactNoop.render(null);
1902 - expect(Scheduler).toFlushAndYield(['Did destroy']);
1841 + await waitForAll(['Did destroy']);
1842 expect(ReactNoop).toMatchRenderedOutput(null);
1843 });
1844
1906 - it('skips effect if inputs have not changed', () => {
1845 + it('skips effect if inputs have not changed', async () => {
1846 function Counter(props) {
1847 const text = `${props.label}: ${props.count}`;
1848 useEffect(() => {
@@ -1914,57 +1853,51 @@ describe('ReactHooksWithNoopRenderer', () => {
1853 }, [props.label, props.count]);
1854 return <Text text={text} />;
1855 }
1917 - act(() => {
1856 + await act(async () => {
1857 ReactNoop.render(<Counter label="Count" count={0} />, () =>
1858 Scheduler.unstable_yieldValue('Sync effect'),
1859 );
1921 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1860 + await waitFor(['Count: 0', 'Sync effect']);
1861 });
1862
1924 - expect(Scheduler).toHaveYielded(['Did create [Count: 0]']);
1863 + assertLog(['Did create [Count: 0]']);
1864 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1865
1927 - act(() => {
1866 + await act(async () => {
1867 ReactNoop.render(<Counter label="Count" count={1} />, () =>
1868 Scheduler.unstable_yieldValue('Sync effect'),
1869 );
1870 // Count changed
1932 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1871 + await waitFor(['Count: 1', 'Sync effect']);
1872 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1873 });
1874
1936 - expect(Scheduler).toHaveYielded([
1937 - 'Did destroy [Count: 0]',
1938 - 'Did create [Count: 1]',
1939 - ]);
1875 + assertLog(['Did destroy [Count: 0]', 'Did create [Count: 1]']);
1876
1941 - act(() => {
1877 + await act(async () => {
1878 ReactNoop.render(<Counter label="Count" count={1} />, () =>
1879 Scheduler.unstable_yieldValue('Sync effect'),
1880 );
1881 // Nothing changed, so no effect should have fired
1946 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1882 + await waitFor(['Count: 1', 'Sync effect']);
1883 });
1884
1949 - expect(Scheduler).toHaveYielded([]);
1885 + assertLog([]);
1886 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1887
1952 - act(() => {
1888 + await act(async () => {
1889 ReactNoop.render(<Counter label="Total" count={1} />, () =>
1890 Scheduler.unstable_yieldValue('Sync effect'),
1891 );
1892 // Label changed
1957 - expect(Scheduler).toFlushAndYieldThrough(['Total: 1', 'Sync effect']);
1893 + await waitFor(['Total: 1', 'Sync effect']);
1894 expect(ReactNoop).toMatchRenderedOutput(<span prop="Total: 1" />);
1895 });
1896
1961 - expect(Scheduler).toHaveYielded([
1962 - 'Did destroy [Count: 1]',
1963 - 'Did create [Total: 1]',
1964 - ]);
1897 + assertLog(['Did destroy [Count: 1]', 'Did create [Total: 1]']);
1898 });
1899
1967 - it('multiple effects', () => {
1900 + it('multiple effects', async () => {
1901 function Counter(props) {
1902 useEffect(() => {
1903 Scheduler.unstable_yieldValue(`Did commit 1 [${props.count}]`);
@@ -1974,27 +1907,27 @@ describe('ReactHooksWithNoopRenderer', () => {
1907 });
1908 return <Text text={'Count: ' + props.count} />;
1909 }
1977 - act(() => {
1910 + await act(async () => {
1911 ReactNoop.render(<Counter count={0} />, () =>
1912 Scheduler.unstable_yieldValue('Sync effect'),
1913 );
1981 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1914 + await waitFor(['Count: 0', 'Sync effect']);
1915 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1916 });
1917
1985 - expect(Scheduler).toHaveYielded(['Did commit 1 [0]', 'Did commit 2 [0]']);
1918 + assertLog(['Did commit 1 [0]', 'Did commit 2 [0]']);
1919
1987 - act(() => {
1920 + await act(async () => {
1921 ReactNoop.render(<Counter count={1} />, () =>
1922 Scheduler.unstable_yieldValue('Sync effect'),
1923 );
1991 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1924 + await waitFor(['Count: 1', 'Sync effect']);
1925 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1926 });
1994 - expect(Scheduler).toHaveYielded(['Did commit 1 [1]', 'Did commit 2 [1]']);
1927 + assertLog(['Did commit 1 [1]', 'Did commit 2 [1]']);
1928 });
1929
1997 - it('unmounts all previous effects before creating any new ones', () => {
1930 + it('unmounts all previous effects before creating any new ones', async () => {
1931 function Counter(props) {
1932 useEffect(() => {
1933 Scheduler.unstable_yieldValue(`Mount A [${props.count}]`);
@@ -2010,24 +1943,24 @@ describe('ReactHooksWithNoopRenderer', () => {
1943 });
1944 return <Text text={'Count: ' + props.count} />;
1945 }
2013 - act(() => {
1946 + await act(async () => {
1947 ReactNoop.render(<Counter count={0} />, () =>
1948 Scheduler.unstable_yieldValue('Sync effect'),
1949 );
2017 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
1950 + await waitFor(['Count: 0', 'Sync effect']);
1951 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1952 });
1953
2021 - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
1954 + assertLog(['Mount A [0]', 'Mount B [0]']);
1955
2023 - act(() => {
1956 + await act(async () => {
1957 ReactNoop.render(<Counter count={1} />, () =>
1958 Scheduler.unstable_yieldValue('Sync effect'),
1959 );
2027 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
1960 + await waitFor(['Count: 1', 'Sync effect']);
1961 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
1962 });
2030 - expect(Scheduler).toHaveYielded([
1963 + assertLog([
1964 'Unmount A [0]',
1965 'Unmount B [0]',
1966 'Mount A [1]',
@@ -2035,7 +1968,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1968 ]);
1969 });
1970
2038 - it('unmounts all previous effects between siblings before creating any new ones', () => {
1971 + it('unmounts all previous effects between siblings before creating any new ones', async () => {
1972 function Counter({count, label}) {
1973 useEffect(() => {
1974 Scheduler.unstable_yieldValue(`Mount ${label} [${count}]`);
@@ -2045,7 +1978,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1978 });
1979 return <Text text={`${label} ${count}`} />;
1980 }
2048 - act(() => {
1981 + await act(async () => {
1982 ReactNoop.render(
1983 <>
1984 <Counter label="A" count={0} />
@@ -2053,7 +1986,7 @@ describe('ReactHooksWithNoopRenderer', () => {
1986 </>,
1987 () => Scheduler.unstable_yieldValue('Sync effect'),
1988 );
2056 - expect(Scheduler).toFlushAndYieldThrough(['A 0', 'B 0', 'Sync effect']);
1989 + await waitFor(['A 0', 'B 0', 'Sync effect']);
1990 expect(ReactNoop).toMatchRenderedOutput(
1991 <>
1992 <span prop="A 0" />
@@ -2062,9 +1995,9 @@ describe('ReactHooksWithNoopRenderer', () => {
1995 );
1996 });
1997
2065 - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
1998 + assertLog(['Mount A [0]', 'Mount B [0]']);
1999
2067 - act(() => {
2000 + await act(async () => {
2001 ReactNoop.render(
2002 <>
2003 <Counter label="A" count={1} />
@@ -2072,7 +2005,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2005 </>,
2006 () => Scheduler.unstable_yieldValue('Sync effect'),
2007 );
2075 - expect(Scheduler).toFlushAndYieldThrough(['A 1', 'B 1', 'Sync effect']);
2008 + await waitFor(['A 1', 'B 1', 'Sync effect']);
2009 expect(ReactNoop).toMatchRenderedOutput(
2010 <>
2011 <span prop="A 1" />
@@ -2080,14 +2013,14 @@ describe('ReactHooksWithNoopRenderer', () => {
2013 </>,
2014 );
2015 });
2083 - expect(Scheduler).toHaveYielded([
2016 + assertLog([
2017 'Unmount A [0]',
2018 'Unmount B [0]',
2019 'Mount A [1]',
2020 'Mount B [1]',
2021 ]);
2022
2090 - act(() => {
2023 + await act(async () => {
2024 ReactNoop.render(
2025 <>
2026 <Counter label="B" count={2} />
@@ -2095,7 +2028,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2028 </>,
2029 () => Scheduler.unstable_yieldValue('Sync effect'),
2030 );
2098 - expect(Scheduler).toFlushAndYieldThrough(['B 2', 'C 0', 'Sync effect']);
2031 + await waitFor(['B 2', 'C 0', 'Sync effect']);
2032 expect(ReactNoop).toMatchRenderedOutput(
2033 <>
2034 <span prop="B 2" />
@@ -2103,7 +2036,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2036 </>,
2037 );
2038 });
2106 - expect(Scheduler).toHaveYielded([
2039 + assertLog([
2040 'Unmount A [1]',
2041 'Unmount B [1]',
2042 'Mount B [2]',
@@ -2111,7 +2044,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2044 ]);
2045 });
2046
2114 - it('handles errors in create on mount', () => {
2047 + it('handles errors in create on mount', async () => {
2048 function Counter(props) {
2049 useEffect(() => {
2050 Scheduler.unstable_yieldValue(`Mount A [${props.count}]`);
@@ -2130,16 +2063,16 @@ describe('ReactHooksWithNoopRenderer', () => {
2063 });
2064 return <Text text={'Count: ' + props.count} />;
2065 }
2133 - act(() => {
2066 + await act(async () => {
2067 ReactNoop.render(<Counter count={0} />, () =>
2068 Scheduler.unstable_yieldValue('Sync effect'),
2069 );
2137 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
2070 + await waitFor(['Count: 0', 'Sync effect']);
2071 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
2072 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2073 });
2074
2142 - expect(Scheduler).toHaveYielded([
2075 + assertLog([
2076 'Mount A [0]',
2077 'Oops!',
2078 // Clean up effect A. There's no effect B to clean-up, because it
@@ -2149,7 +2082,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2082 expect(ReactNoop).toMatchRenderedOutput(null);
2083 });
2084
2152 - it('handles errors in create on update', () => {
2085 + it('handles errors in create on update', async () => {
2086 function Counter(props) {
2087 useEffect(() => {
2088 Scheduler.unstable_yieldValue(`Mount A [${props.count}]`);
@@ -2169,40 +2102,35 @@ describe('ReactHooksWithNoopRenderer', () => {
2102 });
2103 return <Text text={'Count: ' + props.count} />;
2104 }
2172 - act(() => {
2105 + await act(async () => {
2106 ReactNoop.render(<Counter count={0} />, () =>
2107 Scheduler.unstable_yieldValue('Sync effect'),
2108 );
2176 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
2109 + await waitFor(['Count: 0', 'Sync effect']);
2110 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
2111 ReactNoop.flushPassiveEffects();
2179 - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
2112 + assertLog(['Mount A [0]', 'Mount B [0]']);
2113 });
2114
2182 - act(() => {
2115 + await act(async () => {
2116 // This update will trigger an error
2117 ReactNoop.render(<Counter count={1} />, () =>
2118 Scheduler.unstable_yieldValue('Sync effect'),
2119 );
2187 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
2120 + await waitFor(['Count: 1', 'Sync effect']);
2121 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
2122 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2190 - expect(Scheduler).toHaveYielded([
2191 - 'Unmount A [0]',
2192 - 'Unmount B [0]',
2193 - 'Mount A [1]',
2194 - 'Oops!',
2195 - ]);
2123 + assertLog(['Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Oops!']);
2124 expect(ReactNoop).toMatchRenderedOutput(null);
2125 });
2198 - expect(Scheduler).toHaveYielded([
2126 + assertLog([
2127 // Clean up effect A runs passively on unmount.
2128 // There's no effect B to clean-up, because it never mounted.
2129 'Unmount A [1]',
2130 ]);
2131 });
2132
2205 - it('handles errors in destroy on update', () => {
2133 + it('handles errors in destroy on update', async () => {
2134 function Counter(props) {
2135 useEffect(() => {
2136 Scheduler.unstable_yieldValue(`Mount A [${props.count}]`);
@@ -2222,22 +2150,22 @@ describe('ReactHooksWithNoopRenderer', () => {
2150 return <Text text={'Count: ' + props.count} />;
2151 }
2152
2225 - act(() => {
2153 + await act(async () => {
2154 ReactNoop.render(<Counter count={0} />, () =>
2155 Scheduler.unstable_yieldValue('Sync effect'),
2156 );
2229 - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
2157 + await waitFor(['Count: 0', 'Sync effect']);
2158 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
2159 ReactNoop.flushPassiveEffects();
2232 - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
2160 + assertLog(['Mount A [0]', 'Mount B [0]']);
2161 });
2162
2235 - act(() => {
2163 + await act(async () => {
2164 // This update will trigger an error during passive effect unmount
2165 ReactNoop.render(<Counter count={1} />, () =>
2166 Scheduler.unstable_yieldValue('Sync effect'),
2167 );
2240 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
2168 + await waitFor(['Count: 1', 'Sync effect']);
2169 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
2170 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2171
@@ -2245,22 +2173,17 @@ describe('ReactHooksWithNoopRenderer', () => {
2173 // separate pass before flushing any passive creates.
2174 // A result of this two-pass flush is that an error thrown from unmount does
2175 // not block the subsequent create functions from being run.
2248 - expect(Scheduler).toHaveYielded([
2249 - 'Oops!',
2250 - 'Unmount B [0]',
2251 - 'Mount A [1]',
2252 - 'Mount B [1]',
2253 - ]);
2176 + assertLog(['Oops!', 'Unmount B [0]', 'Mount A [1]', 'Mount B [1]']);
2177 });
2178
2179 // <Counter> gets unmounted because an error is thrown above.
2180 // The remaining destroy functions are run later on unmount, since they're passive.
2181 // In this case, one of them throws again (because of how the test is written).
2259 - expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']);
2182 + assertLog(['Oops!', 'Unmount B [1]']);
2183 expect(ReactNoop).toMatchRenderedOutput(null);
2184 });
2185
2263 - it('works with memo', () => {
2186 + it('works with memo', async () => {
2187 function Counter({count}) {
2188 useLayoutEffect(() => {
2189 Scheduler.unstable_yieldValue('Mount: ' + count);
@@ -2273,26 +2196,17 @@ describe('ReactHooksWithNoopRenderer', () => {
2196 ReactNoop.render(<Counter count={0} />, () =>
2197 Scheduler.unstable_yieldValue('Sync effect'),
2198 );
2276 - expect(Scheduler).toFlushAndYieldThrough([
2277 - 'Count: 0',
2278 - 'Mount: 0',
2279 - 'Sync effect',
2280 - ]);
2199 + await waitFor(['Count: 0', 'Mount: 0', 'Sync effect']);
2200 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
2201
2202 ReactNoop.render(<Counter count={1} />, () =>
2203 Scheduler.unstable_yieldValue('Sync effect'),
2204 );
2286 - expect(Scheduler).toFlushAndYieldThrough([
2287 - 'Count: 1',
2288 - 'Unmount: 0',
2289 - 'Mount: 1',
2290 - 'Sync effect',
2291 - ]);
2205 + await waitFor(['Count: 1', 'Unmount: 0', 'Mount: 1', 'Sync effect']);
2206 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
2207
2208 ReactNoop.render(null);
2295 - expect(Scheduler).toFlushAndYieldThrough(['Unmount: 1']);
2209 + await waitFor(['Unmount: 1']);
2210 expect(ReactNoop).toMatchRenderedOutput(null);
2211 });
2212
@@ -2351,7 +2265,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2265 });
2266
2267 // @gate skipUnmountedBoundaries
2354 - it('should use the nearest still-mounted boundary if there are no unmounted boundaries', () => {
2268 + it('should use the nearest still-mounted boundary if there are no unmounted boundaries', async () => {
2269 act(() => {
2270 ReactNoop.render(
2271 <LogOnlyErrorBoundary>
@@ -2360,7 +2274,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2274 );
2275 });
2276
2363 - expect(Scheduler).toHaveYielded([
2277 + assertLog([
2278 'LogOnlyErrorBoundary render',
2279 'BrokenUseEffectCleanup useEffect',
2280 ]);
@@ -2369,7 +2283,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2283 ReactNoop.render(<LogOnlyErrorBoundary />);
2284 });
2285
2372 - expect(Scheduler).toHaveYielded([
2286 + assertLog([
2287 'LogOnlyErrorBoundary render',
2288 'BrokenUseEffectCleanup useEffect destroy',
2289 'LogOnlyErrorBoundary componentDidCatch',
@@ -2377,7 +2291,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2291 });
2292
2293 // @gate skipUnmountedBoundaries
2380 - it('should skip unmounted boundaries and use the nearest still-mounted boundary', () => {
2294 + it('should skip unmounted boundaries and use the nearest still-mounted boundary', async () => {
2295 function Conditional({showChildren}) {
2296 if (showChildren) {
2297 return (
@@ -2398,7 +2312,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2312 );
2313 });
2314
2401 - expect(Scheduler).toHaveYielded([
2315 + assertLog([
2316 'LogOnlyErrorBoundary render',
2317 'ErrorBoundary render success',
2318 'BrokenUseEffectCleanup useEffect',
@@ -2412,7 +2326,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2326 );
2327 });
2328
2415 - expect(Scheduler).toHaveYielded([
2329 + assertLog([
2330 'LogOnlyErrorBoundary render',
2331 'BrokenUseEffectCleanup useEffect destroy',
2332 'LogOnlyErrorBoundary componentDidCatch',
@@ -2420,7 +2334,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2334 });
2335
2336 // @gate skipUnmountedBoundaries
2423 - it('should call getDerivedStateFromError in the nearest still-mounted boundary', () => {
2337 + it('should call getDerivedStateFromError in the nearest still-mounted boundary', async () => {
2338 function Conditional({showChildren}) {
2339 if (showChildren) {
2340 return <BrokenUseEffectCleanup />;
@@ -2437,7 +2351,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2351 );
2352 });
2353
2440 - expect(Scheduler).toHaveYielded([
2354 + assertLog([
2355 'ErrorBoundary render success',
2356 'BrokenUseEffectCleanup useEffect',
2357 ]);
@@ -2450,7 +2364,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2364 );
2365 });
2366
2453 - expect(Scheduler).toHaveYielded([
2367 + assertLog([
2368 'ErrorBoundary render success',
2369 'BrokenUseEffectCleanup useEffect destroy',
2370 'ErrorBoundary static getDerivedStateFromError',
@@ -2464,7 +2378,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2378 });
2379
2380 // @gate skipUnmountedBoundaries
2467 - it('should rethrow error if there are no still-mounted boundaries', () => {
2381 + it('should rethrow error if there are no still-mounted boundaries', async () => {
2382 function Conditional({showChildren}) {
2383 if (showChildren) {
2384 return (
@@ -2481,7 +2395,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2395 ReactNoop.render(<Conditional showChildren={true} />);
2396 });
2397
2484 - expect(Scheduler).toHaveYielded([
2398 + assertLog([
2399 'ErrorBoundary render success',
2400 'BrokenUseEffectCleanup useEffect',
2401 ]);
@@ -2492,15 +2406,13 @@ describe('ReactHooksWithNoopRenderer', () => {
2406 });
2407 }).toThrow('Expected error');
2408
2495 - expect(Scheduler).toHaveYielded([
2496 - 'BrokenUseEffectCleanup useEffect destroy',
2497 - ]);
2409 + assertLog(['BrokenUseEffectCleanup useEffect destroy']);
2410
2411 expect(ReactNoop).toMatchRenderedOutput(null);
2412 });
2413 });
2414
2503 - it('calls passive effect destroy functions for memoized components', () => {
2415 + it('calls passive effect destroy functions for memoized components', async () => {
2416 const Wrapper = ({children}) => children;
2417 function Child() {
2418 React.useEffect(() => {
@@ -2530,11 +2442,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2442 </Wrapper>,
2443 );
2444 });
2533 - expect(Scheduler).toHaveYielded([
2534 - 'render',
2535 - 'layout create',
2536 - 'passive create',
2537 - ]);
2445 + assertLog(['render', 'layout create', 'passive create']);
2446
2447 // Include at least one no-op (memoized) update to trigger original bug.
2448 act(() => {
@@ -2544,7 +2452,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2452 </Wrapper>,
2453 );
2454 });
2547 - expect(Scheduler).toHaveYielded([]);
2455 + assertLog([]);
2456
2457 act(() => {
2458 ReactNoop.render(
@@ -2553,7 +2461,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2461 </Wrapper>,
2462 );
2463 });
2556 - expect(Scheduler).toHaveYielded([
2464 + assertLog([
2465 'render',
2466 'layout destroy',
2467 'layout create',
@@ -2564,10 +2472,10 @@ describe('ReactHooksWithNoopRenderer', () => {
2472 act(() => {
2473 ReactNoop.render(null);
2474 });
2567 - expect(Scheduler).toHaveYielded(['layout destroy', 'passive destroy']);
2475 + assertLog(['layout destroy', 'passive destroy']);
2476 });
2477
2570 - it('calls passive effect destroy functions for descendants of memoized components', () => {
2478 + it('calls passive effect destroy functions for descendants of memoized components', async () => {
2479 const Wrapper = ({children}) => children;
2480 function Child() {
2481 return <Grandchild />;
@@ -2601,11 +2509,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2509 </Wrapper>,
2510 );
2511 });
2604 - expect(Scheduler).toHaveYielded([
2605 - 'render',
2606 - 'layout create',
2607 - 'passive create',
2608 - ]);
2512 + assertLog(['render', 'layout create', 'passive create']);
2513
2514 // Include at least one no-op (memoized) update to trigger original bug.
2515 act(() => {
@@ -2615,7 +2519,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2519 </Wrapper>,
2520 );
2521 });
2618 - expect(Scheduler).toHaveYielded([]);
2522 + assertLog([]);
2523
2524 act(() => {
2525 ReactNoop.render(
@@ -2624,7 +2528,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2528 </Wrapper>,
2529 );
2530 });
2627 - expect(Scheduler).toHaveYielded([
2531 + assertLog([
2532 'render',
2533 'layout destroy',
2534 'layout create',
@@ -2635,10 +2539,10 @@ describe('ReactHooksWithNoopRenderer', () => {
2539 act(() => {
2540 ReactNoop.render(null);
2541 });
2638 - expect(Scheduler).toHaveYielded(['layout destroy', 'passive destroy']);
2542 + assertLog(['layout destroy', 'passive destroy']);
2543 });
2544
2641 - it('assumes passive effect destroy function is either a function or undefined', () => {
2545 + it('assumes passive effect destroy function is either a function or undefined', async () => {
2546 function App(props) {
2547 useEffect(() => {
2548 return props.return;
@@ -2688,7 +2592,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2592 });
2593
2594 describe('useInsertionEffect', () => {
2691 - it('fires insertion effects after snapshots on update', () => {
2595 + it('fires insertion effects after snapshots on update', async () => {
2596 function CounterA(props) {
2597 useInsertionEffect(() => {
2598 Scheduler.unstable_yieldValue(`Create insertion`);
@@ -2712,7 +2616,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2616 }
2617 }
2618
2715 - act(() => {
2619 + await act(async () => {
2620 ReactNoop.render(
2621 <>
2622 <CounterA />
@@ -2720,11 +2624,11 @@ describe('ReactHooksWithNoopRenderer', () => {
2624 </>,
2625 );
2626
2723 - expect(Scheduler).toFlushAndYield(['Create insertion']);
2627 + await waitForAll(['Create insertion']);
2628 });
2629
2630 // Update
2727 - act(() => {
2631 + await act(async () => {
2632 ReactNoop.render(
2633 <>
2634 <CounterA />
@@ -2732,7 +2636,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2636 </>,
2637 );
2638
2735 - expect(Scheduler).toFlushAndYield([
2639 + await waitForAll([
2640 'Get Snapshot',
2641 'Destroy insertion',
2642 'Create insertion',
@@ -2740,14 +2644,14 @@ describe('ReactHooksWithNoopRenderer', () => {
2644 });
2645
2646 // Unmount everything
2743 - act(() => {
2647 + await act(async () => {
2648 ReactNoop.render(null);
2649
2746 - expect(Scheduler).toFlushAndYield(['Destroy insertion']);
2650 + await waitForAll(['Destroy insertion']);
2651 });
2652 });
2653
2750 - it('fires insertion effects before layout effects', () => {
2654 + it('fires insertion effects before layout effects', async () => {
2655 let committedText = '(empty)';
2656
2657 function Counter(props) {
@@ -2784,32 +2688,32 @@ describe('ReactHooksWithNoopRenderer', () => {
2688 });
2689 return null;
2690 }
2787 - act(() => {
2691 + await act(async () => {
2692 ReactNoop.render(<Counter count={0} />);
2693
2790 - expect(Scheduler).toFlushUntilNextPaint([
2694 + await waitForPaint([
2695 'Create insertion [current: (empty)]',
2696 'Create layout [current: 0]',
2697 ]);
2698 expect(committedText).toEqual('0');
2699 });
2700
2797 - expect(Scheduler).toHaveYielded(['Create passive [current: 0]']);
2701 + assertLog(['Create passive [current: 0]']);
2702
2703 // Unmount everything
2800 - act(() => {
2704 + await act(async () => {
2705 ReactNoop.render(null);
2706
2803 - expect(Scheduler).toFlushUntilNextPaint([
2707 + await waitForPaint([
2708 'Destroy insertion [current: 0]',
2709 'Destroy layout [current: 0]',
2710 ]);
2711 });
2712
2809 - expect(Scheduler).toHaveYielded(['Destroy passive [current: 0]']);
2713 + assertLog(['Destroy passive [current: 0]']);
2714 });
2715
2812 - it('force flushes passive effects before firing new insertion effects', () => {
2716 + it('force flushes passive effects before firing new insertion effects', async () => {
2717 let committedText = '(empty)';
2718
2719 function Counter(props) {
@@ -2848,11 +2752,11 @@ describe('ReactHooksWithNoopRenderer', () => {
2752 return null;
2753 }
2754
2851 - act(() => {
2755 + await act(async () => {
2756 React.startTransition(() => {
2757 ReactNoop.render(<Counter count={0} />);
2758 });
2855 - expect(Scheduler).toFlushUntilNextPaint([
2759 + await waitForPaint([
2760 'Create insertion [current: (empty)]',
2761 'Create layout [current: 0]',
2762 ]);
@@ -2861,7 +2765,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2765 React.startTransition(() => {
2766 ReactNoop.render(<Counter count={1} />);
2767 });
2864 - expect(Scheduler).toFlushUntilNextPaint([
2768 + await waitForPaint([
2769 'Create passive [current: 0]',
2770 'Destroy insertion [current: 0]',
2771 'Create insertion [current: 0]',
@@ -2870,13 +2774,13 @@ describe('ReactHooksWithNoopRenderer', () => {
2774 ]);
2775 expect(committedText).toEqual('1');
2776 });
2873 - expect(Scheduler).toHaveYielded([
2777 + assertLog([
2778 'Destroy passive [current: 1]',
2779 'Create passive [current: 1]',
2780 ]);
2781 });
2782
2879 - it('fires all insertion effects (interleaved) before firing any layout effects', () => {
2783 + it('fires all insertion effects (interleaved) before firing any layout effects', async () => {
2784 let committedA = '(empty)';
2785 let committedB = '(empty)';
2786
@@ -2976,14 +2880,14 @@ describe('ReactHooksWithNoopRenderer', () => {
2880 return null;
2881 }
2882
2979 - act(() => {
2883 + await act(async () => {
2884 ReactNoop.render(
2885 <React.Fragment>
2886 <CounterA count={0} />
2887 <CounterB count={0} />
2888 </React.Fragment>,
2889 );
2986 - expect(Scheduler).toFlushAndYield([
2890 + await waitForAll([
2891 // All insertion effects fire before all layout effects
2892 'Create Insertion 1 for Component A [A: (empty), B: (empty)]',
2893 'Create Insertion 2 for Component A [A: 0, B: (empty)]',
@@ -2997,14 +2901,14 @@ describe('ReactHooksWithNoopRenderer', () => {
2901 expect([committedA, committedB]).toEqual(['0', '0']);
2902 });
2903
3000 - act(() => {
2904 + await act(async () => {
2905 ReactNoop.render(
2906 <React.Fragment>
2907 <CounterA count={1} />
2908 <CounterB count={1} />
2909 </React.Fragment>,
2910 );
3007 - expect(Scheduler).toFlushAndYield([
2911 + await waitForAll([
2912 'Destroy Insertion 1 for Component A [A: 0, B: 0]',
2913 'Destroy Insertion 2 for Component A [A: 0, B: 0]',
2914 'Create Insertion 1 for Component A [A: 0, B: 0]',
@@ -3025,10 +2929,10 @@ describe('ReactHooksWithNoopRenderer', () => {
2929 expect([committedA, committedB]).toEqual(['1', '1']);
2930
2931 // Unmount everything
3028 - act(() => {
2932 + await act(async () => {
2933 ReactNoop.render(null);
2934
3031 - expect(Scheduler).toFlushAndYield([
2935 + await waitForAll([
2936 'Destroy Insertion 1 for Component A [A: 1, B: 1]',
2937 'Destroy Insertion 2 for Component A [A: 1, B: 1]',
2938 'Destroy Layout 1 for Component A [A: 1, B: 1]',
@@ -3042,7 +2946,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2946 });
2947 });
2948
3045 - it('assumes insertion effect destroy function is either a function or undefined', () => {
2949 + it('assumes insertion effect destroy function is either a function or undefined', async () => {
2950 function App(props) {
2951 useInsertionEffect(() => {
2952 return props.return;
@@ -3090,7 +2994,7 @@ describe('ReactHooksWithNoopRenderer', () => {
2994 ).toThrow('is not a function');
2995 });
2996
3093 - it('warns when setState is called from insertion effect setup', () => {
2997 + it('warns when setState is called from insertion effect setup', async () => {
2998 function App(props) {
2999 const [, setX] = useState(0);
3000 useInsertionEffect(() => {
@@ -3128,7 +3032,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3032 });
3033 });
3034
3131 - it('warns when setState is called from insertion effect cleanup', () => {
3035 + it('warns when setState is called from insertion effect cleanup', async () => {
3036 function App(props) {
3037 const [, setX] = useState(0);
3038 useInsertionEffect(() => {
@@ -3173,7 +3077,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3077 });
3078
3079 describe('useLayoutEffect', () => {
3176 - it('fires layout effects after the host has been mutated', () => {
3080 + it('fires layout effects after the host has been mutated', async () => {
3081 function getCommittedText() {
3082 const yields = Scheduler.unstable_clearYields();
3083 const children = ReactNoop.getChildrenAsJSX();
@@ -3194,25 +3098,17 @@ describe('ReactHooksWithNoopRenderer', () => {
3098 ReactNoop.render(<Counter count={0} />, () =>
3099 Scheduler.unstable_yieldValue('Sync effect'),
3100 );
3197 - expect(Scheduler).toFlushAndYieldThrough([
3198 - [0],
3199 - 'Current: 0',
3200 - 'Sync effect',
3201 - ]);
3101 + await waitFor([[0], 'Current: 0', 'Sync effect']);
3102 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
3103
3104 ReactNoop.render(<Counter count={1} />, () =>
3105 Scheduler.unstable_yieldValue('Sync effect'),
3106 );
3207 - expect(Scheduler).toFlushAndYieldThrough([
3208 - [1],
3209 - 'Current: 1',
3210 - 'Sync effect',
3211 - ]);
3107 + await waitFor([[1], 'Current: 1', 'Sync effect']);
3108 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
3109 });
3110
3215 - it('force flushes passive effects before firing new layout effects', () => {
3111 + it('force flushes passive effects before firing new layout effects', async () => {
3112 let committedText = '(empty)';
3113
3114 function Counter(props) {
@@ -3243,19 +3139,16 @@ describe('ReactHooksWithNoopRenderer', () => {
3139 return null;
3140 }
3141
3246 - act(() => {
3142 + await act(async () => {
3143 ReactNoop.render(<Counter count={0} />, () =>
3144 Scheduler.unstable_yieldValue('Sync effect'),
3145 );
3250 - expect(Scheduler).toFlushAndYieldThrough([
3251 - 'Mount layout [current: 0]',
3252 - 'Sync effect',
3253 - ]);
3146 + await waitFor(['Mount layout [current: 0]', 'Sync effect']);
3147 expect(committedText).toEqual('0');
3148 ReactNoop.render(<Counter count={1} />, () =>
3149 Scheduler.unstable_yieldValue('Sync effect'),
3150 );
3258 - expect(Scheduler).toFlushAndYieldThrough([
3151 + await waitFor([
3152 'Mount normal [current: 0]',
3153 'Unmount layout [current: 0]',
3154 'Mount layout [current: 1]',
@@ -3264,14 +3157,11 @@ describe('ReactHooksWithNoopRenderer', () => {
3157 expect(committedText).toEqual('1');
3158 });
3159
3267 - expect(Scheduler).toHaveYielded([
3268 - 'Unmount normal [current: 1]',
3269 - 'Mount normal [current: 1]',
3270 - ]);
3160 + assertLog(['Unmount normal [current: 1]', 'Mount normal [current: 1]']);
3161 });
3162
3163 // @gate skipUnmountedBoundaries
3274 - it('catches errors thrown in useLayoutEffect', () => {
3164 + it('catches errors thrown in useLayoutEffect', async () => {
3165 class ErrorBoundary extends React.Component {
3166 state = {error: null};
3167 static getDerivedStateFromError(error) {
@@ -3320,7 +3210,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3210 </ErrorBoundary>,
3211 );
3212
3323 - expect(Scheduler).toFlushAndYield([
3213 + await waitForAll([
3214 'OuterBoundary render success',
3215 'Component render sibling',
3216 'InnerBoundary render success',
@@ -3340,7 +3230,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3230 );
3231
3232 // React should skip over the unmounting boundary and find the nearest still-mounted boundary.
3343 - expect(Scheduler).toFlushAndYield([
3233 + await waitForAll([
3234 'OuterBoundary render success',
3235 'Component render sibling',
3236 'BrokenLayoutEffectDestroy useLayoutEffect destroy',
@@ -3351,7 +3241,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3241 expect(ReactNoop).toMatchRenderedOutput(<span prop="OuterFallback" />);
3242 });
3243
3354 - it('assumes layout effect destroy function is either a function or undefined', () => {
3244 + it('assumes layout effect destroy function is either a function or undefined', async () => {
3245 function App(props) {
3246 useLayoutEffect(() => {
3247 return props.return;
@@ -3401,7 +3291,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3291 });
3292
3293 describe('useCallback', () => {
3404 - it('memoizes callback by comparing inputs', () => {
3294 + it('memoizes callback by comparing inputs', async () => {
3295 class IncrementButton extends React.PureComponent {
3296 increment = () => {
3297 this.props.increment();
@@ -3427,7 +3317,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3317
3318 const button = React.createRef(null);
3319 ReactNoop.render(<Counter incrementBy={1} />);
3430 - expect(Scheduler).toFlushAndYield(['Increment', 'Count: 0']);
3320 + await waitForAll(['Increment', 'Count: 0']);
3321 expect(ReactNoop).toMatchRenderedOutput(
3322 <>
3323 <span prop="Increment" />
@@ -3436,7 +3326,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3326 );
3327
3328 act(button.current.increment);
3439 - expect(Scheduler).toHaveYielded([
3329 + assertLog([
3330 // Button should not re-render, because its props haven't changed
3331 // 'Increment',
3332 'Count: 1',
@@ -3450,7 +3340,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3340
3341 // Increase the increment amount
3342 ReactNoop.render(<Counter incrementBy={10} />);
3453 - expect(Scheduler).toFlushAndYield([
3343 + await waitForAll([
3344 // Inputs did change this time
3345 'Increment',
3346 'Count: 1',
@@ -3464,7 +3354,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3354
3355 // Callback should have updated
3356 act(button.current.increment);
3467 - expect(Scheduler).toHaveYielded(['Count: 11']);
3357 + assertLog(['Count: 11']);
3358 expect(ReactNoop).toMatchRenderedOutput(
3359 <>
3360 <span prop="Increment" />
@@ -3475,7 +3365,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3365 });
3366
3367 describe('useMemo', () => {
3478 - it('memoizes value by comparing to previous inputs', () => {
3368 + it('memoizes value by comparing to previous inputs', async () => {
3369 function CapitalizedText(props) {
3370 const text = props.text;
3371 const capitalizedText = useMemo(() => {
@@ -3486,23 +3376,23 @@ describe('ReactHooksWithNoopRenderer', () => {
3376 }
3377
3378 ReactNoop.render(<CapitalizedText text="hello" />);
3489 - expect(Scheduler).toFlushAndYield(["Capitalize 'hello'", 'HELLO']);
3379 + await waitForAll(["Capitalize 'hello'", 'HELLO']);
3380 expect(ReactNoop).toMatchRenderedOutput(<span prop="HELLO" />);
3381
3382 ReactNoop.render(<CapitalizedText text="hi" />);
3493 - expect(Scheduler).toFlushAndYield(["Capitalize 'hi'", 'HI']);
3383 + await waitForAll(["Capitalize 'hi'", 'HI']);
3384 expect(ReactNoop).toMatchRenderedOutput(<span prop="HI" />);
3385
3386 ReactNoop.render(<CapitalizedText text="hi" />);
3497 - expect(Scheduler).toFlushAndYield(['HI']);
3387 + await waitForAll(['HI']);
3388 expect(ReactNoop).toMatchRenderedOutput(<span prop="HI" />);
3389
3390 ReactNoop.render(<CapitalizedText text="goodbye" />);
3501 - expect(Scheduler).toFlushAndYield(["Capitalize 'goodbye'", 'GOODBYE']);
3391 + await waitForAll(["Capitalize 'goodbye'", 'GOODBYE']);
3392 expect(ReactNoop).toMatchRenderedOutput(<span prop="GOODBYE" />);
3393 });
3394
3505 - it('always re-computes if no inputs are provided', () => {
3395 + it('always re-computes if no inputs are provided', async () => {
3396 function LazyCompute(props) {
3397 const computed = useMemo(props.compute);
3398 return <Text text={computed} />;
@@ -3519,19 +3409,19 @@ describe('ReactHooksWithNoopRenderer', () => {
3409 }
3410
3411 ReactNoop.render(<LazyCompute compute={computeA} />);
3522 - expect(Scheduler).toFlushAndYield(['compute A', 'A']);
3412 + await waitForAll(['compute A', 'A']);
3413
3414 ReactNoop.render(<LazyCompute compute={computeA} />);
3525 - expect(Scheduler).toFlushAndYield(['compute A', 'A']);
3415 + await waitForAll(['compute A', 'A']);
3416
3417 ReactNoop.render(<LazyCompute compute={computeA} />);
3528 - expect(Scheduler).toFlushAndYield(['compute A', 'A']);
3418 + await waitForAll(['compute A', 'A']);
3419
3420 ReactNoop.render(<LazyCompute compute={computeB} />);
3531 - expect(Scheduler).toFlushAndYield(['compute B', 'B']);
3421 + await waitForAll(['compute B', 'B']);
3422 });
3423
3534 - it('should not invoke memoized function during re-renders unless inputs change', () => {
3424 + it('should not invoke memoized function during re-renders unless inputs change', async () => {
3425 function LazyCompute(props) {
3426 const computed = useMemo(
3427 () => props.compute(props.input),
@@ -3550,18 +3440,18 @@ describe('ReactHooksWithNoopRenderer', () => {
3440 }
3441
3442 ReactNoop.render(<LazyCompute compute={compute} input="A" />);
3553 - expect(Scheduler).toFlushAndYield(['compute A', 'A']);
3443 + await waitForAll(['compute A', 'A']);
3444
3445 ReactNoop.render(<LazyCompute compute={compute} input="A" />);
3556 - expect(Scheduler).toFlushAndYield(['A']);
3446 + await waitForAll(['A']);
3447
3448 ReactNoop.render(<LazyCompute compute={compute} input="B" />);
3559 - expect(Scheduler).toFlushAndYield(['compute B', 'B']);
3449 + await waitForAll(['compute B', 'B']);
3450 });
3451 });
3452
3453 describe('useImperativeHandle', () => {
3564 - it('does not update when deps are the same', () => {
3454 + it('does not update when deps are the same', async () => {
3455 const INCREMENT = 'INCREMENT';
3456
3457 function reducer(state, action) {
@@ -3577,21 +3467,21 @@ describe('ReactHooksWithNoopRenderer', () => {
3467 Counter = forwardRef(Counter);
3468 const counter = React.createRef(null);
3469 ReactNoop.render(<Counter ref={counter} />);
3580 - expect(Scheduler).toFlushAndYield(['Count: 0']);
3470 + await waitForAll(['Count: 0']);
3471 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
3472 expect(counter.current.count).toBe(0);
3473
3474 act(() => {
3475 counter.current.dispatch(INCREMENT);
3476 });
3587 - expect(Scheduler).toHaveYielded(['Count: 1']);
3477 + assertLog(['Count: 1']);
3478 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
3479 // Intentionally not updated because of [] deps:
3480 expect(counter.current.count).toBe(0);
3481 });
3482
3483 // Regression test for https://github.com/facebook/react/issues/14782
3594 - it('automatically updates when deps are not specified', () => {
3484 + it('automatically updates when deps are not specified', async () => {
3485 const INCREMENT = 'INCREMENT';
3486
3487 function reducer(state, action) {
@@ -3607,19 +3497,19 @@ describe('ReactHooksWithNoopRenderer', () => {
3497 Counter = forwardRef(Counter);
3498 const counter = React.createRef(null);
3499 ReactNoop.render(<Counter ref={counter} />);
3610 - expect(Scheduler).toFlushAndYield(['Count: 0']);
3500 + await waitForAll(['Count: 0']);
3501 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
3502 expect(counter.current.count).toBe(0);
3503
3504 act(() => {
3505 counter.current.dispatch(INCREMENT);
3506 });
3617 - expect(Scheduler).toHaveYielded(['Count: 1']);
3507 + assertLog(['Count: 1']);
3508 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
3509 expect(counter.current.count).toBe(1);
3510 });
3511
3622 - it('updates when deps are different', () => {
3512 + it('updates when deps are different', async () => {
3513 const INCREMENT = 'INCREMENT';
3514
3515 function reducer(state, action) {
@@ -3643,7 +3533,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3533 Counter = forwardRef(Counter);
3534 const counter = React.createRef(null);
3535 ReactNoop.render(<Counter ref={counter} />);
3646 - expect(Scheduler).toFlushAndYield(['Count: 0']);
3536 + await waitForAll(['Count: 0']);
3537 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
3538 expect(counter.current.count).toBe(0);
3539 expect(totalRefUpdates).toBe(1);
@@ -3651,14 +3541,14 @@ describe('ReactHooksWithNoopRenderer', () => {
3541 act(() => {
3542 counter.current.dispatch(INCREMENT);
3543 });
3654 - expect(Scheduler).toHaveYielded(['Count: 1']);
3544 + assertLog(['Count: 1']);
3545 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
3546 expect(counter.current.count).toBe(1);
3547 expect(totalRefUpdates).toBe(2);
3548
3549 // Update that doesn't change the ref dependencies
3550 ReactNoop.render(<Counter ref={counter} />);
3661 - expect(Scheduler).toFlushAndYield(['Count: 1']);
3551 + await waitForAll(['Count: 1']);
3552 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
3553 expect(counter.current.count).toBe(1);
3554 expect(totalRefUpdates).toBe(2); // Should not increase since last time
@@ -3688,7 +3578,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3578 );
3579 }
3580 ReactNoop.render(<App />);
3691 - expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
3581 + await waitForAll(['Before... Pending: false']);
3582 expect(ReactNoop).toMatchRenderedOutput(
3583 <span prop="Before... Pending: false" />,
3584 );
@@ -3696,7 +3586,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3586 await act(async () => {
3587 transition();
3588
3699 - expect(Scheduler).toFlushAndYield([
3589 + await waitForAll([
3590 'Before... Pending: true',
3591 'Suspend! [After... Pending: false]',
3592 'Loading... Pending: false',
@@ -3715,10 +3605,8 @@ describe('ReactHooksWithNoopRenderer', () => {
3605 );
3606
3607 await resolveText('After... Pending: false');
3718 - expect(Scheduler).toHaveYielded([
3719 - 'Promise resolved [After... Pending: false]',
3720 - ]);
3721 - expect(Scheduler).toFlushAndYield(['After... Pending: false']);
3608 + assertLog(['Promise resolved [After... Pending: false]']);
3609 + await waitForAll(['After... Pending: false']);
3610 expect(ReactNoop).toMatchRenderedOutput(
3611 <span prop="After... Pending: false" />,
3612 );
@@ -3753,7 +3641,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3641 ReactNoop.render(<App />);
3642 });
3643
3756 - expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
3644 + assertLog(['A', 'Suspend! [A]', 'Loading']);
3645 expect(ReactNoop).toMatchRenderedOutput(
3646 <>
3647 <span prop="A" />
@@ -3762,8 +3650,8 @@ describe('ReactHooksWithNoopRenderer', () => {
3650 );
3651
3652 await resolveText('A');
3765 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
3766 - expect(Scheduler).toFlushAndYield(['A']);
3653 + assertLog(['Promise resolved [A]']);
3654 + await waitForAll(['A']);
3655 expect(ReactNoop).toMatchRenderedOutput(
3656 <>
3657 <span prop="A" />
@@ -3773,14 +3661,8 @@ describe('ReactHooksWithNoopRenderer', () => {
3661
3662 await act(async () => {
3663 _setText('B');
3776 - expect(Scheduler).toFlushAndYield([
3777 - 'B',
3778 - 'A',
3779 - 'B',
3780 - 'Suspend! [B]',
3781 - 'Loading',
3782 - ]);
3783 - expect(Scheduler).toFlushAndYield([]);
3664 + await waitForAll(['B', 'A', 'B', 'Suspend! [B]', 'Loading']);
3665 + await waitForAll([]);
3666 expect(ReactNoop).toMatchRenderedOutput(
3667 <>
3668 <span prop="B" />
@@ -3793,7 +3675,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3675 Scheduler.unstable_advanceTime(250);
3676 await advanceTimers(250);
3677 });
3796 - expect(Scheduler).toHaveYielded([]);
3678 + assertLog([]);
3679 expect(ReactNoop).toMatchRenderedOutput(
3680 <>
3681 <span prop="B" />
@@ -3804,7 +3686,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3686 // Even after a long amount of time, we don't show a fallback
3687 Scheduler.unstable_advanceTime(100000);
3688 await advanceTimers(100000);
3807 - expect(Scheduler).toFlushAndYield([]);
3689 + await waitForAll([]);
3690 expect(ReactNoop).toMatchRenderedOutput(
3691 <>
3692 <span prop="B" />
@@ -3815,7 +3697,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3697 await act(async () => {
3698 await resolveText('B');
3699 });
3818 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B', 'B']);
3700 + assertLog(['Promise resolved [B]', 'B', 'B']);
3701 expect(ReactNoop).toMatchRenderedOutput(
3702 <>
3703 <span prop="B" />
@@ -3826,7 +3708,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3708 });
3709
3710 describe('progressive enhancement (not supported)', () => {
3829 - it('mount additional state', () => {
3711 + it('mount additional state', async () => {
3712 let updateA;
3713 let updateB;
3714 // let updateC;
@@ -3848,7 +3730,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3730 }
3731
3732 ReactNoop.render(<App loadC={false} />);
3851 - expect(Scheduler).toFlushAndYield(['A: 0, B: 0, C: [not loaded]']);
3733 + await waitForAll(['A: 0, B: 0, C: [not loaded]']);
3734 expect(ReactNoop).toMatchRenderedOutput(
3735 <span prop="A: 0, B: 0, C: [not loaded]" />,
3736 );
@@ -3858,16 +3740,17 @@ describe('ReactHooksWithNoopRenderer', () => {
3740 updateB(3);
3741 });
3742
3861 - expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: [not loaded]']);
3743 + assertLog(['A: 2, B: 3, C: [not loaded]']);
3744 expect(ReactNoop).toMatchRenderedOutput(
3745 <span prop="A: 2, B: 3, C: [not loaded]" />,
3746 );
3747
3748 ReactNoop.render(<App loadC={true} />);
3867 - expect(() => {
3868 - expect(() => {
3869 - expect(Scheduler).toFlushAndYield(['A: 2, B: 3, C: 0']);
3870 - }).toThrow('Rendered more hooks than during the previous render');
3749 + await expect(async () => {
3750 + await waitForThrow(
3751 + 'Rendered more hooks than during the previous render.',
3752 + );
3753 + assertLog([]);
3754 }).toErrorDev([
3755 'Warning: React has detected a change in the order of Hooks called by App. ' +
3756 'This will lead to bugs and errors if not fixed. For more information, ' +
@@ -3888,7 +3771,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3771 // expect(ReactNoop).toMatchRenderedOutput(<span prop="A: 2, B: 3, C: 4" />]);
3772 });
3773
3891 - it('unmount state', () => {
3774 + it('unmount state', async () => {
3775 let updateA;
3776 let updateB;
3777 let updateC;
@@ -3912,14 +3795,14 @@ describe('ReactHooksWithNoopRenderer', () => {
3795 }
3796
3797 ReactNoop.render(<App loadC={true} />);
3915 - expect(Scheduler).toFlushAndYield(['A: 0, B: 0, C: 0']);
3798 + await waitForAll(['A: 0, B: 0, C: 0']);
3799 expect(ReactNoop).toMatchRenderedOutput(<span prop="A: 0, B: 0, C: 0" />);
3800 act(() => {
3801 updateA(2);
3802 updateB(3);
3803 updateC(4);
3804 });
3922 - expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: 4']);
3805 + assertLog(['A: 2, B: 3, C: 4']);
3806 expect(ReactNoop).toMatchRenderedOutput(<span prop="A: 2, B: 3, C: 4" />);
3807 ReactNoop.render(<App loadC={false} />);
3808 expect(Scheduler).toFlushAndThrow(
@@ -3928,7 +3811,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3811 );
3812 });
3813
3931 - it('unmount effects', () => {
3814 + it('unmount effects', async () => {
3815 function App(props) {
3816 useEffect(() => {
3817 Scheduler.unstable_yieldValue('Mount A');
@@ -3949,21 +3832,22 @@ describe('ReactHooksWithNoopRenderer', () => {
3832 return null;
3833 }
3834
3952 - act(() => {
3835 + await act(async () => {
3836 ReactNoop.render(<App showMore={false} />, () =>
3837 Scheduler.unstable_yieldValue('Sync effect'),
3838 );
3956 - expect(Scheduler).toFlushAndYieldThrough(['Sync effect']);
3839 + await waitFor(['Sync effect']);
3840 });
3841
3959 - expect(Scheduler).toHaveYielded(['Mount A']);
3842 + assertLog(['Mount A']);
3843
3961 - act(() => {
3844 + await act(async () => {
3845 ReactNoop.render(<App showMore={true} />);
3963 - expect(() => {
3964 - expect(() => {
3965 - expect(Scheduler).toFlushAndYield([]);
3966 - }).toThrow('Rendered more hooks than during the previous render');
3846 + await expect(async () => {
3847 + await waitForThrow(
3848 + 'Rendered more hooks than during the previous render.',
3849 + );
3850 + assertLog([]);
3851 }).toErrorDev([
3852 'Warning: React has detected a change in the order of Hooks called by App. ' +
3853 'This will lead to bugs and errors if not fixed. For more information, ' +
@@ -3988,7 +3872,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3872 });
3873 });
3874
3991 - it('useReducer does not eagerly bail out of state updates', () => {
3875 + it('useReducer does not eagerly bail out of state updates', async () => {
3876 // Edge case based on a bug report
3877 let setCounter;
3878 function App() {
@@ -4013,30 +3897,20 @@ describe('ReactHooksWithNoopRenderer', () => {
3897 return count;
3898 }
3899
4016 - act(() => {
3900 + await act(async () => {
3901 ReactNoop.render(<App />);
4018 - expect(Scheduler).toFlushAndYield([
4019 - 'Render: -1',
4020 - 'Effect: 1',
4021 - 'Reducer: 1',
4022 - 'Render: 1',
4023 - ]);
3902 + await waitForAll(['Render: -1', 'Effect: 1', 'Reducer: 1', 'Render: 1']);
3903 expect(ReactNoop).toMatchRenderedOutput('1');
3904 });
3905
3906 act(() => {
3907 setCounter(2);
3908 });
4030 - expect(Scheduler).toHaveYielded([
4031 - 'Render: 1',
4032 - 'Effect: 2',
4033 - 'Reducer: 2',
4034 - 'Render: 2',
4035 - ]);
3909 + assertLog(['Render: 1', 'Effect: 2', 'Reducer: 2', 'Render: 2']);
3910 expect(ReactNoop).toMatchRenderedOutput('2');
3911 });
3912
4039 - it('useReducer does not replay previous no-op actions when other state changes', () => {
3913 + it('useReducer does not replay previous no-op actions when other state changes', async () => {
3914 let increment;
3915 let setDisabled;
3916
@@ -4061,10 +3935,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3935 }
3936
3937 ReactNoop.render(<Counter />);
4064 - expect(Scheduler).toFlushAndYield([
4065 - 'Render disabled: true',
4066 - 'Render count: 0',
4067 - ]);
3938 + await waitForAll(['Render disabled: true', 'Render count: 0']);
3939 expect(ReactNoop).toMatchRenderedOutput('0');
3940
3941 act(() => {
@@ -4073,24 +3944,18 @@ describe('ReactHooksWithNoopRenderer', () => {
3944 increment();
3945 increment();
3946 });
4076 - expect(Scheduler).toHaveYielded([
4077 - 'Render disabled: true',
4078 - 'Render count: 0',
4079 - ]);
3947 + assertLog(['Render disabled: true', 'Render count: 0']);
3948 expect(ReactNoop).toMatchRenderedOutput('0');
3949
3950 act(() => {
3951 // Enabling the updater should *not* replay the previous increment() actions
3952 setDisabled(false);
3953 });
4086 - expect(Scheduler).toHaveYielded([
4087 - 'Render disabled: false',
4088 - 'Render count: 0',
4089 - ]);
3954 + assertLog(['Render disabled: false', 'Render count: 0']);
3955 expect(ReactNoop).toMatchRenderedOutput('0');
3956 });
3957
4093 - it('useReducer does not replay previous no-op actions when props change', () => {
3958 + it('useReducer does not replay previous no-op actions when props change', async () => {
3959 let setDisabled;
3960 let increment;
3961
@@ -4119,10 +3984,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3984 }
3985
3986 ReactNoop.render(<App />);
4122 - expect(Scheduler).toFlushAndYield([
4123 - 'Render disabled: true',
4124 - 'Render count: 0',
4125 - ]);
3987 + await waitForAll(['Render disabled: true', 'Render count: 0']);
3988 expect(ReactNoop).toMatchRenderedOutput('0');
3989
3990 act(() => {
@@ -4131,21 +3993,18 @@ describe('ReactHooksWithNoopRenderer', () => {
3993 increment();
3994 increment();
3995 });
4134 - expect(Scheduler).toHaveYielded(['Render count: 0']);
3996 + assertLog(['Render count: 0']);
3997 expect(ReactNoop).toMatchRenderedOutput('0');
3998
3999 act(() => {
4000 // Enabling the updater should *not* replay the previous increment() actions
4001 setDisabled(false);
4002 });
4141 - expect(Scheduler).toHaveYielded([
4142 - 'Render disabled: false',
4143 - 'Render count: 0',
4144 - ]);
4003 + assertLog(['Render disabled: false', 'Render count: 0']);
4004 expect(ReactNoop).toMatchRenderedOutput('0');
4005 });
4006
4148 - it('useReducer applies potential no-op changes if made relevant by other updates in the batch', () => {
4007 + it('useReducer applies potential no-op changes if made relevant by other updates in the batch', async () => {
4008 let setDisabled;
4009 let increment;
4010
@@ -4174,10 +4033,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4033 }
4034
4035 ReactNoop.render(<App />);
4177 - expect(Scheduler).toFlushAndYield([
4178 - 'Render disabled: true',
4179 - 'Render count: 0',
4180 - ]);
4036 + await waitForAll(['Render disabled: true', 'Render count: 0']);
4037 expect(ReactNoop).toMatchRenderedOutput('0');
4038
4039 act(() => {
@@ -4188,10 +4044,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4044 increment();
4045 setDisabled(false);
4046 });
4191 - expect(Scheduler).toHaveYielded([
4192 - 'Render disabled: false',
4193 - 'Render count: 1',
4194 - ]);
4047 + assertLog(['Render disabled: false', 'Render count: 1']);
4048 expect(ReactNoop).toMatchRenderedOutput('1');
4049 });
4050
@@ -4230,12 +4083,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4083 </>,
4084 );
4085 });
4233 - expect(Scheduler).toHaveYielded([
4234 - 'Render A: 0',
4235 - 'Render B: 0',
4236 - 'Commit A: 0',
4237 - 'Commit B: 0',
4238 - ]);
4086 + assertLog(['Render A: 0', 'Render B: 0', 'Commit A: 0', 'Commit B: 0']);
4087
4088 await act(async () => {
4089 setCounterA(1);
@@ -4246,7 +4094,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4094 setCounterB(1);
4095 setCounterB(0);
4096 });
4249 - expect(Scheduler).toHaveYielded([
4097 + assertLog([
4098 'Render A: 1',
4099 'Render B: 0',
4100 'Commit A: 1',
@@ -4255,7 +4103,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4103 ]);
4104 });
4105
4258 - it('should update latest rendered reducer when a preceding state receives a render phase update', () => {
4106 + it('should update latest rendered reducer when a preceding state receives a render phase update', async () => {
4107 // Similar to previous test, except using a preceding render phase update
4108 // instead of new props.
4109 let dispatch;
@@ -4273,7 +4121,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4121 }
4122
4123 ReactNoop.render(<App />);
4276 - expect(Scheduler).toFlushAndYield([
4124 + await waitForAll([
4125 'Step: 0, Shadow: 0',
4126 'Step: 1, Shadow: 0',
4127 'Step: 2, Shadow: 0',
@@ -4284,11 +4132,11 @@ describe('ReactHooksWithNoopRenderer', () => {
4132 expect(ReactNoop).toMatchRenderedOutput('0');
4133
4134 act(() => dispatch());
4287 - expect(Scheduler).toHaveYielded(['Step: 5, Shadow: 5']);
4135 + assertLog(['Step: 5, Shadow: 5']);
4136 expect(ReactNoop).toMatchRenderedOutput('5');
4137 });
4138
4291 - it('should process the rest pending updates after a render phase update', () => {
4139 + it('should process the rest pending updates after a render phase update', async () => {
4140 // Similar to previous test, except using a preceding render phase update
4141 // instead of new props.
4142 let updateA;
@@ -4346,7 +4194,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4194 </>,
4195 );
4196 });
4349 - expect(Scheduler).toHaveYielded([
4197 + assertLog([
4198 'Mount layout A',
4199 'Mount layout B',
4200 'Mount passive A',
@@ -4362,13 +4210,13 @@ describe('ReactHooksWithNoopRenderer', () => {
4210 </>,
4211 );
4212 });
4365 - expect(Scheduler).toHaveYielded(['Unmount layout A', 'Unmount passive A']);
4213 + assertLog(['Unmount layout A', 'Unmount passive A']);
4214
4215 // Now delete and unmount B.
4216 await act(async () => {
4217 root.render(null);
4218 });
4371 - expect(Scheduler).toHaveYielded(['Unmount layout B', 'Unmount passive B']);
4219 + assertLog(['Unmount layout B', 'Unmount passive B']);
4220 });
4221
4222 it('regression: deleting a tree and unmounting its effects after a reorder', async () => {
@@ -4392,7 +4240,7 @@ describe('ReactHooksWithNoopRenderer', () => {
4240 </>,
4241 );
4242 });
4395 - expect(Scheduler).toHaveYielded(['Mount A', 'Mount B']);
4243 + assertLog(['Mount A', 'Mount B']);
4244
4245 await act(async () => {
4246 root.render(
@@ -4402,13 +4250,13 @@ describe('ReactHooksWithNoopRenderer', () => {
4250 </>,
4251 );
4252 });
4405 - expect(Scheduler).toHaveYielded([]);
4253 + assertLog([]);
4254
4255 await act(async () => {
4256 root.render(null);
4257 });
4258
4411 - expect(Scheduler).toHaveYielded([
4259 + assertLog([
4260 'Unmount B',
4261 // In the regression, the reorder would cause Child A to "forget" that it
4262 // contains passive effects. Then when we deleted the tree, A's unmount
@@ -4446,21 +4294,12 @@ describe('ReactHooksWithNoopRenderer', () => {
4294 await act(async () => {
4295 root.render(<App />);
4296 });
4449 - expect(Scheduler).toHaveYielded([
4450 - 'Suspend! [A]',
4451 - 'Suspend! [B]',
4452 - 'Mount A',
4453 - 'Mount B',
4454 - ]);
4297 + assertLog(['Suspend! [A]', 'Suspend! [B]', 'Mount A', 'Mount B']);
4298
4299 await act(async () => {
4300 await resolveText('A');
4301 });
4459 - expect(Scheduler).toHaveYielded([
4460 - 'Promise resolved [A]',
4461 - 'A',
4462 - 'Suspend! [B]',
4463 - ]);
4302 + assertLog(['Promise resolved [A]', 'A', 'Suspend! [B]']);
4303
4304 await act(async () => {
4305 root.render(null);
@@ -4468,10 +4307,10 @@ describe('ReactHooksWithNoopRenderer', () => {
4307 // In the regression, SuspenseList would cause the children to "forget" that
4308 // it contains passive effects. Then when we deleted the tree, these unmount
4309 // effects would not fire.
4471 - expect(Scheduler).toHaveYielded(['Unmount A', 'Unmount B']);
4310 + assertLog(['Unmount A', 'Unmount B']);
4311 });
4312
4474 - it('effect dependencies are persisted after a render phase update', () => {
4313 + it('effect dependencies are persisted after a render phase update', async () => {
4314 let handleClick;
4315 function Test() {
4316 const [count, setCount] = useState(0);
@@ -4493,24 +4332,24 @@ describe('ReactHooksWithNoopRenderer', () => {
4332 ReactNoop.render(<Test />);
4333 });
4334
4496 - expect(Scheduler).toHaveYielded(['Render: 0', 'Effect: 0']);
4335 + assertLog(['Render: 0', 'Effect: 0']);
4336
4337 act(() => {
4338 handleClick();
4339 });
4340
4502 - expect(Scheduler).toHaveYielded(['Render: 0']);
4341 + assertLog(['Render: 0']);
4342
4343 act(() => {
4344 handleClick();
4345 });
4346
4508 - expect(Scheduler).toHaveYielded(['Render: 0']);
4347 + assertLog(['Render: 0']);
4348
4349 act(() => {
4350 handleClick();
4351 });
4352
4514 - expect(Scheduler).toHaveYielded(['Render: 0']);
4353 + assertLog(['Render: 0']);
4354 });
4355 });
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+207 -213
@@ -14,6 +14,10 @@ let React;
14 let ReactNoop;
15 let Scheduler;
16 let PropTypes;
17 +let waitForAll;
18 +let waitFor;
19 +let waitForThrow;
20 +let assertLog;
21
22 describe('ReactIncremental', () => {
23 beforeEach(() => {
@@ -22,6 +26,12 @@ describe('ReactIncremental', () => {
26 ReactNoop = require('react-noop-renderer');
27 Scheduler = require('scheduler');
28 PropTypes = require('prop-types');
29 +
30 + const InternalTestUtils = require('internal-test-utils');
31 + waitForAll = InternalTestUtils.waitForAll;
32 + waitFor = InternalTestUtils.waitFor;
33 + waitForThrow = InternalTestUtils.waitForThrow;
34 + assertLog = InternalTestUtils.assertLog;
35 });
36
37 // Note: This is based on a similar component we use in www. We can delete
@@ -37,7 +47,7 @@ describe('ReactIncremental', () => {
47 );
48 }
49
40 - it('should render a simple component', () => {
50 + it('should render a simple component', async () => {
51 function Bar() {
52 return <div>Hello World</div>;
53 }
@@ -47,10 +57,10 @@ describe('ReactIncremental', () => {
57 }
58
59 ReactNoop.render(<Foo />);
50 - expect(Scheduler).toFlushWithoutYielding();
60 + await waitForAll([]);
61 });
62
53 - it('should render a simple component, in steps if needed', () => {
63 + it('should render a simple component, in steps if needed', async () => {
64 function Bar() {
65 Scheduler.unstable_yieldValue('Bar');
66 return (
@@ -74,10 +84,10 @@ describe('ReactIncremental', () => {
84 expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
85
86 // Do the rest of the work.
77 - expect(Scheduler).toFlushAndYield(['Bar', 'Bar', 'callback']);
87 + await waitForAll(['Bar', 'Bar', 'callback']);
88 });
89
80 - it('updates a previous render', () => {
90 + it('updates a previous render', async () => {
91 function Header() {
92 Scheduler.unstable_yieldValue('Header');
93 return <h1>Hi</h1>;
@@ -110,7 +120,7 @@ describe('ReactIncremental', () => {
120 ReactNoop.render(<Foo text="foo" />, () =>
121 Scheduler.unstable_yieldValue('renderCallbackCalled'),
122 );
113 - expect(Scheduler).toFlushAndYield([
123 + await waitForAll([
124 'Foo',
125 'Header',
126 'Content',
@@ -128,7 +138,7 @@ describe('ReactIncremental', () => {
138
139 // Since this is an update, it should bail out and reuse the work from
140 // Header and Content.
131 - expect(Scheduler).toFlushAndYield([
141 + await waitForAll([
142 'Foo',
143 'Content',
144 'firstRenderCallbackCalled',
@@ -136,7 +146,7 @@ describe('ReactIncremental', () => {
146 ]);
147 });
148
139 - it('can cancel partially rendered work and restart', () => {
149 + it('can cancel partially rendered work and restart', async () => {
150 function Bar(props) {
151 Scheduler.unstable_yieldValue('Bar');
152 return <div>{props.children}</div>;
@@ -154,13 +164,13 @@ describe('ReactIncremental', () => {
164
165 // Init
166 ReactNoop.render(<Foo text="foo" />);
157 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
167 + await waitForAll(['Foo', 'Bar', 'Bar']);
168
169 React.startTransition(() => {
170 ReactNoop.render(<Foo text="bar" />);
171 });
172 // Flush part of the work
163 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar']);
173 + await waitFor(['Foo', 'Bar']);
174
175 // This will abort the previous work and restart
176 ReactNoop.flushSync(() => ReactNoop.render(null));
@@ -170,13 +180,13 @@ describe('ReactIncremental', () => {
180 });
181
182 // Flush part of the new work
173 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar']);
183 + await waitFor(['Foo', 'Bar']);
184
185 // Flush the rest of the work which now includes the low priority
176 - expect(Scheduler).toFlushAndYield(['Bar']);
186 + await waitForAll(['Bar']);
187 });
188
179 - it('should call callbacks even if updates are aborted', () => {
189 + it('should call callbacks even if updates are aborted', async () => {
190 let inst;
191
192 class Foo extends React.Component {
@@ -199,7 +209,7 @@ describe('ReactIncremental', () => {
209 }
210
211 ReactNoop.render(<Foo />);
202 - expect(Scheduler).toFlushWithoutYielding();
212 + await waitForAll([]);
213
214 React.startTransition(() => {
215 inst.setState(
@@ -212,7 +222,7 @@ describe('ReactIncremental', () => {
222 });
223
224 // Flush part of the work
215 - expect(Scheduler).toFlushAndYieldThrough(['setState1']);
225 + await waitFor(['setState1']);
226
227 // This will abort the previous work and restart
228 ReactNoop.flushSync(() => ReactNoop.render(<Foo />));
@@ -227,17 +237,12 @@ describe('ReactIncremental', () => {
237 });
238
239 // Flush the rest of the work which now includes the low priority
230 - expect(Scheduler).toFlushAndYield([
231 - 'setState1',
232 - 'setState2',
233 - 'callback1',
234 - 'callback2',
235 - ]);
240 + await waitForAll(['setState1', 'setState2', 'callback1', 'callback2']);
241 expect(inst.state).toEqual({text: 'bar', text2: 'baz'});
242 });
243
244 // @gate www
240 - it('can deprioritize unfinished work and resume it later', () => {
245 + it('can deprioritize unfinished work and resume it later', async () => {
246 function Bar(props) {
247 Scheduler.unstable_yieldValue('Bar');
248 return <div>{props.children}</div>;
@@ -266,24 +271,18 @@ describe('ReactIncremental', () => {
271
272 // Init
273 ReactNoop.render(<Foo text="foo" />);
269 - expect(Scheduler).toFlushAndYield([
270 - 'Foo',
271 - 'Bar',
272 - 'Bar',
273 - 'Middle',
274 - 'Middle',
275 - ]);
274 + await waitForAll(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
275
276 // Render part of the work. This should be enough to flush everything except
277 // the middle which has lower priority.
278 ReactNoop.render(<Foo text="bar" />);
280 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar', 'Bar']);
279 + await waitFor(['Foo', 'Bar', 'Bar']);
280 // Flush only the remaining work
282 - expect(Scheduler).toFlushAndYield(['Middle', 'Middle']);
281 + await waitForAll(['Middle', 'Middle']);
282 });
283
284 // @gate www
286 - it('can deprioritize a tree from without dropping work', () => {
285 + it('can deprioritize a tree from without dropping work', async () => {
286 function Bar(props) {
287 Scheduler.unstable_yieldValue('Bar');
288 return <div>{props.children}</div>;
@@ -314,21 +313,21 @@ describe('ReactIncremental', () => {
313 ReactNoop.flushSync(() => {
314 ReactNoop.render(<Foo text="foo" />);
315 });
317 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
318 - expect(Scheduler).toFlushAndYield(['Middle', 'Middle']);
316 + assertLog(['Foo', 'Bar', 'Bar']);
317 + await waitForAll(['Middle', 'Middle']);
318
319 // Render the high priority work (everything except the hidden trees).
320 ReactNoop.flushSync(() => {
321 ReactNoop.render(<Foo text="foo" />);
322 });
324 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
323 + assertLog(['Foo', 'Bar', 'Bar']);
324
325 // The hidden content was deprioritized from high to low priority. A low
326 // priority callback should have been scheduled. Flush it now.
328 - expect(Scheduler).toFlushAndYield(['Middle', 'Middle']);
327 + await waitForAll(['Middle', 'Middle']);
328 });
329
331 - xit('can resume work in a subtree even when a parent bails out', () => {
330 + xit('can resume work in a subtree even when a parent bails out', async () => {
331 function Bar(props) {
332 Scheduler.unstable_yieldValue('Bar');
333 return <div>{props.children}</div>;
@@ -372,19 +371,19 @@ describe('ReactIncremental', () => {
371 ReactNoop.render(<Foo text="foo" />);
372 ReactNoop.flushDeferredPri(52);
373
375 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Tester', 'Bar']);
374 + assertLog(['Foo', 'Bar', 'Tester', 'Bar']);
375
376 // We're now rendering an update that will bail out on updating middle.
377 ReactNoop.render(<Foo text="bar" />);
378 ReactNoop.flushDeferredPri(45 + 5);
379
381 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
380 + assertLog(['Foo', 'Bar', 'Bar']);
381
382 // Flush the rest to make sure that the bailout didn't block this work.
384 - expect(Scheduler).toFlushAndYield(['Middle']);
383 + await waitForAll(['Middle']);
384 });
385
387 - xit('can resume work in a bailed subtree within one pass', () => {
386 + xit('can resume work in a bailed subtree within one pass', async () => {
387 function Bar(props) {
388 Scheduler.unstable_yieldValue('Bar');
389 return <div>{props.children}</div>;
@@ -440,37 +439,37 @@ describe('ReactIncremental', () => {
439 ReactNoop.render(<Foo text="foo" />);
440 ReactNoop.flushDeferredPri(52 + 5);
441
443 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Tester', 'Bar']);
442 + assertLog(['Foo', 'Bar', 'Tester', 'Bar']);
443
444 // Make a quick update which will create a low pri tree on top of the
445 // already low pri tree.
446 ReactNoop.render(<Foo text="bar" />);
447 ReactNoop.flushDeferredPri(15);
448
450 - expect(Scheduler).toHaveYielded(['Foo']);
449 + assertLog(['Foo']);
450
451 // At this point, middle will bail out but it has not yet fully rendered.
452 // Since that is the same priority as its parent tree. This should render
453 // as a single batch. Therefore, it is correct that Middle should be in the
454 // middle. If it occurs after the two "Bar" components then it was flushed
455 // after them which is not correct.
457 - expect(Scheduler).toFlushAndYield(['Bar', 'Middle', 'Bar']);
456 + await waitForAll(['Bar', 'Middle', 'Bar']);
457
458 // Let us try this again without fully finishing the first time. This will
459 // create a hanging subtree that is reconciling at the normal priority.
460 ReactNoop.render(<Foo text="foo" />);
461 ReactNoop.flushDeferredPri(40);
462
464 - expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
463 + assertLog(['Foo', 'Bar']);
464
465 // This update will create a tree that aborts that work and down-prioritizes
466 // it. If the priority levels aren't down-prioritized correctly this may
467 // abort rendering of the down-prioritized content.
468 ReactNoop.render(<Foo text="bar" />);
470 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
469 + await waitForAll(['Foo', 'Bar', 'Bar']);
470 });
471
473 - xit('can resume mounting a class component', () => {
472 + xit('can resume mounting a class component', async () => {
473 let foo;
474 class Parent extends React.Component {
475 shouldComponentUpdate() {
@@ -501,14 +500,14 @@ describe('ReactIncremental', () => {
500
501 ReactNoop.render(<Parent prop="foo" />);
502 ReactNoop.flushDeferredPri(20);
504 - expect(Scheduler).toHaveYielded(['Foo constructor: foo', 'Foo']);
503 + assertLog(['Foo constructor: foo', 'Foo']);
504
505 foo.setState({value: 'bar'});
506
508 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar']);
507 + await waitForAll(['Foo', 'Bar']);
508 });
509
511 - xit('reuses the same instance when resuming a class instance', () => {
510 + xit('reuses the same instance when resuming a class instance', async () => {
511 let foo;
512 class Parent extends React.Component {
513 shouldComponentUpdate() {
@@ -560,7 +559,7 @@ describe('ReactIncremental', () => {
559
560 ReactNoop.render(<Parent prop="foo" />);
561 ReactNoop.flushDeferredPri(25);
563 - expect(Scheduler).toHaveYielded([
562 + assertLog([
563 'constructor: foo',
564 'componentWillMount: foo',
565 'render: foo',
@@ -569,9 +568,9 @@ describe('ReactIncremental', () => {
568
569 foo.setState({value: 'bar'});
570
572 - expect(Scheduler).toFlushWithoutYielding();
571 + await waitForAll([]);
572 expect(constructorCount).toEqual(1);
574 - expect(Scheduler).toHaveYielded([
573 + assertLog([
574 'componentWillMount: foo',
575 'render: foo',
576 'Foo did complete',
@@ -579,7 +578,7 @@ describe('ReactIncremental', () => {
578 ]);
579 });
580
582 - xit('can reuse work done after being preempted', () => {
581 + xit('can reuse work done after being preempted', async () => {
582 function Bar(props) {
583 Scheduler.unstable_yieldValue('Bar');
584 return <div>{props.children}</div>;
@@ -622,42 +621,42 @@ describe('ReactIncremental', () => {
621
622 // We only finish the higher priority work. So the low pri content
623 // has not yet finished mounting.
625 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Middle', 'Bar']);
624 + assertLog(['Foo', 'Bar', 'Middle', 'Bar']);
625
626 // Interrupt the rendering with a quick update. This should not touch the
627 // middle content.
628 ReactNoop.render(<Foo text="foo" text2="bar" step={0} />);
630 - expect(Scheduler).toFlushWithoutYielding();
629 + await waitForAll([]);
630
631 // We've now rendered the entire tree but we didn't have to redo the work
632 // done by the first Middle and Bar already.
634 - expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Middle']);
633 + assertLog(['Foo', 'Bar', 'Middle']);
634
635 // Make a quick update which will schedule low priority work to
636 // update the middle content.
637 ReactNoop.render(<Foo text="bar" text2="bar" step={1} />);
638 ReactNoop.flushDeferredPri(30 + 25 + 5);
639
641 - expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
640 + assertLog(['Foo', 'Bar']);
641
642 // The middle content is now pending rendering...
643 ReactNoop.flushDeferredPri(30 + 5);
645 - expect(Scheduler).toHaveYielded(['Middle', 'Bar']);
644 + assertLog(['Middle', 'Bar']);
645
646 // but we'll interrupt it to render some higher priority work.
647 // The middle content will bailout so it remains untouched.
648 ReactNoop.render(<Foo text="foo" text2="bar" step={1} />);
649 ReactNoop.flushDeferredPri(30);
650
652 - expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
651 + assertLog(['Foo', 'Bar']);
652
653 // Since we did nothing to the middle subtree during the interruption,
654 // we should be able to reuse the reconciliation work that we already did
655 // without restarting.
657 - expect(Scheduler).toFlushAndYield(['Middle']);
656 + await waitForAll(['Middle']);
657 });
658
660 - xit('can reuse work that began but did not complete, after being preempted', () => {
659 + xit('can reuse work that began but did not complete, after being preempted', async () => {
660 let child;
661 let sibling;
662
@@ -705,13 +704,13 @@ describe('ReactIncremental', () => {
704 }
705
706 ReactNoop.render(<Parent />);
708 - expect(Scheduler).toFlushWithoutYielding();
707 + await waitForAll([]);
708
709 // Begin working on a low priority update to Child, but stop before
710 // GreatGrandchild. Child and Grandchild begin but don't complete.
711 child.setState({step: 1});
712 ReactNoop.flushDeferredPri(30);
714 - expect(Scheduler).toHaveYielded(['Child', 'Grandchild']);
713 + assertLog(['Child', 'Grandchild']);
714
715 // Interrupt the current low pri work with a high pri update elsewhere in
716 // the tree.
@@ -719,19 +718,19 @@ describe('ReactIncremental', () => {
718 ReactNoop.flushSync(() => {
719 sibling.setState({});
720 });
722 - expect(Scheduler).toHaveYielded(['Sibling']);
721 + assertLog(['Sibling']);
722
723 // Continue the low pri work. The work on Child and GrandChild was memoized
724 // so they should not be worked on again.
725
727 - expect(Scheduler).toFlushAndYield([
726 + await waitForAll([
727 // No Child
728 // No Grandchild
729 'GreatGrandchild',
730 ]);
731 });
732
734 - xit('can reuse work if shouldComponentUpdate is false, after being preempted', () => {
733 + xit('can reuse work if shouldComponentUpdate is false, after being preempted', async () => {
734 function Bar(props) {
735 Scheduler.unstable_yieldValue('Bar');
736 return <div>{props.children}</div>;
@@ -777,40 +776,33 @@ describe('ReactIncremental', () => {
776
777 // Init
778 ReactNoop.render(<Foo text="foo" step={0} />);
780 - expect(Scheduler).toFlushAndYield([
781 - 'Foo',
782 - 'Bar',
783 - 'Content',
784 - 'Middle',
785 - 'Bar',
786 - 'Middle',
787 - ]);
779 + await waitForAll(['Foo', 'Bar', 'Content', 'Middle', 'Bar', 'Middle']);
780
781 // Make a quick update which will schedule low priority work to
782 // update the middle content.
783 ReactNoop.render(<Foo text="bar" step={1} />);
784 ReactNoop.flushDeferredPri(30 + 5);
785
794 - expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
786 + assertLog(['Foo', 'Bar']);
787
788 // The middle content is now pending rendering...
789 ReactNoop.flushDeferredPri(30 + 25 + 5);
798 - expect(Scheduler).toHaveYielded(['Content', 'Middle', 'Bar']); // One more Middle left.
790 + assertLog(['Content', 'Middle', 'Bar']); // One more Middle left.
791
792 // but we'll interrupt it to render some higher priority work.
793 // The middle content will bailout so it remains untouched.
794 ReactNoop.render(<Foo text="foo" step={1} />);
795 ReactNoop.flushDeferredPri(30);
796
805 - expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
797 + assertLog(['Foo', 'Bar']);
798
799 // Since we did nothing to the middle subtree during the interruption,
800 // we should be able to reuse the reconciliation work that we already did
801 // without restarting.
810 - expect(Scheduler).toFlushAndYield(['Middle']);
802 + await waitForAll(['Middle']);
803 });
804
813 - it('memoizes work even if shouldComponentUpdate returns false', () => {
805 + it('memoizes work even if shouldComponentUpdate returns false', async () => {
806 class Foo extends React.Component {
807 shouldComponentUpdate(nextProps) {
808 // this.props is the memoized props. So this should return true for
@@ -826,13 +818,13 @@ describe('ReactIncremental', () => {
818 }
819
820 ReactNoop.render(<Foo step={1} />);
829 - expect(Scheduler).toFlushAndYield(['render']);
821 + await waitForAll(['render']);
822
823 ReactNoop.render(<Foo step={2} />);
832 - expect(Scheduler).toFlushAndYield(['shouldComponentUpdate: false']);
824 + await waitForAll(['shouldComponentUpdate: false']);
825
826 ReactNoop.render(<Foo step={3} />);
835 - expect(Scheduler).toFlushAndYield([
827 + await waitForAll([
828 // If the memoized props were not updated during last bail out, sCU will
829 // keep returning false.
830 'shouldComponentUpdate: true',
@@ -840,7 +832,7 @@ describe('ReactIncremental', () => {
832 ]);
833 });
834
843 - it('can update in the middle of a tree using setState', () => {
835 + it('can update in the middle of a tree using setState', async () => {
836 let instance;
837 class Bar extends React.Component {
838 constructor() {
@@ -862,14 +854,14 @@ describe('ReactIncremental', () => {
854 }
855
856 ReactNoop.render(<Foo />);
865 - expect(Scheduler).toFlushWithoutYielding();
857 + await waitForAll([]);
858 expect(instance.state).toEqual({a: 'a'});
859 instance.setState({b: 'b'});
868 - expect(Scheduler).toFlushWithoutYielding();
860 + await waitForAll([]);
861 expect(instance.state).toEqual({a: 'a', b: 'b'});
862 });
863
872 - it('can queue multiple state updates', () => {
864 + it('can queue multiple state updates', async () => {
865 let instance;
866 class Bar extends React.Component {
867 constructor() {
@@ -891,16 +883,16 @@ describe('ReactIncremental', () => {
883 }
884
885 ReactNoop.render(<Foo />);
894 - expect(Scheduler).toFlushWithoutYielding();
886 + await waitForAll([]);
887 // Call setState multiple times before flushing
888 instance.setState({b: 'b'});
889 instance.setState({c: 'c'});
890 instance.setState({d: 'd'});
899 - expect(Scheduler).toFlushWithoutYielding();
891 + await waitForAll([]);
892 expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c', d: 'd'});
893 });
894
903 - it('can use updater form of setState', () => {
895 + it('can use updater form of setState', async () => {
896 let instance;
897 class Bar extends React.Component {
898 constructor() {
@@ -926,19 +918,19 @@ describe('ReactIncremental', () => {
918 }
919
920 ReactNoop.render(<Foo multiplier={2} />);
929 - expect(Scheduler).toFlushWithoutYielding();
921 + await waitForAll([]);
922 expect(instance.state.num).toEqual(1);
923 instance.setState(updater);
932 - expect(Scheduler).toFlushWithoutYielding();
924 + await waitForAll([]);
925 expect(instance.state.num).toEqual(2);
926
927 instance.setState(updater);
928 ReactNoop.render(<Foo multiplier={3} />);
937 - expect(Scheduler).toFlushWithoutYielding();
929 + await waitForAll([]);
930 expect(instance.state.num).toEqual(6);
931 });
932
941 - it('can call setState inside update callback', () => {
933 + it('can call setState inside update callback', async () => {
934 let instance;
935 class Bar extends React.Component {
936 constructor() {
@@ -968,15 +960,15 @@ describe('ReactIncremental', () => {
960 }
961
962 ReactNoop.render(<Foo multiplier={2} />);
971 - expect(Scheduler).toFlushWithoutYielding();
963 + await waitForAll([]);
964 instance.setState(updater);
965 instance.setState(updater, callback);
974 - expect(Scheduler).toFlushWithoutYielding();
966 + await waitForAll([]);
967 expect(instance.state.num).toEqual(4);
968 expect(instance.state.called).toEqual(true);
969 });
970
979 - it('can replaceState', () => {
971 + it('can replaceState', async () => {
972 let instance;
973 class Bar extends React.Component {
974 state = {a: 'a'};
@@ -995,15 +987,15 @@ describe('ReactIncremental', () => {
987 }
988
989 ReactNoop.render(<Foo />);
998 - expect(Scheduler).toFlushWithoutYielding();
990 + await waitForAll([]);
991 instance.setState({b: 'b'});
992 instance.setState({c: 'c'});
993 instance.updater.enqueueReplaceState(instance, {d: 'd'});
1002 - expect(Scheduler).toFlushWithoutYielding();
994 + await waitForAll([]);
995 expect(instance.state).toEqual({d: 'd'});
996 });
997
1006 - it('can forceUpdate', () => {
998 + it('can forceUpdate', async () => {
999 function Baz() {
1000 Scheduler.unstable_yieldValue('Baz');
1001 return <div />;
@@ -1034,12 +1026,12 @@ describe('ReactIncremental', () => {
1026 }
1027
1028 ReactNoop.render(<Foo />);
1037 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Baz']);
1029 + await waitForAll(['Foo', 'Bar', 'Baz']);
1030 instance.forceUpdate();
1039 - expect(Scheduler).toFlushAndYield(['Bar', 'Baz']);
1031 + await waitForAll(['Bar', 'Baz']);
1032 });
1033
1042 - it('should clear forceUpdate after update is flushed', () => {
1034 + it('should clear forceUpdate after update is flushed', async () => {
1035 let a = 0;
1036
1037 class Foo extends React.PureComponent {
@@ -1052,14 +1044,14 @@ describe('ReactIncremental', () => {
1044
1045 const foo = React.createRef(null);
1046 ReactNoop.render(<Foo ref={foo} b={0} />);
1055 - expect(Scheduler).toFlushAndYield(['A: 0, B: 0']);
1047 + await waitForAll(['A: 0, B: 0']);
1048
1049 a = 1;
1050 foo.current.forceUpdate();
1059 - expect(Scheduler).toFlushAndYield(['A: 1, B: 0']);
1051 + await waitForAll(['A: 1, B: 0']);
1052
1053 ReactNoop.render(<Foo ref={foo} b={0} />);
1062 - expect(Scheduler).toFlushAndYield([]);
1054 + await waitForAll([]);
1055 });
1056
1057 xit('can call sCU while resuming a partly mounted component', () => {
@@ -1092,7 +1084,7 @@ describe('ReactIncremental', () => {
1084
1085 ReactNoop.render(<Foo step={0} />);
1086 ReactNoop.flushDeferredPri(40);
1095 - expect(Scheduler).toHaveYielded(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
1087 + assertLog(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
1088
1089 expect(instances.size).toBe(3);
1090
@@ -1101,13 +1093,13 @@ describe('ReactIncremental', () => {
1093 // A was memoized and reused. B was memoized but couldn't be reused because
1094 // props differences. C was memoized and reused. D never even started so it
1095 // needed a new instance.
1104 - expect(Scheduler).toHaveYielded(['Foo', 'Bar:B2', 'Bar:D']);
1096 + assertLog(['Foo', 'Bar:B2', 'Bar:D']);
1097
1098 // We expect each rerender to correspond to a new instance.
1099 expect(instances.size).toBe(4);
1100 });
1101
1110 - xit('gets new props when setting state on a partly updated component', () => {
1102 + xit('gets new props when setting state on a partly updated component', async () => {
1103 const instances = [];
1104
1105 class Bar extends React.Component {
@@ -1151,7 +1143,7 @@ describe('ReactIncremental', () => {
1143 <Baz />
1144 </div>,
1145 );
1154 - expect(Scheduler).toFlushWithoutYielding();
1146 + await waitForAll([]);
1147
1148 // Flush part way through with new props, fully completing the first Bar.
1149 // However, it doesn't commit yet.
@@ -1163,15 +1155,15 @@ describe('ReactIncremental', () => {
1155 </div>,
1156 );
1157 ReactNoop.flushDeferredPri(45);
1166 - expect(Scheduler).toHaveYielded(['Foo', 'Bar:A-1', 'Bar:B-1', 'Baz']);
1158 + assertLog(['Foo', 'Bar:A-1', 'Bar:B-1', 'Baz']);
1159
1160 // Make an update to the same Bar.
1161 instances[0].performAction();
1162
1171 - expect(Scheduler).toFlushAndYield(['Bar:A-1', 'Baz']);
1163 + await waitForAll(['Bar:A-1', 'Baz']);
1164 });
1165
1174 - xit('calls componentWillMount twice if the initial render is aborted', () => {
1166 + xit('calls componentWillMount twice if the initial render is aborted', async () => {
1167 class LifeCycle extends React.Component {
1168 state = {x: this.props.x};
1169 UNSAFE_componentWillReceiveProps(nextProps) {
@@ -1213,10 +1205,10 @@ describe('ReactIncremental', () => {
1205 ReactNoop.render(<App x={0} />);
1206 ReactNoop.flushDeferredPri(30);
1207
1216 - expect(Scheduler).toHaveYielded(['App', 'componentWillMount:0-0']);
1208 + assertLog(['App', 'componentWillMount:0-0']);
1209
1210 ReactNoop.render(<App x={1} />);
1219 - expect(Scheduler).toFlushAndYield([
1211 + await waitForAll([
1212 'App',
1213 'componentWillReceiveProps:0-1',
1214 'componentWillMount:1-1',
@@ -1225,7 +1217,7 @@ describe('ReactIncremental', () => {
1217 ]);
1218 });
1219
1228 - xit('uses state set in componentWillMount even if initial render was aborted', () => {
1220 + xit('uses state set in componentWillMount even if initial render was aborted', async () => {
1221 class LifeCycle extends React.Component {
1222 constructor(props) {
1223 super(props);
@@ -1252,14 +1244,10 @@ describe('ReactIncremental', () => {
1244 ReactNoop.render(<App x={0} />);
1245 ReactNoop.flushDeferredPri(20);
1246
1255 - expect(Scheduler).toHaveYielded([
1256 - 'App',
1257 - 'componentWillMount:0(ctor)',
1258 - 'render:0(willMount)',
1259 - ]);
1247 + assertLog(['App', 'componentWillMount:0(ctor)', 'render:0(willMount)']);
1248
1249 ReactNoop.render(<App x={1} />);
1262 - expect(Scheduler).toFlushAndYield([
1250 + await waitForAll([
1251 'App',
1252 'componentWillMount:0(willMount)',
1253 'render:1(willMount)',
@@ -1267,7 +1255,7 @@ describe('ReactIncremental', () => {
1255 ]);
1256 });
1257
1270 - xit('calls componentWill* twice if an update render is aborted', () => {
1258 + xit('calls componentWill* twice if an update render is aborted', async () => {
1259 class LifeCycle extends React.Component {
1260 UNSAFE_componentWillMount() {
1261 Scheduler.unstable_yieldValue('componentWillMount:' + this.props.x);
@@ -1316,7 +1304,7 @@ describe('ReactIncremental', () => {
1304 }
1305
1306 ReactNoop.render(<App x={0} />);
1319 - expect(Scheduler).toFlushAndYield([
1307 + await waitForAll([
1308 'App',
1309 'componentWillMount:0',
1310 'render:0',
@@ -1327,7 +1315,7 @@ describe('ReactIncremental', () => {
1315 ReactNoop.render(<App x={1} />);
1316 ReactNoop.flushDeferredPri(30);
1317
1330 - expect(Scheduler).toHaveYielded([
1318 + assertLog([
1319 'App',
1320 'componentWillReceiveProps:0-1',
1321 'shouldComponentUpdate:0-1',
@@ -1338,7 +1326,7 @@ describe('ReactIncremental', () => {
1326 ]);
1327
1328 ReactNoop.render(<App x={2} />);
1341 - expect(Scheduler).toFlushAndYield([
1329 + await waitForAll([
1330 'App',
1331 'componentWillReceiveProps:1-2',
1332 'shouldComponentUpdate:1-2',
@@ -1350,7 +1338,7 @@ describe('ReactIncremental', () => {
1338 ]);
1339 });
1340
1353 - it('calls getDerivedStateFromProps even for state-only updates', () => {
1341 + it('calls getDerivedStateFromProps even for state-only updates', async () => {
1342 let instance;
1343
1344 class LifeCycle extends React.Component {
@@ -1373,11 +1361,11 @@ describe('ReactIncremental', () => {
1361 }
1362
1363 ReactNoop.render(<LifeCycle />);
1376 - expect(Scheduler).toFlushAndYield(['getDerivedStateFromProps', 'render']);
1364 + await waitForAll(['getDerivedStateFromProps', 'render']);
1365 expect(instance.state).toEqual({foo: 'foo'});
1366
1367 instance.changeState();
1380 - expect(Scheduler).toFlushAndYield([
1368 + await waitForAll([
1369 'getDerivedStateFromProps',
1370 'render',
1371 'componentDidUpdate',
@@ -1385,7 +1373,7 @@ describe('ReactIncremental', () => {
1373 expect(instance.state).toEqual({foo: 'foo'});
1374 });
1375
1388 - it('does not call getDerivedStateFromProps if neither state nor props have changed', () => {
1376 + it('does not call getDerivedStateFromProps if neither state nor props have changed', async () => {
1377 class Parent extends React.Component {
1378 state = {parentRenders: 0};
1379 static getDerivedStateFromProps(props, prevState) {
@@ -1407,18 +1395,14 @@ describe('ReactIncremental', () => {
1395
1396 const child = React.createRef(null);
1397 ReactNoop.render(<Parent />);
1410 - expect(Scheduler).toFlushAndYield([
1411 - 'getDerivedStateFromProps',
1412 - 'Parent',
1413 - 'Child',
1414 - ]);
1398 + await waitForAll(['getDerivedStateFromProps', 'Parent', 'Child']);
1399
1400 // Schedule an update on the child. The parent should not re-render.
1401 child.current.setState({});
1418 - expect(Scheduler).toFlushAndYield(['Child']);
1402 + await waitForAll(['Child']);
1403 });
1404
1421 - xit('does not call componentWillReceiveProps for state-only updates', () => {
1405 + xit('does not call componentWillReceiveProps for state-only updates', async () => {
1406 const instances = [];
1407
1408 class LifeCycle extends React.Component {
@@ -1492,7 +1476,7 @@ describe('ReactIncremental', () => {
1476 }
1477
1478 ReactNoop.render(<App y={0} />);
1495 - expect(Scheduler).toFlushAndYield([
1479 + await waitForAll([
1480 'App',
1481 'Wrap',
1482 'componentWillMount:0',
@@ -1506,7 +1490,7 @@ describe('ReactIncremental', () => {
1490
1491 ReactNoop.flushDeferredPri(25);
1492
1509 - expect(Scheduler).toHaveYielded([
1493 + assertLog([
1494 // no componentWillReceiveProps
1495 'shouldComponentUpdate:0-1',
1496 'componentWillUpdate:0-1',
@@ -1517,7 +1501,7 @@ describe('ReactIncremental', () => {
1501 // LifeCycle
1502 instances[1].tick();
1503
1520 - expect(Scheduler).toFlushAndYield([
1504 + await waitForAll([
1505 // no componentWillReceiveProps
1506 'shouldComponentUpdate:1-2',
1507 'componentWillUpdate:1-2',
@@ -1532,7 +1516,7 @@ describe('ReactIncremental', () => {
1516
1517 ReactNoop.flushDeferredPri(30);
1518
1535 - expect(Scheduler).toHaveYielded([
1519 + assertLog([
1520 'Wrap',
1521 'componentWillReceiveProps',
1522 'shouldComponentUpdate:2-2',
@@ -1544,7 +1528,7 @@ describe('ReactIncremental', () => {
1528 // Next we will update LifeCycle directly but not with new props.
1529 instances[1].tick();
1530
1547 - expect(Scheduler).toFlushAndYield([
1531 + await waitForAll([
1532 // This should not trigger another componentWillReceiveProps because
1533 // we never got new props.
1534 'shouldComponentUpdate:2-3',
@@ -1557,7 +1541,7 @@ describe('ReactIncremental', () => {
1541 // incomplete parents.
1542 });
1543
1560 - xit('skips will/DidUpdate when bailing unless an update was already in progress', () => {
1544 + xit('skips will/DidUpdate when bailing unless an update was already in progress', async () => {
1545 class LifeCycle extends React.Component {
1546 UNSAFE_componentWillMount() {
1547 Scheduler.unstable_yieldValue('componentWillMount');
@@ -1595,7 +1579,7 @@ describe('ReactIncremental', () => {
1579 }
1580
1581 ReactNoop.render(<App x={0} />);
1598 - expect(Scheduler).toFlushAndYield([
1582 + await waitForAll([
1583 'componentWillMount',
1584 'render',
1585 'render sibling',
@@ -1604,7 +1588,7 @@ describe('ReactIncremental', () => {
1588
1589 // Update to same props
1590 ReactNoop.render(<App x={0} />);
1607 - expect(Scheduler).toFlushAndYield([
1591 + await waitForAll([
1592 'componentWillReceiveProps',
1593 'shouldComponentUpdate',
1594 // no componentWillUpdate
@@ -1617,7 +1601,7 @@ describe('ReactIncremental', () => {
1601 ReactNoop.render(<App x={1} />);
1602 ReactNoop.flushDeferredPri(30);
1603
1620 - expect(Scheduler).toHaveYielded([
1604 + assertLog([
1605 'componentWillReceiveProps',
1606 'shouldComponentUpdate',
1607 'componentWillUpdate',
@@ -1628,10 +1612,10 @@ describe('ReactIncremental', () => {
1612
1613 // ...but we'll interrupt it to rerender the same props.
1614 ReactNoop.render(<App x={1} />);
1631 - expect(Scheduler).toFlushWithoutYielding();
1615 + await waitForAll([]);
1616
1617 // We can bail out this time, but we must call componentDidUpdate.
1634 - expect(Scheduler).toHaveYielded([
1618 + assertLog([
1619 'componentWillReceiveProps',
1620 'shouldComponentUpdate',
1621 // no componentWillUpdate
@@ -1641,7 +1625,7 @@ describe('ReactIncremental', () => {
1625 ]);
1626 });
1627
1644 - it('can nest batchedUpdates', () => {
1628 + it('can nest batchedUpdates', async () => {
1629 let instance;
1630
1631 class Foo extends React.Component {
@@ -1653,7 +1637,7 @@ describe('ReactIncremental', () => {
1637 }
1638
1639 ReactNoop.render(<Foo />);
1656 - expect(Scheduler).toFlushWithoutYielding();
1640 + await waitForAll([]);
1641
1642 ReactNoop.flushSync(() => {
1643 ReactNoop.batchedUpdates(() => {
@@ -1678,7 +1662,7 @@ describe('ReactIncremental', () => {
1662
1663 // ReactNoop.flush() not needed because updates are synchronous
1664
1681 - expect(Scheduler).toHaveYielded([
1665 + assertLog([
1666 'end inner batchedUpdates',
1667 'end outer batchedUpdates',
1668 'setState 1',
@@ -1689,7 +1673,7 @@ describe('ReactIncremental', () => {
1673 expect(instance.state.n).toEqual(4);
1674 });
1675
1692 - it('can handle if setState callback throws', () => {
1676 + it('can handle if setState callback throws', async () => {
1677 let instance;
1678
1679 class Foo extends React.Component {
@@ -1701,7 +1685,7 @@ describe('ReactIncremental', () => {
1685 }
1686
1687 ReactNoop.render(<Foo />);
1704 - expect(Scheduler).toFlushWithoutYielding();
1688 + await waitForAll([]);
1689
1690 function updater({n}) {
1691 return {n: n + 1};
@@ -1718,16 +1702,14 @@ describe('ReactIncremental', () => {
1702 Scheduler.unstable_yieldValue('third callback'),
1703 );
1704
1721 - expect(() => {
1722 - expect(Scheduler).toFlushWithoutYielding();
1723 - }).toThrow('callback error');
1705 + await waitForThrow('callback error');
1706
1707 // The third callback isn't called because the second one throws
1726 - expect(Scheduler).toHaveYielded(['first callback', 'second callback']);
1708 + assertLog(['first callback', 'second callback']);
1709 expect(instance.state.n).toEqual(3);
1710 });
1711
1730 - it('merges and masks context', () => {
1712 + it('merges and masks context', async () => {
1713 class Intl extends React.Component {
1714 static childContextTypes = {
1715 locale: PropTypes.string,
@@ -1825,7 +1807,7 @@ describe('ReactIncremental', () => {
1807 </div>
1808 </Intl>,
1809 );
1828 - expect(Scheduler).toFlushAndYield([
1810 + await waitForAll([
1811 'Intl {}',
1812 'ShowLocale {"locale":"fr"}',
1813 'ShowBoth {"locale":"fr"}',
@@ -1839,7 +1821,7 @@ describe('ReactIncremental', () => {
1821 </div>
1822 </Intl>,
1823 );
1842 - expect(Scheduler).toFlushAndYield([
1824 + await waitForAll([
1825 'Intl {}',
1826 'ShowLocale {"locale":"de"}',
1827 'ShowBoth {"locale":"de"}',
@@ -1854,7 +1836,7 @@ describe('ReactIncremental', () => {
1836 </Intl>,
1837 );
1838 });
1857 - expect(Scheduler).toFlushAndYieldThrough(['Intl {}']);
1839 + await waitFor(['Intl {}']);
1840
1841 ReactNoop.render(
1842 <Intl locale="en">
@@ -1865,7 +1847,7 @@ describe('ReactIncremental', () => {
1847 <ShowBoth />
1848 </Intl>,
1849 );
1868 - expect(Scheduler).toFlushAndYield([
1850 + await waitForAll([
1851 'ShowLocale {"locale":"sv"}',
1852 'ShowBoth {"locale":"sv"}',
1853 'Intl {}',
@@ -1882,7 +1864,7 @@ describe('ReactIncremental', () => {
1864 ]);
1865 });
1866
1885 - it('does not leak own context into context provider', () => {
1867 + it('does not leak own context into context provider', async () => {
1868 class Recurse extends React.Component {
1869 static contextTypes = {
1870 n: PropTypes.number,
@@ -1905,7 +1887,7 @@ describe('ReactIncremental', () => {
1887 }
1888
1889 ReactNoop.render(<Recurse />);
1908 - expect(Scheduler).toFlushAndYield([
1890 + await waitForAll([
1891 'Recurse {}',
1892 'Recurse {"n":2}',
1893 'Recurse {"n":1}',
@@ -1955,7 +1937,7 @@ describe('ReactIncremental', () => {
1937 }
1938
1939 // @gate www
1958 - it('provides context when reusing work', () => {
1940 + it('provides context when reusing work', async () => {
1941 class Intl extends React.Component {
1942 static childContextTypes = {
1943 locale: PropTypes.string,
@@ -1997,20 +1979,20 @@ describe('ReactIncremental', () => {
1979 </Intl>,
1980 );
1981 });
2000 - expect(Scheduler).toFlushAndYieldThrough([
1982 + await waitFor([
1983 'Intl {}',
1984 'ShowLocale {"locale":"fr"}',
1985 'ShowLocale {"locale":"fr"}',
1986 ]);
1987
2006 - expect(Scheduler).toFlushAndYield([
1988 + await waitForAll([
1989 'ShowLocale {"locale":"fr"}',
1990 'Intl {}',
1991 'ShowLocale {"locale":"ru"}',
1992 ]);
1993 });
1994
2013 - it('reads context when setState is below the provider', () => {
1995 + it('reads context when setState is below the provider', async () => {
1996 let statefulInst;
1997
1998 class Intl extends React.Component {
@@ -2090,7 +2072,7 @@ describe('ReactIncremental', () => {
2072 </IndirectionFn>
2073 </Intl>,
2074 );
2093 - expect(Scheduler).toFlushAndYield([
2075 + await waitForAll([
2076 'Intl:read {}',
2077 'Intl:provide {"locale":"fr"}',
2078 'IndirectionFn {}',
@@ -2100,13 +2082,13 @@ describe('ReactIncremental', () => {
2082 ]);
2083
2084 statefulInst.setState({x: 1});
2103 - expect(Scheduler).toFlushWithoutYielding();
2085 + await waitForAll([]);
2086 // All work has been memoized because setState()
2087 // happened below the context and could not have affected it.
2106 - expect(Scheduler).toHaveYielded([]);
2088 + assertLog([]);
2089 });
2090
2109 - it('reads context when setState is above the provider', () => {
2091 + it('reads context when setState is above the provider', async () => {
2092 let statefulInst;
2093
2094 class Intl extends React.Component {
@@ -2184,7 +2166,7 @@ describe('ReactIncremental', () => {
2166 </IndirectionFn>
2167 </Stateful>,
2168 );
2187 - expect(Scheduler).toFlushAndYield([
2169 + await waitForAll([
2170 'Intl:read {}',
2171 'Intl:provide {"locale":"fr"}',
2172 'IndirectionFn {}',
@@ -2194,7 +2176,7 @@ describe('ReactIncremental', () => {
2176 ]);
2177
2178 statefulInst.setState({locale: 'gr'});
2197 - expect(Scheduler).toFlushAndYield([
2179 + await waitForAll([
2180 // Intl is below setState() so it might have been
2181 // affected by it. Therefore we re-render and recompute
2182 // its child context.
@@ -2210,7 +2192,7 @@ describe('ReactIncremental', () => {
2192 ]);
2193 });
2194
2213 - it('maintains the correct context when providers bail out due to low priority', () => {
2195 + it('maintains the correct context when providers bail out due to low priority', async () => {
2196 class Root extends React.Component {
2197 render() {
2198 return <Middle {...this.props} />;
@@ -2246,14 +2228,14 @@ describe('ReactIncremental', () => {
2228
2229 // Init
2230 ReactNoop.render(<Root />);
2249 - expect(Scheduler).toFlushWithoutYielding();
2231 + await waitForAll([]);
2232
2233 // Trigger an update in the middle of the tree
2234 instance.setState({});
2253 - expect(Scheduler).toFlushWithoutYielding();
2235 + await waitForAll([]);
2236 });
2237
2256 - it('maintains the correct context when unwinding due to an error in render', () => {
2238 + it('maintains the correct context when unwinding due to an error in render', async () => {
2239 class Root extends React.Component {
2240 componentDidCatch(error) {
2241 // If context is pushed/popped correctly,
@@ -2292,7 +2274,7 @@ describe('ReactIncremental', () => {
2274
2275 // Init
2276 ReactNoop.render(<Root />);
2295 - expect(Scheduler).toFlushWithoutYielding();
2277 + await waitForAll([]);
2278
2279 // Trigger an update in the middle of the tree
2280 // This is necessary to reproduce the error as it currently exists.
@@ -2304,7 +2286,7 @@ describe('ReactIncremental', () => {
2286 );
2287 });
2288
2307 - it('should not recreate masked context unless inputs have changed', () => {
2289 + it('should not recreate masked context unless inputs have changed', async () => {
2290 let scuCounter = 0;
2291
2292 class MyComponent extends React.Component {
@@ -2334,7 +2316,7 @@ describe('ReactIncremental', () => {
2316 }
2317
2318 ReactNoop.render(<MyComponent />);
2337 - expect(Scheduler).toFlushAndYield([
2319 + await waitForAll([
2320 'render',
2321 'componentDidMount',
2322 'shouldComponentUpdate',
@@ -2346,7 +2328,7 @@ describe('ReactIncremental', () => {
2328 ]);
2329 });
2330
2349 - xit('should reuse memoized work if pointers are updated before calling lifecycles', () => {
2331 + xit('should reuse memoized work if pointers are updated before calling lifecycles', async () => {
2332 const cduNextProps = [];
2333 const cduPrevProps = [];
2334 const scuNextProps = [];
@@ -2395,7 +2377,7 @@ describe('ReactIncremental', () => {
2377 // Initial render of the entire tree.
2378 // Renders: Root, Middle, FirstChild, SecondChild
2379 ReactNoop.render(<Root>A</Root>);
2398 - expect(Scheduler).toFlushWithoutYielding();
2380 + await waitForAll([]);
2381
2382 expect(renderCounter).toBe(1);
2383
@@ -2417,7 +2399,7 @@ describe('ReactIncremental', () => {
2399 // The in-progress child content will bailout.
2400 // Renders: Root, Middle, FirstChild, SecondChild
2401 ReactNoop.render(<Root>B</Root>);
2420 - expect(Scheduler).toFlushWithoutYielding();
2402 + await waitForAll([]);
2403
2404 // At this point the higher priority render has completed.
2405 // Since FirstChild props didn't change, sCU returned false.
@@ -2429,7 +2411,7 @@ describe('ReactIncremental', () => {
2411 expect(cduNextProps).toEqual([{children: 'B'}]);
2412 });
2413
2432 - it('updates descendants with new context values', () => {
2414 + it('updates descendants with new context values', async () => {
2415 let instance;
2416
2417 class TopContextProvider extends React.Component {
@@ -2473,12 +2455,12 @@ describe('ReactIncremental', () => {
2455 </TopContextProvider>,
2456 );
2457
2476 - expect(Scheduler).toFlushAndYield(['count:0']);
2458 + await waitForAll(['count:0']);
2459 instance.updateCount();
2478 - expect(Scheduler).toFlushAndYield(['count:1']);
2460 + await waitForAll(['count:1']);
2461 });
2462
2481 - it('updates descendants with multiple context-providing ancestors with new context values', () => {
2463 + it('updates descendants with multiple context-providing ancestors with new context values', async () => {
2464 let instance;
2465
2466 class TopContextProvider extends React.Component {
@@ -2528,12 +2510,12 @@ describe('ReactIncremental', () => {
2510 </TopContextProvider>,
2511 );
2512
2531 - expect(Scheduler).toFlushAndYield(['count:0']);
2513 + await waitForAll(['count:0']);
2514 instance.updateCount();
2533 - expect(Scheduler).toFlushAndYield(['count:1']);
2515 + await waitForAll(['count:1']);
2516 });
2517
2536 - it('should not update descendants with new context values if shouldComponentUpdate returns false', () => {
2518 + it('should not update descendants with new context values if shouldComponentUpdate returns false', async () => {
2519 let instance;
2520
2521 class TopContextProvider extends React.Component {
@@ -2592,12 +2574,12 @@ describe('ReactIncremental', () => {
2574 </TopContextProvider>,
2575 );
2576
2595 - expect(Scheduler).toFlushAndYield(['count:0']);
2577 + await waitForAll(['count:0']);
2578 instance.updateCount();
2597 - expect(Scheduler).toFlushWithoutYielding();
2579 + await waitForAll([]);
2580 });
2581
2600 - it('should update descendants with new context values if setState() is called in the middle of the tree', () => {
2582 + it('should update descendants with new context values if setState() is called in the middle of the tree', async () => {
2583 let middleInstance;
2584 let topInstance;
2585
@@ -2668,14 +2650,14 @@ describe('ReactIncremental', () => {
2650 </TopContextProvider>,
2651 );
2652
2671 - expect(Scheduler).toFlushAndYield(['count:0, name:brian']);
2653 + await waitForAll(['count:0, name:brian']);
2654 topInstance.updateCount();
2673 - expect(Scheduler).toFlushWithoutYielding();
2655 + await waitForAll([]);
2656 middleInstance.updateName('not brian');
2675 - expect(Scheduler).toFlushAndYield(['count:1, name:not brian']);
2657 + await waitForAll(['count:1, name:not brian']);
2658 });
2659
2678 - it('does not interrupt for update at same priority', () => {
2660 + it('does not interrupt for update at same priority', async () => {
2661 function Parent(props) {
2662 Scheduler.unstable_yieldValue('Parent: ' + props.step);
2663 return <Child step={props.step} />;
@@ -2689,15 +2671,15 @@ describe('ReactIncremental', () => {
2671 React.startTransition(() => {
2672 ReactNoop.render(<Parent step={1} />);
2673 });
2692 - expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
2674 + await waitFor(['Parent: 1']);
2675
2676 // Interrupt at same priority
2677 ReactNoop.render(<Parent step={2} />);
2678
2697 - expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
2679 + await waitForAll(['Child: 1', 'Parent: 2', 'Child: 2']);
2680 });
2681
2700 - it('does not interrupt for update at lower priority', () => {
2682 + it('does not interrupt for update at lower priority', async () => {
2683 function Parent(props) {
2684 Scheduler.unstable_yieldValue('Parent: ' + props.step);
2685 return <Child step={props.step} />;
@@ -2711,16 +2693,16 @@ describe('ReactIncremental', () => {
2693 React.startTransition(() => {
2694 ReactNoop.render(<Parent step={1} />);
2695 });
2714 - expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
2696 + await waitFor(['Parent: 1']);
2697
2698 // Interrupt at lower priority
2699 ReactNoop.expire(2000);
2700 ReactNoop.render(<Parent step={2} />);
2701
2720 - expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
2702 + await waitForAll(['Child: 1', 'Parent: 2', 'Child: 2']);
2703 });
2704
2723 - it('does interrupt for update at higher priority', () => {
2705 + it('does interrupt for update at higher priority', async () => {
2706 function Parent(props) {
2707 Scheduler.unstable_yieldValue('Parent: ' + props.step);
2708 return <Child step={props.step} />;
@@ -2734,20 +2716,20 @@ describe('ReactIncremental', () => {
2716 React.startTransition(() => {
2717 ReactNoop.render(<Parent step={1} />);
2718 });
2737 - expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
2719 + await waitFor(['Parent: 1']);
2720
2721 // Interrupt at higher priority
2722 ReactNoop.flushSync(() => ReactNoop.render(<Parent step={2} />));
2741 - expect(Scheduler).toHaveYielded(['Parent: 2', 'Child: 2']);
2723 + assertLog(['Parent: 2', 'Child: 2']);
2724
2743 - expect(Scheduler).toFlushAndYield([]);
2725 + await waitForAll([]);
2726 });
2727
2728 // We sometimes use Maps with Fibers as keys.
2747 - it('does not break with a bad Map polyfill', () => {
2729 + it('does not break with a bad Map polyfill', async () => {
2730 const realMapSet = Map.prototype.set;
2731
2750 - function triggerCodePathThatUsesFibersAsMapKeys() {
2732 + async function triggerCodePathThatUsesFibersAsMapKeys() {
2733 function Thing() {
2734 throw new Error('No.');
2735 }
@@ -2770,8 +2752,8 @@ describe('ReactIncremental', () => {
2752 <Boundary />
2753 </React.StrictMode>,
2754 );
2773 - expect(() => {
2774 - expect(Scheduler).toFlushWithoutYielding();
2755 + await expect(async () => {
2756 + await waitForAll([]);
2757 }).toErrorDev([
2758 'Legacy context API has been detected within a strict-mode tree',
2759 ]);
@@ -2793,9 +2775,15 @@ describe('ReactIncremental', () => {
2775 React = require('react');
2776 ReactNoop = require('react-noop-renderer');
2777 Scheduler = require('scheduler');
2778 + let InternalTestUtils = require('internal-test-utils');
2779 + waitForAll = InternalTestUtils.waitForAll;
2780 + waitFor = InternalTestUtils.waitFor;
2781 + waitForThrow = InternalTestUtils.waitForThrow;
2782 + assertLog = InternalTestUtils.assertLog;
2783 +
2784 try {
2785 receivedNonExtensibleObjects = false;
2798 - triggerCodePathThatUsesFibersAsMapKeys();
2786 + await triggerCodePathThatUsesFibersAsMapKeys();
2787 } finally {
2788 // eslint-disable-next-line no-extend-native
2789 Map.prototype.set = realMapSet;
@@ -2821,8 +2809,14 @@ describe('ReactIncremental', () => {
2809 React = require('react');
2810 ReactNoop = require('react-noop-renderer');
2811 Scheduler = require('scheduler');
2812 + InternalTestUtils = require('internal-test-utils');
2813 + waitForAll = InternalTestUtils.waitForAll;
2814 + waitFor = InternalTestUtils.waitFor;
2815 + waitForThrow = InternalTestUtils.waitForThrow;
2816 + assertLog = InternalTestUtils.assertLog;
2817 +
2818 try {
2825 - triggerCodePathThatUsesFibersAsMapKeys();
2819 + await triggerCodePathThatUsesFibersAsMapKeys();
2820 } finally {
2821 // eslint-disable-next-line no-extend-native
2822 Map.prototype.set = realMapSet;
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+141 -159
@@ -16,6 +16,10 @@ let React;
16 let ReactNoop;
17 let Scheduler;
18 let act;
19 +let assertLog;
20 +let waitForAll;
21 +let waitFor;
22 +let waitForThrow;
23
24 describe('ReactIncrementalErrorHandling', () => {
25 beforeEach(() => {
@@ -27,6 +31,12 @@ describe('ReactIncrementalErrorHandling', () => {
31 ReactNoop = require('react-noop-renderer');
32 Scheduler = require('scheduler');
33 act = require('jest-react').act;
34 +
35 + const InternalTestUtils = require('internal-test-utils');
36 + assertLog = InternalTestUtils.assertLog;
37 + waitForAll = InternalTestUtils.waitForAll;
38 + waitFor = InternalTestUtils.waitFor;
39 + waitForThrow = InternalTestUtils.waitForThrow;
40 });
41
42 afterEach(() => {
@@ -55,7 +65,7 @@ describe('ReactIncrementalErrorHandling', () => {
65 );
66 }
67
58 - it('recovers from errors asynchronously', () => {
68 + it('recovers from errors asynchronously', async () => {
69 class ErrorBoundary extends React.Component {
70 state = {error: null};
71 static getDerivedStateFromError(error) {
@@ -104,7 +114,7 @@ describe('ReactIncrementalErrorHandling', () => {
114 });
115
116 // Start rendering asynchronously
107 - expect(Scheduler).toFlushAndYieldThrough([
117 + await waitFor([
118 'ErrorBoundary (try)',
119 'Indirection',
120 'Indirection',
@@ -114,9 +124,9 @@ describe('ReactIncrementalErrorHandling', () => {
124 ]);
125
126 // Still rendering async...
117 - expect(Scheduler).toFlushAndYieldThrough(['Indirection']);
127 + await waitFor(['Indirection']);
128
119 - expect(Scheduler).toFlushAndYieldThrough([
129 + await waitFor([
130 'Indirection',
131
132 // Call getDerivedStateFromError and re-render the error boundary, this
@@ -153,7 +163,7 @@ describe('ReactIncrementalErrorHandling', () => {
163 );
164 });
165
156 - it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', () => {
166 + it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', async () => {
167 class ErrorBoundary extends React.Component {
168 state = {error: null};
169 componentDidCatch(error) {
@@ -202,7 +212,7 @@ describe('ReactIncrementalErrorHandling', () => {
212 });
213
214 // Start rendering asynchronously
205 - expect(Scheduler).toFlushAndYieldThrough([
215 + await waitFor([
216 'ErrorBoundary (try)',
217 'Indirection',
218 'Indirection',
@@ -212,9 +222,9 @@ describe('ReactIncrementalErrorHandling', () => {
222 ]);
223
224 // Still rendering async...
215 - expect(Scheduler).toFlushAndYieldThrough(['Indirection']);
225 + await waitFor(['Indirection']);
226
217 - expect(Scheduler).toFlushAndYieldThrough([
227 + await waitFor([
228 'Indirection',
229 // Now that the tree is complete, and there's no remaining work, React
230 // reverts to legacy mode to retry one more time before handling the error.
@@ -254,7 +264,7 @@ describe('ReactIncrementalErrorHandling', () => {
264 React.startTransition(() => {
265 ReactNoop.render(<App isBroken={true} />, onCommit);
266 });
257 - expect(Scheduler).toFlushAndYieldThrough(['error']);
267 + await waitFor(['error']);
268
269 React.startTransition(() => {
270 // This update is in a separate batch
@@ -268,7 +278,7 @@ describe('ReactIncrementalErrorHandling', () => {
278 // to recover from the error is synchronous, this should be enough to
279 // finish the rest of the work.
280 Scheduler.unstable_flushNumberOfYields(1);
271 - expect(Scheduler).toHaveYielded([
281 + assertLog([
282 'success',
283 // Nothing commits until the second update completes.
284 'commit',
@@ -280,7 +290,7 @@ describe('ReactIncrementalErrorHandling', () => {
290 });
291
292 // @gate www
283 - it('does not include offscreen work when retrying after an error', () => {
293 + it('does not include offscreen work when retrying after an error', async () => {
294 function App(props) {
295 if (props.isBroken) {
296 Scheduler.unstable_yieldValue('error');
@@ -304,7 +314,7 @@ describe('ReactIncrementalErrorHandling', () => {
314 React.startTransition(() => {
315 ReactNoop.render(<App isBroken={true} />, onCommit);
316 });
307 - expect(Scheduler).toFlushAndYieldThrough(['error']);
317 + await waitFor(['error']);
318
319 expect(ReactNoop).toMatchRenderedOutput(null);
320
@@ -320,7 +330,7 @@ describe('ReactIncrementalErrorHandling', () => {
330 // to recover from the error is synchronous, this should be enough to
331 // finish the rest of the work.
332 Scheduler.unstable_flushNumberOfYields(1);
323 - expect(Scheduler).toHaveYielded([
333 + assertLog([
334 'success',
335 // Nothing commits until the second update completes.
336 'commit',
@@ -335,7 +345,7 @@ describe('ReactIncrementalErrorHandling', () => {
345 );
346
347 // The offscreen content finishes in a subsequent render
338 - expect(Scheduler).toFlushAndYield([]);
348 + await waitForAll([]);
349 expect(ReactNoop).toMatchRenderedOutput(
350 <>
351 Everything is fine
@@ -346,7 +356,7 @@ describe('ReactIncrementalErrorHandling', () => {
356 );
357 });
358
349 - it('retries one more time before handling error', () => {
359 + it('retries one more time before handling error', async () => {
360 function BadRender({unused}) {
361 Scheduler.unstable_yieldValue('BadRender');
362 throw new Error('oops');
@@ -374,19 +384,14 @@ describe('ReactIncrementalErrorHandling', () => {
384 });
385
386 // Render the bad component asynchronously
377 - expect(Scheduler).toFlushAndYieldThrough(['Parent', 'BadRender']);
387 + await waitFor(['Parent', 'BadRender']);
388
389 // Finish the rest of the async work
380 - expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
390 + await waitFor(['Sibling']);
391
392 // Old scheduler renders, commits, and throws synchronously
393 expect(() => Scheduler.unstable_flushNumberOfYields(1)).toThrow('oops');
384 - expect(Scheduler).toHaveYielded([
385 - 'Parent',
386 - 'BadRender',
387 - 'Sibling',
388 - 'commit',
389 - ]);
394 + assertLog(['Parent', 'BadRender', 'Sibling', 'commit']);
395 expect(ReactNoop).toMatchRenderedOutput(null);
396 });
397
@@ -418,7 +423,7 @@ describe('ReactIncrementalErrorHandling', () => {
423 });
424
425 // Render part of the tree
421 - expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
426 + await waitFor(['A', 'B']);
427
428 // Expire the render midway through
429 Scheduler.unstable_advanceTime(10000);
@@ -428,7 +433,7 @@ describe('ReactIncrementalErrorHandling', () => {
433 ReactNoop.flushSync();
434 }).toThrow('Oops');
435
431 - expect(Scheduler).toHaveYielded([
436 + assertLog([
437 // The render expired, but we shouldn't throw out the partial work.
438 // Finish the current level.
439 'Oops',
@@ -446,7 +451,7 @@ describe('ReactIncrementalErrorHandling', () => {
451 expect(ReactNoop).toMatchRenderedOutput(null);
452 });
453
449 - it('calls componentDidCatch multiple times for multiple errors', () => {
454 + it('calls componentDidCatch multiple times for multiple errors', async () => {
455 let id = 0;
456 class BadMount extends React.Component {
457 componentDidMount() {
@@ -481,7 +486,7 @@ describe('ReactIncrementalErrorHandling', () => {
486 </ErrorBoundary>,
487 );
488
484 - expect(Scheduler).toFlushAndYield([
489 + await waitForAll([
490 'ErrorBoundary',
491 'BadMount',
492 'BadMount',
@@ -495,7 +500,7 @@ describe('ReactIncrementalErrorHandling', () => {
500 );
501 });
502
498 - it('catches render error in a boundary during full deferred mounting', () => {
503 + it('catches render error in a boundary during full deferred mounting', async () => {
504 class ErrorBoundary extends React.Component {
505 state = {error: null};
506 componentDidCatch(error) {
@@ -520,13 +525,13 @@ describe('ReactIncrementalErrorHandling', () => {
525 <BrokenRender />
526 </ErrorBoundary>,
527 );
523 - expect(Scheduler).toFlushWithoutYielding();
528 + await waitForAll([]);
529 expect(ReactNoop).toMatchRenderedOutput(
530 <span prop="Caught an error: Hello." />,
531 );
532 });
533
529 - it('catches render error in a boundary during partial deferred mounting', () => {
534 + it('catches render error in a boundary during partial deferred mounting', async () => {
535 class ErrorBoundary extends React.Component {
536 state = {error: null};
537 componentDidCatch(error) {
@@ -558,10 +563,10 @@ describe('ReactIncrementalErrorHandling', () => {
563 );
564 });
565
561 - expect(Scheduler).toFlushAndYieldThrough(['ErrorBoundary render success']);
566 + await waitFor(['ErrorBoundary render success']);
567 expect(ReactNoop).toMatchRenderedOutput(null);
568
564 - expect(Scheduler).toFlushAndYield([
569 + await waitForAll([
570 'BrokenRender',
571 // React retries one more time
572 'ErrorBoundary render success',
@@ -608,7 +613,7 @@ describe('ReactIncrementalErrorHandling', () => {
613 );
614 });
615
611 - expect(Scheduler).toHaveYielded([
616 + assertLog([
617 'ErrorBoundary render success',
618 'BrokenRender',
619
@@ -658,7 +663,7 @@ describe('ReactIncrementalErrorHandling', () => {
663 );
664 });
665
661 - expect(Scheduler).toHaveYielded([
666 + assertLog([
667 'ErrorBoundary render success',
668 'BrokenRender',
669
@@ -675,7 +680,7 @@ describe('ReactIncrementalErrorHandling', () => {
680 );
681 });
682
678 - it('propagates an error from a noop error boundary during full deferred mounting', () => {
683 + it('propagates an error from a noop error boundary during full deferred mounting', async () => {
684 class RethrowErrorBoundary extends React.Component {
685 componentDidCatch(error) {
686 Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
@@ -698,23 +703,22 @@ describe('ReactIncrementalErrorHandling', () => {
703 </RethrowErrorBoundary>,
704 );
705
701 - expect(() => {
702 - expect(Scheduler).toFlushAndYield([
703 - 'RethrowErrorBoundary render',
704 - 'BrokenRender',
706 + await waitForThrow('Hello');
707 + assertLog([
708 + 'RethrowErrorBoundary render',
709 + 'BrokenRender',
710
706 - // React retries one more time
707 - 'RethrowErrorBoundary render',
708 - 'BrokenRender',
711 + // React retries one more time
712 + 'RethrowErrorBoundary render',
713 + 'BrokenRender',
714
710 - // Errored again on retry. Now handle it.
711 - 'RethrowErrorBoundary componentDidCatch',
712 - ]);
713 - }).toThrow('Hello');
715 + // Errored again on retry. Now handle it.
716 + 'RethrowErrorBoundary componentDidCatch',
717 + ]);
718 expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
719 });
720
717 - it('propagates an error from a noop error boundary during partial deferred mounting', () => {
721 + it('propagates an error from a noop error boundary during partial deferred mounting', async () => {
722 class RethrowErrorBoundary extends React.Component {
723 componentDidCatch(error) {
724 Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
@@ -739,12 +743,10 @@ describe('ReactIncrementalErrorHandling', () => {
743 );
744 });
745
742 - expect(Scheduler).toFlushAndYieldThrough(['RethrowErrorBoundary render']);
746 + await waitFor(['RethrowErrorBoundary render']);
747
744 - expect(() => {
745 - expect(Scheduler).toFlushWithoutYielding();
746 - }).toThrow('Hello');
747 - expect(Scheduler).toHaveYielded([
748 + await waitForThrow('Hello');
749 + assertLog([
750 'BrokenRender',
751
752 // React retries one more time
@@ -783,7 +785,7 @@ describe('ReactIncrementalErrorHandling', () => {
785 );
786 });
787 }).toThrow('Hello');
786 - expect(Scheduler).toHaveYielded([
788 + assertLog([
789 'RethrowErrorBoundary render',
790 'BrokenRender',
791
@@ -826,7 +828,7 @@ describe('ReactIncrementalErrorHandling', () => {
828 );
829 });
830 }).toThrow('Hello');
829 - expect(Scheduler).toHaveYielded([
831 + assertLog([
832 'RethrowErrorBoundary render',
833 'BrokenRender',
834
@@ -840,7 +842,7 @@ describe('ReactIncrementalErrorHandling', () => {
842 expect(ReactNoop).toMatchRenderedOutput(null);
843 });
844
843 - it('applies batched updates regardless despite errors in scheduling', () => {
845 + it('applies batched updates regardless despite errors in scheduling', async () => {
846 ReactNoop.render(<span prop="a:1" />);
847 expect(() => {
848 ReactNoop.batchedUpdates(() => {
@@ -849,11 +851,11 @@ describe('ReactIncrementalErrorHandling', () => {
851 throw new Error('Hello');
852 });
853 }).toThrow('Hello');
852 - expect(Scheduler).toFlushWithoutYielding();
854 + await waitForAll([]);
855 expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
856 });
857
856 - it('applies nested batched updates despite errors in scheduling', () => {
858 + it('applies nested batched updates despite errors in scheduling', async () => {
859 ReactNoop.render(<span prop="a:1" />);
860 expect(() => {
861 ReactNoop.batchedUpdates(() => {
@@ -866,12 +868,12 @@ describe('ReactIncrementalErrorHandling', () => {
868 });
869 });
870 }).toThrow('Hello');
869 - expect(Scheduler).toFlushWithoutYielding();
871 + await waitForAll([]);
872 expect(ReactNoop).toMatchRenderedOutput(<span prop="a:5" />);
873 });
874
875 // TODO: Is this a breaking change?
874 - it('defers additional sync work to a separate event after an error', () => {
876 + it('defers additional sync work to a separate event after an error', async () => {
877 ReactNoop.render(<span prop="a:1" />);
878 expect(() => {
879 ReactNoop.flushSync(() => {
@@ -882,11 +884,11 @@ describe('ReactIncrementalErrorHandling', () => {
884 });
885 });
886 }).toThrow('Hello');
885 - Scheduler.unstable_flushAll();
887 + await waitForAll([]);
888 expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
889 });
890
889 - it('can schedule updates after uncaught error in render on mount', () => {
891 + it('can schedule updates after uncaught error in render on mount', async () => {
892 function BrokenRender({unused}) {
893 Scheduler.unstable_yieldValue('BrokenRender');
894 throw new Error('Hello');
@@ -898,20 +900,18 @@ describe('ReactIncrementalErrorHandling', () => {
900 }
901
902 ReactNoop.render(<BrokenRender />);
901 - expect(() => {
902 - expect(Scheduler).toFlushWithoutYielding();
903 - }).toThrow('Hello');
903 + await waitForThrow('Hello');
904 ReactNoop.render(<Foo />);
905 - expect(Scheduler).toHaveYielded([
905 + assertLog([
906 'BrokenRender',
907 // React retries one more time
908 'BrokenRender',
909 // Errored again on retry
910 ]);
911 - expect(Scheduler).toFlushAndYield(['Foo']);
911 + await waitForAll(['Foo']);
912 });
913
914 - it('can schedule updates after uncaught error in render on update', () => {
914 + it('can schedule updates after uncaught error in render on update', async () => {
915 function BrokenRender({shouldThrow}) {
916 Scheduler.unstable_yieldValue('BrokenRender');
917 if (shouldThrow) {
@@ -926,13 +926,11 @@ describe('ReactIncrementalErrorHandling', () => {
926 }
927
928 ReactNoop.render(<BrokenRender shouldThrow={false} />);
929 - expect(Scheduler).toFlushAndYield(['BrokenRender']);
929 + await waitForAll(['BrokenRender']);
930
931 - expect(() => {
932 - ReactNoop.render(<BrokenRender shouldThrow={true} />);
933 - expect(Scheduler).toFlushWithoutYielding();
934 - }).toThrow('Hello');
935 - expect(Scheduler).toHaveYielded([
931 + ReactNoop.render(<BrokenRender shouldThrow={true} />);
932 + await waitForThrow('Hello');
933 + assertLog([
934 'BrokenRender',
935 // React retries one more time
936 'BrokenRender',
@@ -940,10 +938,10 @@ describe('ReactIncrementalErrorHandling', () => {
938 ]);
939
940 ReactNoop.render(<Foo />);
943 - expect(Scheduler).toFlushAndYield(['Foo']);
941 + await waitForAll(['Foo']);
942 });
943
946 - it('can schedule updates after uncaught error during unmounting', () => {
944 + it('can schedule updates after uncaught error during unmounting', async () => {
945 class BrokenComponentWillUnmount extends React.Component {
946 render() {
947 return <div />;
@@ -959,19 +957,17 @@ describe('ReactIncrementalErrorHandling', () => {
957 }
958
959 ReactNoop.render(<BrokenComponentWillUnmount />);
962 - expect(Scheduler).toFlushWithoutYielding();
960 + await waitForAll([]);
961
964 - expect(() => {
965 - ReactNoop.render(<div />);
966 - expect(Scheduler).toFlushWithoutYielding();
967 - }).toThrow('Hello');
962 + ReactNoop.render(<div />);
963 + await waitForThrow('Hello');
964
965 ReactNoop.render(<Foo />);
970 - expect(Scheduler).toFlushAndYield(['Foo']);
966 + await waitForAll(['Foo']);
967 });
968
969 // @gate skipUnmountedBoundaries
974 - it('should not attempt to recover an unmounting error boundary', () => {
970 + it('should not attempt to recover an unmounting error boundary', async () => {
971 class Parent extends React.Component {
972 componentWillUnmount() {
973 Scheduler.unstable_yieldValue('Parent componentWillUnmount');
@@ -1001,22 +997,21 @@ describe('ReactIncrementalErrorHandling', () => {
997 }
998
999 ReactNoop.render(<Parent />);
1004 - expect(Scheduler).toFlushWithoutYielding();
1000 + await waitForAll([]);
1001
1002 // Because the error boundary is also unmounting,
1003 // an error in ThrowsOnUnmount should be rethrown.
1008 - expect(() => {
1009 - ReactNoop.render(null);
1010 - expect(Scheduler).toFlushAndYield([
1011 - 'Parent componentWillUnmount',
1012 - 'ThrowsOnUnmount componentWillUnmount',
1013 - ]);
1014 - }).toThrow('unmount error');
1004 + ReactNoop.render(null);
1005 + await waitForThrow('unmount error');
1006 + await assertLog([
1007 + 'Parent componentWillUnmount',
1008 + 'ThrowsOnUnmount componentWillUnmount',
1009 + ]);
1010
1011 ReactNoop.render(<Parent />);
1012 });
1013
1019 - it('can unmount an error boundary before it is handled', () => {
1014 + it('can unmount an error boundary before it is handled', async () => {
1015 let parent;
1016
1017 class Parent extends React.Component {
@@ -1045,14 +1040,14 @@ describe('ReactIncrementalErrorHandling', () => {
1040 }
1041
1042 ReactNoop.render(<Parent />);
1048 - expect(Scheduler).toFlushWithoutYielding();
1043 + await waitForAll([]);
1044
1045 ReactNoop.flushSync(() => {
1046 ReactNoop.render(<Parent />);
1047 });
1048 });
1049
1055 - it('continues work on other roots despite caught errors', () => {
1050 + it('continues work on other roots despite caught errors', async () => {
1051 class ErrorBoundary extends React.Component {
1052 state = {error: null};
1053 componentDidCatch(error) {
@@ -1079,50 +1074,42 @@ describe('ReactIncrementalErrorHandling', () => {
1074 'a',
1075 );
1076 ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
1082 - expect(Scheduler).toFlushWithoutYielding();
1077 + await waitForAll([]);
1078 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(
1079 <span prop="Caught an error: Hello." />,
1080 );
1086 - expect(Scheduler).toFlushWithoutYielding();
1081 + await waitForAll([]);
1082 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
1083 });
1084
1090 - it('continues work on other roots despite uncaught errors', () => {
1085 + it('continues work on other roots despite uncaught errors', async () => {
1086 function BrokenRender(props) {
1087 throw new Error(props.label);
1088 }
1089
1090 ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
1096 - expect(() => {
1097 - expect(Scheduler).toFlushWithoutYielding();
1098 - }).toThrow('a');
1091 + await waitForThrow('a');
1092 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
1093
1094 ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
1095 ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
1103 - expect(() => {
1104 - expect(Scheduler).toFlushWithoutYielding();
1105 - }).toThrow('a');
1096 + await waitForThrow('a');
1097
1107 - expect(Scheduler).toFlushWithoutYielding();
1098 + await waitForAll([]);
1099 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
1100 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:2" />);
1101
1102 ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
1103 ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
1113 - expect(() => {
1114 - expect(Scheduler).toFlushWithoutYielding();
1115 - }).toThrow('b');
1104 + await waitForThrow('b');
1105 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:3" />);
1106 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
1107
1108 ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
1109 ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
1110 ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
1122 - expect(() => {
1123 - expect(Scheduler).toFlushWithoutYielding();
1124 - }).toThrow('b');
1125 - expect(Scheduler).toFlushWithoutYielding();
1111 + await waitForThrow('b');
1112 + await waitForAll([]);
1113 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:4" />);
1114 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
1115 expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:4" />);
@@ -1132,10 +1119,8 @@ describe('ReactIncrementalErrorHandling', () => {
1119 ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
1120 ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
1121 ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
1135 - expect(() => {
1136 - expect(Scheduler).toFlushWithoutYielding();
1137 - }).toThrow('e');
1138 - expect(Scheduler).toFlushWithoutYielding();
1122 + await waitForThrow('e');
1123 + await waitForAll([]);
1124 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:5" />);
1125 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:5" />);
1126 expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:5" />);
@@ -1149,17 +1134,11 @@ describe('ReactIncrementalErrorHandling', () => {
1134 ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
1135 ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
1136
1152 - expect(() => {
1153 - expect(Scheduler).toFlushWithoutYielding();
1154 - }).toThrow('a');
1155 - expect(() => {
1156 - expect(Scheduler).toFlushWithoutYielding();
1157 - }).toThrow('c');
1158 - expect(() => {
1159 - expect(Scheduler).toFlushWithoutYielding();
1160 - }).toThrow('e');
1137 + await waitForThrow('a');
1138 + await waitForThrow('c');
1139 + await waitForThrow('e');
1140
1162 - expect(Scheduler).toFlushWithoutYielding();
1141 + await waitForAll([]);
1142 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
1143 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:6" />);
1144 expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
@@ -1173,7 +1152,7 @@ describe('ReactIncrementalErrorHandling', () => {
1152 ReactNoop.unmountRootWithID('d');
1153 ReactNoop.unmountRootWithID('e');
1154 ReactNoop.unmountRootWithID('f');
1176 - expect(Scheduler).toFlushWithoutYielding();
1155 + await waitForAll([]);
1156 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
1157 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
1158 expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
@@ -1182,7 +1161,7 @@ describe('ReactIncrementalErrorHandling', () => {
1161 expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null);
1162 });
1163
1185 - it('unwinds the context stack correctly on error', () => {
1164 + it('unwinds the context stack correctly on error', async () => {
1165 class Provider extends React.Component {
1166 static childContextTypes = {message: PropTypes.string};
1167 static contextTypes = {message: PropTypes.string};
@@ -1234,7 +1213,7 @@ describe('ReactIncrementalErrorHandling', () => {
1213 <Connector />
1214 </Provider>,
1215 );
1237 - expect(Scheduler).toFlushWithoutYielding();
1216 + await waitForAll([]);
1217
1218 // If the context stack does not unwind, span will get 'abcde'
1219 expect(ReactNoop).toMatchRenderedOutput(<span prop="a" />);
@@ -1283,7 +1262,7 @@ describe('ReactIncrementalErrorHandling', () => {
1262 );
1263 });
1264
1286 - it('catches reconciler errors in a boundary during update', () => {
1265 + it('catches reconciler errors in a boundary during update', async () => {
1266 class ErrorBoundary extends React.Component {
1267 state = {error: null};
1268 componentDidCatch(error) {
@@ -1307,7 +1286,7 @@ describe('ReactIncrementalErrorHandling', () => {
1286 <BrokenRender fail={false} />
1287 </ErrorBoundary>,
1288 );
1310 - expect(Scheduler).toFlushWithoutYielding();
1289 + await waitForAll([]);
1290
1291 ReactNoop.render(
1292 <ErrorBoundary>
@@ -1334,7 +1313,7 @@ describe('ReactIncrementalErrorHandling', () => {
1313 );
1314 });
1315
1337 - it('recovers from uncaught reconciler errors', () => {
1316 + it('recovers from uncaught reconciler errors', async () => {
1317 const InvalidType = undefined;
1318 expect(() => ReactNoop.render(<InvalidType />)).toErrorDev(
1319 'Warning: React.createElement: type is invalid -- expected a string',
@@ -1350,11 +1329,11 @@ describe('ReactIncrementalErrorHandling', () => {
1329 );
1330
1331 ReactNoop.render(<span prop="hi" />);
1353 - expect(Scheduler).toFlushWithoutYielding();
1332 + await waitForAll([]);
1333 expect(ReactNoop).toMatchRenderedOutput(<span prop="hi" />);
1334 });
1335
1357 - it('unmounts components with uncaught errors', () => {
1336 + it('unmounts components with uncaught errors', async () => {
1337 let inst;
1338
1339 class BrokenRenderAndUnmount extends React.Component {
@@ -1390,14 +1369,21 @@ describe('ReactIncrementalErrorHandling', () => {
1369 </Parent>
1370 </Parent>,
1371 );
1393 - expect(Scheduler).toFlushWithoutYielding();
1372 + await waitForAll([]);
1373
1395 - inst.setState({fail: true});
1374 expect(() => {
1397 - expect(Scheduler).toFlushWithoutYielding();
1398 - }).toThrowError('Hello.');
1375 + expect(() => {
1376 + ReactNoop.flushSync(() => {
1377 + inst.setState({fail: true});
1378 + });
1379 + }).toThrow('Hello.');
1380 +
1381 + // The unmount is queued in a microtask. In order to capture the error
1382 + // that occurs during unmount, we can flush it early with `flushSync`.
1383 + ReactNoop.flushSync();
1384 + }).toThrow('One does not simply unmount me.');
1385
1400 - expect(Scheduler).toHaveYielded([
1386 + assertLog([
1387 // Attempt to clean up.
1388 // Errors in parents shouldn't stop children from unmounting.
1389 'Parent componentWillUnmount [!]',
@@ -1405,13 +1391,9 @@ describe('ReactIncrementalErrorHandling', () => {
1391 'BrokenRenderAndUnmount componentWillUnmount',
1392 ]);
1393 expect(ReactNoop).toMatchRenderedOutput(null);
1408 -
1409 - expect(() => {
1410 - ReactNoop.flushSync();
1411 - }).toThrow('One does not simply unmount me.');
1394 });
1395
1414 - it('does not interrupt unmounting if detaching a ref throws', () => {
1396 + it('does not interrupt unmounting if detaching a ref throws', async () => {
1397 class Bar extends React.Component {
1398 componentWillUnmount() {
1399 Scheduler.unstable_yieldValue('Bar unmount');
@@ -1434,7 +1416,7 @@ describe('ReactIncrementalErrorHandling', () => {
1416 }
1417
1418 ReactNoop.render(<Foo />);
1437 - expect(Scheduler).toFlushAndYield(['barRef attach']);
1419 + await waitForAll(['barRef attach']);
1420 expect(ReactNoop).toMatchRenderedOutput(
1421 <div>
1422 <span prop="Bar" />
@@ -1444,7 +1426,7 @@ describe('ReactIncrementalErrorHandling', () => {
1426 // Unmount
1427 ReactNoop.render(<Foo hide={true} />);
1428 expect(Scheduler).toFlushAndThrow('Detach error');
1447 - expect(Scheduler).toHaveYielded([
1429 + assertLog([
1430 'barRef detach',
1431 // Bar should unmount even though its ref threw an error while detaching
1432 'Bar unmount',
@@ -1465,7 +1447,7 @@ describe('ReactIncrementalErrorHandling', () => {
1447 expect(Scheduler).toFlushAndThrow('Error!');
1448 });
1449
1468 - it('error boundaries capture non-errors', () => {
1450 + it('error boundaries capture non-errors', async () => {
1451 spyOnProd(console, 'error').mockImplementation(() => {});
1452 spyOnDev(console, 'error').mockImplementation(() => {});
1453
@@ -1509,7 +1491,7 @@ describe('ReactIncrementalErrorHandling', () => {
1491 </ErrorBoundary>,
1492 );
1493
1512 - expect(Scheduler).toFlushAndYield([
1494 + await waitForAll([
1495 'ErrorBoundary (try)',
1496 'Indirection',
1497 'BadRender',
@@ -1540,7 +1522,7 @@ describe('ReactIncrementalErrorHandling', () => {
1522
1523 // TODO: Error boundary does not catch promises
1524
1543 - it('continues working on siblings of a component that throws', () => {
1525 + it('continues working on siblings of a component that throws', async () => {
1526 class ErrorBoundary extends React.Component {
1527 state = {error: null};
1528 componentDidCatch(error) {
@@ -1580,7 +1562,7 @@ describe('ReactIncrementalErrorHandling', () => {
1562 </ErrorBoundary>,
1563 );
1564
1583 - expect(Scheduler).toFlushAndYield([
1565 + await waitForAll([
1566 'ErrorBoundary (try)',
1567 'throw',
1568 // Continue rendering siblings after BadRender throws
@@ -1603,7 +1585,7 @@ describe('ReactIncrementalErrorHandling', () => {
1585 );
1586 });
1587
1606 - it('calls the correct lifecycles on the error boundary after catching an error (mixed)', () => {
1588 + it('calls the correct lifecycles on the error boundary after catching an error (mixed)', async () => {
1589 // This test seems a bit contrived, but it's based on an actual regression
1590 // where we checked for the existence of didUpdate instead of didMount, and
1591 // didMount was not defined.
@@ -1632,7 +1614,7 @@ describe('ReactIncrementalErrorHandling', () => {
1614 }
1615
1616 ReactNoop.render(<Parent step={1} />);
1635 - expect(Scheduler).toFlushAndYieldThrough([
1617 + await waitFor([
1618 'render',
1619 'throw',
1620 'render',
@@ -1646,7 +1628,7 @@ describe('ReactIncrementalErrorHandling', () => {
1628 );
1629 });
1630
1649 - it('provides component stack to the error boundary with componentDidCatch', () => {
1631 + it('provides component stack to the error boundary with componentDidCatch', async () => {
1632 class ErrorBoundary extends React.Component {
1633 state = {error: null, errorInfo: null};
1634 componentDidCatch(error, errorInfo) {
@@ -1676,7 +1658,7 @@ describe('ReactIncrementalErrorHandling', () => {
1658 <BrokenRender />
1659 </ErrorBoundary>,
1660 );
1679 - expect(Scheduler).toFlushAndYield(['render error message']);
1661 + await waitForAll(['render error message']);
1662 expect(ReactNoop).toMatchRenderedOutput(
1663 <span
1664 prop={
@@ -1688,7 +1670,7 @@ describe('ReactIncrementalErrorHandling', () => {
1670 );
1671 });
1672
1691 - it('does not provide component stack to the error boundary with getDerivedStateFromError', () => {
1673 + it('does not provide component stack to the error boundary with getDerivedStateFromError', async () => {
1674 class ErrorBoundary extends React.Component {
1675 state = {error: null};
1676 static getDerivedStateFromError(error, errorInfo) {
@@ -1712,13 +1694,13 @@ describe('ReactIncrementalErrorHandling', () => {
1694 <BrokenRender />
1695 </ErrorBoundary>,
1696 );
1715 - expect(Scheduler).toFlushWithoutYielding();
1697 + await waitForAll([]);
1698 expect(ReactNoop).toMatchRenderedOutput(
1699 <span prop="Caught an error: Hello" />,
1700 );
1701 });
1702
1721 - it('provides component stack even if overriding prepareStackTrace', () => {
1703 + it('provides component stack even if overriding prepareStackTrace', async () => {
1704 Error.prepareStackTrace = function (error, callsites) {
1705 const stack = ['An error occurred:', error.message];
1706 for (let i = 0; i < callsites.length; i++) {
@@ -1762,7 +1744,7 @@ describe('ReactIncrementalErrorHandling', () => {
1744 <BrokenRender />
1745 </ErrorBoundary>,
1746 );
1765 - expect(Scheduler).toFlushAndYield(['render error message']);
1747 + await waitForAll(['render error message']);
1748 Error.prepareStackTrace = undefined;
1749
1750 expect(ReactNoop).toMatchRenderedOutput(
@@ -1822,7 +1804,7 @@ describe('ReactIncrementalErrorHandling', () => {
1804 });
1805
1806 // Render past the component that throws, then yield.
1825 - expect(Scheduler).toFlushAndYieldThrough(['Oops']);
1807 + await waitFor(['Oops']);
1808 expect(root).toMatchRenderedOutput(null);
1809 // Interleaved update. When the root completes, instead of throwing the
1810 // error, it should try rendering again. This update will cause it to
@@ -1868,7 +1850,7 @@ describe('ReactIncrementalErrorHandling', () => {
1850
1851 // Render through just the default pri update. The low pri update remains on
1852 // the queue.
1871 - expect(Scheduler).toFlushAndYieldThrough(['Everything is fine.']);
1853 + await waitFor(['Everything is fine.']);
1854
1855 // Schedule a discrete update on a child that triggers an error.
1856 // The root should capture this error. But since there's still a pending
@@ -1878,7 +1860,7 @@ describe('ReactIncrementalErrorHandling', () => {
1860 });
1861 });
1862 // Should render the final state without throwing the error.
1881 - expect(Scheduler).toHaveYielded(['Everything is fine.']);
1863 + assertLog(['Everything is fine.']);
1864 expect(root).toMatchRenderedOutput('Everything is fine.');
1865 });
1866
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+6 -2
@@ -13,6 +13,7 @@
13 let React;
14 let ReactNoop;
15 let Scheduler;
16 +let waitForAll;
17
18 describe('ReactIncrementalErrorLogging', () => {
19 beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactIncrementalErrorLogging', () => {
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
24 +
25 + const InternalTestUtils = require('internal-test-utils');
26 + waitForAll = InternalTestUtils.waitForAll;
27 });
28
29 // Note: in this test file we won't be using toErrorDev() matchers
@@ -151,7 +155,7 @@ describe('ReactIncrementalErrorLogging', () => {
155 }).toThrow('logCapturedError error');
156 });
157
154 - it('resets instance variables before unmounting failed node', () => {
158 + it('resets instance variables before unmounting failed node', async () => {
159 class ErrorBoundary extends React.Component {
160 state = {error: null};
161 componentDidCatch(error) {
@@ -185,7 +189,7 @@ describe('ReactIncrementalErrorLogging', () => {
189 <Foo />
190 </ErrorBoundary>,
191 );
188 - expect(Scheduler).toFlushAndYield(
192 + await waitForAll(
193 [
194 'render: 0',
195
packages/react-reconciler/src/__tests__/ReactIncrementalErrorReplay-test.js
+6 -2
@@ -13,6 +13,7 @@
13 let React;
14 let ReactNoop;
15 let Scheduler;
16 +let waitForAll;
17
18 describe('ReactIncrementalErrorReplay', () => {
19 beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactIncrementalErrorReplay', () => {
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
24 +
25 + const InternalTestUtils = require('internal-test-utils');
26 + waitForAll = InternalTestUtils.waitForAll;
27 });
28
29 it('should fail gracefully on error in the host environment', () => {
@@ -27,7 +31,7 @@ describe('ReactIncrementalErrorReplay', () => {
31 expect(Scheduler).toFlushAndThrow('Error in host config.');
32 });
33
30 - it("should ignore error if it doesn't throw on retry", () => {
34 + it("should ignore error if it doesn't throw on retry", async () => {
35 let didInit = false;
36
37 function badLazyInit() {
@@ -45,6 +49,6 @@ describe('ReactIncrementalErrorReplay', () => {
49 }
50 }
51 ReactNoop.render(<App />);
48 - expect(Scheduler).toFlushWithoutYielding();
52 + await waitForAll([]);
53 });
54 });
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.js
+23 -21
@@ -13,6 +13,8 @@
13 let React;
14 let ReactNoop;
15 let Scheduler;
16 +let waitFor;
17 +let waitForAll;
18
19 describe('ReactIncrementalReflection', () => {
20 beforeEach(() => {
@@ -21,6 +23,10 @@ describe('ReactIncrementalReflection', () => {
23 React = require('react');
24 ReactNoop = require('react-noop-renderer');
25 Scheduler = require('scheduler');
26 +
27 + const InternalTestUtils = require('internal-test-utils');
28 + waitFor = InternalTestUtils.waitFor;
29 + waitForAll = InternalTestUtils.waitForAll;
30 });
31
32 function div(...children) {
@@ -34,7 +40,7 @@ describe('ReactIncrementalReflection', () => {
40 return {type: 'span', children: [], prop, hidden: false};
41 }
42
37 - it('handles isMounted even when the initial render is deferred', () => {
43 + it('handles isMounted even when the initial render is deferred', async () => {
44 const instances = [];
45
46 class Component extends React.Component {
@@ -68,17 +74,17 @@ describe('ReactIncrementalReflection', () => {
74 });
75
76 // Render part way through but don't yet commit the updates.
71 - expect(Scheduler).toFlushAndYieldThrough(['componentWillMount: false']);
77 + await waitFor(['componentWillMount: false']);
78
79 expect(instances[0]._isMounted()).toBe(false);
80
81 // Render the rest and commit the updates.
76 - expect(Scheduler).toFlushAndYield(['componentDidMount: true']);
82 + await waitForAll(['componentDidMount: true']);
83
84 expect(instances[0]._isMounted()).toBe(true);
85 });
86
81 - it('handles isMounted when an unmount is deferred', () => {
87 + it('handles isMounted when an unmount is deferred', async () => {
88 const instances = [];
89
90 class Component extends React.Component {
@@ -109,7 +115,7 @@ describe('ReactIncrementalReflection', () => {
115 }
116
117 ReactNoop.render(<Foo mount={true} />);
112 - expect(Scheduler).toFlushAndYield(['Component']);
118 + await waitForAll(['Component']);
119
120 expect(instances[0]._isMounted()).toBe(true);
121
@@ -118,17 +124,17 @@ describe('ReactIncrementalReflection', () => {
124 });
125 // Render part way through but don't yet commit the updates so it is not
126 // fully unmounted yet.
121 - expect(Scheduler).toFlushAndYieldThrough(['Other']);
127 + await waitFor(['Other']);
128
129 expect(instances[0]._isMounted()).toBe(true);
130
131 // Finish flushing the unmount.
126 - expect(Scheduler).toFlushAndYield(['componentWillUnmount: true']);
132 + await waitForAll(['componentWillUnmount: true']);
133
134 expect(instances[0]._isMounted()).toBe(false);
135 });
136
131 - it('finds no node before insertion and correct node before deletion', () => {
137 + it('finds no node before insertion and correct node before deletion', async () => {
138 let classInstance = null;
139
140 function findInstance(inst) {
@@ -202,18 +208,14 @@ describe('ReactIncrementalReflection', () => {
208 ReactNoop.render(<Foo step={0} />);
209 });
210 // Flush past Component but don't complete rendering everything yet.
205 - expect(Scheduler).toFlushAndYieldThrough([
206 - ['componentWillMount', null],
207 - 'render',
208 - 'render sibling',
209 - ]);
211 + await waitFor([['componentWillMount', null], 'render', 'render sibling']);
212
213 expect(classInstance).toBeDefined();
214 // The instance has been complete but is still not committed so it should
215 // not find any host nodes in it.
216 expect(findInstance(classInstance)).toBe(null);
217
216 - expect(Scheduler).toFlushAndYield([['componentDidMount', span()]]);
218 + await waitForAll([['componentDidMount', span()]]);
219
220 const hostSpan = classInstance.span;
221 expect(hostSpan).toBeDefined();
@@ -223,7 +225,7 @@ describe('ReactIncrementalReflection', () => {
225 // Flush next step which will cause an update but not yet render a new host
226 // node.
227 ReactNoop.render(<Foo step={1} />);
226 - expect(Scheduler).toFlushAndYield([
228 + await waitForAll([
229 ['componentWillUpdate', hostSpan],
230 'render',
231 'render sibling',
@@ -237,7 +239,7 @@ describe('ReactIncrementalReflection', () => {
239 React.startTransition(() => {
240 ReactNoop.render(<Foo step={2} />);
241 });
240 - expect(Scheduler).toFlushAndYieldThrough([
242 + await waitFor([
243 ['componentWillUpdate', hostSpan],
244 'render',
245 'render sibling',
@@ -247,7 +249,7 @@ describe('ReactIncrementalReflection', () => {
249 expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
250
251 // When we finally flush the tree it will get committed.
250 - expect(Scheduler).toFlushAndYield([['componentDidUpdate', div()]]);
252 + await waitForAll([['componentDidUpdate', div()]]);
253
254 const hostDiv = classInstance.div;
255 expect(hostDiv).toBeDefined();
@@ -260,7 +262,7 @@ describe('ReactIncrementalReflection', () => {
262 React.startTransition(() => {
263 ReactNoop.render(<Foo step={3} />);
264 });
263 - expect(Scheduler).toFlushAndYieldThrough([
265 + await waitFor([
266 ['componentWillUpdate', hostDiv],
267 'render',
268 'render sibling',
@@ -269,14 +271,14 @@ describe('ReactIncrementalReflection', () => {
271 // This should still be the host div since the deletion is not committed.
272 expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
273
272 - expect(Scheduler).toFlushAndYield([['componentDidUpdate', null]]);
274 + await waitForAll([['componentDidUpdate', null]]);
275
276 // This should still be the host div since the deletion is not committed.
277 expect(ReactNoop.findInstance(classInstance)).toBe(null);
278
279 // Render a div again
280 ReactNoop.render(<Foo step={4} />);
279 - expect(Scheduler).toFlushAndYield([
281 + await waitForAll([
282 ['componentWillUpdate', null],
283 'render',
284 'render sibling',
@@ -285,6 +287,6 @@ describe('ReactIncrementalReflection', () => {
287
288 // Unmount the component.
289 ReactNoop.render([]);
288 - expect(Scheduler).toFlushAndYield([['componentWillUnmount', hostDiv]]);
290 + await waitForAll([['componentWillUnmount', hostDiv]]);
291 });
292 });
packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js
+35 -25
@@ -14,6 +14,10 @@ let React;
14 let ReactNoop;
15 let Scheduler;
16 let act;
17 +let waitForAll;
18 +let waitFor;
19 +let assertLog;
20 +let waitForPaint;
21
22 describe('ReactIncrementalScheduling', () => {
23 beforeEach(() => {
@@ -23,32 +27,38 @@ describe('ReactIncrementalScheduling', () => {
27 ReactNoop = require('react-noop-renderer');
28 Scheduler = require('scheduler');
29 act = require('jest-react').act;
30 +
31 + const InternalTestUtils = require('internal-test-utils');
32 + waitForAll = InternalTestUtils.waitForAll;
33 + waitFor = InternalTestUtils.waitFor;
34 + assertLog = InternalTestUtils.assertLog;
35 + waitForPaint = InternalTestUtils.waitForPaint;
36 });
37
28 - it('schedules and flushes deferred work', () => {
38 + it('schedules and flushes deferred work', async () => {
39 ReactNoop.render(<span prop="1" />);
40 expect(ReactNoop).toMatchRenderedOutput(null);
41
32 - expect(Scheduler).toFlushWithoutYielding();
42 + await waitForAll([]);
43 expect(ReactNoop).toMatchRenderedOutput(<span prop="1" />);
44 });
45
36 - it('searches for work on other roots once the current root completes', () => {
46 + it('searches for work on other roots once the current root completes', async () => {
47 ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
48 ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
49 ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
50
41 - expect(Scheduler).toFlushWithoutYielding();
51 + await waitForAll([]);
52
53 expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:1" />);
54 expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
55 expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:1" />);
56 });
57
48 - it('schedules top-level updates in order of priority', () => {
58 + it('schedules top-level updates in order of priority', async () => {
59 // Initial render.
60 ReactNoop.render(<span prop={1} />);
51 - expect(Scheduler).toFlushWithoutYielding();
61 + await waitForAll([]);
62 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
63
64 ReactNoop.batchedUpdates(() => {
@@ -64,14 +74,14 @@ describe('ReactIncrementalScheduling', () => {
74
75 // The terminal value should be the last update that was scheduled,
76 // regardless of priority. In this case, that's the last sync update.
67 - expect(Scheduler).toFlushWithoutYielding();
77 + await waitForAll([]);
78 expect(ReactNoop).toMatchRenderedOutput(<span prop={4} />);
79 });
80
71 - it('schedules top-level updates with same priority in order of insertion', () => {
81 + it('schedules top-level updates with same priority in order of insertion', async () => {
82 // Initial render.
83 ReactNoop.render(<span prop={1} />);
74 - expect(Scheduler).toFlushWithoutYielding();
84 + await waitForAll([]);
85 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
86
87 ReactNoop.render(<span prop={2} />);
@@ -79,7 +89,7 @@ describe('ReactIncrementalScheduling', () => {
89 ReactNoop.render(<span prop={4} />);
90 ReactNoop.render(<span prop={5} />);
91
82 - expect(Scheduler).toFlushWithoutYielding();
92 + await waitForAll([]);
93 expect(ReactNoop).toMatchRenderedOutput(<span prop={5} />);
94 });
95
@@ -97,20 +107,20 @@ describe('ReactIncrementalScheduling', () => {
107 ReactNoop.renderToRootWithID(<Text text="b:1" />, 'b');
108 ReactNoop.renderToRootWithID(<Text text="c:1" />, 'c');
109 });
100 - expect(Scheduler).toHaveYielded(['a:1', 'b:1', 'c:1']);
110 + assertLog(['a:1', 'b:1', 'c:1']);
111
112 expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
113 expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
114 expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1');
115
116 // Schedule deferred work in the reverse order
107 - act(() => {
117 + act(async () => {
118 React.startTransition(() => {
119 ReactNoop.renderToRootWithID(<Text text="c:2" />, 'c');
120 ReactNoop.renderToRootWithID(<Text text="b:2" />, 'b');
121 });
122 // Ensure it starts in the order it was scheduled
113 - expect(Scheduler).toFlushAndYieldThrough(['c:2']);
123 + await waitFor(['c:2']);
124
125 expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
126 expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
@@ -122,19 +132,19 @@ describe('ReactIncrementalScheduling', () => {
132 });
133
134 // Keep performing work in the order it was scheduled
125 - expect(Scheduler).toFlushAndYieldThrough(['b:2']);
135 + await waitFor(['b:2']);
136 expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
137 expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
138 expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
139
130 - expect(Scheduler).toFlushAndYieldThrough(['a:2']);
140 + await waitFor(['a:2']);
141 expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2');
142 expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
143 expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
144 });
145 });
146
137 - it('schedules sync updates when inside componentDidMount/Update', () => {
147 + it('schedules sync updates when inside componentDidMount/Update', async () => {
148 let instance;
149
150 class Foo extends React.Component {
@@ -176,7 +186,7 @@ describe('ReactIncrementalScheduling', () => {
186 ReactNoop.render(<Foo />);
187 });
188 // Render without committing
179 - expect(Scheduler).toFlushAndYieldThrough(['render: 0']);
189 + await waitFor(['render: 0']);
190
191 // Do one more unit of work to commit
192 expect(ReactNoop.flushNextYield()).toEqual([
@@ -191,7 +201,7 @@ describe('ReactIncrementalScheduling', () => {
201 React.startTransition(() => {
202 instance.setState({tick: 2});
203 });
194 - expect(Scheduler).toFlushAndYieldThrough(['render: 2']);
204 + await waitFor(['render: 2']);
205 expect(ReactNoop.flushNextYield()).toEqual([
206 'componentDidUpdate: 2',
207 'componentDidUpdate (before setState): 2',
@@ -203,7 +213,7 @@ describe('ReactIncrementalScheduling', () => {
213 ]);
214 });
215
206 - it('can opt-in to async scheduling inside componentDidMount/Update', () => {
216 + it('can opt-in to async scheduling inside componentDidMount/Update', async () => {
217 let instance;
218 class Foo extends React.Component {
219 state = {tick: 0};
@@ -248,7 +258,7 @@ describe('ReactIncrementalScheduling', () => {
258 ReactNoop.render(<Foo />);
259 });
260 // The cDM update should not have flushed yet because it has async priority.
251 - expect(Scheduler).toHaveYielded([
261 + assertLog([
262 'render: 0',
263 'componentDidMount (before setState): 0',
264 'componentDidMount (after setState): 0',
@@ -256,14 +266,14 @@ describe('ReactIncrementalScheduling', () => {
266 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
267
268 // Now flush the cDM update.
259 - expect(Scheduler).toFlushAndYield(['render: 1', 'componentDidUpdate: 1']);
269 + await waitForAll(['render: 1', 'componentDidUpdate: 1']);
270 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
271
272 React.startTransition(() => {
273 instance.setState({tick: 2});
274 });
275
266 - expect(Scheduler).toFlushUntilNextPaint([
276 + await waitForPaint([
277 'render: 2',
278 'componentDidUpdate: 2',
279 'componentDidUpdate (before setState): 2',
@@ -272,11 +282,11 @@ describe('ReactIncrementalScheduling', () => {
282 expect(ReactNoop).toMatchRenderedOutput(<span prop={2} />);
283
284 // Now flush the cDU update.
275 - expect(Scheduler).toFlushAndYield(['render: 3', 'componentDidUpdate: 3']);
285 + await waitForAll(['render: 3', 'componentDidUpdate: 3']);
286 expect(ReactNoop).toMatchRenderedOutput(<span prop={3} />);
287 });
288
279 - it('performs Task work even after time runs out', () => {
289 + it('performs Task work even after time runs out', async () => {
290 class Foo extends React.Component {
291 state = {step: 1};
292 componentDidMount() {
@@ -299,7 +309,7 @@ describe('ReactIncrementalScheduling', () => {
309
310 // This should be just enough to complete all the work, but not enough to
311 // commit it.
302 - expect(Scheduler).toFlushAndYieldThrough(['Foo']);
312 + await waitFor(['Foo']);
313 expect(ReactNoop).toMatchRenderedOutput(null);
314
315 // Do one more unit of work.
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js
+90 -84
@@ -13,6 +13,8 @@
13 let React;
14 let ReactNoop;
15 let Scheduler;
16 +let waitForAll;
17 +let waitFor;
18
19 describe('ReactIncrementalSideEffects', () => {
20 beforeEach(() => {
@@ -21,6 +23,10 @@ describe('ReactIncrementalSideEffects', () => {
23 React = require('react');
24 ReactNoop = require('react-noop-renderer');
25 Scheduler = require('scheduler');
26 +
27 + const InternalTestUtils = require('internal-test-utils');
28 + waitForAll = InternalTestUtils.waitForAll;
29 + waitFor = InternalTestUtils.waitFor;
30 });
31
32 // Note: This is based on a similar component we use in www. We can delete
@@ -36,7 +42,7 @@ describe('ReactIncrementalSideEffects', () => {
42 );
43 }
44
39 - it('can update child nodes of a host instance', () => {
45 + it('can update child nodes of a host instance', async () => {
46 function Bar(props) {
47 return <span>{props.text}</span>;
48 }
@@ -51,7 +57,7 @@ describe('ReactIncrementalSideEffects', () => {
57 }
58
59 ReactNoop.render(<Foo text="Hello" />);
54 - expect(Scheduler).toFlushWithoutYielding();
60 + await waitForAll([]);
61 expect(ReactNoop).toMatchRenderedOutput(
62 <div>
63 <span>Hello</span>
@@ -59,7 +65,7 @@ describe('ReactIncrementalSideEffects', () => {
65 );
66
67 ReactNoop.render(<Foo text="World" />);
62 - expect(Scheduler).toFlushWithoutYielding();
68 + await waitForAll([]);
69 expect(ReactNoop).toMatchRenderedOutput(
70 <div>
71 <span>World</span>
@@ -68,7 +74,7 @@ describe('ReactIncrementalSideEffects', () => {
74 );
75 });
76
71 - it('can update child nodes of a fragment', function () {
77 + it('can update child nodes of a fragment', async function () {
78 function Bar(props) {
79 return <span>{props.text}</span>;
80 }
@@ -88,7 +94,7 @@ describe('ReactIncrementalSideEffects', () => {
94 }
95
96 ReactNoop.render(<Foo text="Hello" />);
91 - expect(Scheduler).toFlushWithoutYielding();
97 + await waitForAll([]);
98 expect(ReactNoop).toMatchRenderedOutput(
99 <div>
100 <span>Hello</span>
@@ -97,7 +103,7 @@ describe('ReactIncrementalSideEffects', () => {
103 );
104
105 ReactNoop.render(<Foo text="World" />);
100 - expect(Scheduler).toFlushWithoutYielding();
106 + await waitForAll([]);
107 expect(ReactNoop).toMatchRenderedOutput(
108 <div>
109 <span>World</span>
@@ -108,7 +114,7 @@ describe('ReactIncrementalSideEffects', () => {
114 );
115
116 ReactNoop.render(<Foo text="Hi" />);
111 - expect(Scheduler).toFlushWithoutYielding();
117 + await waitForAll([]);
118 expect(ReactNoop).toMatchRenderedOutput(
119 <div>
120 <span>Hi</span>
@@ -119,7 +125,7 @@ describe('ReactIncrementalSideEffects', () => {
125 );
126 });
127
122 - it('can update child nodes rendering into text nodes', function () {
128 + it('can update child nodes rendering into text nodes', async function () {
129 function Bar(props) {
130 return props.text;
131 }
@@ -136,15 +142,15 @@ describe('ReactIncrementalSideEffects', () => {
142 }
143
144 ReactNoop.render(<Foo text="Hello" />);
139 - expect(Scheduler).toFlushWithoutYielding();
145 + await waitForAll([]);
146 expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
147
148 ReactNoop.render(<Foo text="World" />);
143 - expect(Scheduler).toFlushWithoutYielding();
149 + await waitForAll([]);
150 expect(ReactNoop).toMatchRenderedOutput(<div>WorldWorld!</div>);
151 });
152
147 - it('can deletes children either components, host or text', function () {
153 + it('can deletes children either components, host or text', async function () {
154 function Bar(props) {
155 return <span prop={props.children} />;
156 }
@@ -160,7 +166,7 @@ describe('ReactIncrementalSideEffects', () => {
166 }
167
168 ReactNoop.render(<Foo show={true} />);
163 - expect(Scheduler).toFlushWithoutYielding();
169 + await waitForAll([]);
170 expect(ReactNoop).toMatchRenderedOutput(
171 <div>
172 <div />
@@ -170,11 +176,11 @@ describe('ReactIncrementalSideEffects', () => {
176 );
177
178 ReactNoop.render(<Foo show={false} />);
173 - expect(Scheduler).toFlushWithoutYielding();
179 + await waitForAll([]);
180 expect(ReactNoop).toMatchRenderedOutput(<div />);
181 });
182
177 - it('can delete a child that changes type - implicit keys', function () {
183 + it('can delete a child that changes type - implicit keys', async function () {
184 let unmounted = false;
185
186 class ClassComponent extends React.Component {
@@ -206,7 +212,7 @@ describe('ReactIncrementalSideEffects', () => {
212 }
213
214 ReactNoop.render(<Foo useClass={true} />);
209 - expect(Scheduler).toFlushWithoutYielding();
215 + await waitForAll([]);
216 expect(ReactNoop).toMatchRenderedOutput(
217 <div>
218 <span prop="Class" />
@@ -217,7 +223,7 @@ describe('ReactIncrementalSideEffects', () => {
223 expect(unmounted).toBe(false);
224
225 ReactNoop.render(<Foo useFunction={true} />);
220 - expect(Scheduler).toFlushWithoutYielding();
226 + await waitForAll([]);
227 expect(ReactNoop).toMatchRenderedOutput(
228 <div>
229 <span prop="Function" />
@@ -228,15 +234,15 @@ describe('ReactIncrementalSideEffects', () => {
234 expect(unmounted).toBe(true);
235
236 ReactNoop.render(<Foo useText={true} />);
231 - expect(Scheduler).toFlushWithoutYielding();
237 + await waitForAll([]);
238 expect(ReactNoop).toMatchRenderedOutput(<div>TextTrail</div>);
239
240 ReactNoop.render(<Foo />);
235 - expect(Scheduler).toFlushWithoutYielding();
241 + await waitForAll([]);
242 expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
243 });
244
239 - it('can delete a child that changes type - explicit keys', function () {
245 + it('can delete a child that changes type - explicit keys', async function () {
246 let unmounted = false;
247
248 class ClassComponent extends React.Component {
@@ -266,7 +272,7 @@ describe('ReactIncrementalSideEffects', () => {
272 }
273
274 ReactNoop.render(<Foo useClass={true} />);
269 - expect(Scheduler).toFlushWithoutYielding();
275 + await waitForAll([]);
276 expect(ReactNoop).toMatchRenderedOutput(
277 <div>
278 <span prop="Class" />
@@ -277,7 +283,7 @@ describe('ReactIncrementalSideEffects', () => {
283 expect(unmounted).toBe(false);
284
285 ReactNoop.render(<Foo useFunction={true} />);
280 - expect(Scheduler).toFlushWithoutYielding();
286 + await waitForAll([]);
287 expect(ReactNoop).toMatchRenderedOutput(
288 <div>
289 <span prop="Function" />
@@ -288,11 +294,11 @@ describe('ReactIncrementalSideEffects', () => {
294 expect(unmounted).toBe(true);
295
296 ReactNoop.render(<Foo />);
291 - expect(Scheduler).toFlushWithoutYielding();
297 + await waitForAll([]);
298 expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
299 });
300
295 - it('can delete a child when it unmounts inside a portal', () => {
301 + it('can delete a child when it unmounts inside a portal', async () => {
302 function Bar(props) {
303 return <span prop={props.children} />;
304 }
@@ -312,7 +318,7 @@ describe('ReactIncrementalSideEffects', () => {
318 <Foo show={true} />
319 </div>,
320 );
315 - expect(Scheduler).toFlushWithoutYielding();
321 + await waitForAll([]);
322 expect(ReactNoop).toMatchRenderedOutput(<div />);
323 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
324 <>
@@ -327,7 +333,7 @@ describe('ReactIncrementalSideEffects', () => {
333 <Foo show={false} />
334 </div>,
335 );
330 - expect(Scheduler).toFlushWithoutYielding();
336 + await waitForAll([]);
337 expect(ReactNoop).toMatchRenderedOutput(<div />);
338 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
339
@@ -336,7 +342,7 @@ describe('ReactIncrementalSideEffects', () => {
342 <Foo show={true} />
343 </div>,
344 );
339 - expect(Scheduler).toFlushWithoutYielding();
345 + await waitForAll([]);
346 expect(ReactNoop).toMatchRenderedOutput(<div />);
347 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
348 <>
@@ -347,17 +353,17 @@ describe('ReactIncrementalSideEffects', () => {
353 );
354
355 ReactNoop.render(null);
350 - expect(Scheduler).toFlushWithoutYielding();
356 + await waitForAll([]);
357 expect(ReactNoop).toMatchRenderedOutput(null);
358 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
359
360 ReactNoop.render(<Foo show={false} />);
355 - expect(Scheduler).toFlushWithoutYielding();
361 + await waitForAll([]);
362 expect(ReactNoop).toMatchRenderedOutput(null);
363 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
364
365 ReactNoop.render(<Foo show={true} />);
360 - expect(Scheduler).toFlushWithoutYielding();
366 + await waitForAll([]);
367 expect(ReactNoop).toMatchRenderedOutput(null);
368 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
369 <>
@@ -368,12 +374,12 @@ describe('ReactIncrementalSideEffects', () => {
374 );
375
376 ReactNoop.render(null);
371 - expect(Scheduler).toFlushWithoutYielding();
377 + await waitForAll([]);
378 expect(ReactNoop).toMatchRenderedOutput(null);
379 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
380 });
381
376 - it('can delete a child when it unmounts with a portal', () => {
382 + it('can delete a child when it unmounts with a portal', async () => {
383 function Bar(props) {
384 return <span prop={props.children} />;
385 }
@@ -393,7 +399,7 @@ describe('ReactIncrementalSideEffects', () => {
399 <Foo />
400 </div>,
401 );
396 - expect(Scheduler).toFlushWithoutYielding();
402 + await waitForAll([]);
403 expect(ReactNoop).toMatchRenderedOutput(<div />);
404 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
405 <>
@@ -404,12 +410,12 @@ describe('ReactIncrementalSideEffects', () => {
410 );
411
412 ReactNoop.render(null);
407 - expect(Scheduler).toFlushWithoutYielding();
413 + await waitForAll([]);
414 expect(ReactNoop).toMatchRenderedOutput(null);
415 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
416
417 ReactNoop.render(<Foo />);
412 - expect(Scheduler).toFlushWithoutYielding();
418 + await waitForAll([]);
419 expect(ReactNoop).toMatchRenderedOutput(null);
420 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
421 <>
@@ -420,12 +426,12 @@ describe('ReactIncrementalSideEffects', () => {
426 );
427
428 ReactNoop.render(null);
423 - expect(Scheduler).toFlushWithoutYielding();
429 + await waitForAll([]);
430 expect(ReactNoop).toMatchRenderedOutput(null);
431 expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
432 });
433
428 - it('does not update child nodes if a flush is aborted', () => {
434 + it('does not update child nodes if a flush is aborted', async () => {
435 function Bar(props) {
436 Scheduler.unstable_yieldValue('Bar');
437 return <span prop={props.text} />;
@@ -445,7 +451,7 @@ describe('ReactIncrementalSideEffects', () => {
451 }
452
453 ReactNoop.render(<Foo text="Hello" />);
448 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar', 'Bar']);
454 + await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
455 expect(ReactNoop).toMatchRenderedOutput(
456 <div>
457 <div>
@@ -461,7 +467,7 @@ describe('ReactIncrementalSideEffects', () => {
467 });
468
469 // Flush some of the work without committing
464 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar']);
470 + await waitFor(['Foo', 'Bar']);
471 expect(ReactNoop).toMatchRenderedOutput(
472 <div>
473 <div>
@@ -474,7 +480,7 @@ describe('ReactIncrementalSideEffects', () => {
480 });
481
482 // @gate www
477 - it('preserves a previously rendered node when deprioritized', () => {
483 + it('preserves a previously rendered node when deprioritized', async () => {
484 function Middle(props) {
485 Scheduler.unstable_yieldValue('Middle');
486 return <span prop={props.children} />;
@@ -492,7 +498,7 @@ describe('ReactIncrementalSideEffects', () => {
498 }
499
500 ReactNoop.render(<Foo text="foo" />);
495 - expect(Scheduler).toFlushAndYield(['Foo', 'Middle']);
501 + await waitForAll(['Foo', 'Middle']);
502
503 expect(ReactNoop.getChildrenAsJSX()).toEqual(
504 <div>
@@ -505,7 +511,7 @@ describe('ReactIncrementalSideEffects', () => {
511 ReactNoop.render(<Foo text="bar" />, () =>
512 Scheduler.unstable_yieldValue('commit'),
513 );
508 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'commit']);
514 + await waitFor(['Foo', 'commit']);
515 expect(ReactNoop.getChildrenAsJSX()).toEqual(
516 <div>
517 <div hidden={true}>
@@ -514,7 +520,7 @@ describe('ReactIncrementalSideEffects', () => {
520 </div>,
521 );
522
517 - expect(Scheduler).toFlushAndYield(['Middle']);
523 + await waitForAll(['Middle']);
524 expect(ReactNoop.getChildrenAsJSX()).toEqual(
525 <div>
526 <div hidden={true}>
@@ -525,7 +531,7 @@ describe('ReactIncrementalSideEffects', () => {
531 });
532
533 // @gate www
528 - it('can reuse side-effects after being preempted', () => {
534 + it('can reuse side-effects after being preempted', async () => {
535 function Bar(props) {
536 Scheduler.unstable_yieldValue('Bar');
537 return <span prop={props.children} />;
@@ -556,7 +562,7 @@ describe('ReactIncrementalSideEffects', () => {
562
563 // Init
564 ReactNoop.render(<Foo text="foo" step={0} />);
559 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
565 + await waitForAll(['Foo', 'Bar', 'Bar']);
566
567 expect(ReactNoop.getChildrenAsJSX()).toEqual(
568 <div hidden={true}>
@@ -572,7 +578,7 @@ describe('ReactIncrementalSideEffects', () => {
578 ReactNoop.render(<Foo text="bar" step={1} />, () =>
579 Scheduler.unstable_yieldValue('commit'),
580 );
575 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'commit', 'Bar']);
581 + await waitFor(['Foo', 'commit', 'Bar']);
582
583 // The tree remains unchanged.
584 expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -588,7 +594,7 @@ describe('ReactIncrementalSideEffects', () => {
594 // render some higher priority work. The middle content will bailout so
595 // it remains untouched which means that it should reuse it next time.
596 ReactNoop.render(<Foo text="foo" step={1} />);
591 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
597 + await waitForAll(['Foo', 'Bar', 'Bar']);
598
599 // Since we did nothing to the middle subtree during the interruption,
600 // we should be able to reuse the reconciliation work that we already did
@@ -605,7 +611,7 @@ describe('ReactIncrementalSideEffects', () => {
611 });
612
613 // @gate www
608 - it('can reuse side-effects after being preempted, if shouldComponentUpdate is false', () => {
614 + it('can reuse side-effects after being preempted, if shouldComponentUpdate is false', async () => {
615 class Bar extends React.Component {
616 shouldComponentUpdate(nextProps) {
617 return this.props.children !== nextProps.children;
@@ -642,7 +648,7 @@ describe('ReactIncrementalSideEffects', () => {
648
649 // Init
650 ReactNoop.render(<Foo text="foo" step={0} />);
645 - expect(Scheduler).toFlushAndYield(['Foo', 'Content', 'Bar', 'Bar']);
651 + await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
652
653 expect(ReactNoop.getChildrenAsJSX()).toEqual(
654 <div hidden={true}>
@@ -656,7 +662,7 @@ describe('ReactIncrementalSideEffects', () => {
662 // Make a quick update which will schedule low priority work to
663 // update the middle content.
664 ReactNoop.render(<Foo text="bar" step={1} />);
659 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Content', 'Bar']);
665 + await waitFor(['Foo', 'Content', 'Bar']);
666
667 // The tree remains unchanged.
668 expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -672,7 +678,7 @@ describe('ReactIncrementalSideEffects', () => {
678 // render some higher priority work. The middle content will bailout so
679 // it remains untouched which means that it should reuse it next time.
680 ReactNoop.render(<Foo text="foo" step={1} />);
675 - expect(Scheduler).toFlushAndYield(['Foo', 'Content', 'Bar', 'Bar']);
681 + await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
682
683 // Since we did nothing to the middle subtree during the interruption,
684 // we should be able to reuse the reconciliation work that we already did
@@ -688,7 +694,7 @@ describe('ReactIncrementalSideEffects', () => {
694 );
695 });
696
691 - it('can update a completed tree before it has a chance to commit', () => {
697 + it('can update a completed tree before it has a chance to commit', async () => {
698 function Foo(props) {
699 Scheduler.unstable_yieldValue('Foo');
700 return <span prop={props.step} />;
@@ -697,7 +703,7 @@ describe('ReactIncrementalSideEffects', () => {
703 ReactNoop.render(<Foo step={1} />);
704 });
705 // This should be just enough to complete the tree without committing it
700 - expect(Scheduler).toFlushAndYieldThrough(['Foo']);
706 + await waitFor(['Foo']);
707 expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
708 // To confirm, perform one more unit of work. The tree should now
709 // be flushed.
@@ -708,7 +714,7 @@ describe('ReactIncrementalSideEffects', () => {
714 ReactNoop.render(<Foo step={2} />);
715 });
716 // This should be just enough to complete the tree without committing it
711 - expect(Scheduler).toFlushAndYieldThrough(['Foo']);
717 + await waitFor(['Foo']);
718 expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
719 // This time, before we commit the tree, we update the root component with
720 // new props
@@ -723,12 +729,12 @@ describe('ReactIncrementalSideEffects', () => {
729 expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
730 // If we flush the rest of the work, we should get another commit that
731 // renders 3. If it renders 2 again, that means an update was dropped.
726 - expect(Scheduler).toFlushWithoutYielding();
732 + await waitForAll([]);
733 expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
734 });
735
736 // @gate www
731 - it('updates a child even though the old props is empty', () => {
737 + it('updates a child even though the old props is empty', async () => {
738 function Foo(props) {
739 return (
740 <LegacyHiddenDiv mode="hidden">
@@ -738,7 +744,7 @@ describe('ReactIncrementalSideEffects', () => {
744 }
745
746 ReactNoop.render(<Foo />);
741 - expect(Scheduler).toFlushWithoutYielding();
747 + await waitForAll([]);
748 expect(ReactNoop.getChildrenAsJSX()).toEqual(
749 <div hidden={true}>
750 <span prop={1} />
@@ -746,7 +752,7 @@ describe('ReactIncrementalSideEffects', () => {
752 );
753 });
754
749 - xit('can defer side-effects and resume them later on', () => {
755 + xit('can defer side-effects and resume them later on', async () => {
756 class Bar extends React.Component {
757 shouldComponentUpdate(nextProps) {
758 return this.props.idx !== nextProps.idx;
@@ -809,7 +815,7 @@ describe('ReactIncrementalSideEffects', () => {
815 </div>,
816 );
817 ReactNoop.render(<Foo tick={3} idx={1} />);
812 - expect(Scheduler).toFlushWithoutYielding();
818 + await waitForAll([]);
819 expect(ReactNoop).toMatchRenderedOutput(
820 <div>
821 <span prop={3} />
@@ -829,7 +835,7 @@ describe('ReactIncrementalSideEffects', () => {
835 expect(innerSpanA).toBe(innerSpanB);
836 });
837
832 - xit('can defer side-effects and reuse them later - complex', function () {
838 + xit('can defer side-effects and reuse them later - complex', async function () {
839 let ops = [];
840
841 class Bar extends React.Component {
@@ -892,7 +898,7 @@ describe('ReactIncrementalSideEffects', () => {
898 expect(ops).toEqual(['Foo']);
899 ops = [];
900
895 - expect(Scheduler).toFlushWithoutYielding();
901 + await waitForAll([]);
902 expect(ReactNoop).toMatchRenderedOutput([
903 <div>
904 <span prop={1} />,
@@ -959,7 +965,7 @@ describe('ReactIncrementalSideEffects', () => {
965
966 // We should now be able to reuse some of the work we've already done
967 // and replay those side-effects.
962 - expect(Scheduler).toFlushWithoutYielding();
968 + await waitForAll([]);
969 expect(ReactNoop).toMatchRenderedOutput([
970 <div>
971 <span prop={3} />,
@@ -979,7 +985,7 @@ describe('ReactIncrementalSideEffects', () => {
985 });
986
987 // @gate www
982 - it('deprioritizes setStates that happens within a deprioritized tree', () => {
988 + it('deprioritizes setStates that happens within a deprioritized tree', async () => {
989 const barInstances = [];
990
991 class Bar extends React.Component {
@@ -1010,7 +1016,7 @@ describe('ReactIncrementalSideEffects', () => {
1016 );
1017 }
1018 ReactNoop.render(<Foo tick={0} idx={0} />);
1013 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar', 'Bar']);
1019 + await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
1020 expect(ReactNoop.getChildrenAsJSX()).toEqual(
1021 <div>
1022 <span prop={0} />
@@ -1023,7 +1029,7 @@ describe('ReactIncrementalSideEffects', () => {
1029 );
1030
1031 ReactNoop.render(<Foo tick={1} idx={1} />);
1026 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar', 'Bar']);
1032 + await waitFor(['Foo', 'Bar', 'Bar']);
1033 expect(ReactNoop.getChildrenAsJSX()).toEqual(
1034 <div>
1035 {/* Updated */}
@@ -1041,7 +1047,7 @@ describe('ReactIncrementalSideEffects', () => {
1047 // This should not be enough time to render the content of all the hidden
1048 // items. Including the set state since that is deprioritized.
1049 // ReactNoop.flushDeferredPri(35);
1044 - expect(Scheduler).toFlushAndYieldThrough(['Bar']);
1050 + await waitFor(['Bar']);
1051 expect(ReactNoop.getChildrenAsJSX()).toEqual(
1052 <div>
1053 {/* Updated */}
@@ -1057,7 +1063,7 @@ describe('ReactIncrementalSideEffects', () => {
1063
1064 // However, once we render fully, we will have enough time to finish it all
1065 // at once.
1060 - expect(Scheduler).toFlushAndYield(['Bar', 'Bar']);
1066 + await waitForAll(['Bar', 'Bar']);
1067 expect(ReactNoop.getChildrenAsJSX()).toEqual(
1068 <div>
1069 <span prop={1} />
@@ -1074,7 +1080,7 @@ describe('ReactIncrementalSideEffects', () => {
1080 // moves to "current" without flushing due to having lower priority. Does this
1081 // even happen? Maybe a child doesn't get processed because it is lower prio?
1082
1077 - it('calls callback after update is flushed', () => {
1083 + it('calls callback after update is flushed', async () => {
1084 let instance;
1085 class Foo extends React.Component {
1086 constructor() {
@@ -1088,18 +1094,18 @@ describe('ReactIncrementalSideEffects', () => {
1094 }
1095
1096 ReactNoop.render(<Foo />);
1091 - expect(Scheduler).toFlushWithoutYielding();
1097 + await waitForAll([]);
1098 expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
1099 let called = false;
1100 instance.setState({text: 'bar'}, () => {
1101 expect(ReactNoop).toMatchRenderedOutput(<span prop="bar" />);
1102 called = true;
1103 });
1098 - expect(Scheduler).toFlushWithoutYielding();
1104 + await waitForAll([]);
1105 expect(called).toBe(true);
1106 });
1107
1102 - it('calls setState callback even if component bails out', () => {
1108 + it('calls setState callback even if component bails out', async () => {
1109 let instance;
1110 class Foo extends React.Component {
1111 constructor() {
@@ -1116,19 +1122,19 @@ describe('ReactIncrementalSideEffects', () => {
1122 }
1123
1124 ReactNoop.render(<Foo />);
1119 - expect(Scheduler).toFlushWithoutYielding();
1125 + await waitForAll([]);
1126 expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
1127 let called = false;
1128 instance.setState({}, () => {
1129 called = true;
1130 });
1125 - expect(Scheduler).toFlushWithoutYielding();
1131 + await waitForAll([]);
1132 expect(called).toBe(true);
1133 });
1134
1135 // TODO: Test that callbacks are not lost if an update is preempted.
1136
1131 - it('calls componentWillUnmount after a deletion, even if nested', () => {
1137 + it('calls componentWillUnmount after a deletion, even if nested', async () => {
1138 const ops = [];
1139
1140 class Bar extends React.Component {
@@ -1170,11 +1176,11 @@ describe('ReactIncrementalSideEffects', () => {
1176 }
1177
1178 ReactNoop.render(<Foo show={true} />);
1173 - expect(Scheduler).toFlushWithoutYielding();
1179 + await waitForAll([]);
1180 expect(ops).toEqual([]);
1181
1182 ReactNoop.render(<Foo show={false} />);
1177 - expect(Scheduler).toFlushWithoutYielding();
1183 + await waitForAll([]);
1184 expect(ops).toEqual([
1185 'A',
1186 'Wrapper',
@@ -1188,7 +1194,7 @@ describe('ReactIncrementalSideEffects', () => {
1194 ]);
1195 });
1196
1191 - it('calls componentDidMount/Update after insertion/update', () => {
1197 + it('calls componentDidMount/Update after insertion/update', async () => {
1198 let ops = [];
1199
1200 class Bar extends React.Component {
@@ -1233,7 +1239,7 @@ describe('ReactIncrementalSideEffects', () => {
1239 }
1240
1241 ReactNoop.render(<Foo />);
1236 - expect(Scheduler).toFlushWithoutYielding();
1242 + await waitForAll([]);
1243 expect(ops).toEqual([
1244 'mount:A',
1245 'mount:B',
@@ -1249,7 +1255,7 @@ describe('ReactIncrementalSideEffects', () => {
1255 ops = [];
1256
1257 ReactNoop.render(<Foo />);
1252 - expect(Scheduler).toFlushWithoutYielding();
1258 + await waitForAll([]);
1259 expect(ops).toEqual([
1260 'update:A',
1261 'update:B',
@@ -1263,7 +1269,7 @@ describe('ReactIncrementalSideEffects', () => {
1269 ]);
1270 });
1271
1266 - it('invokes ref callbacks after insertion/update/unmount', () => {
1272 + it('invokes ref callbacks after insertion/update/unmount', async () => {
1273 let classInstance = null;
1274
1275 let ops = [];
@@ -1310,7 +1316,7 @@ describe('ReactIncrementalSideEffects', () => {
1316
1317 // Refs that switch function instances get reinvoked
1318 ReactNoop.render(<Foo show={true} />);
1313 - expect(Scheduler).toFlushWithoutYielding();
1319 + await waitForAll([]);
1320 expect(ops).toEqual([
1321 // detach all refs that switched handlers first.
1322 null,
@@ -1323,7 +1329,7 @@ describe('ReactIncrementalSideEffects', () => {
1329 ops = [];
1330
1331 ReactNoop.render(<Foo show={false} />);
1326 - expect(Scheduler).toFlushWithoutYielding();
1332 + await waitForAll([]);
1333 expect(ops).toEqual([
1334 // unmount
1335 null,
@@ -1334,7 +1340,7 @@ describe('ReactIncrementalSideEffects', () => {
1340 // TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
1341 // expected way for aborted and resumed render life-cycles.
1342
1337 - it('supports string refs', () => {
1343 + it('supports string refs', async () => {
1344 let fooInstance = null;
1345
1346 class Bar extends React.Component {
@@ -1354,8 +1360,8 @@ describe('ReactIncrementalSideEffects', () => {
1360 }
1361
1362 ReactNoop.render(<Foo />);
1357 - expect(() => {
1358 - expect(Scheduler).toFlushWithoutYielding();
1363 + await expect(async () => {
1364 + await waitForAll([]);
1365 }).toErrorDev([
1366 'Warning: Component "Foo" contains the string ref "bar". ' +
1367 'Support for string refs will be removed in a future major release. ' +
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+57 -65
@@ -15,6 +15,9 @@ let ReactNoop;
15 let Scheduler;
16 let ContinuousEventPriority;
17 let act;
18 +let waitForAll;
19 +let waitFor;
20 +let assertLog;
21
22 describe('ReactIncrementalUpdates', () => {
23 beforeEach(() => {
@@ -26,6 +29,11 @@ describe('ReactIncrementalUpdates', () => {
29 act = require('jest-react').act;
30 ContinuousEventPriority =
31 require('react-reconciler/constants').ContinuousEventPriority;
32 +
33 + const InternalTestUtils = require('internal-test-utils');
34 + waitForAll = InternalTestUtils.waitForAll;
35 + waitFor = InternalTestUtils.waitFor;
36 + assertLog = InternalTestUtils.assertLog;
37 });
38
39 function flushNextRenderIfExpired() {
@@ -37,7 +45,7 @@ describe('ReactIncrementalUpdates', () => {
45 ReactNoop.flushSync();
46 }
47
40 - it('applies updates in order of priority', () => {
48 + it('applies updates in order of priority', async () => {
49 let state;
50 class Foo extends React.Component {
51 state = {};
@@ -58,14 +66,14 @@ describe('ReactIncrementalUpdates', () => {
66 }
67
68 ReactNoop.render(<Foo />);
61 - expect(Scheduler).toFlushAndYieldThrough(['commit']);
69 + await waitFor(['commit']);
70
71 expect(state).toEqual({a: 'a'});
64 - expect(Scheduler).toFlushWithoutYielding();
72 + await waitForAll([]);
73 expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
74 });
75
68 - it('applies updates with equal priority in insertion order', () => {
76 + it('applies updates with equal priority in insertion order', async () => {
77 let state;
78 class Foo extends React.Component {
79 state = {};
@@ -82,11 +90,11 @@ describe('ReactIncrementalUpdates', () => {
90 }
91
92 ReactNoop.render(<Foo />);
85 - expect(Scheduler).toFlushWithoutYielding();
93 + await waitForAll([]);
94 expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
95 });
96
89 - it('only drops updates with equal or lesser priority when replaceState is called', () => {
97 + it('only drops updates with equal or lesser priority when replaceState is called', async () => {
98 let instance;
99 class Foo extends React.Component {
100 state = {};
@@ -104,7 +112,7 @@ describe('ReactIncrementalUpdates', () => {
112 }
113
114 ReactNoop.render(<Foo />);
107 - expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']);
115 + await waitForAll(['render', 'componentDidMount']);
116
117 ReactNoop.flushSync(() => {
118 React.startTransition(() => {
@@ -122,14 +130,14 @@ describe('ReactIncrementalUpdates', () => {
130 // Even though a replaceState has been already scheduled, it hasn't been
131 // flushed yet because it has async priority.
132 expect(instance.state).toEqual({a: 'a', b: 'b'});
125 - expect(Scheduler).toHaveYielded(['render', 'componentDidUpdate']);
133 + assertLog(['render', 'componentDidUpdate']);
134
127 - expect(Scheduler).toFlushAndYield(['render', 'componentDidUpdate']);
135 + await waitForAll(['render', 'componentDidUpdate']);
136 // Now the rest of the updates are flushed, including the replaceState.
137 expect(instance.state).toEqual({c: 'c', d: 'd'});
138 });
139
132 - it('can abort an update, schedule additional updates, and resume', () => {
140 + it('can abort an update, schedule additional updates, and resume', async () => {
141 let instance;
142 class Foo extends React.Component {
143 state = {};
@@ -140,7 +148,7 @@ describe('ReactIncrementalUpdates', () => {
148 }
149
150 ReactNoop.render(<Foo />);
143 - expect(Scheduler).toFlushWithoutYielding();
151 + await waitForAll([]);
152
153 function createUpdate(letter) {
154 return () => {
@@ -159,7 +167,7 @@ describe('ReactIncrementalUpdates', () => {
167 });
168
169 // Begin the updates but don't flush them yet
162 - expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']);
170 + await waitFor(['a', 'b', 'c']);
171 expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
172
173 // Schedule some more updates at different priorities
@@ -174,11 +182,11 @@ describe('ReactIncrementalUpdates', () => {
182
183 // The sync updates should have flushed, but not the async ones.
184 if (gate(flags => flags.enableUnifiedSyncLane)) {
177 - expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
185 + assertLog(['d', 'e', 'f']);
186 expect(ReactNoop).toMatchRenderedOutput(<span prop="def" />);
187 } else {
188 // Update d was dropped and replaced by e.
181 - expect(Scheduler).toHaveYielded(['e', 'f']);
189 + assertLog(['e', 'f']);
190 expect(ReactNoop).toMatchRenderedOutput(<span prop="ef" />);
191 }
192
@@ -186,7 +194,7 @@ describe('ReactIncrementalUpdates', () => {
194 // they should be processed again, to ensure that the terminal state
195 // is deterministic.
196 if (gate(flags => !flags.enableUnifiedSyncLane)) {
189 - expect(Scheduler).toFlushAndYield([
197 + await waitForAll([
198 // Since 'g' is in a transition, we'll process 'd' separately first.
199 // That causes us to process 'd' with 'e' and 'f' rebased.
200 'd',
@@ -202,7 +210,7 @@ describe('ReactIncrementalUpdates', () => {
210 'g',
211 ]);
212 } else {
205 - expect(Scheduler).toFlushAndYield([
213 + await waitForAll([
214 // Then we'll re-process everything for 'g'.
215 'a',
216 'b',
@@ -216,7 +224,7 @@ describe('ReactIncrementalUpdates', () => {
224 expect(ReactNoop).toMatchRenderedOutput(<span prop="abcdefg" />);
225 });
226
219 - it('can abort an update, schedule a replaceState, and resume', () => {
227 + it('can abort an update, schedule a replaceState, and resume', async () => {
228 let instance;
229 class Foo extends React.Component {
230 state = {};
@@ -227,7 +235,7 @@ describe('ReactIncrementalUpdates', () => {
235 }
236
237 ReactNoop.render(<Foo />);
230 - expect(Scheduler).toFlushWithoutYielding();
238 + await waitForAll([]);
239
240 function createUpdate(letter) {
241 return () => {
@@ -246,7 +254,7 @@ describe('ReactIncrementalUpdates', () => {
254 });
255
256 // Begin the updates but don't flush them yet
249 - expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']);
257 + await waitFor(['a', 'b', 'c']);
258 expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
259
260 // Schedule some more updates at different priorities
@@ -264,10 +272,10 @@ describe('ReactIncrementalUpdates', () => {
272
273 // The sync updates should have flushed, but not the async ones.
274 if (gate(flags => flags.enableUnifiedSyncLane)) {
267 - expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
275 + assertLog(['d', 'e', 'f']);
276 } else {
277 // Update d was dropped and replaced by e.
270 - expect(Scheduler).toHaveYielded(['e', 'f']);
278 + assertLog(['e', 'f']);
279 }
280 expect(ReactNoop).toMatchRenderedOutput(<span prop="f" />);
281
@@ -275,7 +283,7 @@ describe('ReactIncrementalUpdates', () => {
283 // they should be processed again, to ensure that the terminal state
284 // is deterministic.
285 if (gate(flags => !flags.enableUnifiedSyncLane)) {
278 - expect(Scheduler).toFlushAndYield([
286 + await waitForAll([
287 // Since 'g' is in a transition, we'll process 'd' separately first.
288 // That causes us to process 'd' with 'e' and 'f' rebased.
289 'd',
@@ -291,7 +299,7 @@ describe('ReactIncrementalUpdates', () => {
299 'g',
300 ]);
301 } else {
294 - expect(Scheduler).toFlushAndYield([
302 + await waitForAll([
303 // Then we'll re-process everything for 'g'.
304 'a',
305 'b',
@@ -305,7 +313,7 @@ describe('ReactIncrementalUpdates', () => {
313 expect(ReactNoop).toMatchRenderedOutput(<span prop="fg" />);
314 });
315
308 - it('passes accumulation of previous updates to replaceState updater function', () => {
316 + it('passes accumulation of previous updates to replaceState updater function', async () => {
317 let instance;
318 class Foo extends React.Component {
319 state = {};
@@ -315,7 +323,7 @@ describe('ReactIncrementalUpdates', () => {
323 }
324 }
325 ReactNoop.render(<Foo />);
318 - expect(Scheduler).toFlushWithoutYielding();
326 + await waitForAll([]);
327
328 instance.setState({a: 'a'});
329 instance.setState({b: 'b'});
@@ -324,11 +332,11 @@ describe('ReactIncrementalUpdates', () => {
332 instance.updater.enqueueReplaceState(instance, previousState => ({
333 previousState,
334 }));
327 - expect(Scheduler).toFlushWithoutYielding();
335 + await waitForAll([]);
336 expect(instance.state).toEqual({previousState: {a: 'a', b: 'b'}});
337 });
338
331 - it('does not call callbacks that are scheduled by another callback until a later commit', () => {
339 + it('does not call callbacks that are scheduled by another callback until a later commit', async () => {
340 class Foo extends React.Component {
341 state = {};
342 componentDidMount() {
@@ -347,7 +355,7 @@ describe('ReactIncrementalUpdates', () => {
355 }
356
357 ReactNoop.render(<Foo />);
350 - expect(Scheduler).toFlushAndYield([
358 + await waitForAll([
359 'render',
360 'did mount',
361 'render',
@@ -357,7 +365,7 @@ describe('ReactIncrementalUpdates', () => {
365 ]);
366 });
367
360 - it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
368 + it('gives setState during reconciliation the same priority as whatever level is currently reconciling', async () => {
369 let instance;
370
371 class Foo extends React.Component {
@@ -373,7 +381,7 @@ describe('ReactIncrementalUpdates', () => {
381 }
382 }
383 ReactNoop.render(<Foo />);
376 - expect(Scheduler).toFlushAndYield(['render']);
384 + await waitForAll(['render']);
385
386 ReactNoop.flushSync(() => {
387 instance.setState({a: 'a'});
@@ -384,17 +392,13 @@ describe('ReactIncrementalUpdates', () => {
392 expect(instance.state).toEqual({a: 'a', b: 'b'});
393
394 if (gate(flags => flags.deferRenderPhaseUpdateToNextBatch)) {
387 - expect(Scheduler).toHaveYielded([
388 - 'componentWillReceiveProps',
389 - 'render',
390 - 'render',
391 - ]);
395 + assertLog(['componentWillReceiveProps', 'render', 'render']);
396 } else {
393 - expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
397 + assertLog(['componentWillReceiveProps', 'render']);
398 }
399 });
400
397 - it('updates triggered from inside a class setState updater', () => {
401 + it('updates triggered from inside a class setState updater', async () => {
402 let instance;
403 class Foo extends React.Component {
404 state = {};
@@ -406,7 +410,7 @@ describe('ReactIncrementalUpdates', () => {
410 }
411
412 ReactNoop.render(<Foo />);
409 - expect(Scheduler).toFlushAndYield([
413 + await waitForAll([
414 // Initial render
415 'render',
416 ]);
@@ -451,7 +455,7 @@ describe('ReactIncrementalUpdates', () => {
455 this.setState({a: 'a'});
456 return {b: 'b'};
457 });
454 - expect(Scheduler).toFlushAndYield(
458 + await waitForAll(
459 gate(flags =>
460 flags.deferRenderPhaseUpdateToNextBatch
461 ? // In the new reconciler, updates inside the render phase are
@@ -529,24 +533,20 @@ describe('ReactIncrementalUpdates', () => {
533 return null;
534 }
535
532 - act(() => {
536 + act(async () => {
537 React.startTransition(() => {
538 ReactNoop.render(<App />);
539 });
540 flushNextRenderIfExpired();
537 - expect(Scheduler).toHaveYielded([]);
538 - expect(Scheduler).toFlushAndYield([
539 - 'Render: 0',
540 - 'Commit: 0',
541 - 'Render: 1',
542 - ]);
541 + assertLog([]);
542 + await waitForAll(['Render: 0', 'Commit: 0', 'Render: 1']);
543
544 Scheduler.unstable_advanceTime(10000);
545 React.startTransition(() => {
546 setCount(2);
547 });
548 flushNextRenderIfExpired();
549 - expect(Scheduler).toHaveYielded([]);
549 + assertLog([]);
550 });
551 });
552
@@ -559,7 +559,7 @@ describe('ReactIncrementalUpdates', () => {
559 ReactNoop.flushSync(() => {
560 ReactNoop.render(<Text text="A" />);
561 });
562 - expect(Scheduler).toHaveYielded(['A']);
562 + assertLog(['A']);
563
564 Scheduler.unstable_advanceTime(10000);
565
@@ -567,7 +567,7 @@ describe('ReactIncrementalUpdates', () => {
567 ReactNoop.render(<Text text="B" />);
568 });
569 flushNextRenderIfExpired();
570 - expect(Scheduler).toHaveYielded([]);
570 + assertLog([]);
571 });
572
573 it('regression: does not expire soon due to previous expired work', () => {
@@ -581,7 +581,7 @@ describe('ReactIncrementalUpdates', () => {
581 });
582 Scheduler.unstable_advanceTime(10000);
583 flushNextRenderIfExpired();
584 - expect(Scheduler).toHaveYielded(['A']);
584 + assertLog(['A']);
585
586 Scheduler.unstable_advanceTime(10000);
587
@@ -589,7 +589,7 @@ describe('ReactIncrementalUpdates', () => {
589 ReactNoop.render(<Text text="B" />);
590 });
591 flushNextRenderIfExpired();
592 - expect(Scheduler).toHaveYielded([]);
592 + assertLog([]);
593 });
594
595 it('when rebasing, does not exclude updates that were already committed, regardless of priority', async () => {
@@ -620,7 +620,7 @@ describe('ReactIncrementalUpdates', () => {
620 await act(async () => {
621 root.render(<App />);
622 });
623 - expect(Scheduler).toHaveYielded(['Committed: ']);
623 + assertLog(['Committed: ']);
624 expect(root).toMatchRenderedOutput(null);
625
626 await act(async () => {
@@ -633,13 +633,9 @@ describe('ReactIncrementalUpdates', () => {
633 );
634 });
635 if (gate(flags => flags.enableUnifiedSyncLane)) {
636 - expect(Scheduler).toHaveYielded([
637 - 'Committed: B',
638 - 'Committed: BCD',
639 - 'Committed: ABCD',
640 - ]);
636 + assertLog(['Committed: B', 'Committed: BCD', 'Committed: ABCD']);
637 } else {
642 - expect(Scheduler).toHaveYielded([
638 + assertLog([
639 // A and B are pending. B is higher priority, so we'll render that first.
640 'Committed: B',
641 // Because A comes first in the queue, we're now in rebase mode. B must
@@ -685,7 +681,7 @@ describe('ReactIncrementalUpdates', () => {
681 await act(async () => {
682 root.render(<App />);
683 });
688 - expect(Scheduler).toHaveYielded([]);
684 + assertLog([]);
685 expect(root).toMatchRenderedOutput(null);
686
687 await act(async () => {
@@ -697,13 +693,9 @@ describe('ReactIncrementalUpdates', () => {
693 );
694 });
695 if (gate(flags => flags.enableUnifiedSyncLane)) {
700 - expect(Scheduler).toHaveYielded([
701 - 'Committed: B',
702 - 'Committed: BCD',
703 - 'Committed: ABCD',
704 - ]);
696 + assertLog(['Committed: B', 'Committed: BCD', 'Committed: ABCD']);
697 } else {
706 - expect(Scheduler).toHaveYielded([
698 + assertLog([
699 // A and B are pending. B is higher priority, so we'll render that first.
700 'Committed: B',
701 // Because A comes first in the queue, we're now in rebase mode. B must
packages/react-reconciler/src/__tests__/ReactInterleavedUpdates-test.js
+16 -8
@@ -5,6 +5,9 @@ let startTransition;
5 let useState;
6 let useEffect;
7 let act;
8 +let assertLog;
9 +let waitFor;
10 +let waitForPaint;
11
12 describe('ReactInterleavedUpdates', () => {
13 beforeEach(() => {
@@ -17,6 +20,11 @@ describe('ReactInterleavedUpdates', () => {
20 startTransition = React.startTransition;
21 useState = React.useState;
22 useEffect = React.useEffect;
23 +
24 + const InternalTestUtils = require('internal-test-utils');
25 + assertLog = InternalTestUtils.assertLog;
26 + waitFor = InternalTestUtils.waitFor;
27 + waitForPaint = InternalTestUtils.waitForPaint;
28 });
29
30 function Text({text}) {
@@ -53,7 +61,7 @@ describe('ReactInterleavedUpdates', () => {
61 </>,
62 );
63 });
56 - expect(Scheduler).toHaveYielded([0, 0, 0]);
64 + assertLog([0, 0, 0]);
65 expect(root).toMatchRenderedOutput('000');
66
67 await act(async () => {
@@ -61,7 +69,7 @@ describe('ReactInterleavedUpdates', () => {
69 updateChildren(1);
70 });
71 // Partially render the children. Only the first one.
64 - expect(Scheduler).toFlushAndYieldThrough([1]);
72 + await waitFor([1]);
73
74 // In an interleaved event, schedule an update on each of the children.
75 // Including the two that haven't rendered yet.
@@ -70,11 +78,11 @@ describe('ReactInterleavedUpdates', () => {
78 });
79
80 // We should continue rendering without including the interleaved updates.
73 - expect(Scheduler).toFlushUntilNextPaint([1, 1]);
81 + await waitForPaint([1, 1]);
82 expect(root).toMatchRenderedOutput('111');
83 });
84 // The interleaved updates flush in a separate render.
77 - expect(Scheduler).toHaveYielded([2, 2, 2]);
85 + assertLog([2, 2, 2]);
86 expect(root).toMatchRenderedOutput('222');
87 });
88
@@ -96,7 +104,7 @@ describe('ReactInterleavedUpdates', () => {
104 await act(async () => {
105 root.render(<App />);
106 });
99 - expect(Scheduler).toHaveYielded(['A0', 'B0', 'C0']);
107 + assertLog(['A0', 'B0', 'C0']);
108 expect(root).toMatchRenderedOutput('A0B0C0');
109
110 await act(async () => {
@@ -104,7 +112,7 @@ describe('ReactInterleavedUpdates', () => {
112 startTransition(() => {
113 setStep(1);
114 });
107 - expect(Scheduler).toFlushAndYieldThrough(['A1', 'B1']);
115 + await waitFor(['A1', 'B1']);
116
117 // Schedule an interleaved update. This gets placed on a special queue.
118 startTransition(() => {
@@ -112,7 +120,7 @@ describe('ReactInterleavedUpdates', () => {
120 });
121
122 // Finish rendering the first update.
115 - expect(Scheduler).toFlushUntilNextPaint(['C1']);
123 + await waitForPaint(['C1']);
124
125 // Schedule another update. (In the regression case, this was treated
126 // as a normal, non-interleaved update and it was inserted into the queue
@@ -122,7 +130,7 @@ describe('ReactInterleavedUpdates', () => {
130 });
131 });
132 // The last update should win.
125 - expect(Scheduler).toHaveYielded(['A3', 'B3', 'C3']);
133 + assertLog(['A3', 'B3', 'C3']);
134 expect(root).toMatchRenderedOutput('A3B3C3');
135 });
136 });
packages/react/src/__tests__/ReactProfiler-test.internal.js
+4 -4
@@ -1874,7 +1874,7 @@ describe(`onPostCommit`, () => {
1874 loadModules();
1875 });
1876
1877 - it('should report time spent in passive effects', () => {
1877 + it('should report time spent in passive effects', async () => {
1878 const callback = jest.fn();
1879
1880 const ComponentWithEffects = () => {
@@ -1910,7 +1910,7 @@ describe(`onPostCommit`, () => {
1910 </React.Profiler>,
1911 );
1912 });
1913 - Scheduler.unstable_flushAll();
1913 + await waitForAll([]);
1914
1915 expect(callback).toHaveBeenCalledTimes(1);
1916
@@ -1931,7 +1931,7 @@ describe(`onPostCommit`, () => {
1931 </React.Profiler>,
1932 );
1933 });
1934 - Scheduler.unstable_flushAll();
1934 + await waitForAll([]);
1935
1936 expect(callback).toHaveBeenCalledTimes(2);
1937
@@ -1950,7 +1950,7 @@ describe(`onPostCommit`, () => {
1950 <React.Profiler id="unmount-test" onPostCommit={callback} />,
1951 );
1952 });
1953 - Scheduler.unstable_flushAll();
1953 + await waitForAll([]);
1954
1955 expect(callback).toHaveBeenCalledTimes(3);
1956