@samitouri / QOS-React / commits / 6a4a261ee8

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() {