Codemod tests to waitFor pattern (6/?) (#26305)
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 4, 2023 at 11:45 UTC
e98695db918721a08eae0a2dceeb5719c1aa7261
10 files changed
+517
-556
packages/react-reconciler/src/__tests__/ReactMemo-test.js
+44
-48
@@ -18,6 +18,8 @@ let ReactNoop;
18
let Suspense;
19
let Scheduler;
20
let act;
21
+let waitForAll;
22
+let assertLog;
23
24
describe('memo', () => {
25
beforeEach(() => {
@@ -29,6 +31,10 @@ describe('memo', () => {
31
Scheduler = require('scheduler');
32
act = require('jest-react').act;
33
({Suspense} = React);
34
+
35
+ const InternalTestUtils = require('internal-test-utils');
36
+ waitForAll = InternalTestUtils.waitForAll;
37
+ assertLog = InternalTestUtils.assertLog;
38
});
39
40
function Text(props) {
@@ -105,9 +111,7 @@ describe('memo', () => {
111
<Counter count={0} />
112
</Suspense>,
113
);
108
- expect(Scheduler).toFlushAndYield(['Loading...']);
109
- await Promise.resolve();
110
- expect(Scheduler).toFlushAndYield([0]);
114
+ await waitForAll(['Loading...', 0]);
115
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
116
117
// Should bail out because props have not changed
@@ -116,7 +120,7 @@ describe('memo', () => {
120
<Counter count={0} />
121
</Suspense>,
122
);
119
- expect(Scheduler).toFlushAndYield([]);
123
+ await waitForAll([]);
124
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
125
126
// Should update because count prop changed
@@ -125,7 +129,7 @@ describe('memo', () => {
129
<Counter count={1} />
130
</Suspense>,
131
);
128
- expect(Scheduler).toFlushAndYield([1]);
132
+ await waitForAll([1]);
133
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
134
});
135
@@ -160,19 +164,17 @@ describe('memo', () => {
164
165
const parent = React.createRef(null);
166
ReactNoop.render(<Parent ref={parent} />);
163
- expect(Scheduler).toFlushAndYield(['Loading...']);
164
- await Promise.resolve();
165
- expect(Scheduler).toFlushAndYield(['Count: 0']);
167
+ await waitForAll(['Loading...', 'Count: 0']);
168
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
169
170
// Should bail out because props have not changed
171
ReactNoop.render(<Parent ref={parent} />);
170
- expect(Scheduler).toFlushAndYield([]);
172
+ await waitForAll([]);
173
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
174
175
// Should update because there was a context change
176
parent.current.setState({count: 1});
175
- expect(Scheduler).toFlushAndYield(['Count: 1']);
177
+ await waitForAll(['Count: 1']);
178
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
179
});
180
@@ -273,7 +275,7 @@ describe('memo', () => {
275
await act(async () => {
276
root.render(<App prop="A" />);
277
});
276
- expect(Scheduler).toHaveYielded([
278
+ assertLog([
279
'SimpleMemo [A0]',
280
'ComplexMemo [A0]',
281
'MemoWithIndirection [A0]',
@@ -283,7 +285,7 @@ describe('memo', () => {
285
await act(async () => {
286
root.render(<App prop="B" />);
287
});
286
- expect(Scheduler).toHaveYielded([
288
+ assertLog([
289
'SimpleMemo [B0]',
290
'ComplexMemo [B0]',
291
'MemoWithIndirection [B0]',
@@ -298,7 +300,7 @@ describe('memo', () => {
300
root.render(<App prop="B" />);
301
});
302
// Nothing re-renders
301
- expect(Scheduler).toHaveYielded([]);
303
+ assertLog([]);
304
305
// Demonstrate what happens when the prop object changes, it bails out
306
// because all the props are the same, but we still render the
@@ -309,7 +311,7 @@ describe('memo', () => {
311
});
312
// The components should re-render with the new local state, but none
313
// of the props objects should have changed
312
- expect(Scheduler).toHaveYielded([
314
+ assertLog([
315
'SimpleMemo [B1]',
316
'ComplexMemo [B1]',
317
'MemoWithIndirection [B1]',
@@ -322,7 +324,7 @@ describe('memo', () => {
324
});
325
// The components should re-render with the new local state, but none
326
// of the props objects should have changed
325
- expect(Scheduler).toHaveYielded([
327
+ assertLog([
328
'SimpleMemo [B2]',
329
'ComplexMemo [B2]',
330
'MemoWithIndirection [B2]',
@@ -345,9 +347,7 @@ describe('memo', () => {
347
<Counter count={0} />
348
</Suspense>,
349
);
348
- expect(Scheduler).toFlushAndYield(['Loading...']);
349
- await Promise.resolve();
350
- expect(Scheduler).toFlushAndYield([0]);
350
+ await waitForAll(['Loading...', 0]);
351
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
352
353
// Should bail out because props have not changed
@@ -356,7 +356,7 @@ describe('memo', () => {
356
<Counter count={0} />
357
</Suspense>,
358
);
359
- expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 0']);
359
+ await waitForAll(['Old count: 0, New count: 0']);
360
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
361
362
// Should update because count prop changed
@@ -365,7 +365,7 @@ describe('memo', () => {
365
<Counter count={1} />
366
</Suspense>,
367
);
368
- expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 1', 1]);
368
+ await waitForAll(['Old count: 0, New count: 1', 1]);
369
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
370
});
371
@@ -383,9 +383,7 @@ describe('memo', () => {
383
<Counter count={0} />
384
</Suspense>,
385
);
386
- expect(Scheduler).toFlushAndYield(['Loading...']);
387
- await Promise.resolve();
388
- expect(Scheduler).toFlushAndYield(['0!']);
386
+ await waitForAll(['Loading...', '0!']);
387
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
388
389
// Should bail out because props have not changed
@@ -394,7 +392,7 @@ describe('memo', () => {
392
<Counter count={0} />
393
</Suspense>,
394
);
397
- expect(Scheduler).toFlushAndYield([]);
395
+ await waitForAll([]);
396
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
397
398
// Should update because count prop changed
@@ -403,7 +401,7 @@ describe('memo', () => {
401
<Counter count={1} />
402
</Suspense>,
403
);
406
- expect(Scheduler).toFlushAndYield(['1!']);
404
+ await waitForAll(['1!']);
405
expect(ReactNoop).toMatchRenderedOutput(<span prop="1!" />);
406
});
407
@@ -436,10 +434,8 @@ describe('memo', () => {
434
<Counter e={5} />
435
</Suspense>,
436
);
439
- expect(Scheduler).toFlushAndYield(['Loading...']);
440
- await Promise.resolve();
441
- expect(() => {
442
- expect(Scheduler).toFlushAndYield([15]);
437
+ await expect(async () => {
438
+ await waitForAll(['Loading...', 15]);
439
}).toErrorDev([
440
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
441
]);
@@ -451,7 +447,7 @@ describe('memo', () => {
447
<Counter e={5} />
448
</Suspense>,
449
);
454
- expect(Scheduler).toFlushAndYield([]);
450
+ await waitForAll([]);
451
expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
452
453
// Should update because count prop changed
@@ -460,7 +456,7 @@ describe('memo', () => {
456
<Counter e={10} />
457
</Suspense>,
458
);
463
- expect(Scheduler).toFlushAndYield([20]);
459
+ await waitForAll([20]);
460
expect(ReactNoop).toMatchRenderedOutput(<span prop={20} />);
461
});
462
@@ -480,7 +476,7 @@ describe('memo', () => {
476
);
477
});
478
483
- it('validates propTypes declared on the inner component', () => {
479
+ it('validates propTypes declared on the inner component', async () => {
480
function FnInner(props) {
481
return props.inner;
482
}
@@ -488,23 +484,23 @@ describe('memo', () => {
484
const Fn = React.memo(FnInner);
485
486
// Mount
491
- expect(() => {
487
+ await expect(async () => {
488
ReactNoop.render(<Fn inner="2" />);
493
- expect(Scheduler).toFlushWithoutYielding();
489
+ await waitForAll([]);
490
}).toErrorDev(
491
'Invalid prop `inner` of type `string` supplied to `FnInner`, expected `number`.',
492
);
493
494
// Update
499
- expect(() => {
495
+ await expect(async () => {
496
ReactNoop.render(<Fn inner={false} />);
501
- expect(Scheduler).toFlushWithoutYielding();
497
+ await waitForAll([]);
498
}).toErrorDev(
499
'Invalid prop `inner` of type `boolean` supplied to `FnInner`, expected `number`.',
500
);
501
});
502
507
- it('validates propTypes declared on the outer component', () => {
503
+ it('validates propTypes declared on the outer component', async () => {
504
function FnInner(props) {
505
return props.outer;
506
}
@@ -512,25 +508,25 @@ describe('memo', () => {
508
Fn.propTypes = {outer: PropTypes.number.isRequired};
509
510
// Mount
515
- expect(() => {
511
+ await expect(async () => {
512
ReactNoop.render(<Fn outer="3" />);
517
- expect(Scheduler).toFlushWithoutYielding();
513
+ await waitForAll([]);
514
}).toErrorDev(
515
// Outer props are checked in createElement
516
'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.',
517
);
518
519
// Update
524
- expect(() => {
520
+ await expect(async () => {
521
ReactNoop.render(<Fn outer={false} />);
526
- expect(Scheduler).toFlushWithoutYielding();
522
+ await waitForAll([]);
523
}).toErrorDev(
524
// Outer props are checked in createElement
525
'Invalid prop `outer` of type `boolean` supplied to `FnInner`, expected `number`.',
526
);
527
});
528
533
- it('validates nested propTypes declarations', () => {
529
+ it('validates nested propTypes declarations', async () => {
530
function Inner(props) {
531
return props.inner + props.middle + props.outer;
532
}
@@ -549,20 +545,20 @@ describe('memo', () => {
545
<Outer />
546
</div>,
547
);
552
- expect(() => {
553
- expect(Scheduler).toFlushWithoutYielding();
548
+ await expect(async () => {
549
+ await waitForAll([]);
550
}).toErrorDev([
551
'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
552
]);
553
554
// Mount
559
- expect(() => {
555
+ await expect(async () => {
556
ReactNoop.render(
557
<div>
558
<Outer inner="2" middle="3" outer="4" />
559
</div>,
560
);
565
- expect(Scheduler).toFlushWithoutYielding();
561
+ await waitForAll([]);
562
}).toErrorDev([
563
'Invalid prop `outer` of type `string` supplied to `Inner`, expected `number`.',
564
'Invalid prop `middle` of type `string` supplied to `Inner`, expected `number`.',
@@ -570,13 +566,13 @@ describe('memo', () => {
566
]);
567
568
// Update
573
- expect(() => {
569
+ await expect(async () => {
570
ReactNoop.render(
571
<div>
572
<Outer inner={false} middle={false} outer={false} />
573
</div>,
574
);
579
- expect(Scheduler).toFlushWithoutYielding();
575
+ await waitForAll([]);
576
}).toErrorDev([
577
'Invalid prop `outer` of type `boolean` supplied to `Inner`, expected `number`.',
578
'Invalid prop `middle` of type `boolean` supplied to `Inner`, expected `number`.',
packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+98
-90
@@ -14,6 +14,8 @@ let useContext;
14
let ReactNoop;
15
let Scheduler;
16
let gen;
17
+let waitForAll;
18
+let waitFor;
19
20
describe('ReactNewContext', () => {
21
beforeEach(() => {
@@ -24,6 +26,10 @@ describe('ReactNewContext', () => {
26
ReactNoop = require('react-noop-renderer');
27
Scheduler = require('scheduler');
28
gen = require('random-seed');
29
+
30
+ const InternalTestUtils = require('internal-test-utils');
31
+ waitForAll = InternalTestUtils.waitForAll;
32
+ waitFor = InternalTestUtils.waitFor;
33
});
34
35
afterEach(() => {
@@ -110,7 +116,7 @@ describe('ReactNewContext', () => {
116
117
function sharedContextTests(label, getConsumer) {
118
describe(`reading context with ${label}`, () => {
113
- it('simple mount and update', () => {
119
+ it('simple mount and update', async () => {
120
const Context = React.createContext(1);
121
const Consumer = getConsumer(Context);
122
@@ -131,16 +137,16 @@ describe('ReactNewContext', () => {
137
}
138
139
ReactNoop.render(<App value={2} />);
134
- expect(Scheduler).toFlushWithoutYielding();
140
+ await waitForAll([]);
141
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
142
143
// Update
144
ReactNoop.render(<App value={3} />);
139
- expect(Scheduler).toFlushWithoutYielding();
145
+ await waitForAll([]);
146
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
147
});
148
143
- it('propagates through shouldComponentUpdate false', () => {
149
+ it('propagates through shouldComponentUpdate false', async () => {
150
const Context = React.createContext(1);
151
const ContextConsumer = getConsumer(Context);
152
@@ -189,7 +195,7 @@ describe('ReactNewContext', () => {
195
}
196
197
ReactNoop.render(<App value={2} />);
192
- expect(Scheduler).toFlushAndYield([
198
+ await waitForAll([
199
'App',
200
'Provider',
201
'Indirection',
@@ -201,15 +207,11 @@ describe('ReactNewContext', () => {
207
208
// Update
209
ReactNoop.render(<App value={3} />);
204
- expect(Scheduler).toFlushAndYield([
205
- 'App',
206
- 'Provider',
207
- 'Consumer render prop',
208
- ]);
210
+ await waitForAll(['App', 'Provider', 'Consumer render prop']);
211
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
212
});
213
212
- it('consumers bail out if context value is the same', () => {
214
+ it('consumers bail out if context value is the same', async () => {
215
const Context = React.createContext(1);
216
const ContextConsumer = getConsumer(Context);
217
@@ -258,7 +260,7 @@ describe('ReactNewContext', () => {
260
}
261
262
ReactNoop.render(<App value={2} />);
261
- expect(Scheduler).toFlushAndYield([
263
+ await waitForAll([
264
'App',
265
'Provider',
266
'Indirection',
@@ -270,7 +272,7 @@ describe('ReactNewContext', () => {
272
273
// Update with the same context value
274
ReactNoop.render(<App value={2} />);
273
- expect(Scheduler).toFlushAndYield([
275
+ await waitForAll([
276
'App',
277
'Provider',
278
// Don't call render prop again
@@ -278,7 +280,7 @@ describe('ReactNewContext', () => {
280
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
281
});
282
281
- it('nested providers', () => {
283
+ it('nested providers', async () => {
284
const Context = React.createContext(1);
285
const Consumer = getConsumer(Context);
286
@@ -325,16 +327,16 @@ describe('ReactNewContext', () => {
327
}
328
329
ReactNoop.render(<App value={2} />);
328
- expect(Scheduler).toFlushWithoutYielding();
330
+ await waitForAll([]);
331
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 8" />);
332
333
// Update
334
ReactNoop.render(<App value={3} />);
333
- expect(Scheduler).toFlushWithoutYielding();
335
+ await waitForAll([]);
336
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 12" />);
337
});
338
337
- it('should provide the correct (default) values to consumers outside of a provider', () => {
339
+ it('should provide the correct (default) values to consumers outside of a provider', async () => {
340
const FooContext = React.createContext({value: 'foo-initial'});
341
const BarContext = React.createContext({value: 'bar-initial'});
342
const FooConsumer = getConsumer(FooContext);
@@ -369,10 +371,10 @@ describe('ReactNewContext', () => {
371
</BarConsumer>
372
</>,
373
);
372
- expect(Scheduler).toFlushWithoutYielding();
374
+ await waitForAll([]);
375
});
376
375
- it('multiple consumers in different branches', () => {
377
+ it('multiple consumers in different branches', async () => {
378
const Context = React.createContext(1);
379
const Consumer = getConsumer(Context);
380
@@ -420,7 +422,7 @@ describe('ReactNewContext', () => {
422
}
423
424
ReactNoop.render(<App value={2} />);
423
- expect(Scheduler).toFlushWithoutYielding();
425
+ await waitForAll([]);
426
expect(ReactNoop).toMatchRenderedOutput(
427
<>
428
<span prop="Result: 4" />
@@ -430,7 +432,7 @@ describe('ReactNewContext', () => {
432
433
// Update
434
ReactNoop.render(<App value={3} />);
433
- expect(Scheduler).toFlushWithoutYielding();
435
+ await waitForAll([]);
436
expect(ReactNoop).toMatchRenderedOutput(
437
<>
438
<span prop="Result: 6" />
@@ -440,7 +442,7 @@ describe('ReactNewContext', () => {
442
443
// Another update
444
ReactNoop.render(<App value={4} />);
443
- expect(Scheduler).toFlushWithoutYielding();
445
+ await waitForAll([]);
446
expect(ReactNoop).toMatchRenderedOutput(
447
<>
448
<span prop="Result: 8" />
@@ -449,7 +451,7 @@ describe('ReactNewContext', () => {
451
);
452
});
453
452
- it('compares context values with Object.is semantics', () => {
454
+ it('compares context values with Object.is semantics', async () => {
455
const Context = React.createContext(1);
456
const ContextConsumer = getConsumer(Context);
457
@@ -498,7 +500,7 @@ describe('ReactNewContext', () => {
500
}
501
502
ReactNoop.render(<App value={NaN} />);
501
- expect(Scheduler).toFlushAndYield([
503
+ await waitForAll([
504
'App',
505
'Provider',
506
'Indirection',
@@ -510,7 +512,7 @@ describe('ReactNewContext', () => {
512
513
// Update
514
ReactNoop.render(<App value={NaN} />);
513
- expect(Scheduler).toFlushAndYield([
515
+ await waitForAll([
516
'App',
517
'Provider',
518
// Consumer should not re-render again
@@ -519,7 +521,7 @@ describe('ReactNewContext', () => {
521
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: NaN" />);
522
});
523
522
- it('context unwinds when interrupted', () => {
524
+ it('context unwinds when interrupted', async () => {
525
const Context = React.createContext('Default');
526
const ContextConsumer = getConsumer(Context);
527
@@ -564,14 +566,14 @@ describe('ReactNewContext', () => {
566
}
567
568
ReactNoop.render(<App value="A" />);
567
- expect(Scheduler).toFlushWithoutYielding();
569
+ await waitForAll([]);
570
expect(ReactNoop).toMatchRenderedOutput(
571
// The second provider should use the default value.
572
<span prop="Result: Does not unwind" />,
573
);
574
});
575
574
- it("does not re-render if there's an update in a child", () => {
576
+ it("does not re-render if there's an update in a child", async () => {
577
const Context = React.createContext(0);
578
const Consumer = getConsumer(Context);
579
@@ -603,19 +605,19 @@ describe('ReactNewContext', () => {
605
606
// Initial mount
607
ReactNoop.render(<App value={1} />);
606
- expect(Scheduler).toFlushAndYield(['Consumer render prop', 'Child']);
608
+ await waitForAll(['Consumer render prop', 'Child']);
609
expect(ReactNoop).toMatchRenderedOutput(
610
<span prop="Context: 1, Step: 0" />,
611
);
612
613
child.setState({step: 1});
612
- expect(Scheduler).toFlushAndYield(['Child']);
614
+ await waitForAll(['Child']);
615
expect(ReactNoop).toMatchRenderedOutput(
616
<span prop="Context: 1, Step: 1" />,
617
);
618
});
619
618
- it('consumer bails out if value is unchanged and something above bailed out', () => {
620
+ it('consumer bails out if value is unchanged and something above bailed out', async () => {
621
const Context = React.createContext(0);
622
const Consumer = getConsumer(Context);
623
@@ -660,7 +662,7 @@ describe('ReactNewContext', () => {
662
663
// Initial mount
664
ReactNoop.render(<App value={1} />);
663
- expect(Scheduler).toFlushAndYield([
665
+ await waitForAll([
666
'App',
667
'PureIndirection',
668
'ChildWithInlineRenderCallback',
@@ -677,7 +679,7 @@ describe('ReactNewContext', () => {
679
680
// Update (bailout)
681
ReactNoop.render(<App value={1} />);
680
- expect(Scheduler).toFlushAndYield(['App']);
682
+ await waitForAll(['App']);
683
expect(ReactNoop).toMatchRenderedOutput(
684
<>
685
<span prop={1} />
@@ -687,7 +689,7 @@ describe('ReactNewContext', () => {
689
690
// Update (no bailout)
691
ReactNoop.render(<App value={2} />);
690
- expect(Scheduler).toFlushAndYield(['App', 'Consumer', 'Consumer']);
692
+ await waitForAll(['App', 'Consumer', 'Consumer']);
693
expect(ReactNoop).toMatchRenderedOutput(
694
<>
695
<span prop={2} />
@@ -697,7 +699,7 @@ describe('ReactNewContext', () => {
699
});
700
701
// @gate www
700
- it("context consumer doesn't bail out inside hidden subtree", () => {
702
+ it("context consumer doesn't bail out inside hidden subtree", async () => {
703
const Context = React.createContext('dark');
704
const Consumer = getConsumer(Context);
705
@@ -712,7 +714,7 @@ describe('ReactNewContext', () => {
714
}
715
716
ReactNoop.render(<App theme="dark" />);
715
- expect(Scheduler).toFlushAndYield(['dark']);
717
+ await waitForAll(['dark']);
718
expect(ReactNoop.getChildrenAsJSX()).toEqual(
719
<div hidden={true}>
720
<span prop="dark" />
@@ -720,7 +722,7 @@ describe('ReactNewContext', () => {
722
);
723
724
ReactNoop.render(<App theme="light" />);
723
- expect(Scheduler).toFlushAndYield(['light']);
725
+ await waitForAll(['light']);
726
expect(ReactNoop.getChildrenAsJSX()).toEqual(
727
<div hidden={true}>
728
<span prop="light" />
@@ -729,7 +731,7 @@ describe('ReactNewContext', () => {
731
});
732
733
// This is a regression case for https://github.com/facebook/react/issues/12389.
732
- it('does not run into an infinite loop', () => {
734
+ it('does not run into an infinite loop', async () => {
735
const Context = React.createContext(null);
736
const Consumer = getConsumer(Context);
737
@@ -759,15 +761,15 @@ describe('ReactNewContext', () => {
761
}
762
763
ReactNoop.render(<App reverse={false} />);
762
- expect(Scheduler).toFlushWithoutYielding();
764
+ await waitForAll([]);
765
ReactNoop.render(<App reverse={true} />);
764
- expect(Scheduler).toFlushWithoutYielding();
766
+ await waitForAll([]);
767
ReactNoop.render(<App reverse={false} />);
766
- expect(Scheduler).toFlushWithoutYielding();
768
+ await waitForAll([]);
769
});
770
771
// This is a regression case for https://github.com/facebook/react/issues/12686
770
- it('does not skip some siblings', () => {
772
+ it('does not skip some siblings', async () => {
773
const Context = React.createContext(0);
774
const ContextConsumer = getConsumer(Context);
775
@@ -816,7 +818,7 @@ describe('ReactNewContext', () => {
818
// Initial mount
819
let inst;
820
ReactNoop.render(<App ref={ref => (inst = ref)} />);
819
- expect(Scheduler).toFlushAndYield(['App']);
821
+ await waitForAll(['App']);
822
expect(ReactNoop).toMatchRenderedOutput(
823
<>
824
<span prop="static 1" />
@@ -825,7 +827,7 @@ describe('ReactNewContext', () => {
827
);
828
// Update the first time
829
inst.setState({step: 1});
828
- expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
830
+ await waitForAll(['App', 'Consumer']);
831
expect(ReactNoop).toMatchRenderedOutput(
832
<>
833
<span prop="static 1" />
@@ -835,7 +837,7 @@ describe('ReactNewContext', () => {
837
);
838
// Update the second time
839
inst.setState({step: 2});
838
- expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
840
+ await waitForAll(['App', 'Consumer']);
841
expect(ReactNoop).toMatchRenderedOutput(
842
<>
843
<span prop="static 1" />
@@ -863,7 +865,7 @@ describe('ReactNewContext', () => {
865
);
866
});
867
866
- it('warns if multiple renderers concurrently render the same context', () => {
868
+ it('warns if multiple renderers concurrently render the same context', async () => {
869
spyOnDev(console, 'error').mockImplementation(() => {});
870
const Context = React.createContext(0);
871
@@ -885,17 +887,20 @@ describe('ReactNewContext', () => {
887
ReactNoop.render(<App value={1} />);
888
});
889
// Render past the Provider, but don't commit yet
888
- expect(Scheduler).toFlushAndYieldThrough(['Foo']);
890
+ await waitFor(['Foo']);
891
892
// Get a new copy of ReactNoop
893
jest.resetModules();
894
React = require('react');
895
ReactNoop = require('react-noop-renderer');
896
Scheduler = require('scheduler');
897
+ const InternalTestUtils = require('internal-test-utils');
898
+ waitForAll = InternalTestUtils.waitForAll;
899
+ waitFor = InternalTestUtils.waitFor;
900
901
// Render the provider again using a different renderer
902
ReactNoop.render(<App value={1} />);
898
- expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
903
+ await waitForAll(['Foo', 'Foo']);
904
905
if (__DEV__) {
906
expect(console.error.mock.calls[0][0]).toContain(
@@ -905,7 +910,7 @@ describe('ReactNewContext', () => {
910
}
911
});
912
908
- it('does not warn if multiple renderers use the same context sequentially', () => {
913
+ it('does not warn if multiple renderers use the same context sequentially', async () => {
914
spyOnDev(console, 'error');
915
const Context = React.createContext(0);
916
@@ -926,24 +931,27 @@ describe('ReactNewContext', () => {
931
React.startTransition(() => {
932
ReactNoop.render(<App value={1} />);
933
});
929
- expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
934
+ await waitForAll(['Foo', 'Foo']);
935
936
// Get a new copy of ReactNoop
937
jest.resetModules();
938
React = require('react');
939
ReactNoop = require('react-noop-renderer');
940
Scheduler = require('scheduler');
941
+ const InternalTestUtils = require('internal-test-utils');
942
+ waitForAll = InternalTestUtils.waitForAll;
943
+ waitFor = InternalTestUtils.waitFor;
944
945
// Render the provider again using a different renderer
946
ReactNoop.render(<App value={1} />);
939
- expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
947
+ await waitForAll(['Foo', 'Foo']);
948
949
if (__DEV__) {
950
expect(console.error).not.toHaveBeenCalled();
951
}
952
});
953
946
- it('provider bails out if children and value are unchanged (like sCU)', () => {
954
+ it('provider bails out if children and value are unchanged (like sCU)', async () => {
955
const Context = React.createContext(0);
956
957
function Child() {
@@ -962,19 +970,19 @@ describe('ReactNewContext', () => {
970
971
// Initial mount
972
ReactNoop.render(<App value={1} />);
965
- expect(Scheduler).toFlushAndYield(['App', 'Child']);
973
+ await waitForAll(['App', 'Child']);
974
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
975
976
// Update
977
ReactNoop.render(<App value={1} />);
970
- expect(Scheduler).toFlushAndYield([
978
+ await waitForAll([
979
'App',
980
// Child does not re-render
981
]);
982
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
983
});
984
977
- it('provider does not bail out if legacy context changed above', () => {
985
+ it('provider does not bail out if legacy context changed above', async () => {
986
const Context = React.createContext(0);
987
988
function Child() {
@@ -1021,22 +1029,22 @@ describe('ReactNewContext', () => {
1029
</App>
1030
</LegacyProvider>,
1031
);
1024
- expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
1032
+ await waitForAll(['LegacyProvider', 'App', 'Child']);
1033
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1034
1035
// Update App with same value (should bail out)
1036
appRef.current.setState({value: 1});
1029
- expect(Scheduler).toFlushAndYield(['App']);
1037
+ await waitForAll(['App']);
1038
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1039
1040
// Update LegacyProvider (should not bail out)
1041
legacyProviderRef.current.setState({value: 1});
1034
- expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
1042
+ await waitForAll(['LegacyProvider', 'App', 'Child']);
1043
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1044
1045
// Update App with same value (should bail out)
1046
appRef.current.setState({value: 1});
1039
- expect(Scheduler).toFlushAndYield(['App']);
1047
+ await waitForAll(['App']);
1048
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1049
});
1050
});
@@ -1055,7 +1063,7 @@ describe('ReactNewContext', () => {
1063
}
1064
});
1065
1058
- it('can read other contexts inside consumer render prop', () => {
1066
+ it('can read other contexts inside consumer render prop', async () => {
1067
const FooContext = React.createContext(0);
1068
const BarContext = React.createContext(0);
1069
@@ -1092,17 +1100,17 @@ describe('ReactNewContext', () => {
1100
}
1101
1102
ReactNoop.render(<App foo={1} bar={1} />);
1095
- expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1']);
1103
+ await waitForAll(['Foo: 1, Bar: 1']);
1104
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 1, Bar: 1" />);
1105
1106
// Update foo
1107
ReactNoop.render(<App foo={2} bar={1} />);
1100
- expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
1108
+ await waitForAll(['Foo: 2, Bar: 1']);
1109
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 1" />);
1110
1111
// Update bar
1112
ReactNoop.render(<App foo={2} bar={2} />);
1105
- expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
1113
+ await waitForAll(['Foo: 2, Bar: 2']);
1114
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 2" />);
1115
});
1116
@@ -1111,7 +1119,7 @@ describe('ReactNewContext', () => {
1119
// If we bailed out on referential equality, it would be confusing that you
1120
// can call this.setState(), but an autobound render callback "blocked" the update.
1121
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
1114
- it('consumer does not bail out if there were no bailouts above it', () => {
1122
+ it('consumer does not bail out if there were no bailouts above it', async () => {
1123
const Context = React.createContext(0);
1124
const Consumer = Context.Consumer;
1125
@@ -1138,12 +1146,12 @@ describe('ReactNewContext', () => {
1146
// Initial mount
1147
let inst;
1148
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1141
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1149
+ await waitForAll(['App', 'App#renderConsumer']);
1150
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1151
1152
// Update
1153
inst.setState({text: 'goodbye'});
1146
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1154
+ await waitForAll(['App', 'App#renderConsumer']);
1155
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1156
});
1157
});
@@ -1152,7 +1160,7 @@ describe('ReactNewContext', () => {
1160
// Unstable changedBits API was removed. Port this test to context selectors
1161
// once that exists.
1162
// @gate FIXME
1155
- it('can read the same context multiple times in the same function', () => {
1163
+ it('can read the same context multiple times in the same function', async () => {
1164
const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
1165
let result = 0;
1166
if (a.foo !== b.foo) {
@@ -1212,7 +1220,7 @@ describe('ReactNewContext', () => {
1220
}
1221
1222
ReactNoop.render(<App foo={1} bar={1} baz={1} />);
1215
- expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1', 'Baz: 1']);
1223
+ await waitForAll(['Foo: 1, Bar: 1', 'Baz: 1']);
1224
expect(ReactNoop).toMatchRenderedOutput([
1225
<span prop="Foo: 1, Bar: 1" />,
1226
<span prop="Baz: 1" />,
@@ -1220,7 +1228,7 @@ describe('ReactNewContext', () => {
1228
1229
// Update only foo
1230
ReactNoop.render(<App foo={2} bar={1} baz={1} />);
1223
- expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
1231
+ await waitForAll(['Foo: 2, Bar: 1']);
1232
expect(ReactNoop).toMatchRenderedOutput([
1233
<span prop="Foo: 2, Bar: 1" />,
1234
<span prop="Baz: 1" />,
@@ -1228,7 +1236,7 @@ describe('ReactNewContext', () => {
1236
1237
// Update only bar
1238
ReactNoop.render(<App foo={2} bar={2} baz={1} />);
1231
- expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
1239
+ await waitForAll(['Foo: 2, Bar: 2']);
1240
expect(ReactNoop).toMatchRenderedOutput([
1241
<span prop="Foo: 2, Bar: 2" />,
1242
<span prop="Baz: 1" />,
@@ -1236,7 +1244,7 @@ describe('ReactNewContext', () => {
1244
1245
// Update only baz
1246
ReactNoop.render(<App foo={2} bar={2} baz={2} />);
1239
- expect(Scheduler).toFlushAndYield(['Baz: 2']);
1247
+ await waitForAll(['Baz: 2']);
1248
expect(ReactNoop).toMatchRenderedOutput([
1249
<span prop="Foo: 2, Bar: 2" />,
1250
<span prop="Baz: 2" />,
@@ -1248,7 +1256,7 @@ describe('ReactNewContext', () => {
1256
// If we bailed out on referential equality, it would be confusing that you
1257
// can call this.setState(), but an autobound render callback "blocked" the update.
1258
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
1251
- it('does not bail out if there were no bailouts above it', () => {
1259
+ it('does not bail out if there were no bailouts above it', async () => {
1260
const Context = React.createContext(0);
1261
1262
class Consumer extends React.Component {
@@ -1281,12 +1289,12 @@ describe('ReactNewContext', () => {
1289
// Initial mount
1290
let inst;
1291
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1284
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1292
+ await waitForAll(['App', 'App#renderConsumer']);
1293
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1294
1295
// Update
1296
inst.setState({text: 'goodbye'});
1289
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1297
+ await waitForAll(['App', 'App#renderConsumer']);
1298
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1299
});
1300
@@ -1361,7 +1369,7 @@ describe('ReactNewContext', () => {
1369
// If we bailed out on referential equality, it would be confusing that you
1370
// can call this.setState(), but an autobound render callback "blocked" the update.
1371
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
1364
- it('does not bail out if there were no bailouts above it', () => {
1372
+ it('does not bail out if there were no bailouts above it', async () => {
1373
const Context = React.createContext(0);
1374
1375
function Consumer({children}) {
@@ -1392,17 +1400,17 @@ describe('ReactNewContext', () => {
1400
// Initial mount
1401
let inst;
1402
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1395
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1403
+ await waitForAll(['App', 'App#renderConsumer']);
1404
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1405
1406
// Update
1407
inst.setState({text: 'goodbye'});
1400
- expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1408
+ await waitForAll(['App', 'App#renderConsumer']);
1409
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1410
});
1411
});
1412
1405
- it('unwinds after errors in complete phase', () => {
1413
+ it('unwinds after errors in complete phase', async () => {
1414
const Context = React.createContext(0);
1415
1416
// This is a regression test for stack misalignment
@@ -1419,7 +1427,7 @@ describe('ReactNewContext', () => {
1427
<Context.Consumer>{value => <span prop={value} />}</Context.Consumer>
1428
</Context.Provider>,
1429
);
1422
- expect(Scheduler).toFlushWithoutYielding();
1430
+ await waitForAll([]);
1431
expect(ReactNoop).toMatchRenderedOutput(<span prop={10} />);
1432
});
1433
@@ -1631,7 +1639,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1639
});
1640
});
1641
1634
- it('should warn with an error message when using context as a consumer in DEV', () => {
1642
+ it('should warn with an error message when using context as a consumer in DEV', async () => {
1643
const BarContext = React.createContext({value: 'bar-initial'});
1644
const BarConsumer = BarContext;
1645
@@ -1647,9 +1655,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1655
);
1656
}
1657
1650
- expect(() => {
1658
+ await expect(async () => {
1659
ReactNoop.render(<Component />);
1652
- expect(Scheduler).toFlushWithoutYielding();
1660
+ await waitForAll([]);
1661
}).toErrorDev(
1662
'Rendering <Context> directly is not supported and will be removed in ' +
1663
'a future major release. Did you mean to render <Context.Consumer> instead?',
@@ -1657,7 +1665,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1665
});
1666
1667
// False positive regression test.
1660
- it('should not warn when using Consumer from React < 16.6 with newer renderer', () => {
1668
+ it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => {
1669
const BarContext = React.createContext({value: 'bar-initial'});
1670
// React 16.5 and earlier didn't have a separate object.
1671
BarContext.Consumer = BarContext;
@@ -1675,10 +1683,10 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1683
}
1684
1685
ReactNoop.render(<Component />);
1678
- expect(Scheduler).toFlushWithoutYielding();
1686
+ await waitForAll([]);
1687
});
1688
1681
- it('should warn with an error message when using nested context consumers in DEV', () => {
1689
+ it('should warn with an error message when using nested context consumers in DEV', async () => {
1690
const BarContext = React.createContext({value: 'bar-initial'});
1691
const BarConsumer = BarContext;
1692
@@ -1694,16 +1702,16 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1702
);
1703
}
1704
1697
- expect(() => {
1705
+ await expect(async () => {
1706
ReactNoop.render(<Component />);
1699
- expect(Scheduler).toFlushWithoutYielding();
1707
+ await waitForAll([]);
1708
}).toErrorDev(
1709
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
1710
'a future major release. Did you mean to render <Context.Consumer> instead?',
1711
);
1712
});
1713
1706
- it('should warn with an error message when using Context.Consumer.Provider DEV', () => {
1714
+ it('should warn with an error message when using Context.Consumer.Provider DEV', async () => {
1715
const BarContext = React.createContext({value: 'bar-initial'});
1716
1717
function Component() {
@@ -1718,9 +1726,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1726
);
1727
}
1728
1721
- expect(() => {
1729
+ await expect(async () => {
1730
ReactNoop.render(<Component />);
1723
- expect(Scheduler).toFlushWithoutYielding();
1731
+ await waitForAll([]);
1732
}).toErrorDev(
1733
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
1734
'a future major release. Did you mean to render <Context.Provider> instead?',
packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
+4
-3
@@ -13,6 +13,7 @@ const React = require('react');
13
const ReactNoop = require('react-noop-renderer');
14
const Scheduler = require('scheduler');
15
const act = require('jest-react').act;
16
+const {assertLog, waitForAll} = require('internal-test-utils');
17
18
// TODO: These tests are no longer specific to the noop renderer
19
// implementation. They test the internal implementation we use in the React
@@ -34,7 +35,7 @@ describe('internal act()', () => {
35
/>,
36
);
37
});
37
- expect(Scheduler).toFlushWithoutYielding();
38
+ await waitForAll([]);
39
expect(calledLog).toEqual([0]);
40
});
41
@@ -56,8 +57,8 @@ describe('internal act()', () => {
57
await act(async () => {
58
ReactNoop.render(<App />);
59
});
59
- expect(Scheduler).toHaveYielded(['stage 1', 'stage 2']);
60
- expect(Scheduler).toFlushWithoutYielding();
60
+ assertLog(['stage 1', 'stage 2']);
61
+ await waitForAll([]);
62
expect(ReactNoop).toMatchRenderedOutput('1');
63
});
64
});
packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
+100
-115
@@ -10,6 +10,9 @@ let useEffect;
10
let useMemo;
11
let useRef;
12
let startTransition;
13
+let waitForPaint;
14
+let waitFor;
15
+let assertLog;
16
17
describe('ReactOffscreen', () => {
18
beforeEach(() => {
@@ -27,6 +30,11 @@ describe('ReactOffscreen', () => {
30
useMemo = React.useMemo;
31
useRef = React.useRef;
32
startTransition = React.startTransition;
33
+
34
+ const InternalTestUtils = require('internal-test-utils');
35
+ waitForPaint = InternalTestUtils.waitForPaint;
36
+ waitFor = InternalTestUtils.waitFor;
37
+ assertLog = InternalTestUtils.assertLog;
38
});
39
40
function Text(props) {
@@ -68,10 +76,10 @@ describe('ReactOffscreen', () => {
76
const root = ReactNoop.createRoot();
77
await act(async () => {
78
root.render(<App mode="unstable-defer-without-hiding" />);
71
- expect(Scheduler).toFlushUntilNextPaint(['Normal']);
79
+ await waitForPaint(['Normal']);
80
expect(root).toMatchRenderedOutput(<span prop="Normal" />);
81
});
74
- expect(Scheduler).toHaveYielded(['Deferred']);
82
+ assertLog(['Deferred']);
83
expect(root).toMatchRenderedOutput(
84
<>
85
<span prop="Normal" />
@@ -83,7 +91,7 @@ describe('ReactOffscreen', () => {
91
await act(async () => {
92
root.render(<App mode="visible" />);
93
});
86
- expect(Scheduler).toHaveYielded(['Normal', 'Deferred']);
94
+ assertLog(['Normal', 'Deferred']);
95
expect(root).toMatchRenderedOutput(
96
<>
97
<span prop="Normal" />
@@ -93,7 +101,7 @@ describe('ReactOffscreen', () => {
101
102
await act(async () => {
103
root.render(<App mode="unstable-defer-without-hiding" />);
96
- expect(Scheduler).toFlushUntilNextPaint(['Normal']);
104
+ await waitForPaint(['Normal']);
105
expect(root).toMatchRenderedOutput(
106
<>
107
<span prop="Normal" />
@@ -101,7 +109,7 @@ describe('ReactOffscreen', () => {
109
</>,
110
);
111
});
104
- expect(Scheduler).toHaveYielded(['Deferred']);
112
+ assertLog(['Deferred']);
113
expect(root).toMatchRenderedOutput(
114
<>
115
<span prop="Normal" />
@@ -133,7 +141,7 @@ describe('ReactOffscreen', () => {
141
ReactNoop.flushSync();
142
143
// Should not defer the hidden tree
136
- expect(Scheduler).toHaveYielded(['A', 'Outside']);
144
+ assertLog(['A', 'Outside']);
145
});
146
expect(root).toMatchRenderedOutput(
147
<>
@@ -146,7 +154,7 @@ describe('ReactOffscreen', () => {
154
await act(async () => {
155
setState('B');
156
});
149
- expect(Scheduler).toHaveYielded(['B']);
157
+ assertLog(['B']);
158
expect(root).toMatchRenderedOutput(
159
<>
160
<span prop="B" />
@@ -175,11 +183,11 @@ describe('ReactOffscreen', () => {
183
</>,
184
);
185
// Should defer the hidden tree.
178
- expect(Scheduler).toFlushUntilNextPaint(['Outside']);
186
+ await waitForPaint(['Outside']);
187
});
188
189
// The hidden tree was rendered at lower priority.
182
- expect(Scheduler).toHaveYielded(['A']);
190
+ assertLog(['A']);
191
192
expect(root).toMatchRenderedOutput(
193
<>
@@ -192,7 +200,7 @@ describe('ReactOffscreen', () => {
200
await act(async () => {
201
setState('B');
202
});
195
- expect(Scheduler).toHaveYielded(['B']);
203
+ assertLog(['B']);
204
expect(root).toMatchRenderedOutput(
205
<>
206
<span prop="B" />
@@ -224,7 +232,7 @@ describe('ReactOffscreen', () => {
232
);
233
});
234
// No layout effect.
227
- expect(Scheduler).toHaveYielded(['Child']);
235
+ assertLog(['Child']);
236
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
237
238
// Unhide the tree. The layout effect is mounted.
@@ -235,7 +243,7 @@ describe('ReactOffscreen', () => {
243
</Offscreen>,
244
);
245
});
238
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
246
+ assertLog(['Child', 'Mount layout']);
247
expect(root).toMatchRenderedOutput(<span prop="Child" />);
248
});
249
@@ -259,7 +267,7 @@ describe('ReactOffscreen', () => {
267
</Offscreen>,
268
);
269
});
262
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
270
+ assertLog(['Child', 'Mount layout']);
271
expect(root).toMatchRenderedOutput(<span prop="Child" />);
272
273
// Hide the tree. The layout effect is unmounted.
@@ -270,7 +278,7 @@ describe('ReactOffscreen', () => {
278
</Offscreen>,
279
);
280
});
273
- expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
281
+ assertLog(['Unmount layout', 'Child']);
282
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
283
284
// Unhide the tree. The layout effect is re-mounted.
@@ -281,7 +289,7 @@ describe('ReactOffscreen', () => {
289
</Offscreen>,
290
);
291
});
284
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
292
+ assertLog(['Child', 'Mount layout']);
293
expect(root).toMatchRenderedOutput(<span prop="Child" />);
294
});
295
@@ -316,7 +324,7 @@ describe('ReactOffscreen', () => {
324
);
325
});
326
319
- expect(Scheduler).toHaveYielded(['child']);
327
+ assertLog(['child']);
328
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
329
330
await act(async () => {
@@ -330,7 +338,7 @@ describe('ReactOffscreen', () => {
338
);
339
});
340
333
- expect(Scheduler).toHaveYielded(['child']);
341
+ assertLog(['child']);
342
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
343
344
await act(async () => {
@@ -344,7 +352,7 @@ describe('ReactOffscreen', () => {
352
);
353
});
354
347
- expect(Scheduler).toHaveYielded(['child']);
355
+ assertLog(['child']);
356
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
357
358
await act(async () => {
@@ -372,7 +380,7 @@ describe('ReactOffscreen', () => {
380
);
381
});
382
375
- expect(Scheduler).toHaveYielded(['child']);
383
+ assertLog(['child']);
384
385
await act(async () => {
386
// Outer offscreen is hidden.
@@ -386,7 +394,7 @@ describe('ReactOffscreen', () => {
394
);
395
});
396
389
- expect(Scheduler).toHaveYielded(['child']);
397
+ assertLog(['child']);
398
});
399
400
// @gate enableOffscreen
@@ -410,7 +418,7 @@ describe('ReactOffscreen', () => {
418
</Offscreen>,
419
);
420
});
413
- expect(Scheduler).toHaveYielded(['Child']);
421
+ assertLog(['Child']);
422
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
423
424
// Show the tree. The layout effect is mounted.
@@ -421,7 +429,7 @@ describe('ReactOffscreen', () => {
429
</Offscreen>,
430
);
431
});
424
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
432
+ assertLog(['Child', 'Mount layout']);
433
expect(root).toMatchRenderedOutput(<span prop="Child" />);
434
435
// Hide the tree again. The layout effect is un-mounted.
@@ -432,7 +440,7 @@ describe('ReactOffscreen', () => {
440
</Offscreen>,
441
);
442
});
435
- expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
443
+ assertLog(['Unmount layout', 'Child']);
444
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
445
});
446
@@ -458,7 +466,7 @@ describe('ReactOffscreen', () => {
466
</Offscreen>,
467
);
468
});
461
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
469
+ assertLog(['Child', 'Mount layout']);
470
expect(root).toMatchRenderedOutput(<span prop="Child" />);
471
472
// Hide the tree. The layout effect is unmounted.
@@ -469,7 +477,7 @@ describe('ReactOffscreen', () => {
477
</Offscreen>,
478
);
479
});
472
- expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
480
+ assertLog(['Unmount layout', 'Child']);
481
482
// After the layout effect is unmounted, the child is hidden.
483
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
@@ -497,7 +505,7 @@ describe('ReactOffscreen', () => {
505
</LegacyHidden>,
506
);
507
});
500
- expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
508
+ assertLog(['Child', 'Mount layout']);
509
510
await act(async () => {
511
root.render(
@@ -506,7 +514,7 @@ describe('ReactOffscreen', () => {
514
</LegacyHidden>,
515
);
516
});
509
- expect(Scheduler).toHaveYielded(['Child']);
517
+ assertLog(['Child']);
518
519
await act(async () => {
520
root.render(
@@ -515,12 +523,12 @@ describe('ReactOffscreen', () => {
523
</LegacyHidden>,
524
);
525
});
518
- expect(Scheduler).toHaveYielded(['Child']);
526
+ assertLog(['Child']);
527
528
await act(async () => {
529
root.render(null);
530
});
523
- expect(Scheduler).toHaveYielded(['Unmount layout']);
531
+ assertLog(['Unmount layout']);
532
});
533
534
// @gate enableOffscreen
@@ -665,7 +673,7 @@ describe('ReactOffscreen', () => {
673
await act(async () => {
674
root.render(<App show={false} />);
675
});
668
- expect(Scheduler).toHaveYielded(['Outer: 0', 'Inner: 0']);
676
+ assertLog(['Outer: 0', 'Inner: 0']);
677
expect(root).toMatchRenderedOutput(
678
<>
679
<span prop="Outer: 0" />
@@ -681,7 +689,7 @@ describe('ReactOffscreen', () => {
689
setInner(1);
690
// Only the outer updates finishes because the inner update is inside a
691
// hidden tree. The outer update is deferred to a later render.
684
- expect(Scheduler).toFlushUntilNextPaint(['Outer: 1']);
692
+ await waitForPaint(['Outer: 1']);
693
expect(root).toMatchRenderedOutput(
694
<>
695
<span prop="Outer: 1" />
@@ -706,7 +714,7 @@ describe('ReactOffscreen', () => {
714
root.render(<App show={true} />);
715
});
716
709
- expect(Scheduler).toHaveYielded([
717
+ assertLog([
718
'Outer: 1',
719
720
// There are two pending updates on Inner, but only the first one
@@ -723,7 +731,7 @@ describe('ReactOffscreen', () => {
731
);
732
expect(areOuterAndInnerConsistent()).toBe(true);
733
});
726
- expect(Scheduler).toHaveYielded(['Outer: 2', 'Inner: 2']);
734
+ assertLog(['Outer: 2', 'Inner: 2']);
735
expect(root).toMatchRenderedOutput(
736
<>
737
<span prop="Outer: 2" />
@@ -746,7 +754,7 @@ describe('ReactOffscreen', () => {
754
await act(async () => {
755
root.render(<Offscreen hidden={false} />);
756
});
749
- expect(Scheduler).toHaveYielded([]);
757
+ assertLog([]);
758
expect(root).toMatchRenderedOutput(null);
759
760
await act(async () => {
@@ -759,7 +767,7 @@ describe('ReactOffscreen', () => {
767
</Offscreen>,
768
);
769
});
762
- expect(Scheduler).toFlushAndYieldThrough(['Initial']);
770
+ await waitFor(['Initial']);
771
772
// Before it finishes rendering, the whole tree gets deleted
773
ReactNoop.flushSync(() => {
@@ -802,7 +810,7 @@ describe('ReactOffscreen', () => {
810
</Offscreen>,
811
);
812
});
805
- expect(Scheduler).toHaveYielded(['A']);
813
+ assertLog(['A']);
814
expect(root).toMatchRenderedOutput(<span hidden={true} prop="A" />);
815
816
// Schedule an update to a hidden class component. The update will finish
@@ -813,7 +821,7 @@ describe('ReactOffscreen', () => {
821
Scheduler.unstable_yieldValue('B update finished');
822
});
823
});
816
- expect(Scheduler).toHaveYielded(['B']);
824
+ assertLog(['B']);
825
expect(root).toMatchRenderedOutput(<span hidden={true} prop="B" />);
826
827
// Now reveal the hidden component. Simultaneously, schedule another
@@ -829,11 +837,7 @@ describe('ReactOffscreen', () => {
837
Scheduler.unstable_yieldValue('C update finished');
838
});
839
});
832
- expect(Scheduler).toHaveYielded([
833
- 'C',
834
- 'B update finished',
835
- 'C update finished',
836
- ]);
840
+ assertLog(['C', 'B update finished', 'C update finished']);
841
expect(root).toMatchRenderedOutput(<span prop="C" />);
842
});
843
@@ -863,7 +867,7 @@ describe('ReactOffscreen', () => {
867
</Offscreen>,
868
);
869
});
866
- expect(Scheduler).toHaveYielded(['componentDidMount']);
870
+ assertLog(['componentDidMount']);
871
872
// Hide the class component
873
await act(async () => {
@@ -873,7 +877,7 @@ describe('ReactOffscreen', () => {
877
</Offscreen>,
878
);
879
});
876
- expect(Scheduler).toHaveYielded(['componentWillUnmount']);
880
+ assertLog(['componentWillUnmount']);
881
882
// Reappear the class component. componentDidMount should fire, not
883
// componentDidUpdate.
@@ -884,7 +888,7 @@ describe('ReactOffscreen', () => {
888
</Offscreen>,
889
);
890
});
887
- expect(Scheduler).toHaveYielded(['componentDidMount']);
891
+ assertLog(['componentDidMount']);
892
});
893
894
// @gate enableOffscreen
@@ -911,7 +915,7 @@ describe('ReactOffscreen', () => {
915
</Offscreen>,
916
);
917
});
914
- expect(Scheduler).toHaveYielded(['Mount B']);
918
+ assertLog(['Mount B']);
919
920
// Hide the component
921
await act(async () => {
@@ -921,7 +925,7 @@ describe('ReactOffscreen', () => {
925
</Offscreen>,
926
);
927
});
924
- expect(Scheduler).toHaveYielded(['Unmount B']);
928
+ assertLog(['Unmount B']);
929
930
// Reappear the component and also add some new siblings.
931
await act(async () => {
@@ -936,7 +940,7 @@ describe('ReactOffscreen', () => {
940
// B's effect should fire in between A and C even though it's been reused
941
// from a previous render. In other words, it's the same order as if all
942
// three siblings were brand new.
939
- expect(Scheduler).toHaveYielded(['Mount A', 'Mount B', 'Mount C']);
943
+ assertLog(['Mount A', 'Mount B', 'Mount C']);
944
},
945
);
946
@@ -967,7 +971,7 @@ describe('ReactOffscreen', () => {
971
</Offscreen>,
972
);
973
});
970
- expect(Scheduler).toHaveYielded(['Mount B']);
974
+ assertLog(['Mount B']);
975
976
// We're going to schedule an update on a hidden component, so stash a
977
// reference to its setState before the ref gets detached
@@ -981,7 +985,7 @@ describe('ReactOffscreen', () => {
985
</Offscreen>,
986
);
987
});
984
- expect(Scheduler).toHaveYielded(['Unmount B']);
988
+ assertLog(['Unmount B']);
989
990
// Reappear the component and also add some new siblings.
991
await act(async () => {
@@ -999,12 +1003,7 @@ describe('ReactOffscreen', () => {
1003
// B's effect should fire in between A and C even though it's been reused
1004
// from a previous render. In other words, it's the same order as if all
1005
// three siblings were brand new.
1002
- expect(Scheduler).toHaveYielded([
1003
- 'Mount A',
1004
- 'Mount B',
1005
- 'setState callback B',
1006
- 'Mount C',
1007
- ]);
1006
+ assertLog(['Mount A', 'Mount B', 'setState callback B', 'Mount C']);
1007
},
1008
);
1009
@@ -1037,7 +1036,7 @@ describe('ReactOffscreen', () => {
1036
await act(async () => {
1037
root.render(<App showMore={false} />);
1038
});
1040
- expect(Scheduler).toHaveYielded([
1039
+ assertLog([
1040
// First mount the outer visible shell
1041
'Shell',
1042
'Mount Shell',
@@ -1060,7 +1059,7 @@ describe('ReactOffscreen', () => {
1059
await act(async () => {
1060
root.render(<App showMore={true} />);
1061
});
1063
- expect(Scheduler).toHaveYielded([
1062
+ assertLog([
1063
'Shell',
1064
'More',
1065
@@ -1100,7 +1099,7 @@ describe('ReactOffscreen', () => {
1099
await act(async () => {
1100
root.render(<App showMore={false} />);
1101
});
1103
- expect(Scheduler).toHaveYielded([
1102
+ assertLog([
1103
// First mount the outer visible shell
1104
'Shell',
1105
'Mount Shell',
@@ -1123,7 +1122,7 @@ describe('ReactOffscreen', () => {
1122
await act(async () => {
1123
root.render(<App showMore={true} />);
1124
});
1126
- expect(Scheduler).toHaveYielded(['Shell', 'More']);
1125
+ assertLog(['Shell', 'More']);
1126
});
1127
1128
// @gate enableOffscreen
@@ -1155,14 +1154,14 @@ describe('ReactOffscreen', () => {
1154
await act(async () => {
1155
root.render(<App show={true} step={1} />);
1156
});
1158
- expect(Scheduler).toHaveYielded([1, 'Commit mount [1]']);
1157
+ assertLog([1, 'Commit mount [1]']);
1158
expect(root).toMatchRenderedOutput(<span prop={1} />);
1159
1160
// Hide the tree. This will unmount the effect.
1161
await act(async () => {
1162
root.render(<App show={false} step={1} />);
1163
});
1165
- expect(Scheduler).toHaveYielded(['Commit unmount [1]']);
1164
+ assertLog(['Commit unmount [1]']);
1165
expect(root).toMatchRenderedOutput(<span hidden={true} prop={1} />);
1166
1167
// Update.
@@ -1170,7 +1169,7 @@ describe('ReactOffscreen', () => {
1169
root.render(<App show={false} step={2} />);
1170
});
1171
// The update is prerendered but no effects are fired
1173
- expect(Scheduler).toHaveYielded([2]);
1172
+ assertLog([2]);
1173
expect(root).toMatchRenderedOutput(<span hidden={true} prop={2} />);
1174
1175
// Reveal the tree.
@@ -1179,7 +1178,7 @@ describe('ReactOffscreen', () => {
1178
});
1179
// The update doesn't render because it was already prerendered, but we do
1180
// fire the effect.
1182
- expect(Scheduler).toHaveYielded(['Commit mount [2]']);
1181
+ assertLog(['Commit mount [2]']);
1182
expect(root).toMatchRenderedOutput(<span prop={2} />);
1183
});
1184
@@ -1216,24 +1215,24 @@ describe('ReactOffscreen', () => {
1215
await act(async () => {
1216
root.render(<App show={true} />);
1217
});
1219
- expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
1218
+ assertLog(['Mount Child', 'Mount Parent']);
1219
1220
// First demonstrate what happens during a normal deletion
1221
await act(async () => {
1222
root.render(null);
1223
});
1225
- expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
1224
+ assertLog(['Unmount Parent', 'Unmount Child']);
1225
1226
// Now redo the same thing but hide instead of deleting
1227
await act(async () => {
1228
root.render(<App show={true} />);
1229
});
1231
- expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
1230
+ assertLog(['Mount Child', 'Mount Parent']);
1231
await act(async () => {
1232
root.render(<App show={false} />);
1233
});
1234
// The order is the same as during a deletion: parent before child
1236
- expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
1235
+ assertLog(['Unmount Parent', 'Unmount Child']);
1236
});
1237
1238
// TODO: As of now, there's no way to hide a tree without also unmounting its
@@ -1270,12 +1269,7 @@ describe('ReactOffscreen', () => {
1269
await act(async () => {
1270
root.render(<App showMore={true} step={1} />);
1271
});
1273
- expect(Scheduler).toHaveYielded([
1274
- 'Shell 1',
1275
- 'More 1',
1276
- 'Mount Shell 1',
1277
- 'Mount More 1',
1278
- ]);
1272
+ assertLog(['Shell 1', 'More 1', 'Mount Shell 1', 'Mount More 1']);
1273
expect(root).toMatchRenderedOutput(
1274
<>
1275
<span prop="Shell 1" />
@@ -1287,7 +1281,7 @@ describe('ReactOffscreen', () => {
1281
await act(async () => {
1282
root.render(<App showMore={false} step={2} />);
1283
});
1290
- expect(Scheduler).toHaveYielded([
1284
+ assertLog([
1285
// First update the outer visible shell
1286
'Shell 2',
1287
'Unmount Shell 1',
@@ -1342,7 +1336,7 @@ describe('ReactOffscreen', () => {
1336
await act(async () => {
1337
root.render(<App showOuter={false} showInner={false} />);
1338
});
1345
- expect(Scheduler).toHaveYielded(['Outer']);
1339
+ assertLog(['Outer']);
1340
expect(root).toMatchRenderedOutput(
1341
<div hidden={true}>
1342
<span prop="Outer" />
@@ -1353,7 +1347,7 @@ describe('ReactOffscreen', () => {
1347
await act(async () => {
1348
root.render(<App showOuter={false} showInner={true} />);
1349
});
1356
- expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
1350
+ assertLog(['Outer', 'Inner']);
1351
expect(root).toMatchRenderedOutput(
1352
<div hidden={true}>
1353
<span prop="Outer" />
@@ -1369,7 +1363,7 @@ describe('ReactOffscreen', () => {
1363
});
1364
// The effects fire, but the tree is not re-rendered because it already
1365
// prerendered.
1372
- expect(Scheduler).toHaveYielded(['Mount Outer', 'Mount Inner']);
1366
+ assertLog(['Mount Outer', 'Mount Inner']);
1367
expect(root).toMatchRenderedOutput(
1368
<div>
1369
<span prop="Outer" />
@@ -1418,7 +1412,7 @@ describe('ReactOffscreen', () => {
1412
await act(async () => {
1413
root.render(<App showOuter={false} showInner={false} />);
1414
});
1421
- expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
1415
+ assertLog(['Outer', 'Inner']);
1416
// Both the inner and the outer tree should be hidden. Hiding the inner tree
1417
// is arguably redundant, but the advantage of hiding both is that later you
1418
// can reveal the outer tree without having to examine the inner one.
@@ -1435,7 +1429,7 @@ describe('ReactOffscreen', () => {
1429
await act(async () => {
1430
root.render(<App showOuter={true} showInner={false} />);
1431
});
1438
- expect(Scheduler).toHaveYielded(['Mount Outer']);
1432
+ assertLog(['Mount Outer']);
1433
expect(root).toMatchRenderedOutput(
1434
<div>
1435
<span prop="Outer" />
@@ -1535,7 +1529,7 @@ describe('ReactOffscreen', () => {
1529
root.render(<App />);
1530
});
1531
1538
- expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
1532
+ assertLog(['HighPriorityComponent 0', 'Child 0']);
1533
expect(root).toMatchRenderedOutput(
1534
<>
1535
<span prop="HighPriorityComponent 0" />
@@ -1549,10 +1543,7 @@ describe('ReactOffscreen', () => {
1543
await act(async () => {
1544
updateChildState(1);
1545
updateHighPriorityComponentState(1);
1552
- expect(Scheduler).toFlushUntilNextPaint([
1553
- 'HighPriorityComponent 1',
1554
- 'Child 1',
1555
- ]);
1546
+ await waitForPaint(['HighPriorityComponent 1', 'Child 1']);
1547
expect(root).toMatchRenderedOutput(
1548
<>
1549
<span prop="HighPriorityComponent 1" />
@@ -1569,7 +1560,7 @@ describe('ReactOffscreen', () => {
1560
await act(async () => {
1561
updateChildState(2);
1562
updateHighPriorityComponentState(2);
1572
- expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']);
1563
+ await waitForPaint(['HighPriorityComponent 2']);
1564
expect(root).toMatchRenderedOutput(
1565
<>
1566
<span prop="HighPriorityComponent 2" />
@@ -1578,7 +1569,7 @@ describe('ReactOffscreen', () => {
1569
);
1570
});
1571
1581
- expect(Scheduler).toHaveYielded(['Child 2']);
1572
+ assertLog(['Child 2']);
1573
expect(root).toMatchRenderedOutput(
1574
<>
1575
<span prop="HighPriorityComponent 2" />
@@ -1594,10 +1585,7 @@ describe('ReactOffscreen', () => {
1585
await act(async () => {
1586
updateChildState(3);
1587
updateHighPriorityComponentState(3);
1597
- expect(Scheduler).toFlushUntilNextPaint([
1598
- 'HighPriorityComponent 3',
1599
- 'Child 3',
1600
- ]);
1588
+ await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
1589
expect(root).toMatchRenderedOutput(
1590
<>
1591
<span prop="HighPriorityComponent 3" />
@@ -1666,7 +1654,7 @@ describe('ReactOffscreen', () => {
1654
root.render(<App />);
1655
});
1656
1669
- expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
1657
+ assertLog(['HighPriorityComponent 0', 'Child 0']);
1658
1659
nextRenderTriggerDetach = true;
1660
@@ -1675,7 +1663,7 @@ describe('ReactOffscreen', () => {
1663
await act(async () => {
1664
updateChildState(1);
1665
updateHighPriorityComponentState(1);
1678
- expect(Scheduler).toFlushUntilNextPaint([
1666
+ await waitForPaint([
1667
'HighPriorityComponent 1',
1668
'Child 1',
1669
'HighPriorityComponent 2',
@@ -1688,7 +1676,7 @@ describe('ReactOffscreen', () => {
1676
);
1677
});
1678
1691
- expect(Scheduler).toHaveYielded(['Child 2']);
1679
+ assertLog(['Child 2']);
1680
expect(root).toMatchRenderedOutput(
1681
<>
1682
<span prop="HighPriorityComponent 2" />
@@ -1703,10 +1691,7 @@ describe('ReactOffscreen', () => {
1691
await act(async () => {
1692
updateChildState(3);
1693
updateHighPriorityComponentState(3);
1706
- expect(Scheduler).toFlushUntilNextPaint([
1707
- 'HighPriorityComponent 3',
1708
- 'Child 3',
1709
- ]);
1694
+ await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
1695
expect(root).toMatchRenderedOutput(
1696
<>
1697
<span prop="HighPriorityComponent 3" />
@@ -1865,33 +1850,33 @@ describe('ReactOffscreen', () => {
1850
1851
expect(offscreenRef).not.toBeNull();
1852
expect(spanRef.current).not.toBeNull();
1868
- expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
1853
+ assertLog(['Mount Layout Child', 'Mount Child']);
1854
1855
await act(async () => {
1856
offscreenRef.detach();
1857
});
1858
1859
expect(spanRef.current).toBeNull();
1875
- expect(Scheduler).toHaveYielded(['Unmount Layout Child', 'Unmount Child']);
1860
+ assertLog(['Unmount Layout Child', 'Unmount Child']);
1861
1862
// Calling attach on already attached Offscreen.
1863
await act(async () => {
1864
offscreenRef.detach();
1865
});
1866
1882
- expect(Scheduler).toHaveYielded([]);
1867
+ assertLog([]);
1868
1869
await act(async () => {
1870
offscreenRef.attach();
1871
});
1872
1873
expect(spanRef.current).not.toBeNull();
1889
- expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
1874
+ assertLog(['Mount Layout Child', 'Mount Child']);
1875
1876
// Calling attach on already attached Offscreen
1877
offscreenRef.attach();
1878
1894
- expect(Scheduler).toHaveYielded([]);
1879
+ assertLog([]);
1880
});
1881
1882
// @gate enableOffscreen
@@ -1919,7 +1904,7 @@ describe('ReactOffscreen', () => {
1904
root.render(<App />);
1905
});
1906
1922
- expect(Scheduler).toHaveYielded([
1907
+ assertLog([
1908
'outer',
1909
'middle',
1910
'inner',
@@ -1940,7 +1925,7 @@ describe('ReactOffscreen', () => {
1925
1926
expect(innerOffscreen).toBeNull();
1927
1943
- expect(Scheduler).toHaveYielded([
1928
+ assertLog([
1929
'unmount layout middle',
1930
'unmount layout inner',
1931
'unmount middle',
@@ -1951,7 +1936,7 @@ describe('ReactOffscreen', () => {
1936
outerOffscreen.attach();
1937
});
1938
1954
- expect(Scheduler).toHaveYielded([
1939
+ assertLog([
1940
'mount layout inner',
1941
'mount layout middle',
1942
'mount inner',
@@ -1962,27 +1947,27 @@ describe('ReactOffscreen', () => {
1947
innerOffscreen.detach();
1948
});
1949
1965
- expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
1950
+ assertLog(['unmount layout inner', 'unmount inner']);
1951
1952
// Calling detach on already detached Offscreen.
1953
await act(async () => {
1954
innerOffscreen.detach();
1955
});
1956
1972
- expect(Scheduler).toHaveYielded([]);
1957
+ assertLog([]);
1958
1959
await act(async () => {
1960
innerOffscreen.attach();
1961
});
1962
1978
- expect(Scheduler).toHaveYielded(['mount layout inner', 'mount inner']);
1963
+ assertLog(['mount layout inner', 'mount inner']);
1964
1965
await act(async () => {
1966
innerOffscreen.detach();
1967
outerOffscreen.attach();
1968
});
1969
1985
- expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
1970
+ assertLog(['unmount layout inner', 'unmount inner']);
1971
});
1972
1973
// @gate enableOffscreen
@@ -2011,7 +1996,7 @@ describe('ReactOffscreen', () => {
1996
root.render(<App />);
1997
});
1998
2014
- expect(Scheduler).toHaveYielded(['attach child']);
1999
+ assertLog(['attach child']);
2000
2001
await act(async () => {
2002
const instance = offscreen.current;
@@ -2020,14 +2005,14 @@ describe('ReactOffscreen', () => {
2005
instance.attach();
2006
});
2007
2023
- expect(Scheduler).toHaveYielded([]);
2008
+ assertLog([]);
2009
2010
await act(async () => {
2011
const instance = offscreen.current;
2012
instance.detach();
2013
});
2014
2030
- expect(Scheduler).toHaveYielded(['detach child']);
2015
+ assertLog(['detach child']);
2016
2017
await act(async () => {
2018
const instance = offscreen.current;
@@ -2036,7 +2021,7 @@ describe('ReactOffscreen', () => {
2021
instance.detach();
2022
});
2023
2039
- expect(Scheduler).toHaveYielded([]);
2024
+ assertLog([]);
2025
});
2026
2027
// @gate enableOffscreen
@@ -2070,6 +2055,6 @@ describe('ReactOffscreen', () => {
2055
await act(() => {
2056
root.render(<App />);
2057
});
2073
- expect(Scheduler).toHaveYielded(['attach child']);
2058
+ assertLog(['attach child']);
2059
});
2060
});
packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
+24
-22
@@ -9,6 +9,8 @@ let useState;
9
let useEffect;
10
let startTransition;
11
let textCache;
12
+let waitForPaint;
13
+let assertLog;
14
15
describe('ReactOffscreen', () => {
16
beforeEach(() => {
@@ -25,6 +27,10 @@ describe('ReactOffscreen', () => {
27
useEffect = React.useEffect;
28
startTransition = React.startTransition;
29
30
+ const InternalTestUtils = require('internal-test-utils');
31
+ waitForPaint = InternalTestUtils.waitForPaint;
32
+ assertLog = InternalTestUtils.assertLog;
33
+
34
textCache = new Map();
35
});
36
@@ -115,7 +121,7 @@ describe('ReactOffscreen', () => {
121
await act(async () => {
122
root.render(<App />);
123
});
118
- expect(Scheduler).toHaveYielded(['Visible', 'Suspend! [Hidden]']);
124
+ assertLog(['Visible', 'Suspend! [Hidden]']);
125
expect(root).toMatchRenderedOutput(<span>Visible</span>);
126
127
// When the data resolves, we should be able to finish prerendering
@@ -123,7 +129,7 @@ describe('ReactOffscreen', () => {
129
await act(async () => {
130
await resolveText('Hidden');
131
});
126
- expect(Scheduler).toHaveYielded(['Hidden']);
132
+ assertLog(['Hidden']);
133
expect(root).toMatchRenderedOutput(
134
<>
135
<span>Visible</span>
@@ -155,11 +161,7 @@ describe('ReactOffscreen', () => {
161
await act(async () => {
162
root.render(<App />);
163
});
158
- expect(Scheduler).toHaveYielded([
159
- 'Visible',
160
- 'Suspend! [Hidden]',
161
- 'Loading...',
162
- ]);
164
+ assertLog(['Visible', 'Suspend! [Hidden]', 'Loading...']);
165
// Nearest Suspense boundary switches to a fallback even though the
166
// suspended content is hidden.
167
expect(root).toMatchRenderedOutput(
@@ -197,7 +199,7 @@ describe('ReactOffscreen', () => {
199
</Details>,
200
);
201
});
200
- expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
202
+ assertLog(['Closed', 'Suspend! [Async]']);
203
expect(root).toMatchRenderedOutput(<span>Closed</span>);
204
205
// But when we switch the boundary from hidden to visible, it should
@@ -211,7 +213,7 @@ describe('ReactOffscreen', () => {
213
);
214
});
215
});
214
- expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
216
+ assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
217
// It should suspend with delay to prevent the already-visible Suspense
218
// boundary from switching to a fallback
219
expect(root).toMatchRenderedOutput(<span>Closed</span>);
@@ -220,7 +222,7 @@ describe('ReactOffscreen', () => {
222
await act(async () => {
223
await resolveText('Async');
224
});
223
- expect(Scheduler).toHaveYielded(['Open', 'Async']);
225
+ assertLog(['Open', 'Async']);
226
expect(root).toMatchRenderedOutput(
227
<>
228
<span>Open</span>
@@ -254,7 +256,7 @@ describe('ReactOffscreen', () => {
256
</Details>,
257
);
258
});
257
- expect(Scheduler).toHaveYielded(['Open', '(empty)']);
259
+ assertLog(['Open', '(empty)']);
260
expect(root).toMatchRenderedOutput(
261
<>
262
<span>Open</span>
@@ -272,7 +274,7 @@ describe('ReactOffscreen', () => {
274
);
275
});
276
});
275
- expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
277
+ assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
278
// It should suspend with delay to prevent the already-visible Suspense
279
// boundary from switching to a fallback
280
expect(root).toMatchRenderedOutput(
@@ -294,7 +296,7 @@ describe('ReactOffscreen', () => {
296
});
297
// Now the visible part of the tree can commit without being blocked
298
// by the suspended content, which is hidden.
297
- expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
299
+ assertLog(['Closed', 'Suspend! [Async]']);
300
expect(root).toMatchRenderedOutput(
301
<>
302
<span>Closed</span>
@@ -306,7 +308,7 @@ describe('ReactOffscreen', () => {
308
await act(async () => {
309
await resolveText('Async');
310
});
309
- expect(Scheduler).toHaveYielded(['Async']);
311
+ assertLog(['Async']);
312
expect(root).toMatchRenderedOutput(
313
<>
314
<span>Closed</span>
@@ -339,7 +341,7 @@ describe('ReactOffscreen', () => {
341
await act(async () => {
342
root.render(<App show={false} />);
343
});
342
- expect(Scheduler).toHaveYielded(['A']);
344
+ assertLog(['A']);
345
346
await act(async () => {
347
startTransition(() => {
@@ -377,7 +379,7 @@ describe('ReactOffscreen', () => {
379
await act(async () => {
380
root.render(<App show={false} />);
381
});
380
- expect(Scheduler).toHaveYielded(['A0']);
382
+ assertLog(['A0']);
383
expect(root).toMatchRenderedOutput(<span hidden={true}>A0</span>);
384
385
await act(async () => {
@@ -388,7 +390,7 @@ describe('ReactOffscreen', () => {
390
setText('B');
391
});
392
});
391
- expect(Scheduler).toHaveYielded([
393
+ assertLog([
394
// The high priority render suspends again
395
'Suspend! [B0]',
396
// There's still pending work in another lane, so we should attempt
@@ -401,7 +403,7 @@ describe('ReactOffscreen', () => {
403
await act(async () => {
404
resolveText('B1');
405
});
404
- expect(Scheduler).toHaveYielded(['B1']);
406
+ assertLog(['B1']);
407
expect(root).toMatchRenderedOutput(<span hidden={true}>B1</span>);
408
});
409
@@ -465,7 +467,7 @@ describe('ReactOffscreen', () => {
467
await act(async () => {
468
root.render(<App show={true} />);
469
});
468
- expect(Scheduler).toHaveYielded([
470
+ assertLog([
471
'Outer: 0',
472
'Inner: 0',
473
'Async: 0',
@@ -487,7 +489,7 @@ describe('ReactOffscreen', () => {
489
// In the same render, also hide the offscreen tree.
490
root.render(<App show={false} />);
491
490
- expect(Scheduler).toFlushUntilNextPaint([
492
+ await waitForPaint([
493
// The outer update will commit, but the inner update is deferred until
494
// a later render.
495
'Outer: 1',
@@ -535,7 +537,7 @@ describe('ReactOffscreen', () => {
537
ReactNoop.flushSync(() => {
538
root.render(<App show={true} />);
539
});
538
- expect(Scheduler).toHaveYielded([
540
+ assertLog([
541
'Outer: 1',
542
543
// There are two pending updates on Inner, but only the first one
@@ -549,7 +551,7 @@ describe('ReactOffscreen', () => {
551
'Inner and outer are consistent',
552
]);
553
});
552
- expect(Scheduler).toHaveYielded([
554
+ assertLog([
555
'Outer: 2',
556
'Inner: 2',
557
'Suspend! [Async: 2]',
packages/react-reconciler/src/__tests__/ReactPersistent-test.js
+16
-15
@@ -12,7 +12,7 @@
12
13
let React;
14
let ReactNoopPersistent;
15
-let Scheduler;
15
+let waitForAll;
16
17
describe('ReactPersistent', () => {
18
beforeEach(() => {
@@ -20,7 +20,8 @@ describe('ReactPersistent', () => {
20
21
React = require('react');
22
ReactNoopPersistent = require('react-noop-renderer/persistent');
23
- Scheduler = require('scheduler');
23
+ const InternalTestUtils = require('internal-test-utils');
24
+ waitForAll = InternalTestUtils.waitForAll;
25
});
26
27
// Inlined from shared folder so we can run this test on a bundle.
@@ -57,7 +58,7 @@ describe('ReactPersistent', () => {
58
return ReactNoopPersistent.dangerouslyGetChildren();
59
}
60
60
- it('can update child nodes of a host instance', () => {
61
+ it('can update child nodes of a host instance', async () => {
62
function Bar(props) {
63
return <span>{props.text}</span>;
64
}
@@ -72,19 +73,19 @@ describe('ReactPersistent', () => {
73
}
74
75
render(<Foo text="Hello" />);
75
- expect(Scheduler).toFlushWithoutYielding();
76
+ await waitForAll([]);
77
const originalChildren = dangerouslyGetChildren();
78
expect(originalChildren).toEqual([div(span())]);
79
80
render(<Foo text="World" />);
80
- expect(Scheduler).toFlushWithoutYielding();
81
+ await waitForAll([]);
82
const newChildren = dangerouslyGetChildren();
83
expect(newChildren).toEqual([div(span(), span())]);
84
85
expect(originalChildren).toEqual([div(span())]);
86
});
87
87
- it('can reuse child nodes between updates', () => {
88
+ it('can reuse child nodes between updates', async () => {
89
function Baz(props) {
90
return <span prop={props.text} />;
91
}
@@ -106,12 +107,12 @@ describe('ReactPersistent', () => {
107
}
108
109
render(<Foo text="Hello" />);
109
- expect(Scheduler).toFlushWithoutYielding();
110
+ await waitForAll([]);
111
const originalChildren = dangerouslyGetChildren();
112
expect(originalChildren).toEqual([div(span('Hello'))]);
113
114
render(<Foo text="World" />);
114
- expect(Scheduler).toFlushWithoutYielding();
115
+ await waitForAll([]);
116
const newChildren = dangerouslyGetChildren();
117
expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
118
@@ -121,7 +122,7 @@ describe('ReactPersistent', () => {
122
expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
123
});
124
124
- it('can update child text nodes', () => {
125
+ it('can update child text nodes', async () => {
126
function Foo(props) {
127
return (
128
<div>
@@ -132,19 +133,19 @@ describe('ReactPersistent', () => {
133
}
134
135
render(<Foo text="Hello" />);
135
- expect(Scheduler).toFlushWithoutYielding();
136
+ await waitForAll([]);
137
const originalChildren = dangerouslyGetChildren();
138
expect(originalChildren).toEqual([div('Hello', span())]);
139
140
render(<Foo text="World" />);
140
- expect(Scheduler).toFlushWithoutYielding();
141
+ await waitForAll([]);
142
const newChildren = dangerouslyGetChildren();
143
expect(newChildren).toEqual([div('World', span())]);
144
145
expect(originalChildren).toEqual([div('Hello', span())]);
146
});
147
147
- it('supports portals', () => {
148
+ it('supports portals', async () => {
149
function Parent(props) {
150
return <div>{props.children}</div>;
151
}
@@ -173,7 +174,7 @@ describe('ReactPersistent', () => {
174
const portalContainer = {rootID: 'persistent-portal-test', children: []};
175
const emptyPortalChildSet = portalContainer.children;
176
render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
176
- expect(Scheduler).toFlushWithoutYielding();
177
+ await waitForAll([]);
178
179
expect(emptyPortalChildSet).toEqual([]);
180
@@ -187,7 +188,7 @@ describe('ReactPersistent', () => {
188
{createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
189
</Parent>,
190
);
190
- expect(Scheduler).toFlushWithoutYielding();
191
+ await waitForAll([]);
192
193
const newChildren = dangerouslyGetChildren();
194
expect(newChildren).toEqual([div()]);
@@ -204,7 +205,7 @@ describe('ReactPersistent', () => {
205
206
// Deleting the Portal, should clear its children
207
render(<Parent />);
207
- expect(Scheduler).toFlushWithoutYielding();
208
+ await waitForAll([]);
209
210
const clearedPortalChildren = portalContainer.children;
211
expect(clearedPortalChildren).toEqual([]);
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+29
-13
@@ -18,6 +18,10 @@ let NormalPriority;
18
let IdlePriority;
19
let runWithPriority;
20
let startTransition;
21
+let waitForAll;
22
+let waitForPaint;
23
+let assertLog;
24
+let waitFor;
25
26
describe('ReactSchedulerIntegration', () => {
27
beforeEach(() => {
@@ -31,6 +35,12 @@ describe('ReactSchedulerIntegration', () => {
35
IdlePriority = Scheduler.unstable_IdlePriority;
36
runWithPriority = Scheduler.unstable_runWithPriority;
37
startTransition = React.startTransition;
38
+
39
+ const InternalTestUtils = require('internal-test-utils');
40
+ waitForAll = InternalTestUtils.waitForAll;
41
+ waitForPaint = InternalTestUtils.waitForPaint;
42
+ assertLog = InternalTestUtils.assertLog;
43
+ waitFor = InternalTestUtils.waitFor;
44
});
45
46
// Note: This is based on a similar component we use in www. We can delete
@@ -76,11 +86,11 @@ describe('ReactSchedulerIntegration', () => {
86
await act(async () => {
87
ReactNoop.render(<CleanupEffect />);
88
});
79
- expect(Scheduler).toHaveYielded([]);
89
+ assertLog([]);
90
await act(async () => {
91
ReactNoop.render(<Effects />);
92
});
83
- expect(Scheduler).toHaveYielded([
93
+ assertLog([
94
'Cleanup Layout Effect',
95
'Layout Effect',
96
'Passive Effect',
@@ -90,12 +100,12 @@ describe('ReactSchedulerIntegration', () => {
100
]);
101
});
102
93
- it('requests a paint after committing', () => {
103
+ it('requests a paint after committing', async () => {
104
const scheduleCallback = Scheduler.unstable_scheduleCallback;
105
106
const root = ReactNoop.createRoot();
107
root.render('Initial');
98
- Scheduler.unstable_flushAll();
108
+ await waitForAll([]);
109
110
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
111
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
@@ -115,7 +125,7 @@ describe('ReactSchedulerIntegration', () => {
125
// Flush everything up to the next paint. Should yield after the
126
// React commit.
127
Scheduler.unstable_flushUntilNextPaint();
118
- expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
128
+ assertLog(['A', 'B', 'C']);
129
});
130
131
// @gate www
@@ -141,7 +151,7 @@ describe('ReactSchedulerIntegration', () => {
151
root.render(<App label="A" />);
152
153
// Commit the visible content
144
- expect(Scheduler).toFlushUntilNextPaint(['Visible: A']);
154
+ await waitForPaint(['Visible: A']);
155
expect(root).toMatchRenderedOutput(
156
<>
157
Visible: A
@@ -156,7 +166,7 @@ describe('ReactSchedulerIntegration', () => {
166
});
167
168
// The next commit should only include the visible content
159
- expect(Scheduler).toFlushUntilNextPaint(['Visible: B']);
169
+ await waitForPaint(['Visible: B']);
170
expect(root).toMatchRenderedOutput(
171
<>
172
Visible: B
@@ -166,7 +176,7 @@ describe('ReactSchedulerIntegration', () => {
176
});
177
178
// The hidden content commits later
169
- expect(Scheduler).toHaveYielded(['Hidden: B']);
179
+ assertLog(['Hidden: B']);
180
expect(root).toMatchRenderedOutput(
181
<>
182
Visible: B<div hidden={true}>Hidden: B</div>
@@ -201,6 +211,12 @@ describe(
211
ReactNoop = require('react-noop-renderer');
212
Scheduler = require('scheduler');
213
startTransition = React.startTransition;
214
+
215
+ const InternalTestUtils = require('internal-test-utils');
216
+ waitForAll = InternalTestUtils.waitForAll;
217
+ waitForPaint = InternalTestUtils.waitForPaint;
218
+ assertLog = InternalTestUtils.assertLog;
219
+ waitFor = InternalTestUtils.waitFor;
220
});
221
222
afterEach(() => {
@@ -244,8 +260,8 @@ describe(
260
261
await act(async () => {
262
ReactNoop.render(<App />);
247
- expect(Scheduler).toFlushUntilNextPaint([]);
248
- expect(Scheduler).toFlushUntilNextPaint([]);
263
+ await waitForPaint([]);
264
+ await waitForPaint([]);
265
});
266
});
267
@@ -276,13 +292,13 @@ describe(
292
startTransition(() => {
293
ReactNoop.render(<App />);
294
});
279
- expect(Scheduler).toFlushAndYieldThrough(['A']);
295
+ await waitFor(['A']);
296
297
// Start logging whenever shouldYield is called
298
logDuringShouldYield = true;
299
// Let's call it once to confirm the mock actually works
300
Scheduler.unstable_shouldYield();
285
- expect(Scheduler).toHaveYielded(['shouldYield']);
301
+ assertLog(['shouldYield']);
302
303
// Expire the task
304
Scheduler.unstable_advanceTime(10000);
@@ -299,7 +315,7 @@ describe(
315
// Because the render expired, React should finish the tree without
316
// consulting `shouldYield` again
317
Scheduler.unstable_flushNumberOfYields(1);
302
- expect(Scheduler).toHaveYielded(['B', 'C']);
318
+ assertLog(['B', 'C']);
319
});
320
});
321
},
packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
+6
-2
@@ -8,6 +8,7 @@ let getCacheForType;
8
9
let caches;
10
let seededCache;
11
+let assertLog;
12
13
describe('ReactSuspenseWithNoopRenderer', () => {
14
beforeEach(() => {
@@ -22,6 +23,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
23
24
getCacheForType = React.unstable_getCacheForType;
25
26
+ const InternalTestUtils = require('internal-test-utils');
27
+ assertLog = InternalTestUtils.assertLog;
28
+
29
caches = [];
30
seededCache = null;
31
});
@@ -160,7 +164,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
164
await act(async () => {
165
root.render(<App />);
166
});
163
- expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
167
+ assertLog(['Suspend! [Async]']);
168
expect(root).toMatchRenderedOutput('Loading...');
169
170
// When the promise resolves, a passive static effect flag is added. In the
@@ -169,7 +173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
173
await act(async () => {
174
resolveText('Async');
175
});
172
- expect(Scheduler).toHaveYielded(['Async', 'Effect']);
176
+ assertLog(['Async', 'Effect']);
177
expect(root).toMatchRenderedOutput('Async');
178
});
179
});
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+182
-238
@@ -9,6 +9,11 @@ let act;
9
let TextResource;
10
let textResourceShouldFail;
11
12
+let assertLog;
13
+let waitForPaint;
14
+let waitForAll;
15
+let waitFor;
16
+
17
describe('ReactSuspense', () => {
18
beforeEach(() => {
19
jest.resetModules();
@@ -23,6 +28,12 @@ describe('ReactSuspense', () => {
28
29
Suspense = React.Suspense;
30
31
+ const InternalTestUtils = require('internal-test-utils');
32
+ waitForAll = InternalTestUtils.waitForAll;
33
+ waitForPaint = InternalTestUtils.waitForPaint;
34
+ assertLog = InternalTestUtils.assertLog;
35
+ waitFor = InternalTestUtils.waitFor;
36
+
37
TextResource = ReactCache.unstable_createResource(
38
([text, ms = 0]) => {
39
let listeners = null;
@@ -94,7 +105,7 @@ describe('ReactSuspense', () => {
105
}
106
}
107
97
- it('suspends rendering and continues later', () => {
108
+ it('suspends rendering and continues later', async () => {
109
function Bar(props) {
110
Scheduler.unstable_yieldValue('Bar');
111
return props.children;
@@ -119,7 +130,7 @@ describe('ReactSuspense', () => {
130
unstable_isConcurrent: true,
131
});
132
122
- expect(Scheduler).toFlushAndYield(['Foo']);
133
+ await waitForAll(['Foo']);
134
expect(root).toMatchRenderedOutput(null);
135
136
// Navigate the shell to now render the child content.
@@ -128,7 +139,7 @@ describe('ReactSuspense', () => {
139
root.update(<Foo renderBar={true} />);
140
});
141
131
- expect(Scheduler).toFlushAndYield([
142
+ await waitForAll([
143
'Foo',
144
'Bar',
145
// A suspends
@@ -142,18 +153,18 @@ describe('ReactSuspense', () => {
153
// Flush some of the time
154
jest.advanceTimersByTime(50);
155
// Still nothing...
145
- expect(Scheduler).toFlushWithoutYielding();
156
+ await waitForAll([]);
157
expect(root).toMatchRenderedOutput(null);
158
159
// Flush the promise completely
160
jest.advanceTimersByTime(50);
161
// Renders successfully
151
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
152
- expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
162
+ assertLog(['Promise resolved [A]']);
163
+ await waitForAll(['Foo', 'Bar', 'A', 'B']);
164
expect(root).toMatchRenderedOutput('AB');
165
});
166
156
- it('suspends siblings and later recovers each independently', () => {
167
+ it('suspends siblings and later recovers each independently', async () => {
168
// Render two sibling Suspense components
169
const root = ReactTestRenderer.create(
170
<>
@@ -169,7 +180,7 @@ describe('ReactSuspense', () => {
180
},
181
);
182
172
- expect(Scheduler).toFlushAndYield([
183
+ await waitForAll([
184
'Suspend! [A]',
185
'Loading A...',
186
'Suspend! [B]',
@@ -182,19 +193,19 @@ describe('ReactSuspense', () => {
193
// show the placeholder
194
jest.advanceTimersByTime(5000);
195
// TODO: Should we throw if you forget to call toHaveYielded?
185
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
186
- expect(Scheduler).toFlushAndYield(['A']);
196
+ assertLog(['Promise resolved [A]']);
197
+ await waitForAll(['A']);
198
expect(root).toMatchRenderedOutput('ALoading B...');
199
200
// Advance time by enough that the second Suspense's promise resolves
201
// and switches back to the normal view
202
jest.advanceTimersByTime(1000);
192
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
193
- expect(Scheduler).toFlushAndYield(['B']);
203
+ assertLog(['Promise resolved [B]']);
204
+ await waitForAll(['B']);
205
expect(root).toMatchRenderedOutput('AB');
206
});
207
197
- it('interrupts current render if promise resolves before current render phase', () => {
208
+ it('interrupts current render if promise resolves before current render phase', async () => {
209
let didResolve = false;
210
const listeners = [];
211
@@ -231,7 +242,7 @@ describe('ReactSuspense', () => {
242
unstable_isConcurrent: true,
243
},
244
);
234
- expect(Scheduler).toFlushAndYield(['Initial']);
245
+ await waitForAll(['Initial']);
246
expect(root).toMatchRenderedOutput('Initial');
247
248
// The update will suspend.
@@ -247,19 +258,15 @@ describe('ReactSuspense', () => {
258
);
259
});
260
// Yield past the Suspense boundary but don't complete the last sibling.
250
- expect(Scheduler).toFlushAndYieldThrough([
251
- 'Suspend!',
252
- 'Loading...',
253
- 'After Suspense',
254
- ]);
261
+ await waitFor(['Suspend!', 'Loading...', 'After Suspense']);
262
263
// The promise resolves before the current render phase has completed
264
resolveThenable();
258
- expect(Scheduler).toHaveYielded([]);
265
+ assertLog([]);
266
expect(root).toMatchRenderedOutput('Initial');
267
268
// Start over from the root, instead of continuing.
262
- expect(Scheduler).toFlushAndYield([
269
+ await waitForAll([
270
// Async renders again *before* Sibling
271
'Async',
272
'After Suspense',
@@ -268,7 +275,7 @@ describe('ReactSuspense', () => {
275
expect(root).toMatchRenderedOutput('AsyncAfter SuspenseSibling');
276
});
277
271
- it('throttles fallback committing globally', () => {
278
+ it('throttles fallback committing globally', async () => {
279
function Foo() {
280
Scheduler.unstable_yieldValue('Foo');
281
return (
@@ -290,7 +297,7 @@ describe('ReactSuspense', () => {
297
unstable_isConcurrent: true,
298
});
299
293
- expect(Scheduler).toFlushAndYield([
300
+ await waitForAll([
301
'Foo',
302
'Suspend! [A]',
303
'Suspend! [B]',
@@ -302,8 +309,8 @@ describe('ReactSuspense', () => {
309
// Resolve A.
310
jest.advanceTimersByTime(200);
311
Scheduler.unstable_advanceTime(200);
305
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
306
- expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
312
+ assertLog(['Promise resolved [A]']);
313
+ await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
314
315
// By this point, we have enough info to show "A" and "Loading more..."
316
// However, we've just shown the outer fallback. So we'll delay
@@ -313,17 +320,17 @@ describe('ReactSuspense', () => {
320
// Resolve B.
321
jest.advanceTimersByTime(100);
322
Scheduler.unstable_advanceTime(100);
316
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
323
+ assertLog(['Promise resolved [B]']);
324
325
// By this point, B has resolved.
326
// We're still showing the outer fallback.
327
expect(root).toMatchRenderedOutput('Loading...');
321
- expect(Scheduler).toFlushAndYield(['A', 'B']);
328
+ await waitForAll(['A', 'B']);
329
// Then contents of both should pop in together.
330
expect(root).toMatchRenderedOutput('AB');
331
});
332
326
- it('does not throttle fallback committing for too long', () => {
333
+ it('does not throttle fallback committing for too long', async () => {
334
function Foo() {
335
Scheduler.unstable_yieldValue('Foo');
336
return (
@@ -345,7 +352,7 @@ describe('ReactSuspense', () => {
352
unstable_isConcurrent: true,
353
});
354
348
- expect(Scheduler).toFlushAndYield([
355
+ await waitForAll([
356
'Foo',
357
'Suspend! [A]',
358
'Suspend! [B]',
@@ -357,8 +364,8 @@ describe('ReactSuspense', () => {
364
// Resolve A.
365
jest.advanceTimersByTime(200);
366
Scheduler.unstable_advanceTime(200);
360
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
361
- expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
367
+ assertLog(['Promise resolved [A]']);
368
+ await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
369
370
// By this point, we have enough info to show "A" and "Loading more..."
371
// However, we've just shown the outer fallback. So we'll delay
@@ -374,8 +381,8 @@ describe('ReactSuspense', () => {
381
// Resolve B.
382
jest.advanceTimersByTime(500);
383
Scheduler.unstable_advanceTime(500);
377
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
378
- expect(Scheduler).toFlushAndYield(['B']);
384
+ assertLog(['Promise resolved [B]']);
385
+ await waitForAll(['B']);
386
expect(root).toMatchRenderedOutput('AB');
387
});
388
@@ -404,16 +411,16 @@ describe('ReactSuspense', () => {
411
</Suspense>,
412
);
413
407
- expect(Scheduler).toHaveYielded(['Loading...']);
414
+ assertLog(['Loading...']);
415
expect(root).toMatchRenderedOutput('Loading...');
416
417
await LazyClass;
418
412
- expect(Scheduler).toFlushUntilNextPaint(['Hi', 'Did mount: Hi']);
419
+ await waitForPaint(['Hi', 'Did mount: Hi']);
420
expect(root).toMatchRenderedOutput('Hi');
421
});
422
416
- it('updates memoized child of suspense component when context updates (simple memo)', () => {
423
+ it('updates memoized child of suspense component when context updates (simple memo)', async () => {
424
const {useContext, createContext, useState, memo} = React;
425
426
const ValueContext = createContext(null);
@@ -451,23 +458,23 @@ describe('ReactSuspense', () => {
458
const root = ReactTestRenderer.create(<App />, {
459
unstable_isConcurrent: true,
460
});
454
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
461
+ await waitForAll(['Suspend! [default]', 'Loading...']);
462
jest.advanceTimersByTime(1000);
463
457
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
458
- expect(Scheduler).toFlushAndYield(['default']);
464
+ assertLog(['Promise resolved [default]']);
465
+ await waitForAll(['default']);
466
expect(root).toMatchRenderedOutput('default');
467
468
act(() => setValue('new value'));
462
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
469
+ assertLog(['Suspend! [new value]', 'Loading...']);
470
jest.advanceTimersByTime(1000);
471
465
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
466
- expect(Scheduler).toFlushAndYield(['new value']);
472
+ assertLog(['Promise resolved [new value]']);
473
+ await waitForAll(['new value']);
474
expect(root).toMatchRenderedOutput('new value');
475
});
476
470
- it('updates memoized child of suspense component when context updates (manual memo)', () => {
477
+ it('updates memoized child of suspense component when context updates (manual memo)', async () => {
478
const {useContext, createContext, useState, memo} = React;
479
480
const ValueContext = createContext(null);
@@ -510,23 +517,23 @@ describe('ReactSuspense', () => {
517
const root = ReactTestRenderer.create(<App />, {
518
unstable_isConcurrent: true,
519
});
513
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
520
+ await waitForAll(['Suspend! [default]', 'Loading...']);
521
jest.advanceTimersByTime(1000);
522
516
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
517
- expect(Scheduler).toFlushAndYield(['default']);
523
+ assertLog(['Promise resolved [default]']);
524
+ await waitForAll(['default']);
525
expect(root).toMatchRenderedOutput('default');
526
527
act(() => setValue('new value'));
521
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
528
+ assertLog(['Suspend! [new value]', 'Loading...']);
529
jest.advanceTimersByTime(1000);
530
524
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
525
- expect(Scheduler).toFlushAndYield(['new value']);
531
+ assertLog(['Promise resolved [new value]']);
532
+ await waitForAll(['new value']);
533
expect(root).toMatchRenderedOutput('new value');
534
});
535
529
- it('updates memoized child of suspense component when context updates (function)', () => {
536
+ it('updates memoized child of suspense component when context updates (function)', async () => {
537
const {useContext, createContext, useState} = React;
538
539
const ValueContext = createContext(null);
@@ -567,23 +574,23 @@ describe('ReactSuspense', () => {
574
unstable_isConcurrent: true,
575
},
576
);
570
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
577
+ await waitForAll(['Suspend! [default]', 'Loading...']);
578
jest.advanceTimersByTime(1000);
579
573
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
574
- expect(Scheduler).toFlushAndYield(['default']);
580
+ assertLog(['Promise resolved [default]']);
581
+ await waitForAll(['default']);
582
expect(root).toMatchRenderedOutput('default');
583
584
act(() => setValue('new value'));
578
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
585
+ assertLog(['Suspend! [new value]', 'Loading...']);
586
jest.advanceTimersByTime(1000);
587
581
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
582
- expect(Scheduler).toFlushAndYield(['new value']);
588
+ assertLog(['Promise resolved [new value]']);
589
+ await waitForAll(['new value']);
590
expect(root).toMatchRenderedOutput('new value');
591
});
592
586
- it('updates memoized child of suspense component when context updates (forwardRef)', () => {
593
+ it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
594
const {forwardRef, useContext, createContext, useState} = React;
595
596
const ValueContext = createContext(null);
@@ -624,23 +631,23 @@ describe('ReactSuspense', () => {
631
unstable_isConcurrent: true,
632
},
633
);
627
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
634
+ await waitForAll(['Suspend! [default]', 'Loading...']);
635
jest.advanceTimersByTime(1000);
636
630
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
631
- expect(Scheduler).toFlushAndYield(['default']);
637
+ assertLog(['Promise resolved [default]']);
638
+ await waitForAll(['default']);
639
expect(root).toMatchRenderedOutput('default');
640
641
act(() => setValue('new value'));
635
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
642
+ assertLog(['Suspend! [new value]', 'Loading...']);
643
jest.advanceTimersByTime(1000);
644
638
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
639
- expect(Scheduler).toFlushAndYield(['new value']);
645
+ assertLog(['Promise resolved [new value]']);
646
+ await waitForAll(['new value']);
647
expect(root).toMatchRenderedOutput('new value');
648
});
649
643
- it('re-fires layout effects when re-showing Suspense', () => {
650
+ it('re-fires layout effects when re-showing Suspense', async () => {
651
function TextWithLayout(props) {
652
Scheduler.unstable_yieldValue(props.text);
653
React.useLayoutEffect(() => {
@@ -668,28 +675,21 @@ describe('ReactSuspense', () => {
675
unstable_isConcurrent: true,
676
});
677
671
- expect(Scheduler).toFlushAndYield(['Child 1', 'create layout']);
678
+ await waitForAll(['Child 1', 'create layout']);
679
expect(root).toMatchRenderedOutput('Child 1');
680
681
act(() => {
682
_setShow(true);
683
});
677
- expect(Scheduler).toHaveYielded([
678
- 'Child 1',
679
- 'Suspend! [Child 2]',
680
- 'Loading...',
681
- ]);
684
+ assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
685
jest.advanceTimersByTime(1000);
683
- expect(Scheduler).toHaveYielded([
684
- 'destroy layout',
685
- 'Promise resolved [Child 2]',
686
- ]);
687
- expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2', 'create layout']);
686
+ assertLog(['destroy layout', 'Promise resolved [Child 2]']);
687
+ await waitForAll(['Child 1', 'Child 2', 'create layout']);
688
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
689
});
690
691
describe('outside concurrent mode', () => {
692
- it('a mounted class component can suspend without losing state', () => {
692
+ it('a mounted class component can suspend without losing state', async () => {
693
class TextWithLifecycle extends React.Component {
694
componentDidMount() {
695
Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
@@ -754,7 +754,7 @@ describe('ReactSuspense', () => {
754
755
const root = ReactTestRenderer.create(<App />);
756
757
- expect(Scheduler).toHaveYielded([
757
+ assertLog([
758
'A',
759
'Suspend! [B:1]',
760
'C',
@@ -770,8 +770,8 @@ describe('ReactSuspense', () => {
770
771
jest.advanceTimersByTime(100);
772
773
- expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
774
- expect(Scheduler).toFlushUntilNextPaint([
773
+ assertLog(['Promise resolved [B:1]']);
774
+ await waitForPaint([
775
'B:1',
776
'Unmount [Loading...]',
777
// Should be a mount, not an update
@@ -780,25 +780,17 @@ describe('ReactSuspense', () => {
780
expect(root).toMatchRenderedOutput('AB:1C');
781
782
instance.setState({step: 2});
783
- expect(Scheduler).toHaveYielded([
784
- 'Suspend! [B:2]',
785
- 'Loading...',
786
- 'Mount [Loading...]',
787
- ]);
783
+ assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
784
expect(root).toMatchRenderedOutput('Loading...');
785
786
jest.advanceTimersByTime(100);
787
792
- expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
793
- expect(Scheduler).toFlushUntilNextPaint([
794
- 'B:2',
795
- 'Unmount [Loading...]',
796
- 'Update [B:2]',
797
- ]);
788
+ assertLog(['Promise resolved [B:2]']);
789
+ await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
790
expect(root).toMatchRenderedOutput('AB:2C');
791
});
792
801
- it('bails out on timed-out primary children even if they receive an update', () => {
793
+ it('bails out on timed-out primary children even if they receive an update', async () => {
794
let instance;
795
class Stateful extends React.Component {
796
state = {step: 1};
@@ -819,38 +811,30 @@ describe('ReactSuspense', () => {
811
812
const root = ReactTestRenderer.create(<App text="A" />);
813
822
- expect(Scheduler).toHaveYielded([
823
- 'Stateful: 1',
824
- 'Suspend! [A]',
825
- 'Loading...',
826
- ]);
814
+ assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
815
816
jest.advanceTimersByTime(1000);
817
830
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
831
- expect(Scheduler).toFlushUntilNextPaint(['A']);
818
+ assertLog(['Promise resolved [A]']);
819
+ await waitForPaint(['A']);
820
expect(root).toMatchRenderedOutput('Stateful: 1A');
821
822
root.update(<App text="B" />);
835
- expect(Scheduler).toHaveYielded([
836
- 'Stateful: 1',
837
- 'Suspend! [B]',
838
- 'Loading...',
839
- ]);
823
+ assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
824
expect(root).toMatchRenderedOutput('Loading...');
825
826
instance.setState({step: 2});
843
- expect(Scheduler).toHaveYielded(['Stateful: 2', 'Suspend! [B]']);
827
+ assertLog(['Stateful: 2', 'Suspend! [B]']);
828
expect(root).toMatchRenderedOutput('Loading...');
829
830
jest.advanceTimersByTime(1000);
831
848
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
849
- expect(Scheduler).toFlushUntilNextPaint(['B']);
832
+ assertLog(['Promise resolved [B]']);
833
+ await waitForPaint(['B']);
834
expect(root).toMatchRenderedOutput('Stateful: 2B');
835
});
836
853
- it('when updating a timed-out tree, always retries the suspended component', () => {
837
+ it('when updating a timed-out tree, always retries the suspended component', async () => {
838
let instance;
839
class Stateful extends React.Component {
840
state = {step: 1};
@@ -879,28 +863,20 @@ describe('ReactSuspense', () => {
863
864
const root = ReactTestRenderer.create(<App text="A" />);
865
882
- expect(Scheduler).toHaveYielded([
883
- 'Stateful: 1',
884
- 'Suspend! [A]',
885
- 'Loading...',
886
- ]);
866
+ assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
867
868
jest.advanceTimersByTime(1000);
869
890
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
891
- expect(Scheduler).toFlushUntilNextPaint(['A']);
870
+ assertLog(['Promise resolved [A]']);
871
+ await waitForPaint(['A']);
872
expect(root).toMatchRenderedOutput('Stateful: 1A');
873
874
root.update(<App text="B" />);
895
- expect(Scheduler).toHaveYielded([
896
- 'Stateful: 1',
897
- 'Suspend! [B]',
898
- 'Loading...',
899
- ]);
875
+ assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
876
expect(root).toMatchRenderedOutput('Loading...');
877
878
instance.setState({step: 2});
903
- expect(Scheduler).toHaveYielded([
879
+ assertLog([
880
'Stateful: 2',
881
882
// The suspended component should suspend again. If it doesn't, the
@@ -912,8 +888,8 @@ describe('ReactSuspense', () => {
888
889
jest.advanceTimersByTime(1000);
890
915
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
916
- expect(Scheduler).toFlushUntilNextPaint(['B']);
891
+ assertLog(['Promise resolved [B]']);
892
+ await waitForPaint(['B']);
893
expect(root).toMatchRenderedOutput('Stateful: 2B');
894
});
895
@@ -949,14 +925,14 @@ describe('ReactSuspense', () => {
925
}
926
927
const root = ReactTestRenderer.create(<App text="A" />);
952
- expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
928
+ assertLog(['Suspend! [A]', 'Loading...']);
929
root.update(<Text text="B" />);
930
// Should not fire componentWillUnmount
955
- expect(Scheduler).toHaveYielded(['B']);
931
+ assertLog(['B']);
932
expect(root).toMatchRenderedOutput('B');
933
});
934
959
- it('suspends in a component that also contains useEffect', () => {
935
+ it('suspends in a component that also contains useEffect', async () => {
936
const {useLayoutEffect} = React;
937
938
function AsyncTextWithEffect(props) {
@@ -989,14 +965,14 @@ describe('ReactSuspense', () => {
965
}
966
967
ReactTestRenderer.create(<App text="A" />);
992
- expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
968
+ assertLog(['Suspend! [A]', 'Loading...']);
969
jest.advanceTimersByTime(500);
970
995
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
996
- expect(Scheduler).toFlushUntilNextPaint(['A', 'Did commit: A']);
971
+ assertLog(['Promise resolved [A]']);
972
+ await waitForPaint(['A', 'Did commit: A']);
973
});
974
999
- it('retries when an update is scheduled on a timed out tree', () => {
975
+ it('retries when an update is scheduled on a timed out tree', async () => {
976
let instance;
977
class Stateful extends React.Component {
978
state = {step: 1};
@@ -1019,33 +995,30 @@ describe('ReactSuspense', () => {
995
});
996
997
// Initial render
1022
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 1]', 'Loading...']);
998
+ await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
999
jest.advanceTimersByTime(1000);
1024
- expect(Scheduler).toHaveYielded(['Promise resolved [Step: 1]']);
1025
- expect(Scheduler).toFlushAndYield(['Step: 1']);
1000
+ assertLog(['Promise resolved [Step: 1]']);
1001
+ await waitForAll(['Step: 1']);
1002
expect(root).toMatchRenderedOutput('Step: 1');
1003
1004
// Update that suspends
1005
instance.setState({step: 2});
1030
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 2]', 'Loading...']);
1006
+ await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
1007
jest.advanceTimersByTime(500);
1008
expect(root).toMatchRenderedOutput('Loading...');
1009
1010
// Update while still suspended
1011
instance.setState({step: 3});
1036
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 3]']);
1012
+ await waitForAll(['Suspend! [Step: 3]']);
1013
expect(root).toMatchRenderedOutput('Loading...');
1014
1015
jest.advanceTimersByTime(1000);
1040
- expect(Scheduler).toHaveYielded([
1041
- 'Promise resolved [Step: 2]',
1042
- 'Promise resolved [Step: 3]',
1043
- ]);
1044
- expect(Scheduler).toFlushAndYield(['Step: 3']);
1016
+ assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
1017
+ await waitForAll(['Step: 3']);
1018
expect(root).toMatchRenderedOutput('Step: 3');
1019
});
1020
1048
- it('does not remount the fallback while suspended children resolve in legacy mode', () => {
1021
+ it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
1022
let mounts = 0;
1023
class ShouldMountOnce extends React.Component {
1024
componentDidMount() {
@@ -1069,18 +1042,18 @@ describe('ReactSuspense', () => {
1042
const root = ReactTestRenderer.create(<App />);
1043
1044
// Initial render
1072
- expect(Scheduler).toHaveYielded([
1045
+ assertLog([
1046
'Suspend! [Child 1]',
1047
'Suspend! [Child 2]',
1048
'Suspend! [Child 3]',
1049
'Loading...',
1050
]);
1078
- expect(Scheduler).toFlushAndYield([]);
1051
+ await waitForAll([]);
1052
1053
jest.advanceTimersByTime(1000);
1054
1082
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
1083
- expect(Scheduler).toFlushUntilNextPaint([
1055
+ assertLog(['Promise resolved [Child 1]']);
1056
+ await waitForPaint([
1057
'Child 1',
1058
'Suspend! [Child 2]',
1059
'Suspend! [Child 3]',
@@ -1088,23 +1061,20 @@ describe('ReactSuspense', () => {
1061
1062
jest.advanceTimersByTime(1000);
1063
1091
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
1092
- expect(Scheduler).toFlushUntilNextPaint([
1093
- 'Child 2',
1094
- 'Suspend! [Child 3]',
1095
- ]);
1064
+ assertLog(['Promise resolved [Child 2]']);
1065
+ await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
1066
1067
jest.advanceTimersByTime(1000);
1068
1099
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
1100
- expect(Scheduler).toFlushUntilNextPaint(['Child 3']);
1069
+ assertLog(['Promise resolved [Child 3]']);
1070
+ await waitForPaint(['Child 3']);
1071
expect(root).toMatchRenderedOutput(
1072
['Child 1', 'Child 2', 'Child 3'].join(''),
1073
);
1074
expect(mounts).toBe(1);
1075
});
1076
1107
- it('does not get stuck with fallback in concurrent mode for a large delay', () => {
1077
+ it('does not get stuck with fallback in concurrent mode for a large delay', async () => {
1078
function App(props) {
1079
return (
1080
<Suspense fallback={<Text text="Loading..." />}>
@@ -1118,21 +1088,21 @@ describe('ReactSuspense', () => {
1088
unstable_isConcurrent: true,
1089
});
1090
1121
- expect(Scheduler).toFlushAndYield([
1091
+ await waitForAll([
1092
'Suspend! [Child 1]',
1093
'Suspend! [Child 2]',
1094
'Loading...',
1095
]);
1096
jest.advanceTimersByTime(1000);
1127
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
1128
- expect(Scheduler).toFlushAndYield(['Child 1', 'Suspend! [Child 2]']);
1097
+ assertLog(['Promise resolved [Child 1]']);
1098
+ await waitForAll(['Child 1', 'Suspend! [Child 2]']);
1099
jest.advanceTimersByTime(6000);
1130
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
1131
- expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2']);
1100
+ assertLog(['Promise resolved [Child 2]']);
1101
+ await waitForAll(['Child 1', 'Child 2']);
1102
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
1103
});
1104
1135
- it('reuses effects, including deletions, from the suspended tree', () => {
1105
+ it('reuses effects, including deletions, from the suspended tree', async () => {
1106
const {useState} = React;
1107
1108
let setTab;
@@ -1149,46 +1119,34 @@ describe('ReactSuspense', () => {
1119
}
1120
1121
const root = ReactTestRenderer.create(<App />);
1152
- expect(Scheduler).toHaveYielded([
1153
- 'Suspend! [Tab: 0]',
1154
- ' + sibling',
1155
- 'Loading...',
1156
- ]);
1122
+ assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
1123
expect(root).toMatchRenderedOutput('Loading...');
1124
jest.advanceTimersByTime(1000);
1125
1160
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
1161
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 0']);
1126
+ assertLog(['Promise resolved [Tab: 0]']);
1127
+ await waitForPaint(['Tab: 0']);
1128
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
1129
1130
act(() => setTab(1));
1165
- expect(Scheduler).toHaveYielded([
1166
- 'Suspend! [Tab: 1]',
1167
- ' + sibling',
1168
- 'Loading...',
1169
- ]);
1131
+ assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
1132
expect(root).toMatchRenderedOutput('Loading...');
1133
jest.advanceTimersByTime(1000);
1134
1173
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
1174
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 1']);
1135
+ assertLog(['Promise resolved [Tab: 1]']);
1136
+ await waitForPaint(['Tab: 1']);
1137
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
1138
1139
act(() => setTab(2));
1178
- expect(Scheduler).toHaveYielded([
1179
- 'Suspend! [Tab: 2]',
1180
- ' + sibling',
1181
- 'Loading...',
1182
- ]);
1140
+ assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
1141
expect(root).toMatchRenderedOutput('Loading...');
1142
jest.advanceTimersByTime(1000);
1143
1186
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
1187
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 2']);
1144
+ assertLog(['Promise resolved [Tab: 2]']);
1145
+ await waitForPaint(['Tab: 2']);
1146
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
1147
});
1148
1191
- it('does not warn if an mounted component is pinged', () => {
1149
+ it('does not warn if an mounted component is pinged', async () => {
1150
const {useState} = React;
1151
1152
const root = ReactTestRenderer.create(null);
@@ -1218,23 +1176,23 @@ describe('ReactSuspense', () => {
1176
</Suspense>,
1177
);
1178
1221
- expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
1179
+ assertLog(['Suspend! [A:0]', 'Loading...']);
1180
jest.advanceTimersByTime(1000);
1181
1224
- expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
1225
- expect(Scheduler).toFlushUntilNextPaint(['A:0']);
1182
+ assertLog(['Promise resolved [A:0]']);
1183
+ await waitForPaint(['A:0']);
1184
expect(root).toMatchRenderedOutput('A:0');
1185
1186
act(() => setStep(1));
1229
- expect(Scheduler).toHaveYielded(['Suspend! [A:1]', 'Loading...']);
1187
+ assertLog(['Suspend! [A:1]', 'Loading...']);
1188
expect(root).toMatchRenderedOutput('Loading...');
1189
1190
root.update(null);
1233
- expect(Scheduler).toFlushWithoutYielding();
1191
+ await waitForAll([]);
1192
jest.advanceTimersByTime(1000);
1193
});
1194
1237
- it('memoizes promise listeners per thread ID to prevent redundant renders', () => {
1195
+ it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
1196
function App() {
1197
return (
1198
<Suspense fallback={<Text text="Loading..." />}>
@@ -1249,18 +1207,13 @@ describe('ReactSuspense', () => {
1207
1208
root.update(<App />);
1209
1252
- expect(Scheduler).toHaveYielded([
1253
- 'Suspend! [A]',
1254
- 'Suspend! [B]',
1255
- 'Suspend! [C]',
1256
- 'Loading...',
1257
- ]);
1210
+ assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
1211
1212
// Resolve A
1213
jest.advanceTimersByTime(1000);
1214
1262
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1263
- expect(Scheduler).toFlushUntilNextPaint([
1215
+ assertLog(['Promise resolved [A]']);
1216
+ await waitForPaint([
1217
'A',
1218
// The promises for B and C have now been thrown twice
1219
'Suspend! [B]',
@@ -1270,8 +1223,8 @@ describe('ReactSuspense', () => {
1223
// Resolve B
1224
jest.advanceTimersByTime(1000);
1225
1273
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1274
- expect(Scheduler).toFlushUntilNextPaint([
1226
+ assertLog(['Promise resolved [B]']);
1227
+ await waitForPaint([
1228
// Even though the promise for B was thrown twice, we should only
1229
// re-render once.
1230
'B',
@@ -1282,8 +1235,8 @@ describe('ReactSuspense', () => {
1235
// Resolve C
1236
jest.advanceTimersByTime(1000);
1237
1285
- expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
1286
- expect(Scheduler).toFlushUntilNextPaint([
1238
+ assertLog(['Promise resolved [C]']);
1239
+ await waitForPaint([
1240
// Even though the promise for C was thrown three times, we should only
1241
// re-render once.
1242
'C',
@@ -1328,7 +1281,7 @@ describe('ReactSuspense', () => {
1281
jest.advanceTimersByTime(1000);
1282
});
1283
1331
- it('updates memoized child of suspense component when context updates (simple memo)', () => {
1284
+ it('updates memoized child of suspense component when context updates (simple memo)', async () => {
1285
const {useContext, createContext, useState, memo} = React;
1286
1287
const ValueContext = createContext(null);
@@ -1364,23 +1317,23 @@ describe('ReactSuspense', () => {
1317
}
1318
1319
const root = ReactTestRenderer.create(<App />);
1367
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1320
+ assertLog(['Suspend! [default]', 'Loading...']);
1321
jest.advanceTimersByTime(1000);
1322
1370
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1371
- expect(Scheduler).toFlushUntilNextPaint(['default']);
1323
+ assertLog(['Promise resolved [default]']);
1324
+ await waitForPaint(['default']);
1325
expect(root).toMatchRenderedOutput('default');
1326
1327
act(() => setValue('new value'));
1375
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1328
+ assertLog(['Suspend! [new value]', 'Loading...']);
1329
jest.advanceTimersByTime(1000);
1330
1378
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1379
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
1331
+ assertLog(['Promise resolved [new value]']);
1332
+ await waitForPaint(['new value']);
1333
expect(root).toMatchRenderedOutput('new value');
1334
});
1335
1383
- it('updates memoized child of suspense component when context updates (manual memo)', () => {
1336
+ it('updates memoized child of suspense component when context updates (manual memo)', async () => {
1337
const {useContext, createContext, useState, memo} = React;
1338
1339
const ValueContext = createContext(null);
@@ -1421,23 +1374,23 @@ describe('ReactSuspense', () => {
1374
}
1375
1376
const root = ReactTestRenderer.create(<App />);
1424
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1377
+ assertLog(['Suspend! [default]', 'Loading...']);
1378
jest.advanceTimersByTime(1000);
1379
1427
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1428
- expect(Scheduler).toFlushUntilNextPaint(['default']);
1380
+ assertLog(['Promise resolved [default]']);
1381
+ await waitForPaint(['default']);
1382
expect(root).toMatchRenderedOutput('default');
1383
1384
act(() => setValue('new value'));
1432
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1385
+ assertLog(['Suspend! [new value]', 'Loading...']);
1386
jest.advanceTimersByTime(1000);
1387
1435
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1436
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
1388
+ assertLog(['Promise resolved [new value]']);
1389
+ await waitForPaint(['new value']);
1390
expect(root).toMatchRenderedOutput('new value');
1391
});
1392
1440
- it('updates memoized child of suspense component when context updates (function)', () => {
1393
+ it('updates memoized child of suspense component when context updates (function)', async () => {
1394
const {useContext, createContext, useState} = React;
1395
1396
const ValueContext = createContext(null);
@@ -1477,23 +1430,23 @@ describe('ReactSuspense', () => {
1430
</Suspense>
1431
</App>,
1432
);
1480
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1433
+ assertLog(['Suspend! [default]', 'Loading...']);
1434
jest.advanceTimersByTime(1000);
1435
1483
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1484
- expect(Scheduler).toFlushUntilNextPaint(['default']);
1436
+ assertLog(['Promise resolved [default]']);
1437
+ await waitForPaint(['default']);
1438
expect(root).toMatchRenderedOutput('default');
1439
1440
act(() => setValue('new value'));
1488
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1441
+ assertLog(['Suspend! [new value]', 'Loading...']);
1442
jest.advanceTimersByTime(1000);
1443
1491
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1492
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
1444
+ assertLog(['Promise resolved [new value]']);
1445
+ await waitForPaint(['new value']);
1446
expect(root).toMatchRenderedOutput('new value');
1447
});
1448
1496
- it('updates memoized child of suspense component when context updates (forwardRef)', () => {
1449
+ it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
1450
const {forwardRef, useContext, createContext, useState} = React;
1451
1452
const ValueContext = createContext(null);
@@ -1529,19 +1482,19 @@ describe('ReactSuspense', () => {
1482
}
1483
1484
const root = ReactTestRenderer.create(<App />);
1532
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1485
+ assertLog(['Suspend! [default]', 'Loading...']);
1486
jest.advanceTimersByTime(1000);
1487
1535
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1536
- expect(Scheduler).toFlushUntilNextPaint(['default']);
1488
+ assertLog(['Promise resolved [default]']);
1489
+ await waitForPaint(['default']);
1490
expect(root).toMatchRenderedOutput('default');
1491
1492
act(() => setValue('new value'));
1540
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1493
+ assertLog(['Suspend! [new value]', 'Loading...']);
1494
jest.advanceTimersByTime(1000);
1495
1543
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1544
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
1496
+ assertLog(['Promise resolved [new value]']);
1497
+ await waitForPaint(['new value']);
1498
expect(root).toMatchRenderedOutput('new value');
1499
});
1500
@@ -1587,24 +1540,15 @@ describe('ReactSuspense', () => {
1540
}
1541
1542
const root = ReactTestRenderer.create(<App />);
1590
- expect(Scheduler).toHaveYielded([
1591
- 'Received context value [default]',
1592
- 'default',
1593
- ]);
1543
+ assertLog(['Received context value [default]', 'default']);
1544
expect(root).toMatchRenderedOutput('default');
1545
1546
act(() => setValue('new value'));
1597
- expect(Scheduler).toHaveYielded([
1598
- 'Received context value [new value]',
1599
- 'Loading...',
1600
- ]);
1547
+ assertLog(['Received context value [new value]', 'Loading...']);
1548
expect(root).toMatchRenderedOutput('Loading...');
1549
1550
act(() => setValue('default'));
1604
- expect(Scheduler).toHaveYielded([
1605
- 'Received context value [default]',
1606
- 'default',
1607
- ]);
1551
+ assertLog(['Received context value [default]', 'default']);
1552
expect(root).toMatchRenderedOutput('default');
1553
});
1554
});
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+14
-10
@@ -12,6 +12,7 @@
12
let React;
13
let ReactNoop;
14
let Scheduler;
15
+let waitForAll;
16
17
describe('ReactSuspense', () => {
18
beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactSuspense', () => {
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
function createThenable() {
@@ -83,13 +87,13 @@ describe('ReactSuspense', () => {
87
);
88
89
ReactNoop.render(element);
86
- expect(Scheduler).toFlushWithoutYielding();
90
+ await waitForAll([]);
91
expect(ReactNoop).toMatchRenderedOutput('Waiting');
92
expect(ops).toEqual([new Set([promise])]);
93
ops = [];
94
95
await resolve();
92
- expect(Scheduler).toFlushWithoutYielding();
96
+ await waitForAll([]);
97
expect(ReactNoop).toMatchRenderedOutput('Done');
98
expect(ops).toEqual([]);
99
});
@@ -122,27 +126,27 @@ describe('ReactSuspense', () => {
126
);
127
128
ReactNoop.render(element);
125
- expect(Scheduler).toFlushWithoutYielding();
129
+ await waitForAll([]);
130
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
131
expect(ops).toEqual([new Set([promise1, promise2])]);
132
ops = [];
133
134
await resolve1();
135
ReactNoop.render(element);
132
- expect(Scheduler).toFlushWithoutYielding();
136
+ await waitForAll([]);
137
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
138
expect(ops).toEqual([new Set([promise2])]);
139
ops = [];
140
141
await resolve2();
142
ReactNoop.render(element);
139
- expect(Scheduler).toFlushWithoutYielding();
143
+ await waitForAll([]);
144
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
145
expect(ops).toEqual([]);
146
});
147
148
// @gate www
145
- it('nested suspense promises are reported only for their tier', () => {
149
+ it('nested suspense promises are reported only for their tier', async () => {
150
const {promise, PromiseComp} = createThenable();
151
152
const ops1 = [];
@@ -167,7 +171,7 @@ describe('ReactSuspense', () => {
171
);
172
173
ReactNoop.render(element);
170
- expect(Scheduler).toFlushWithoutYielding();
174
+ await waitForAll([]);
175
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
176
expect(ops1).toEqual([]);
177
expect(ops2).toEqual([new Set([promise])]);
@@ -209,7 +213,7 @@ describe('ReactSuspense', () => {
213
);
214
215
ReactNoop.render(element);
212
- expect(Scheduler).toFlushWithoutYielding();
216
+ await waitForAll([]);
217
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
218
expect(ops1).toEqual([new Set([promise1])]);
219
expect(ops2).toEqual([]);
@@ -218,7 +222,7 @@ describe('ReactSuspense', () => {
222
223
await resolve1();
224
ReactNoop.render(element);
221
- expect(Scheduler).toFlushWithoutYielding();
225
+ await waitForAll([]);
226
227
// Force fallback to commit.
228
// TODO: Should be able to use `act` here.
@@ -232,7 +236,7 @@ describe('ReactSuspense', () => {
236
237
await resolve2();
238
ReactNoop.render(element);
235
- expect(Scheduler).toFlushWithoutYielding();
239
+ await waitForAll([]);
240
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
241
expect(ops1).toEqual([]);
242
expect(ops2).toEqual([]);