1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. /**
    
  11.  * This is a renderer of React that doesn't have a render target output.
    
  12.  * It is useful to demonstrate the internals of the reconciler in isolation
    
  13.  * and for testing semantics of reconciliation separate from the host
    
  14.  * environment.
    
  15.  */
    
  16. 
    
  17. import type {
    
  18.   Fiber,
    
  19.   TransitionTracingCallbacks,
    
  20. } from 'react-reconciler/src/ReactInternalTypes';
    
  21. import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue';
    
  22. import type {ReactNodeList} from 'shared/ReactTypes';
    
  23. import type {RootTag} from 'react-reconciler/src/ReactRootTags';
    
  24. 
    
  25. import * as Scheduler from 'scheduler/unstable_mock';
    
  26. import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
    
  27. import isArray from 'shared/isArray';
    
  28. import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
    
  29. import {
    
  30.   DefaultEventPriority,
    
  31.   IdleEventPriority,
    
  32.   ConcurrentRoot,
    
  33.   LegacyRoot,
    
  34. } from 'react-reconciler/constants';
    
  35. 
    
  36. type Container = {
    
  37.   rootID: string,
    
  38.   children: Array<Instance | TextInstance>,
    
  39.   pendingChildren: Array<Instance | TextInstance>,
    
  40.   ...
    
  41. };
    
  42. type Props = {
    
  43.   prop: any,
    
  44.   hidden: boolean,
    
  45.   children?: mixed,
    
  46.   bottom?: null | number,
    
  47.   left?: null | number,
    
  48.   right?: null | number,
    
  49.   top?: null | number,
    
  50.   src?: string,
    
  51.   ...
    
  52. };
    
  53. type Instance = {
    
  54.   type: string,
    
  55.   id: number,
    
  56.   parent: number,
    
  57.   children: Array<Instance | TextInstance>,
    
  58.   text: string | null,
    
  59.   prop: any,
    
  60.   hidden: boolean,
    
  61.   context: HostContext,
    
  62. };
    
  63. type TextInstance = {
    
  64.   text: string,
    
  65.   id: number,
    
  66.   parent: number,
    
  67.   hidden: boolean,
    
  68.   context: HostContext,
    
  69. };
    
  70. type HostContext = Object;
    
  71. type CreateRootOptions = {
    
  72.   unstable_transitionCallbacks?: TransitionTracingCallbacks,
    
  73.   ...
    
  74. };
    
  75. 
    
  76. type SuspenseyCommitSubscription = {
    
  77.   pendingCount: number,
    
  78.   commit: null | (() => void),
    
  79. };
    
  80. 
    
  81. export type TransitionStatus = mixed;
    
  82. 
    
  83. const NO_CONTEXT = {};
    
  84. const UPPERCASE_CONTEXT = {};
    
  85. if (__DEV__) {
    
  86.   Object.freeze(NO_CONTEXT);
    
  87. }
    
  88. 
    
  89. function createReactNoop(reconciler: Function, useMutation: boolean) {
    
  90.   let instanceCounter = 0;
    
  91.   let hostUpdateCounter = 0;
    
  92.   let hostCloneCounter = 0;
    
  93. 
    
  94.   function appendChildToContainerOrInstance(
    
  95.     parentInstance: Container | Instance,
    
  96.     child: Instance | TextInstance,
    
  97.   ): void {
    
  98.     const prevParent = child.parent;
    
  99.     if (prevParent !== -1 && prevParent !== parentInstance.id) {
    
  100.       throw new Error('Reparenting is not allowed');
    
  101.     }
    
  102.     child.parent = parentInstance.id;
    
  103.     const index = parentInstance.children.indexOf(child);
    
  104.     if (index !== -1) {
    
  105.       parentInstance.children.splice(index, 1);
    
  106.     }
    
  107.     parentInstance.children.push(child);
    
  108.   }
    
  109. 
    
  110.   function appendChildToContainer(
    
  111.     parentInstance: Container,
    
  112.     child: Instance | TextInstance,
    
  113.   ): void {
    
  114.     if (typeof parentInstance.rootID !== 'string') {
    
  115.       // Some calls to this aren't typesafe.
    
  116.       // This helps surface mistakes in tests.
    
  117.       throw new Error(
    
  118.         'appendChildToContainer() first argument is not a container.',
    
  119.       );
    
  120.     }
    
  121.     appendChildToContainerOrInstance(parentInstance, child);
    
  122.   }
    
  123. 
    
  124.   function appendChild(
    
  125.     parentInstance: Instance,
    
  126.     child: Instance | TextInstance,
    
  127.   ): void {
    
  128.     if (typeof (parentInstance: any).rootID === 'string') {
    
  129.       // Some calls to this aren't typesafe.
    
  130.       // This helps surface mistakes in tests.
    
  131.       throw new Error('appendChild() first argument is not an instance.');
    
  132.     }
    
  133.     appendChildToContainerOrInstance(parentInstance, child);
    
  134.   }
    
  135. 
    
  136.   function insertInContainerOrInstanceBefore(
    
  137.     parentInstance: Container | Instance,
    
  138.     child: Instance | TextInstance,
    
  139.     beforeChild: Instance | TextInstance,
    
  140.   ): void {
    
  141.     const index = parentInstance.children.indexOf(child);
    
  142.     if (index !== -1) {
    
  143.       parentInstance.children.splice(index, 1);
    
  144.     }
    
  145.     const beforeIndex = parentInstance.children.indexOf(beforeChild);
    
  146.     if (beforeIndex === -1) {
    
  147.       throw new Error('This child does not exist.');
    
  148.     }
    
  149.     parentInstance.children.splice(beforeIndex, 0, child);
    
  150.   }
    
  151. 
    
  152.   function insertInContainerBefore(
    
  153.     parentInstance: Container,
    
  154.     child: Instance | TextInstance,
    
  155.     beforeChild: Instance | TextInstance,
    
  156.   ) {
    
  157.     if (typeof parentInstance.rootID !== 'string') {
    
  158.       // Some calls to this aren't typesafe.
    
  159.       // This helps surface mistakes in tests.
    
  160.       throw new Error(
    
  161.         'insertInContainerBefore() first argument is not a container.',
    
  162.       );
    
  163.     }
    
  164.     insertInContainerOrInstanceBefore(parentInstance, child, beforeChild);
    
  165.   }
    
  166. 
    
  167.   function insertBefore(
    
  168.     parentInstance: Instance,
    
  169.     child: Instance | TextInstance,
    
  170.     beforeChild: Instance | TextInstance,
    
  171.   ) {
    
  172.     if (typeof (parentInstance: any).rootID === 'string') {
    
  173.       // Some calls to this aren't typesafe.
    
  174.       // This helps surface mistakes in tests.
    
  175.       throw new Error('insertBefore() first argument is not an instance.');
    
  176.     }
    
  177.     insertInContainerOrInstanceBefore(parentInstance, child, beforeChild);
    
  178.   }
    
  179. 
    
  180.   function clearContainer(container: Container): void {
    
  181.     container.children.splice(0);
    
  182.   }
    
  183. 
    
  184.   function removeChildFromContainerOrInstance(
    
  185.     parentInstance: Container | Instance,
    
  186.     child: Instance | TextInstance,
    
  187.   ): void {
    
  188.     const index = parentInstance.children.indexOf(child);
    
  189.     if (index === -1) {
    
  190.       throw new Error('This child does not exist.');
    
  191.     }
    
  192.     parentInstance.children.splice(index, 1);
    
  193.   }
    
  194. 
    
  195.   function removeChildFromContainer(
    
  196.     parentInstance: Container,
    
  197.     child: Instance | TextInstance,
    
  198.   ): void {
    
  199.     if (typeof parentInstance.rootID !== 'string') {
    
  200.       // Some calls to this aren't typesafe.
    
  201.       // This helps surface mistakes in tests.
    
  202.       throw new Error(
    
  203.         'removeChildFromContainer() first argument is not a container.',
    
  204.       );
    
  205.     }
    
  206.     removeChildFromContainerOrInstance(parentInstance, child);
    
  207.   }
    
  208. 
    
  209.   function removeChild(
    
  210.     parentInstance: Instance,
    
  211.     child: Instance | TextInstance,
    
  212.   ): void {
    
  213.     if (typeof (parentInstance: any).rootID === 'string') {
    
  214.       // Some calls to this aren't typesafe.
    
  215.       // This helps surface mistakes in tests.
    
  216.       throw new Error('removeChild() first argument is not an instance.');
    
  217.     }
    
  218.     removeChildFromContainerOrInstance(parentInstance, child);
    
  219.   }
    
  220. 
    
  221.   function cloneInstance(
    
  222.     instance: Instance,
    
  223.     type: string,
    
  224.     oldProps: Props,
    
  225.     newProps: Props,
    
  226.     keepChildren: boolean,
    
  227.     children: ?$ReadOnlyArray<Instance>,
    
  228.   ): Instance {
    
  229.     if (__DEV__) {
    
  230.       checkPropStringCoercion(newProps.children, 'children');
    
  231.     }
    
  232.     const clone = {
    
  233.       id: instance.id,
    
  234.       type: type,
    
  235.       parent: instance.parent,
    
  236.       children: keepChildren ? instance.children : children ?? [],
    
  237.       text: shouldSetTextContent(type, newProps)
    
  238.         ? computeText((newProps.children: any) + '', instance.context)
    
  239.         : null,
    
  240.       prop: newProps.prop,
    
  241.       hidden: !!newProps.hidden,
    
  242.       context: instance.context,
    
  243.     };
    
  244. 
    
  245.     if (type === 'suspensey-thing' && typeof newProps.src === 'string') {
    
  246.       clone.src = newProps.src;
    
  247.     }
    
  248. 
    
  249.     Object.defineProperty(clone, 'id', {
    
  250.       value: clone.id,
    
  251.       enumerable: false,
    
  252.     });
    
  253.     Object.defineProperty(clone, 'parent', {
    
  254.       value: clone.parent,
    
  255.       enumerable: false,
    
  256.     });
    
  257.     Object.defineProperty(clone, 'text', {
    
  258.       value: clone.text,
    
  259.       enumerable: false,
    
  260.     });
    
  261.     Object.defineProperty(clone, 'context', {
    
  262.       value: clone.context,
    
  263.       enumerable: false,
    
  264.     });
    
  265.     hostCloneCounter++;
    
  266.     return clone;
    
  267.   }
    
  268. 
    
  269.   function shouldSetTextContent(type: string, props: Props): boolean {
    
  270.     if (type === 'errorInBeginPhase') {
    
  271.       throw new Error('Error in host config.');
    
  272.     }
    
  273.     return (
    
  274.       typeof props.children === 'string' || typeof props.children === 'number'
    
  275.     );
    
  276.   }
    
  277. 
    
  278.   function computeText(rawText, hostContext) {
    
  279.     return hostContext === UPPERCASE_CONTEXT ? rawText.toUpperCase() : rawText;
    
  280.   }
    
  281. 
    
  282.   type SuspenseyThingRecord = {
    
  283.     status: 'pending' | 'fulfilled',
    
  284.     subscriptions: Array<SuspenseyCommitSubscription> | null,
    
  285.   };
    
  286. 
    
  287.   let suspenseyThingCache: Map<
    
  288.     SuspenseyThingRecord,
    
  289.     'pending' | 'fulfilled',
    
  290.   > | null = null;
    
  291. 
    
  292.   // Represents a subscription for all the suspensey things that block a
    
  293.   // particular commit. Once they've all loaded, the commit phase can proceed.
    
  294.   let suspenseyCommitSubscription: SuspenseyCommitSubscription | null = null;
    
  295. 
    
  296.   function startSuspendingCommit(): void {
    
  297.     // This is where we might suspend on things that aren't associated with a
    
  298.     // particular node, like document.fonts.ready.
    
  299.     suspenseyCommitSubscription = null;
    
  300.   }
    
  301. 
    
  302.   function suspendInstance(type: string, props: Props): void {
    
  303.     const src = props.src;
    
  304.     if (type === 'suspensey-thing' && typeof src === 'string') {
    
  305.       // Attach a listener to the suspensey thing and create a subscription
    
  306.       // object that uses reference counting to track when all the suspensey
    
  307.       // things have loaded.
    
  308.       const record = suspenseyThingCache.get(src);
    
  309.       if (record === undefined) {
    
  310.         throw new Error('Could not find record for key.');
    
  311.       }
    
  312.       if (record.status === 'fulfilled') {
    
  313.         // Already loaded.
    
  314.       } else if (record.status === 'pending') {
    
  315.         if (suspenseyCommitSubscription === null) {
    
  316.           suspenseyCommitSubscription = {
    
  317.             pendingCount: 1,
    
  318.             commit: null,
    
  319.           };
    
  320.         } else {
    
  321.           suspenseyCommitSubscription.pendingCount++;
    
  322.         }
    
  323.         // Stash the subscription on the record. In `resolveSuspenseyThing`,
    
  324.         // we'll use this fire the commit once all the things have loaded.
    
  325.         if (record.subscriptions === null) {
    
  326.           record.subscriptions = [];
    
  327.         }
    
  328.         record.subscriptions.push(suspenseyCommitSubscription);
    
  329.       }
    
  330.     } else {
    
  331.       throw new Error(
    
  332.         'Did not expect this host component to be visited when suspending ' +
    
  333.           'the commit. Did you check the SuspendCommit flag?',
    
  334.       );
    
  335.     }
    
  336.   }
    
  337. 
    
  338.   function waitForCommitToBeReady():
    
  339.     | ((commit: () => mixed) => () => void)
    
  340.     | null {
    
  341.     const subscription = suspenseyCommitSubscription;
    
  342.     if (subscription !== null) {
    
  343.       suspenseyCommitSubscription = null;
    
  344.       return (commit: () => void) => {
    
  345.         subscription.commit = commit;
    
  346.         const cancelCommit = () => {
    
  347.           subscription.commit = null;
    
  348.         };
    
  349.         return cancelCommit;
    
  350.       };
    
  351.     }
    
  352.     return null;
    
  353.   }
    
  354. 
    
  355.   const sharedHostConfig = {
    
  356.     supportsSingletons: false,
    
  357. 
    
  358.     getRootHostContext() {
    
  359.       return NO_CONTEXT;
    
  360.     },
    
  361. 
    
  362.     getChildHostContext(parentHostContext: HostContext, type: string) {
    
  363.       if (type === 'offscreen') {
    
  364.         return parentHostContext;
    
  365.       }
    
  366.       if (type === 'uppercase') {
    
  367.         return UPPERCASE_CONTEXT;
    
  368.       }
    
  369.       return NO_CONTEXT;
    
  370.     },
    
  371. 
    
  372.     getPublicInstance(instance) {
    
  373.       return instance;
    
  374.     },
    
  375. 
    
  376.     createInstance(
    
  377.       type: string,
    
  378.       props: Props,
    
  379.       rootContainerInstance: Container,
    
  380.       hostContext: HostContext,
    
  381.       internalInstanceHandle: Object,
    
  382.     ): Instance {
    
  383.       if (type === 'errorInCompletePhase') {
    
  384.         throw new Error('Error in host config.');
    
  385.       }
    
  386.       if (__DEV__) {
    
  387.         // The `if` statement here prevents auto-disabling of the safe coercion
    
  388.         // ESLint rule, so we must manually disable it below.
    
  389.         if (shouldSetTextContent(type, props)) {
    
  390.           checkPropStringCoercion(props.children, 'children');
    
  391.         }
    
  392.       }
    
  393.       const inst = {
    
  394.         id: instanceCounter++,
    
  395.         type: type,
    
  396.         children: [],
    
  397.         parent: -1,
    
  398.         text: shouldSetTextContent(type, props)
    
  399.           ? // eslint-disable-next-line react-internal/safe-string-coercion
    
  400.             computeText((props.children: any) + '', hostContext)
    
  401.           : null,
    
  402.         prop: props.prop,
    
  403.         hidden: !!props.hidden,
    
  404.         context: hostContext,
    
  405.       };
    
  406. 
    
  407.       if (type === 'suspensey-thing' && typeof props.src === 'string') {
    
  408.         inst.src = props.src;
    
  409.       }
    
  410. 
    
  411.       // Hide from unit tests
    
  412.       Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
    
  413.       Object.defineProperty(inst, 'parent', {
    
  414.         value: inst.parent,
    
  415.         enumerable: false,
    
  416.       });
    
  417.       Object.defineProperty(inst, 'text', {
    
  418.         value: inst.text,
    
  419.         enumerable: false,
    
  420.       });
    
  421.       Object.defineProperty(inst, 'context', {
    
  422.         value: inst.context,
    
  423.         enumerable: false,
    
  424.       });
    
  425.       Object.defineProperty(inst, 'fiber', {
    
  426.         value: internalInstanceHandle,
    
  427.         enumerable: false,
    
  428.       });
    
  429.       return inst;
    
  430.     },
    
  431. 
    
  432.     appendInitialChild(
    
  433.       parentInstance: Instance,
    
  434.       child: Instance | TextInstance,
    
  435.     ): void {
    
  436.       const prevParent = child.parent;
    
  437.       if (prevParent !== -1 && prevParent !== parentInstance.id) {
    
  438.         throw new Error('Reparenting is not allowed');
    
  439.       }
    
  440.       child.parent = parentInstance.id;
    
  441.       parentInstance.children.push(child);
    
  442.     },
    
  443. 
    
  444.     finalizeInitialChildren(
    
  445.       domElement: Instance,
    
  446.       type: string,
    
  447.       props: Props,
    
  448.     ): boolean {
    
  449.       return false;
    
  450.     },
    
  451. 
    
  452.     shouldSetTextContent,
    
  453. 
    
  454.     createTextInstance(
    
  455.       text: string,
    
  456.       rootContainerInstance: Container,
    
  457.       hostContext: Object,
    
  458.       internalInstanceHandle: Object,
    
  459.     ): TextInstance {
    
  460.       if (hostContext === UPPERCASE_CONTEXT) {
    
  461.         text = text.toUpperCase();
    
  462.       }
    
  463.       const inst = {
    
  464.         text: text,
    
  465.         id: instanceCounter++,
    
  466.         parent: -1,
    
  467.         hidden: false,
    
  468.         context: hostContext,
    
  469.       };
    
  470.       // Hide from unit tests
    
  471.       Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
    
  472.       Object.defineProperty(inst, 'parent', {
    
  473.         value: inst.parent,
    
  474.         enumerable: false,
    
  475.       });
    
  476.       Object.defineProperty(inst, 'context', {
    
  477.         value: inst.context,
    
  478.         enumerable: false,
    
  479.       });
    
  480.       return inst;
    
  481.     },
    
  482. 
    
  483.     scheduleTimeout: setTimeout,
    
  484.     cancelTimeout: clearTimeout,
    
  485.     noTimeout: -1,
    
  486. 
    
  487.     supportsMicrotasks: true,
    
  488.     scheduleMicrotask:
    
  489.       typeof queueMicrotask === 'function'
    
  490.         ? queueMicrotask
    
  491.         : typeof Promise !== 'undefined'
    
  492.         ? callback =>
    
  493.             Promise.resolve(null)
    
  494.               .then(callback)
    
  495.               .catch(error => {
    
  496.                 setTimeout(() => {
    
  497.                   throw error;
    
  498.                 });
    
  499.               })
    
  500.         : setTimeout,
    
  501. 
    
  502.     prepareForCommit(): null | Object {
    
  503.       return null;
    
  504.     },
    
  505. 
    
  506.     resetAfterCommit(): void {},
    
  507. 
    
  508.     getCurrentEventPriority() {
    
  509.       return currentEventPriority;
    
  510.     },
    
  511. 
    
  512.     shouldAttemptEagerTransition(): boolean {
    
  513.       return false;
    
  514.     },
    
  515. 
    
  516.     now: Scheduler.unstable_now,
    
  517. 
    
  518.     isPrimaryRenderer: true,
    
  519.     warnsIfNotActing: true,
    
  520.     supportsHydration: false,
    
  521. 
    
  522.     getInstanceFromNode() {
    
  523.       throw new Error('Not yet implemented.');
    
  524.     },
    
  525. 
    
  526.     beforeActiveInstanceBlur() {
    
  527.       // NO-OP
    
  528.     },
    
  529. 
    
  530.     afterActiveInstanceBlur() {
    
  531.       // NO-OP
    
  532.     },
    
  533. 
    
  534.     preparePortalMount() {
    
  535.       // NO-OP
    
  536.     },
    
  537. 
    
  538.     prepareScopeUpdate() {},
    
  539. 
    
  540.     getInstanceFromScope() {
    
  541.       throw new Error('Not yet implemented.');
    
  542.     },
    
  543. 
    
  544.     detachDeletedInstance() {},
    
  545. 
    
  546.     logRecoverableError() {
    
  547.       // no-op
    
  548.     },
    
  549. 
    
  550.     requestPostPaintCallback(callback) {
    
  551.       const endTime = Scheduler.unstable_now();
    
  552.       callback(endTime);
    
  553.     },
    
  554. 
    
  555.     maySuspendCommit(type: string, props: Props): boolean {
    
  556.       // Asks whether it's possible for this combination of type and props
    
  557.       // to ever need to suspend. This is different from asking whether it's
    
  558.       // currently ready because even if it's ready now, it might get purged
    
  559.       // from the cache later.
    
  560.       return type === 'suspensey-thing' && typeof props.src === 'string';
    
  561.     },
    
  562. 
    
  563.     mayResourceSuspendCommit(resource: mixed): boolean {
    
  564.       throw new Error(
    
  565.         'Resources are not implemented for React Noop yet. This method should not be called',
    
  566.       );
    
  567.     },
    
  568. 
    
  569.     preloadInstance(type: string, props: Props): boolean {
    
  570.       if (type !== 'suspensey-thing' || typeof props.src !== 'string') {
    
  571.         throw new Error('Attempted to preload unexpected instance: ' + type);
    
  572.       }
    
  573. 
    
  574.       // In addition to preloading an instance, this method asks whether the
    
  575.       // instance is ready to be committed. If it's not, React may yield to the
    
  576.       // main thread and ask again. It's possible a load event will fire in
    
  577.       // between, in which case we can avoid showing a fallback.
    
  578.       if (suspenseyThingCache === null) {
    
  579.         suspenseyThingCache = new Map();
    
  580.       }
    
  581.       const record = suspenseyThingCache.get(props.src);
    
  582.       if (record === undefined) {
    
  583.         const newRecord: SuspenseyThingRecord = {
    
  584.           status: 'pending',
    
  585.           subscriptions: null,
    
  586.         };
    
  587.         suspenseyThingCache.set(props.src, newRecord);
    
  588.         const onLoadStart = props.onLoadStart;
    
  589.         if (typeof onLoadStart === 'function') {
    
  590.           onLoadStart();
    
  591.         }
    
  592.         return false;
    
  593.       } else {
    
  594.         // If this is false, React will trigger a fallback, if needed.
    
  595.         return record.status === 'fulfilled';
    
  596.       }
    
  597.     },
    
  598. 
    
  599.     preloadResource(resource: mixed): boolean {
    
  600.       throw new Error(
    
  601.         'Resources are not implemented for React Noop yet. This method should not be called',
    
  602.       );
    
  603.     },
    
  604. 
    
  605.     startSuspendingCommit,
    
  606.     suspendInstance,
    
  607. 
    
  608.     suspendResource(resource: mixed): void {
    
  609.       throw new Error(
    
  610.         'Resources are not implemented for React Noop yet. This method should not be called',
    
  611.       );
    
  612.     },
    
  613. 
    
  614.     waitForCommitToBeReady,
    
  615. 
    
  616.     NotPendingTransition: (null: TransitionStatus),
    
  617.   };
    
  618. 
    
  619.   const hostConfig = useMutation
    
  620.     ? {
    
  621.         ...sharedHostConfig,
    
  622. 
    
  623.         supportsMutation: true,
    
  624.         supportsPersistence: false,
    
  625. 
    
  626.         commitMount(instance: Instance, type: string, newProps: Props): void {
    
  627.           // Noop
    
  628.         },
    
  629. 
    
  630.         commitUpdate(
    
  631.           instance: Instance,
    
  632.           updatePayload: Object,
    
  633.           type: string,
    
  634.           oldProps: Props,
    
  635.           newProps: Props,
    
  636.         ): void {
    
  637.           if (oldProps === null) {
    
  638.             throw new Error('Should have old props');
    
  639.           }
    
  640.           hostUpdateCounter++;
    
  641.           instance.prop = newProps.prop;
    
  642.           instance.hidden = !!newProps.hidden;
    
  643. 
    
  644.           if (type === 'suspensey-thing' && typeof newProps.src === 'string') {
    
  645.             instance.src = newProps.src;
    
  646.           }
    
  647. 
    
  648.           if (shouldSetTextContent(type, newProps)) {
    
  649.             if (__DEV__) {
    
  650.               checkPropStringCoercion(newProps.children, 'children');
    
  651.             }
    
  652.             instance.text = computeText(
    
  653.               (newProps.children: any) + '',
    
  654.               instance.context,
    
  655.             );
    
  656.           }
    
  657.         },
    
  658. 
    
  659.         commitTextUpdate(
    
  660.           textInstance: TextInstance,
    
  661.           oldText: string,
    
  662.           newText: string,
    
  663.         ): void {
    
  664.           hostUpdateCounter++;
    
  665.           textInstance.text = computeText(newText, textInstance.context);
    
  666.         },
    
  667. 
    
  668.         appendChild,
    
  669.         appendChildToContainer,
    
  670.         insertBefore,
    
  671.         insertInContainerBefore,
    
  672.         removeChild,
    
  673.         removeChildFromContainer,
    
  674.         clearContainer,
    
  675. 
    
  676.         hideInstance(instance: Instance): void {
    
  677.           instance.hidden = true;
    
  678.         },
    
  679. 
    
  680.         hideTextInstance(textInstance: TextInstance): void {
    
  681.           textInstance.hidden = true;
    
  682.         },
    
  683. 
    
  684.         unhideInstance(instance: Instance, props: Props): void {
    
  685.           if (!props.hidden) {
    
  686.             instance.hidden = false;
    
  687.           }
    
  688.         },
    
  689. 
    
  690.         unhideTextInstance(textInstance: TextInstance, text: string): void {
    
  691.           textInstance.hidden = false;
    
  692.         },
    
  693. 
    
  694.         resetTextContent(instance: Instance): void {
    
  695.           instance.text = null;
    
  696.         },
    
  697.       }
    
  698.     : {
    
  699.         ...sharedHostConfig,
    
  700.         supportsMutation: false,
    
  701.         supportsPersistence: true,
    
  702. 
    
  703.         cloneInstance,
    
  704.         clearContainer,
    
  705. 
    
  706.         createContainerChildSet(): Array<Instance | TextInstance> {
    
  707.           return [];
    
  708.         },
    
  709. 
    
  710.         appendChildToContainerChildSet(
    
  711.           childSet: Array<Instance | TextInstance>,
    
  712.           child: Instance | TextInstance,
    
  713.         ): void {
    
  714.           childSet.push(child);
    
  715.         },
    
  716. 
    
  717.         finalizeContainerChildren(
    
  718.           container: Container,
    
  719.           newChildren: Array<Instance | TextInstance>,
    
  720.         ): void {
    
  721.           container.pendingChildren = newChildren;
    
  722.           if (
    
  723.             newChildren.length === 1 &&
    
  724.             newChildren[0].text === 'Error when completing root'
    
  725.           ) {
    
  726.             // Trigger an error for testing purposes
    
  727.             throw Error('Error when completing root');
    
  728.           }
    
  729.         },
    
  730. 
    
  731.         replaceContainerChildren(
    
  732.           container: Container,
    
  733.           newChildren: Array<Instance | TextInstance>,
    
  734.         ): void {
    
  735.           container.children = newChildren;
    
  736.         },
    
  737. 
    
  738.         cloneHiddenInstance(
    
  739.           instance: Instance,
    
  740.           type: string,
    
  741.           props: Props,
    
  742.         ): Instance {
    
  743.           const clone = cloneInstance(instance, type, props, props, true, null);
    
  744.           clone.hidden = true;
    
  745.           return clone;
    
  746.         },
    
  747. 
    
  748.         cloneHiddenTextInstance(
    
  749.           instance: TextInstance,
    
  750.           text: string,
    
  751.         ): TextInstance {
    
  752.           const clone = {
    
  753.             text: instance.text,
    
  754.             id: instance.id,
    
  755.             parent: instance.parent,
    
  756.             hidden: true,
    
  757.             context: instance.context,
    
  758.           };
    
  759.           // Hide from unit tests
    
  760.           Object.defineProperty(clone, 'id', {
    
  761.             value: clone.id,
    
  762.             enumerable: false,
    
  763.           });
    
  764.           Object.defineProperty(clone, 'parent', {
    
  765.             value: clone.parent,
    
  766.             enumerable: false,
    
  767.           });
    
  768.           Object.defineProperty(clone, 'context', {
    
  769.             value: clone.context,
    
  770.             enumerable: false,
    
  771.           });
    
  772.           return clone;
    
  773.         },
    
  774.       };
    
  775. 
    
  776.   const NoopRenderer = reconciler(hostConfig);
    
  777. 
    
  778.   const rootContainers = new Map();
    
  779.   const roots = new Map();
    
  780.   const DEFAULT_ROOT_ID = '<default>';
    
  781. 
    
  782.   let currentEventPriority = DefaultEventPriority;
    
  783. 
    
  784.   function childToJSX(child, text) {
    
  785.     if (text !== null) {
    
  786.       return text;
    
  787.     }
    
  788.     if (child === null) {
    
  789.       return null;
    
  790.     }
    
  791.     if (typeof child === 'string') {
    
  792.       return child;
    
  793.     }
    
  794.     if (isArray(child)) {
    
  795.       if (child.length === 0) {
    
  796.         return null;
    
  797.       }
    
  798.       if (child.length === 1) {
    
  799.         return childToJSX(child[0], null);
    
  800.       }
    
  801.       const children = child.map(c => childToJSX(c, null));
    
  802.       if (children.every(c => typeof c === 'string' || typeof c === 'number')) {
    
  803.         return children.join('');
    
  804.       }
    
  805.       return children;
    
  806.     }
    
  807.     if (isArray(child.children)) {
    
  808.       // This is an instance.
    
  809.       const instance: Instance = (child: any);
    
  810.       const children = childToJSX(instance.children, instance.text);
    
  811.       const props = ({prop: instance.prop}: any);
    
  812.       if (instance.hidden) {
    
  813.         props.hidden = true;
    
  814.       }
    
  815.       if (instance.src) {
    
  816.         props.src = instance.src;
    
  817.       }
    
  818.       if (children !== null) {
    
  819.         props.children = children;
    
  820.       }
    
  821.       return {
    
  822.         $$typeof: REACT_ELEMENT_TYPE,
    
  823.         type: instance.type,
    
  824.         key: null,
    
  825.         ref: null,
    
  826.         props: props,
    
  827.         _owner: null,
    
  828.         _store: __DEV__ ? {} : undefined,
    
  829.       };
    
  830.     }
    
  831.     // This is a text instance
    
  832.     const textInstance: TextInstance = (child: any);
    
  833.     if (textInstance.hidden) {
    
  834.       return '';
    
  835.     }
    
  836.     return textInstance.text;
    
  837.   }
    
  838. 
    
  839.   function getChildren(root) {
    
  840.     if (root) {
    
  841.       return root.children;
    
  842.     } else {
    
  843.       return null;
    
  844.     }
    
  845.   }
    
  846. 
    
  847.   function getPendingChildren(root) {
    
  848.     if (root) {
    
  849.       return root.children;
    
  850.     } else {
    
  851.       return null;
    
  852.     }
    
  853.   }
    
  854. 
    
  855.   function getChildrenAsJSX(root) {
    
  856.     const children = childToJSX(getChildren(root), null);
    
  857.     if (children === null) {
    
  858.       return null;
    
  859.     }
    
  860.     if (isArray(children)) {
    
  861.       return {
    
  862.         $$typeof: REACT_ELEMENT_TYPE,
    
  863.         type: REACT_FRAGMENT_TYPE,
    
  864.         key: null,
    
  865.         ref: null,
    
  866.         props: {children},
    
  867.         _owner: null,
    
  868.         _store: __DEV__ ? {} : undefined,
    
  869.       };
    
  870.     }
    
  871.     return children;
    
  872.   }
    
  873. 
    
  874.   function getPendingChildrenAsJSX(root) {
    
  875.     const children = childToJSX(getChildren(root), null);
    
  876.     if (children === null) {
    
  877.       return null;
    
  878.     }
    
  879.     if (isArray(children)) {
    
  880.       return {
    
  881.         $$typeof: REACT_ELEMENT_TYPE,
    
  882.         type: REACT_FRAGMENT_TYPE,
    
  883.         key: null,
    
  884.         ref: null,
    
  885.         props: {children},
    
  886.         _owner: null,
    
  887.         _store: __DEV__ ? {} : undefined,
    
  888.       };
    
  889.     }
    
  890.     return children;
    
  891.   }
    
  892. 
    
  893.   function flushSync<R>(fn: () => R): R {
    
  894.     if (__DEV__) {
    
  895.       if (NoopRenderer.isAlreadyRendering()) {
    
  896.         console.error(
    
  897.           'flushSync was called from inside a lifecycle method. React cannot ' +
    
  898.             'flush when React is already rendering. Consider moving this call to ' +
    
  899.             'a scheduler task or micro task.',
    
  900.         );
    
  901.       }
    
  902.     }
    
  903.     return NoopRenderer.flushSync(fn);
    
  904.   }
    
  905. 
    
  906.   function onRecoverableError(error) {
    
  907.     // TODO: Turn this on once tests are fixed
    
  908.     // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
    
  909.     // console.error(error);
    
  910.   }
    
  911. 
    
  912.   let idCounter = 0;
    
  913. 
    
  914.   const ReactNoop = {
    
  915.     _Scheduler: Scheduler,
    
  916. 
    
  917.     getChildren(rootID: string = DEFAULT_ROOT_ID) {
    
  918.       throw new Error(
    
  919.         'No longer supported due to bad performance when used with `expect()`. ' +
    
  920.           'Use `ReactNoop.getChildrenAsJSX()` instead or, if you really need to, `dangerouslyGetChildren` after you carefully considered the warning in its JSDOC.',
    
  921.       );
    
  922.     },
    
  923. 
    
  924.     getPendingChildren(rootID: string = DEFAULT_ROOT_ID) {
    
  925.       throw new Error(
    
  926.         'No longer supported due to bad performance when used with `expect()`. ' +
    
  927.           'Use `ReactNoop.getPendingChildrenAsJSX()` instead or, if you really need to, `dangerouslyGetPendingChildren` after you carefully considered the warning in its JSDOC.',
    
  928.       );
    
  929.     },
    
  930. 
    
  931.     /**
    
  932.      * Prefer using `getChildrenAsJSX`.
    
  933.      * Using the returned children in `.toEqual` has very poor performance on mismatch due to deep equality checking of fiber structures.
    
  934.      * Make sure you deeply remove enumerable properties before passing it to `.toEqual`, or, better, use `getChildrenAsJSX` or `toMatchRenderedOutput`.
    
  935.      */
    
  936.     dangerouslyGetChildren(rootID: string = DEFAULT_ROOT_ID) {
    
  937.       const container = rootContainers.get(rootID);
    
  938.       return getChildren(container);
    
  939.     },
    
  940. 
    
  941.     /**
    
  942.      * Prefer using `getPendingChildrenAsJSX`.
    
  943.      * Using the returned children in `.toEqual` has very poor performance on mismatch due to deep equality checking of fiber structures.
    
  944.      * Make sure you deeply remove enumerable properties before passing it to `.toEqual`, or, better, use `getChildrenAsJSX` or `toMatchRenderedOutput`.
    
  945.      */
    
  946.     dangerouslyGetPendingChildren(rootID: string = DEFAULT_ROOT_ID) {
    
  947.       const container = rootContainers.get(rootID);
    
  948.       return getPendingChildren(container);
    
  949.     },
    
  950. 
    
  951.     getOrCreateRootContainer(rootID: string = DEFAULT_ROOT_ID, tag: RootTag) {
    
  952.       let root = roots.get(rootID);
    
  953.       if (!root) {
    
  954.         const container = {rootID: rootID, pendingChildren: [], children: []};
    
  955.         rootContainers.set(rootID, container);
    
  956.         root = NoopRenderer.createContainer(
    
  957.           container,
    
  958.           tag,
    
  959.           null,
    
  960.           null,
    
  961.           false,
    
  962.           '',
    
  963.           onRecoverableError,
    
  964.           null,
    
  965.         );
    
  966.         roots.set(rootID, root);
    
  967.       }
    
  968.       return root.current.stateNode.containerInfo;
    
  969.     },
    
  970. 
    
  971.     // TODO: Replace ReactNoop.render with createRoot + root.render
    
  972.     createRoot(options?: CreateRootOptions) {
    
  973.       const container = {
    
  974.         rootID: '' + idCounter++,
    
  975.         pendingChildren: [],
    
  976.         children: [],
    
  977.       };
    
  978.       const fiberRoot = NoopRenderer.createContainer(
    
  979.         container,
    
  980.         ConcurrentRoot,
    
  981.         null,
    
  982.         null,
    
  983.         false,
    
  984.         '',
    
  985.         onRecoverableError,
    
  986.         options && options.unstable_transitionCallbacks
    
  987.           ? options.unstable_transitionCallbacks
    
  988.           : null,
    
  989.       );
    
  990.       return {
    
  991.         _Scheduler: Scheduler,
    
  992.         render(children: ReactNodeList) {
    
  993.           NoopRenderer.updateContainer(children, fiberRoot, null, null);
    
  994.         },
    
  995.         getChildren() {
    
  996.           return getChildren(container);
    
  997.         },
    
  998.         getChildrenAsJSX() {
    
  999.           return getChildrenAsJSX(container);
    
  1000.         },
    
  1001.       };
    
  1002.     },
    
  1003. 
    
  1004.     createLegacyRoot() {
    
  1005.       const container = {
    
  1006.         rootID: '' + idCounter++,
    
  1007.         pendingChildren: [],
    
  1008.         children: [],
    
  1009.       };
    
  1010.       const fiberRoot = NoopRenderer.createContainer(
    
  1011.         container,
    
  1012.         LegacyRoot,
    
  1013.         null,
    
  1014.         null,
    
  1015.         false,
    
  1016.         '',
    
  1017.         onRecoverableError,
    
  1018.         null,
    
  1019.       );
    
  1020.       return {
    
  1021.         _Scheduler: Scheduler,
    
  1022.         render(children: ReactNodeList) {
    
  1023.           NoopRenderer.updateContainer(children, fiberRoot, null, null);
    
  1024.         },
    
  1025.         getChildren() {
    
  1026.           return getChildren(container);
    
  1027.         },
    
  1028.         getChildrenAsJSX() {
    
  1029.           return getChildrenAsJSX(container);
    
  1030.         },
    
  1031.       };
    
  1032.     },
    
  1033. 
    
  1034.     getChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
    
  1035.       const container = rootContainers.get(rootID);
    
  1036.       return getChildrenAsJSX(container);
    
  1037.     },
    
  1038. 
    
  1039.     getPendingChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
    
  1040.       const container = rootContainers.get(rootID);
    
  1041.       return getPendingChildrenAsJSX(container);
    
  1042.     },
    
  1043. 
    
  1044.     getSuspenseyThingStatus(src): string | null {
    
  1045.       if (suspenseyThingCache === null) {
    
  1046.         return null;
    
  1047.       } else {
    
  1048.         const record = suspenseyThingCache.get(src);
    
  1049.         return record === undefined ? null : record.status;
    
  1050.       }
    
  1051.     },
    
  1052. 
    
  1053.     resolveSuspenseyThing(key: string): void {
    
  1054.       if (suspenseyThingCache === null) {
    
  1055.         suspenseyThingCache = new Map();
    
  1056.       }
    
  1057.       const record = suspenseyThingCache.get(key);
    
  1058.       if (record === undefined) {
    
  1059.         const newRecord: SuspenseyThingRecord = {
    
  1060.           status: 'fulfilled',
    
  1061.           subscriptions: null,
    
  1062.         };
    
  1063.         suspenseyThingCache.set(key, newRecord);
    
  1064.       } else {
    
  1065.         if (record.status === 'pending') {
    
  1066.           record.status = 'fulfilled';
    
  1067.           const subscriptions = record.subscriptions;
    
  1068.           if (subscriptions !== null) {
    
  1069.             record.subscriptions = null;
    
  1070.             for (let i = 0; i < subscriptions.length; i++) {
    
  1071.               const subscription = subscriptions[i];
    
  1072.               subscription.pendingCount--;
    
  1073.               if (subscription.pendingCount === 0) {
    
  1074.                 const commit = subscription.commit;
    
  1075.                 subscription.commit = null;
    
  1076.                 commit();
    
  1077.               }
    
  1078.             }
    
  1079.           }
    
  1080.         }
    
  1081.       }
    
  1082.     },
    
  1083. 
    
  1084.     resetSuspenseyThingCache() {
    
  1085.       suspenseyThingCache = null;
    
  1086.     },
    
  1087. 
    
  1088.     createPortal(
    
  1089.       children: ReactNodeList,
    
  1090.       container: Container,
    
  1091.       key: ?string = null,
    
  1092.     ) {
    
  1093.       return NoopRenderer.createPortal(children, container, null, key);
    
  1094.     },
    
  1095. 
    
  1096.     // Shortcut for testing a single root
    
  1097.     render(element: React$Element<any>, callback: ?Function) {
    
  1098.       ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
    
  1099.     },
    
  1100. 
    
  1101.     renderLegacySyncRoot(element: React$Element<any>, callback: ?Function) {
    
  1102.       const rootID = DEFAULT_ROOT_ID;
    
  1103.       const container = ReactNoop.getOrCreateRootContainer(rootID, LegacyRoot);
    
  1104.       const root = roots.get(container.rootID);
    
  1105.       NoopRenderer.updateContainer(element, root, null, callback);
    
  1106.     },
    
  1107. 
    
  1108.     renderToRootWithID(
    
  1109.       element: React$Element<any>,
    
  1110.       rootID: string,
    
  1111.       callback: ?Function,
    
  1112.     ) {
    
  1113.       const container = ReactNoop.getOrCreateRootContainer(
    
  1114.         rootID,
    
  1115.         ConcurrentRoot,
    
  1116.       );
    
  1117.       const root = roots.get(container.rootID);
    
  1118.       NoopRenderer.updateContainer(element, root, null, callback);
    
  1119.     },
    
  1120. 
    
  1121.     unmountRootWithID(rootID: string) {
    
  1122.       const root = roots.get(rootID);
    
  1123.       if (root) {
    
  1124.         NoopRenderer.updateContainer(null, root, null, () => {
    
  1125.           roots.delete(rootID);
    
  1126.           rootContainers.delete(rootID);
    
  1127.         });
    
  1128.       }
    
  1129.     },
    
  1130. 
    
  1131.     findInstance(
    
  1132.       componentOrElement: Element | ?React$Component<any, any>,
    
  1133.     ): null | Instance | TextInstance {
    
  1134.       if (componentOrElement == null) {
    
  1135.         return null;
    
  1136.       }
    
  1137.       // Unsound duck typing.
    
  1138.       const component = (componentOrElement: any);
    
  1139.       if (typeof component.id === 'number') {
    
  1140.         return component;
    
  1141.       }
    
  1142.       if (__DEV__) {
    
  1143.         return NoopRenderer.findHostInstanceWithWarning(
    
  1144.           component,
    
  1145.           'findInstance',
    
  1146.         );
    
  1147.       }
    
  1148.       return NoopRenderer.findHostInstance(component);
    
  1149.     },
    
  1150. 
    
  1151.     flushNextYield(): Array<mixed> {
    
  1152.       Scheduler.unstable_flushNumberOfYields(1);
    
  1153.       return Scheduler.unstable_clearLog();
    
  1154.     },
    
  1155. 
    
  1156.     startTrackingHostCounters(): void {
    
  1157.       hostUpdateCounter = 0;
    
  1158.       hostCloneCounter = 0;
    
  1159.     },
    
  1160. 
    
  1161.     stopTrackingHostCounters():
    
  1162.       | {
    
  1163.           hostUpdateCounter: number,
    
  1164.         }
    
  1165.       | {
    
  1166.           hostCloneCounter: number,
    
  1167.         } {
    
  1168.       const result = useMutation
    
  1169.         ? {
    
  1170.             hostUpdateCounter,
    
  1171.           }
    
  1172.         : {
    
  1173.             hostCloneCounter,
    
  1174.           };
    
  1175.       hostUpdateCounter = 0;
    
  1176.       hostCloneCounter = 0;
    
  1177. 
    
  1178.       return result;
    
  1179.     },
    
  1180. 
    
  1181.     expire: Scheduler.unstable_advanceTime,
    
  1182. 
    
  1183.     flushExpired(): Array<mixed> {
    
  1184.       return Scheduler.unstable_flushExpired();
    
  1185.     },
    
  1186. 
    
  1187.     unstable_runWithPriority: NoopRenderer.runWithPriority,
    
  1188. 
    
  1189.     batchedUpdates: NoopRenderer.batchedUpdates,
    
  1190. 
    
  1191.     deferredUpdates: NoopRenderer.deferredUpdates,
    
  1192. 
    
  1193.     discreteUpdates: NoopRenderer.discreteUpdates,
    
  1194. 
    
  1195.     idleUpdates<T>(fn: () => T): T {
    
  1196.       const prevEventPriority = currentEventPriority;
    
  1197.       currentEventPriority = IdleEventPriority;
    
  1198.       try {
    
  1199.         fn();
    
  1200.       } finally {
    
  1201.         currentEventPriority = prevEventPriority;
    
  1202.       }
    
  1203.     },
    
  1204. 
    
  1205.     flushSync,
    
  1206.     flushPassiveEffects: NoopRenderer.flushPassiveEffects,
    
  1207. 
    
  1208.     // Logs the current state of the tree.
    
  1209.     dumpTree(rootID: string = DEFAULT_ROOT_ID) {
    
  1210.       const root = roots.get(rootID);
    
  1211.       const rootContainer = rootContainers.get(rootID);
    
  1212.       if (!root || !rootContainer) {
    
  1213.         // eslint-disable-next-line react-internal/no-production-logging
    
  1214.         console.log('Nothing rendered yet.');
    
  1215.         return;
    
  1216.       }
    
  1217. 
    
  1218.       const bufferedLog = [];
    
  1219.       function log(...args) {
    
  1220.         bufferedLog.push(...args, '\n');
    
  1221.       }
    
  1222. 
    
  1223.       function logHostInstances(
    
  1224.         children: Array<Instance | TextInstance>,
    
  1225.         depth,
    
  1226.       ) {
    
  1227.         for (let i = 0; i < children.length; i++) {
    
  1228.           const child = children[i];
    
  1229.           const indent = '  '.repeat(depth);
    
  1230.           if (typeof child.text === 'string') {
    
  1231.             log(indent + '- ' + child.text);
    
  1232.           } else {
    
  1233.             log(indent + '- ' + child.type + '#' + child.id);
    
  1234.             logHostInstances(child.children, depth + 1);
    
  1235.           }
    
  1236.         }
    
  1237.       }
    
  1238.       function logContainer(container: Container, depth) {
    
  1239.         log('  '.repeat(depth) + '- [root#' + container.rootID + ']');
    
  1240.         logHostInstances(container.children, depth + 1);
    
  1241.       }
    
  1242. 
    
  1243.       function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
    
  1244.         log('  '.repeat(depth + 1) + 'QUEUED UPDATES');
    
  1245.         const first = updateQueue.firstBaseUpdate;
    
  1246.         const update = first;
    
  1247.         if (update !== null) {
    
  1248.           do {
    
  1249.             log(
    
  1250.               '  '.repeat(depth + 1) + '~',
    
  1251.               '[' + update.expirationTime + ']',
    
  1252.             );
    
  1253.           } while (update !== null);
    
  1254.         }
    
  1255. 
    
  1256.         const lastPending = updateQueue.shared.pending;
    
  1257.         if (lastPending !== null) {
    
  1258.           const firstPending = lastPending.next;
    
  1259.           const pendingUpdate = firstPending;
    
  1260.           if (pendingUpdate !== null) {
    
  1261.             do {
    
  1262.               log(
    
  1263.                 '  '.repeat(depth + 1) + '~',
    
  1264.                 '[' + pendingUpdate.expirationTime + ']',
    
  1265.               );
    
  1266.             } while (pendingUpdate !== null && pendingUpdate !== firstPending);
    
  1267.           }
    
  1268.         }
    
  1269.       }
    
  1270. 
    
  1271.       function logFiber(fiber: Fiber, depth) {
    
  1272.         log(
    
  1273.           '  '.repeat(depth) +
    
  1274.             '- ' +
    
  1275.             // need to explicitly coerce Symbol to a string
    
  1276.             (fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
    
  1277.           '[' +
    
  1278.             fiber.childExpirationTime +
    
  1279.             (fiber.pendingProps ? '*' : '') +
    
  1280.             ']',
    
  1281.         );
    
  1282.         if (fiber.updateQueue) {
    
  1283.           logUpdateQueue(fiber.updateQueue, depth);
    
  1284.         }
    
  1285.         // const childInProgress = fiber.progressedChild;
    
  1286.         // if (childInProgress && childInProgress !== fiber.child) {
    
  1287.         //   log(
    
  1288.         //     '  '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
    
  1289.         //   );
    
  1290.         //   logFiber(childInProgress, depth + 1);
    
  1291.         //   if (fiber.child) {
    
  1292.         //     log('  '.repeat(depth + 1) + 'CURRENT');
    
  1293.         //   }
    
  1294.         // } else if (fiber.child && fiber.updateQueue) {
    
  1295.         //   log('  '.repeat(depth + 1) + 'CHILDREN');
    
  1296.         // }
    
  1297.         if (fiber.child) {
    
  1298.           logFiber(fiber.child, depth + 1);
    
  1299.         }
    
  1300.         if (fiber.sibling) {
    
  1301.           logFiber(fiber.sibling, depth);
    
  1302.         }
    
  1303.       }
    
  1304. 
    
  1305.       log('HOST INSTANCES:');
    
  1306.       logContainer(rootContainer, 0);
    
  1307.       log('FIBERS:');
    
  1308.       logFiber(root.current, 0);
    
  1309. 
    
  1310.       // eslint-disable-next-line react-internal/no-production-logging
    
  1311.       console.log(...bufferedLog);
    
  1312.     },
    
  1313. 
    
  1314.     getRoot(rootID: string = DEFAULT_ROOT_ID) {
    
  1315.       return roots.get(rootID);
    
  1316.     },
    
  1317.   };
    
  1318. 
    
  1319.   return ReactNoop;
    
  1320. }
    
  1321. 
    
  1322. export default createReactNoop;