Test suspended children are hidden before layout in persistent mode (#15030)
Refs behave differently in persistent mode, so instead of a ref, the persistent mode version of this test asserts on the output of the host tree.
Andrew Clark committed
Mar 11, 2019 at 11:19 UTC
6a4a261ee8cf2f95c7a725670dad7763836d8284
2 files changed
+78
-6
packages/react-noop-renderer/src/createReactNoop.js
+34
-3
@@ -32,6 +32,7 @@ type Thenable = {
32
type Container = {
33
rootID: string,
34
children: Array<Instance | TextInstance>,
35
+ pendingChildren: Array<Instance | TextInstance>,
36
};
37
type Props = {prop: any, hidden: boolean, children?: mixed};
38
type Instance = {|
@@ -457,7 +458,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
458
finalizeContainerChildren(
459
container: Container,
460
newChildren: Array<Instance | TextInstance>,
460
- ): void {},
461
+ ): void {
462
+ container.pendingChildren = newChildren;
463
+ },
464
465
replaceContainerChildren(
466
container: Container,
@@ -581,13 +584,22 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
584
}
585
},
586
587
+ getPendingChildren(rootID: string = DEFAULT_ROOT_ID) {
588
+ const container = rootContainers.get(rootID);
589
+ if (container) {
590
+ return container.pendingChildren;
591
+ } else {
592
+ return null;
593
+ }
594
+ },
595
+
596
getOrCreateRootContainer(
597
rootID: string = DEFAULT_ROOT_ID,
598
isConcurrent: boolean = false,
599
) {
600
let root = roots.get(rootID);
601
if (!root) {
590
- const container = {rootID: rootID, children: []};
602
+ const container = {rootID: rootID, pendingChildren: [], children: []};
603
rootContainers.set(rootID, container);
604
root = NoopRenderer.createContainer(container, isConcurrent, false);
605
roots.set(rootID, root);
@@ -614,6 +626,25 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
626
return children;
627
},
628
629
+ getPendingChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
630
+ const children = childToJSX(ReactNoop.getPendingChildren(rootID), null);
631
+ if (children === null) {
632
+ return null;
633
+ }
634
+ if (Array.isArray(children)) {
635
+ return {
636
+ $$typeof: REACT_ELEMENT_TYPE,
637
+ type: REACT_FRAGMENT_TYPE,
638
+ key: null,
639
+ ref: null,
640
+ props: {children},
641
+ _owner: null,
642
+ _store: __DEV__ ? {} : undefined,
643
+ };
644
+ }
645
+ return children;
646
+ },
647
+
648
createPortal(
649
children: ReactNodeList,
650
container: Container,
@@ -778,7 +809,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
809
// Trick to flush passive effects without exposing an internal API:
810
// Create a throwaway root and schedule a dummy update on it.
811
const rootID = 'bloopandthenmoreletterstoavoidaconflict';
781
- const container = {rootID: rootID, children: []};
812
+ const container = {rootID: rootID, pendingChildren: [], children: []};
813
rootContainers.set(rootID, container);
814
const root = NoopRenderer.createContainer(container, true, false);
815
NoopRenderer.updateContainer(null, root, null, null);
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+44
-3
@@ -1387,9 +1387,50 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1387
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1388
});
1389
1390
- if (!global.__PERSISTENT__) {
1391
- // TODO: Write persistent version of this test
1392
- it('toggles visibility during the mutation phase', async () => {
1390
+ if (global.__PERSISTENT__) {
1391
+ it('hides/unhides suspended children before layout effects fire (persistent)', async () => {
1392
+ const {useRef, useLayoutEffect} = React;
1393
+
1394
+ function Parent() {
1395
+ const child = useRef(null);
1396
+
1397
+ useLayoutEffect(() => {
1398
+ Scheduler.yieldValue(ReactNoop.getPendingChildrenAsJSX());
1399
+ });
1400
+
1401
+ return (
1402
+ <span ref={child} hidden={false}>
1403
+ <AsyncText ms={1000} text="Hi" />
1404
+ </span>
1405
+ );
1406
+ }
1407
+
1408
+ function App(props) {
1409
+ return (
1410
+ <Suspense fallback={<Text text="Loading..." />}>
1411
+ <Parent />
1412
+ </Suspense>
1413
+ );
1414
+ }
1415
+
1416
+ ReactNoop.renderLegacySyncRoot(<App middleText="B" />);
1417
+
1418
+ expect(Scheduler).toHaveYielded([
1419
+ 'Suspend! [Hi]',
1420
+ 'Loading...',
1421
+ // The child should have already been hidden
1422
+ <React.Fragment>
1423
+ <span hidden={true} />
1424
+ <span prop="Loading..." />
1425
+ </React.Fragment>,
1426
+ ]);
1427
+
1428
+ await advanceTimers(1000);
1429
+
1430
+ expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
1431
+ });
1432
+ } else {
1433
+ it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
1434
const {useRef, useLayoutEffect} = React;
1435
1436
function Parent() {