Skip to content

Commit 6a4a261

Browse files
authored
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.
1 parent bc8bd24 commit 6a4a261

File tree

2 files changed

+78
-6
lines changed

2 files changed

+78
-6
lines changed

packages/react-noop-renderer/src/createReactNoop.js

+34-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Thenable = {
3232
type Container = {
3333
rootID: string,
3434
children: Array<Instance | TextInstance>,
35+
pendingChildren: Array<Instance | TextInstance>,
3536
};
3637
type Props = {prop: any, hidden: boolean, children?: mixed};
3738
type Instance = {|
@@ -457,7 +458,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
457458
finalizeContainerChildren(
458459
container: Container,
459460
newChildren: Array<Instance | TextInstance>,
460-
): void {},
461+
): void {
462+
container.pendingChildren = newChildren;
463+
},
461464

462465
replaceContainerChildren(
463466
container: Container,
@@ -581,13 +584,22 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
581584
}
582585
},
583586

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+
584596
getOrCreateRootContainer(
585597
rootID: string = DEFAULT_ROOT_ID,
586598
isConcurrent: boolean = false,
587599
) {
588600
let root = roots.get(rootID);
589601
if (!root) {
590-
const container = {rootID: rootID, children: []};
602+
const container = {rootID: rootID, pendingChildren: [], children: []};
591603
rootContainers.set(rootID, container);
592604
root = NoopRenderer.createContainer(container, isConcurrent, false);
593605
roots.set(rootID, root);
@@ -614,6 +626,25 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
614626
return children;
615627
},
616628

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+
617648
createPortal(
618649
children: ReactNodeList,
619650
container: Container,
@@ -778,7 +809,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
778809
// Trick to flush passive effects without exposing an internal API:
779810
// Create a throwaway root and schedule a dummy update on it.
780811
const rootID = 'bloopandthenmoreletterstoavoidaconflict';
781-
const container = {rootID: rootID, children: []};
812+
const container = {rootID: rootID, pendingChildren: [], children: []};
782813
rootContainers.set(rootID, container);
783814
const root = NoopRenderer.createContainer(container, true, false);
784815
NoopRenderer.updateContainer(null, root, null, null);

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js

+44-3
Original file line numberDiff line numberDiff line change
@@ -1387,9 +1387,50 @@ describe('ReactSuspenseWithNoopRenderer', () => {
13871387
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
13881388
});
13891389

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 () => {
13931434
const {useRef, useLayoutEffect} = React;
13941435

13951436
function Parent() {

0 commit comments

Comments
 (0)