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. let React;
    
  11. let ReactNoop;
    
  12. let Scheduler;
    
  13. let act;
    
  14. let Suspense;
    
  15. let getCacheForType;
    
  16. let caches;
    
  17. let seededCache;
    
  18. let ErrorBoundary;
    
  19. let waitForAll;
    
  20. let waitFor;
    
  21. let assertLog;
    
  22. 
    
  23. // TODO: These tests don't pass in persistent mode yet. Need to implement.
    
  24. 
    
  25. describe('ReactSuspenseEffectsSemantics', () => {
    
  26.   beforeEach(() => {
    
  27.     jest.resetModules();
    
  28. 
    
  29.     React = require('react');
    
  30.     ReactNoop = require('react-noop-renderer');
    
  31.     Scheduler = require('scheduler');
    
  32.     act = require('internal-test-utils').act;
    
  33.     Suspense = React.Suspense;
    
  34. 
    
  35.     getCacheForType = React.unstable_getCacheForType;
    
  36. 
    
  37.     const InternalTestUtils = require('internal-test-utils');
    
  38.     waitForAll = InternalTestUtils.waitForAll;
    
  39.     waitFor = InternalTestUtils.waitFor;
    
  40.     assertLog = InternalTestUtils.assertLog;
    
  41. 
    
  42.     caches = [];
    
  43.     seededCache = null;
    
  44. 
    
  45.     ErrorBoundary = class extends React.Component {
    
  46.       state = {error: null};
    
  47.       componentDidCatch(error) {
    
  48.         this.setState({error});
    
  49.       }
    
  50.       render() {
    
  51.         if (this.state.error) {
    
  52.           Scheduler.log('ErrorBoundary render: catch');
    
  53.           return this.props.fallback;
    
  54.         }
    
  55.         Scheduler.log('ErrorBoundary render: try');
    
  56.         return this.props.children;
    
  57.       }
    
  58.     };
    
  59.   });
    
  60. 
    
  61.   function createTextCache() {
    
  62.     if (seededCache !== null) {
    
  63.       // Trick to seed a cache before it exists.
    
  64.       // TODO: Need a built-in API to seed data before the initial render (i.e.
    
  65.       // not a refresh because nothing has mounted yet).
    
  66.       const cache = seededCache;
    
  67.       seededCache = null;
    
  68.       return cache;
    
  69.     }
    
  70. 
    
  71.     const data = new Map();
    
  72.     const version = caches.length + 1;
    
  73.     const cache = {
    
  74.       version,
    
  75.       data,
    
  76.       resolve(text) {
    
  77.         const record = data.get(text);
    
  78.         if (record === undefined) {
    
  79.           const newRecord = {
    
  80.             status: 'resolved',
    
  81.             value: text,
    
  82.           };
    
  83.           data.set(text, newRecord);
    
  84.         } else if (record.status === 'pending') {
    
  85.           const thenable = record.value;
    
  86.           record.status = 'resolved';
    
  87.           record.value = text;
    
  88.           thenable.pings.forEach(t => t());
    
  89.         }
    
  90.       },
    
  91.       reject(text, error) {
    
  92.         const record = data.get(text);
    
  93.         if (record === undefined) {
    
  94.           const newRecord = {
    
  95.             status: 'rejected',
    
  96.             value: error,
    
  97.           };
    
  98.           data.set(text, newRecord);
    
  99.         } else if (record.status === 'pending') {
    
  100.           const thenable = record.value;
    
  101.           record.status = 'rejected';
    
  102.           record.value = error;
    
  103.           thenable.pings.forEach(t => t());
    
  104.         }
    
  105.       },
    
  106.     };
    
  107.     caches.push(cache);
    
  108.     return cache;
    
  109.   }
    
  110. 
    
  111.   function readText(text) {
    
  112.     const textCache = getCacheForType(createTextCache);
    
  113.     const record = textCache.data.get(text);
    
  114.     if (record !== undefined) {
    
  115.       switch (record.status) {
    
  116.         case 'pending':
    
  117.           Scheduler.log(`Suspend:${text}`);
    
  118.           throw record.value;
    
  119.         case 'rejected':
    
  120.           Scheduler.log(`Error:${text}`);
    
  121.           throw record.value;
    
  122.         case 'resolved':
    
  123.           return textCache.version;
    
  124.       }
    
  125.     } else {
    
  126.       Scheduler.log(`Suspend:${text}`);
    
  127. 
    
  128.       const thenable = {
    
  129.         pings: [],
    
  130.         then(resolve) {
    
  131.           if (newRecord.status === 'pending') {
    
  132.             thenable.pings.push(resolve);
    
  133.           } else {
    
  134.             Promise.resolve().then(() => resolve(newRecord.value));
    
  135.           }
    
  136.         },
    
  137.       };
    
  138. 
    
  139.       const newRecord = {
    
  140.         status: 'pending',
    
  141.         value: thenable,
    
  142.       };
    
  143.       textCache.data.set(text, newRecord);
    
  144. 
    
  145.       throw thenable;
    
  146.     }
    
  147.   }
    
  148. 
    
  149.   function Text({children = null, text}) {
    
  150.     Scheduler.log(`Text:${text} render`);
    
  151.     React.useLayoutEffect(() => {
    
  152.       Scheduler.log(`Text:${text} create layout`);
    
  153.       return () => {
    
  154.         Scheduler.log(`Text:${text} destroy layout`);
    
  155.       };
    
  156.     }, []);
    
  157.     React.useEffect(() => {
    
  158.       Scheduler.log(`Text:${text} create passive`);
    
  159.       return () => {
    
  160.         Scheduler.log(`Text:${text} destroy passive`);
    
  161.       };
    
  162.     }, []);
    
  163.     return <span prop={text}>{children}</span>;
    
  164.   }
    
  165. 
    
  166.   function AsyncText({children = null, text}) {
    
  167.     readText(text);
    
  168.     Scheduler.log(`AsyncText:${text} render`);
    
  169.     React.useLayoutEffect(() => {
    
  170.       Scheduler.log(`AsyncText:${text} create layout`);
    
  171.       return () => {
    
  172.         Scheduler.log(`AsyncText:${text} destroy layout`);
    
  173.       };
    
  174.     }, []);
    
  175.     React.useEffect(() => {
    
  176.       Scheduler.log(`AsyncText:${text} create passive`);
    
  177.       return () => {
    
  178.         Scheduler.log(`AsyncText:${text} destroy passive`);
    
  179.       };
    
  180.     }, []);
    
  181.     return <span prop={text}>{children}</span>;
    
  182.   }
    
  183. 
    
  184.   function resolveMostRecentTextCache(text) {
    
  185.     if (caches.length === 0) {
    
  186.       throw Error('Cache does not exist.');
    
  187.     } else {
    
  188.       // Resolve the most recently created cache. An older cache can by
    
  189.       // resolved with `caches[index].resolve(text)`.
    
  190.       caches[caches.length - 1].resolve(text);
    
  191.     }
    
  192.   }
    
  193. 
    
  194.   const resolveText = resolveMostRecentTextCache;
    
  195. 
    
  196.   function advanceTimers(ms) {
    
  197.     // Note: This advances Jest's virtual time but not React's. Use
    
  198.     // ReactNoop.expire for that.
    
  199.     if (typeof ms !== 'number') {
    
  200.       throw new Error('Must specify ms');
    
  201.     }
    
  202.     jest.advanceTimersByTime(ms);
    
  203.     // Wait until the end of the current tick
    
  204.     // We cannot use a timer since we're faking them
    
  205.     return Promise.resolve().then(() => {});
    
  206.   }
    
  207. 
    
  208.   describe('when a component suspends during initial mount', () => {
    
  209.     // @gate enableLegacyCache
    
  210.     it('should not change behavior in concurrent mode', async () => {
    
  211.       class ClassText extends React.Component {
    
  212.         componentDidMount() {
    
  213.           const {text} = this.props;
    
  214.           Scheduler.log(`ClassText:${text} componentDidMount`);
    
  215.         }
    
  216.         componentDidUpdate() {
    
  217.           const {text} = this.props;
    
  218.           Scheduler.log(`ClassText:${text} componentDidUpdate`);
    
  219.         }
    
  220.         componentWillUnmount() {
    
  221.           const {text} = this.props;
    
  222.           Scheduler.log(`ClassText:${text} componentWillUnmount`);
    
  223.         }
    
  224.         render() {
    
  225.           const {children, text} = this.props;
    
  226.           Scheduler.log(`ClassText:${text} render`);
    
  227.           return <span prop={text}>{children}</span>;
    
  228.         }
    
  229.       }
    
  230. 
    
  231.       function App({children = null}) {
    
  232.         Scheduler.log('App render');
    
  233.         React.useLayoutEffect(() => {
    
  234.           Scheduler.log('App create layout');
    
  235.           return () => {
    
  236.             Scheduler.log('App destroy layout');
    
  237.           };
    
  238.         }, []);
    
  239.         React.useEffect(() => {
    
  240.           Scheduler.log('App create passive');
    
  241.           return () => {
    
  242.             Scheduler.log('App destroy passive');
    
  243.           };
    
  244.         }, []);
    
  245.         return (
    
  246.           <>
    
  247.             <Suspense fallback={<Text text="Fallback" />}>
    
  248.               <Text text="Inside:Before" />
    
  249.               {children}
    
  250.               <ClassText text="Inside:After" />
    
  251.             </Suspense>
    
  252.             <Text text="Outside" />
    
  253.           </>
    
  254.         );
    
  255.       }
    
  256. 
    
  257.       // Mount and suspend.
    
  258.       await act(() => {
    
  259.         ReactNoop.render(
    
  260.           <App>
    
  261.             <AsyncText text="Async" ms={1000} />
    
  262.           </App>,
    
  263.         );
    
  264.       });
    
  265.       assertLog([
    
  266.         'App render',
    
  267.         'Text:Inside:Before render',
    
  268.         'Suspend:Async',
    
  269.         'Text:Fallback render',
    
  270.         'Text:Outside render',
    
  271.         'Text:Fallback create layout',
    
  272.         'Text:Outside create layout',
    
  273.         'App create layout',
    
  274.         'Text:Fallback create passive',
    
  275.         'Text:Outside create passive',
    
  276.         'App create passive',
    
  277.       ]);
    
  278.       expect(ReactNoop).toMatchRenderedOutput(
    
  279.         <>
    
  280.           <span prop="Fallback" />
    
  281.           <span prop="Outside" />
    
  282.         </>,
    
  283.       );
    
  284. 
    
  285.       // Resolving the suspended resource should
    
  286.       await act(async () => {
    
  287.         await resolveText('Async');
    
  288.       });
    
  289.       assertLog([
    
  290.         'Text:Inside:Before render',
    
  291.         'AsyncText:Async render',
    
  292.         'ClassText:Inside:After render',
    
  293.         'Text:Fallback destroy layout',
    
  294.         'Text:Inside:Before create layout',
    
  295.         'AsyncText:Async create layout',
    
  296.         'ClassText:Inside:After componentDidMount',
    
  297.         'Text:Fallback destroy passive',
    
  298.         'Text:Inside:Before create passive',
    
  299.         'AsyncText:Async create passive',
    
  300.       ]);
    
  301.       expect(ReactNoop).toMatchRenderedOutput(
    
  302.         <>
    
  303.           <span prop="Inside:Before" />
    
  304.           <span prop="Async" />
    
  305.           <span prop="Inside:After" />
    
  306.           <span prop="Outside" />
    
  307.         </>,
    
  308.       );
    
  309. 
    
  310.       await act(() => {
    
  311.         ReactNoop.render(null);
    
  312.       });
    
  313.       assertLog([
    
  314.         'App destroy layout',
    
  315.         'Text:Inside:Before destroy layout',
    
  316.         'AsyncText:Async destroy layout',
    
  317.         'ClassText:Inside:After componentWillUnmount',
    
  318.         'Text:Outside destroy layout',
    
  319.         'App destroy passive',
    
  320.         'Text:Inside:Before destroy passive',
    
  321.         'AsyncText:Async destroy passive',
    
  322.         'Text:Outside destroy passive',
    
  323.       ]);
    
  324.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  325.     });
    
  326. 
    
  327.     // @gate enableLegacyCache
    
  328.     it('should not change behavior in sync', async () => {
    
  329.       class ClassText extends React.Component {
    
  330.         componentDidMount() {
    
  331.           const {text} = this.props;
    
  332.           Scheduler.log(`ClassText:${text} componentDidMount`);
    
  333.         }
    
  334.         componentDidUpdate() {
    
  335.           const {text} = this.props;
    
  336.           Scheduler.log(`ClassText:${text} componentDidUpdate`);
    
  337.         }
    
  338.         componentWillUnmount() {
    
  339.           const {text} = this.props;
    
  340.           Scheduler.log(`ClassText:${text} componentWillUnmount`);
    
  341.         }
    
  342.         render() {
    
  343.           const {children, text} = this.props;
    
  344.           Scheduler.log(`ClassText:${text} render`);
    
  345.           return <span prop={text}>{children}</span>;
    
  346.         }
    
  347.       }
    
  348. 
    
  349.       function App({children = null}) {
    
  350.         Scheduler.log('App render');
    
  351.         React.useLayoutEffect(() => {
    
  352.           Scheduler.log('App create layout');
    
  353.           return () => {
    
  354.             Scheduler.log('App destroy layout');
    
  355.           };
    
  356.         }, []);
    
  357.         React.useEffect(() => {
    
  358.           Scheduler.log('App create passive');
    
  359.           return () => {
    
  360.             Scheduler.log('App destroy passive');
    
  361.           };
    
  362.         }, []);
    
  363.         return (
    
  364.           <>
    
  365.             <Suspense fallback={<Text text="Fallback" />}>
    
  366.               <Text text="Inside:Before" />
    
  367.               {children}
    
  368.               <ClassText text="Inside:After" />
    
  369.             </Suspense>
    
  370.             <Text text="Outside" />
    
  371.           </>
    
  372.         );
    
  373.       }
    
  374. 
    
  375.       // Mount and suspend.
    
  376.       await act(() => {
    
  377.         ReactNoop.renderLegacySyncRoot(
    
  378.           <App>
    
  379.             <AsyncText text="Async" ms={1000} />
    
  380.           </App>,
    
  381.         );
    
  382.       });
    
  383.       assertLog([
    
  384.         'App render',
    
  385.         'Text:Inside:Before render',
    
  386.         'Suspend:Async',
    
  387.         'ClassText:Inside:After render',
    
  388.         'Text:Fallback render',
    
  389.         'Text:Outside render',
    
  390.         'Text:Inside:Before create layout',
    
  391.         'ClassText:Inside:After componentDidMount',
    
  392.         'Text:Fallback create layout',
    
  393.         'Text:Outside create layout',
    
  394.         'App create layout',
    
  395.         'Text:Inside:Before create passive',
    
  396.         'Text:Fallback create passive',
    
  397.         'Text:Outside create passive',
    
  398.         'App create passive',
    
  399.       ]);
    
  400.       expect(ReactNoop).toMatchRenderedOutput(
    
  401.         <>
    
  402.           <span prop="Inside:Before" hidden={true} />
    
  403.           <span prop="Inside:After" hidden={true} />
    
  404.           <span prop="Fallback" />
    
  405.           <span prop="Outside" />
    
  406.         </>,
    
  407.       );
    
  408. 
    
  409.       // Resolving the suspended resource should
    
  410.       await act(async () => {
    
  411.         await resolveText('Async');
    
  412.       });
    
  413.       assertLog([
    
  414.         'AsyncText:Async render',
    
  415.         'Text:Fallback destroy layout',
    
  416.         'AsyncText:Async create layout',
    
  417.         'Text:Fallback destroy passive',
    
  418.         'AsyncText:Async create passive',
    
  419.       ]);
    
  420.       expect(ReactNoop).toMatchRenderedOutput(
    
  421.         <>
    
  422.           <span prop="Inside:Before" />
    
  423.           <span prop="Async" />
    
  424.           <span prop="Inside:After" />
    
  425.           <span prop="Outside" />
    
  426.         </>,
    
  427.       );
    
  428. 
    
  429.       await act(() => {
    
  430.         ReactNoop.renderLegacySyncRoot(null);
    
  431.       });
    
  432.       assertLog([
    
  433.         'App destroy layout',
    
  434.         'Text:Inside:Before destroy layout',
    
  435.         'AsyncText:Async destroy layout',
    
  436.         'ClassText:Inside:After componentWillUnmount',
    
  437.         'Text:Outside destroy layout',
    
  438.         'App destroy passive',
    
  439.         'Text:Inside:Before destroy passive',
    
  440.         'AsyncText:Async destroy passive',
    
  441.         'Text:Outside destroy passive',
    
  442.       ]);
    
  443.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  444.     });
    
  445.   });
    
  446. 
    
  447.   describe('layout effects within a tree that re-suspends in an update', () => {
    
  448.     // @gate enableLegacyCache
    
  449.     it('should not be destroyed or recreated in legacy roots', async () => {
    
  450.       function App({children = null}) {
    
  451.         Scheduler.log('App render');
    
  452.         React.useLayoutEffect(() => {
    
  453.           Scheduler.log('App create layout');
    
  454.           return () => {
    
  455.             Scheduler.log('App destroy layout');
    
  456.           };
    
  457.         }, []);
    
  458.         React.useEffect(() => {
    
  459.           Scheduler.log('App create passive');
    
  460.           return () => {
    
  461.             Scheduler.log('App destroy passive');
    
  462.           };
    
  463.         }, []);
    
  464.         return (
    
  465.           <>
    
  466.             <Suspense fallback={<Text text="Fallback" />}>
    
  467.               <Text text="Inside:Before" />
    
  468.               {children}
    
  469.               <Text text="Inside:After" />
    
  470.             </Suspense>
    
  471.             <Text text="Outside" />
    
  472.           </>
    
  473.         );
    
  474.       }
    
  475. 
    
  476.       // Mount
    
  477.       await act(() => {
    
  478.         ReactNoop.renderLegacySyncRoot(<App />);
    
  479.       });
    
  480.       assertLog([
    
  481.         'App render',
    
  482.         'Text:Inside:Before render',
    
  483.         'Text:Inside:After render',
    
  484.         'Text:Outside render',
    
  485.         'Text:Inside:Before create layout',
    
  486.         'Text:Inside:After create layout',
    
  487.         'Text:Outside create layout',
    
  488.         'App create layout',
    
  489.         'Text:Inside:Before create passive',
    
  490.         'Text:Inside:After create passive',
    
  491.         'Text:Outside create passive',
    
  492.         'App create passive',
    
  493.       ]);
    
  494.       expect(ReactNoop).toMatchRenderedOutput(
    
  495.         <>
    
  496.           <span prop="Inside:Before" />
    
  497.           <span prop="Inside:After" />
    
  498.           <span prop="Outside" />
    
  499.         </>,
    
  500.       );
    
  501. 
    
  502.       // Schedule an update that causes React to suspend.
    
  503.       await act(() => {
    
  504.         ReactNoop.renderLegacySyncRoot(
    
  505.           <App>
    
  506.             <AsyncText text="Async" ms={1000} />
    
  507.           </App>,
    
  508.         );
    
  509.       });
    
  510.       assertLog([
    
  511.         'App render',
    
  512.         'Text:Inside:Before render',
    
  513.         'Suspend:Async',
    
  514.         'Text:Inside:After render',
    
  515.         'Text:Fallback render',
    
  516.         'Text:Outside render',
    
  517.         'Text:Fallback create layout',
    
  518.         'Text:Fallback create passive',
    
  519.       ]);
    
  520.       expect(ReactNoop).toMatchRenderedOutput(
    
  521.         <>
    
  522.           <span prop="Inside:Before" hidden={true} />
    
  523.           <span prop="Inside:After" hidden={true} />
    
  524.           <span prop="Fallback" />
    
  525.           <span prop="Outside" />
    
  526.         </>,
    
  527.       );
    
  528. 
    
  529.       await advanceTimers(1000);
    
  530. 
    
  531.       // Noop since sync root has already committed
    
  532.       assertLog([]);
    
  533.       expect(ReactNoop).toMatchRenderedOutput(
    
  534.         <>
    
  535.           <span prop="Inside:Before" hidden={true} />
    
  536.           <span prop="Inside:After" hidden={true} />
    
  537.           <span prop="Fallback" />
    
  538.           <span prop="Outside" />
    
  539.         </>,
    
  540.       );
    
  541. 
    
  542.       // Resolving the suspended resource should re-create inner layout effects.
    
  543.       await act(async () => {
    
  544.         await resolveText('Async');
    
  545.       });
    
  546.       assertLog([
    
  547.         'AsyncText:Async render',
    
  548.         'Text:Fallback destroy layout',
    
  549.         'AsyncText:Async create layout',
    
  550.         'Text:Fallback destroy passive',
    
  551.         'AsyncText:Async create passive',
    
  552.       ]);
    
  553.       expect(ReactNoop).toMatchRenderedOutput(
    
  554.         <>
    
  555.           <span prop="Inside:Before" />
    
  556.           <span prop="Async" />
    
  557.           <span prop="Inside:After" />
    
  558.           <span prop="Outside" />
    
  559.         </>,
    
  560.       );
    
  561. 
    
  562.       await act(() => {
    
  563.         ReactNoop.renderLegacySyncRoot(null);
    
  564.       });
    
  565.       assertLog([
    
  566.         'App destroy layout',
    
  567.         'Text:Inside:Before destroy layout',
    
  568.         'AsyncText:Async destroy layout',
    
  569.         'Text:Inside:After destroy layout',
    
  570.         'Text:Outside destroy layout',
    
  571.         'App destroy passive',
    
  572.         'Text:Inside:Before destroy passive',
    
  573.         'AsyncText:Async destroy passive',
    
  574.         'Text:Inside:After destroy passive',
    
  575.         'Text:Outside destroy passive',
    
  576.       ]);
    
  577.     });
    
  578. 
    
  579.     // @gate enableLegacyCache
    
  580.     it('should be destroyed and recreated for function components', async () => {
    
  581.       function App({children = null}) {
    
  582.         Scheduler.log('App render');
    
  583.         React.useLayoutEffect(() => {
    
  584.           Scheduler.log('App create layout');
    
  585.           return () => {
    
  586.             Scheduler.log('App destroy layout');
    
  587.           };
    
  588.         }, []);
    
  589.         React.useEffect(() => {
    
  590.           Scheduler.log('App create passive');
    
  591.           return () => {
    
  592.             Scheduler.log('App destroy passive');
    
  593.           };
    
  594.         }, []);
    
  595.         return (
    
  596.           <>
    
  597.             <Suspense fallback={<Text text="Fallback" />}>
    
  598.               <Text text="Inside:Before" />
    
  599.               {children}
    
  600.               <Text text="Inside:After" />
    
  601.             </Suspense>
    
  602.             <Text text="Outside" />
    
  603.           </>
    
  604.         );
    
  605.       }
    
  606. 
    
  607.       await act(() => {
    
  608.         ReactNoop.render(<App />);
    
  609.       });
    
  610.       assertLog([
    
  611.         'App render',
    
  612.         'Text:Inside:Before render',
    
  613.         'Text:Inside:After render',
    
  614.         'Text:Outside render',
    
  615.         'Text:Inside:Before create layout',
    
  616.         'Text:Inside:After create layout',
    
  617.         'Text:Outside create layout',
    
  618.         'App create layout',
    
  619.         'Text:Inside:Before create passive',
    
  620.         'Text:Inside:After create passive',
    
  621.         'Text:Outside create passive',
    
  622.         'App create passive',
    
  623.       ]);
    
  624.       expect(ReactNoop).toMatchRenderedOutput(
    
  625.         <>
    
  626.           <span prop="Inside:Before" />
    
  627.           <span prop="Inside:After" />
    
  628.           <span prop="Outside" />
    
  629.         </>,
    
  630.       );
    
  631. 
    
  632.       // Schedule an update that causes React to suspend.
    
  633.       await act(async () => {
    
  634.         ReactNoop.render(
    
  635.           <App>
    
  636.             <AsyncText text="Async" ms={1000} />
    
  637.           </App>,
    
  638.         );
    
  639.         await waitFor([
    
  640.           'App render',
    
  641.           'Text:Inside:Before render',
    
  642.           'Suspend:Async',
    
  643.           'Text:Fallback render',
    
  644.           'Text:Outside render',
    
  645.           'Text:Inside:Before destroy layout',
    
  646.           'Text:Inside:After destroy layout',
    
  647.           'Text:Fallback create layout',
    
  648.         ]);
    
  649.         await waitForAll(['Text:Fallback create passive']);
    
  650.         expect(ReactNoop).toMatchRenderedOutput(
    
  651.           <>
    
  652.             <span prop="Inside:Before" hidden={true} />
    
  653.             <span prop="Inside:After" hidden={true} />
    
  654.             <span prop="Fallback" />
    
  655.             <span prop="Outside" />
    
  656.           </>,
    
  657.         );
    
  658.       });
    
  659. 
    
  660.       // Resolving the suspended resource should re-create inner layout effects.
    
  661.       await act(async () => {
    
  662.         await resolveText('Async');
    
  663.       });
    
  664.       assertLog([
    
  665.         'Text:Inside:Before render',
    
  666.         'AsyncText:Async render',
    
  667.         'Text:Inside:After render',
    
  668.         'Text:Fallback destroy layout',
    
  669.         'Text:Inside:Before create layout',
    
  670.         'AsyncText:Async create layout',
    
  671.         'Text:Inside:After create layout',
    
  672.         'Text:Fallback destroy passive',
    
  673.         'AsyncText:Async create passive',
    
  674.       ]);
    
  675.       expect(ReactNoop).toMatchRenderedOutput(
    
  676.         <>
    
  677.           <span prop="Inside:Before" />
    
  678.           <span prop="Async" />
    
  679.           <span prop="Inside:After" />
    
  680.           <span prop="Outside" />
    
  681.         </>,
    
  682.       );
    
  683. 
    
  684.       await act(() => {
    
  685.         ReactNoop.render(null);
    
  686.       });
    
  687.       assertLog([
    
  688.         'App destroy layout',
    
  689.         'Text:Inside:Before destroy layout',
    
  690.         'AsyncText:Async destroy layout',
    
  691.         'Text:Inside:After destroy layout',
    
  692.         'Text:Outside destroy layout',
    
  693.         'App destroy passive',
    
  694.         'Text:Inside:Before destroy passive',
    
  695.         'AsyncText:Async destroy passive',
    
  696.         'Text:Inside:After destroy passive',
    
  697.         'Text:Outside destroy passive',
    
  698.       ]);
    
  699.     });
    
  700. 
    
  701.     // @gate enableLegacyCache
    
  702.     it('should be destroyed and recreated for class components', async () => {
    
  703.       class ClassText extends React.Component {
    
  704.         componentDidMount() {
    
  705.           const {text} = this.props;
    
  706.           Scheduler.log(`ClassText:${text} componentDidMount`);
    
  707.         }
    
  708.         componentDidUpdate() {
    
  709.           const {text} = this.props;
    
  710.           Scheduler.log(`ClassText:${text} componentDidUpdate`);
    
  711.         }
    
  712.         componentWillUnmount() {
    
  713.           const {text} = this.props;
    
  714.           Scheduler.log(`ClassText:${text} componentWillUnmount`);
    
  715.         }
    
  716.         render() {
    
  717.           const {children, text} = this.props;
    
  718.           Scheduler.log(`ClassText:${text} render`);
    
  719.           return <span prop={text}>{children}</span>;
    
  720.         }
    
  721.       }
    
  722. 
    
  723.       function App({children = null}) {
    
  724.         Scheduler.log('App render');
    
  725.         React.useLayoutEffect(() => {
    
  726.           Scheduler.log('App create layout');
    
  727.           return () => {
    
  728.             Scheduler.log('App destroy layout');
    
  729.           };
    
  730.         }, []);
    
  731.         React.useEffect(() => {
    
  732.           Scheduler.log('App create passive');
    
  733.           return () => {
    
  734.             Scheduler.log('App destroy passive');
    
  735.           };
    
  736.         }, []);
    
  737.         return (
    
  738.           <>
    
  739.             <Suspense fallback={<ClassText text="Fallback" />}>
    
  740.               <ClassText text="Inside:Before" />
    
  741.               {children}
    
  742.               <ClassText text="Inside:After" />
    
  743.             </Suspense>
    
  744.             <ClassText text="Outside" />
    
  745.           </>
    
  746.         );
    
  747.       }
    
  748. 
    
  749.       // Mount
    
  750.       await act(() => {
    
  751.         ReactNoop.render(<App />);
    
  752.       });
    
  753.       assertLog([
    
  754.         'App render',
    
  755.         'ClassText:Inside:Before render',
    
  756.         'ClassText:Inside:After render',
    
  757.         'ClassText:Outside render',
    
  758.         'ClassText:Inside:Before componentDidMount',
    
  759.         'ClassText:Inside:After componentDidMount',
    
  760.         'ClassText:Outside componentDidMount',
    
  761.         'App create layout',
    
  762.         'App create passive',
    
  763.       ]);
    
  764.       expect(ReactNoop).toMatchRenderedOutput(
    
  765.         <>
    
  766.           <span prop="Inside:Before" />
    
  767.           <span prop="Inside:After" />
    
  768.           <span prop="Outside" />
    
  769.         </>,
    
  770.       );
    
  771. 
    
  772.       // Schedule an update that causes React to suspend.
    
  773.       await act(async () => {
    
  774.         ReactNoop.render(
    
  775.           <App>
    
  776.             <AsyncText text="Async" ms={1000} />
    
  777.           </App>,
    
  778.         );
    
  779. 
    
  780.         await waitFor([
    
  781.           'App render',
    
  782.           'ClassText:Inside:Before render',
    
  783.           'Suspend:Async',
    
  784.           'ClassText:Fallback render',
    
  785.           'ClassText:Outside render',
    
  786.           'ClassText:Inside:Before componentWillUnmount',
    
  787.           'ClassText:Inside:After componentWillUnmount',
    
  788.           'ClassText:Fallback componentDidMount',
    
  789.           'ClassText:Outside componentDidUpdate',
    
  790.         ]);
    
  791.         expect(ReactNoop).toMatchRenderedOutput(
    
  792.           <>
    
  793.             <span prop="Inside:Before" hidden={true} />
    
  794.             <span prop="Inside:After" hidden={true} />
    
  795.             <span prop="Fallback" />
    
  796.             <span prop="Outside" />
    
  797.           </>,
    
  798.         );
    
  799.       });
    
  800. 
    
  801.       // Resolving the suspended resource should re-create inner layout effects.
    
  802.       await act(async () => {
    
  803.         await resolveText('Async');
    
  804.       });
    
  805.       assertLog([
    
  806.         'ClassText:Inside:Before render',
    
  807.         'AsyncText:Async render',
    
  808.         'ClassText:Inside:After render',
    
  809.         'ClassText:Fallback componentWillUnmount',
    
  810.         'ClassText:Inside:Before componentDidMount',
    
  811.         'AsyncText:Async create layout',
    
  812.         'ClassText:Inside:After componentDidMount',
    
  813.         'AsyncText:Async create passive',
    
  814.       ]);
    
  815.       expect(ReactNoop).toMatchRenderedOutput(
    
  816.         <>
    
  817.           <span prop="Inside:Before" />
    
  818.           <span prop="Async" />
    
  819.           <span prop="Inside:After" />
    
  820.           <span prop="Outside" />
    
  821.         </>,
    
  822.       );
    
  823.       await act(() => {
    
  824.         ReactNoop.render(null);
    
  825.       });
    
  826.       assertLog([
    
  827.         'App destroy layout',
    
  828.         'ClassText:Inside:Before componentWillUnmount',
    
  829.         'AsyncText:Async destroy layout',
    
  830.         'ClassText:Inside:After componentWillUnmount',
    
  831.         'ClassText:Outside componentWillUnmount',
    
  832.         'App destroy passive',
    
  833.         'AsyncText:Async destroy passive',
    
  834.       ]);
    
  835.     });
    
  836. 
    
  837.     // @gate enableLegacyCache
    
  838.     it('should be destroyed and recreated when nested below host components', async () => {
    
  839.       function App({children = null}) {
    
  840.         Scheduler.log('App render');
    
  841.         React.useLayoutEffect(() => {
    
  842.           Scheduler.log('App create layout');
    
  843.           return () => {
    
  844.             Scheduler.log('App destroy layout');
    
  845.           };
    
  846.         }, []);
    
  847.         React.useEffect(() => {
    
  848.           Scheduler.log('App create passive');
    
  849.           return () => {
    
  850.             Scheduler.log('App destroy passive');
    
  851.           };
    
  852.         }, []);
    
  853.         return (
    
  854.           <Suspense fallback={<Text text="Fallback" />}>
    
  855.             {children}
    
  856.             <Text text="Outer">
    
  857.               <Text text="Inner" />
    
  858.             </Text>
    
  859.           </Suspense>
    
  860.         );
    
  861.       }
    
  862. 
    
  863.       // Mount
    
  864.       await act(() => {
    
  865.         ReactNoop.render(<App />);
    
  866.       });
    
  867.       assertLog([
    
  868.         'App render',
    
  869.         'Text:Outer render',
    
  870.         'Text:Inner render',
    
  871.         'Text:Inner create layout',
    
  872.         'Text:Outer create layout',
    
  873.         'App create layout',
    
  874.         'Text:Inner create passive',
    
  875.         'Text:Outer create passive',
    
  876.         'App create passive',
    
  877.       ]);
    
  878.       expect(ReactNoop).toMatchRenderedOutput(
    
  879.         <span prop="Outer">
    
  880.           <span prop="Inner" />
    
  881.         </span>,
    
  882.       );
    
  883. 
    
  884.       // Schedule an update that causes React to suspend.
    
  885.       await act(async () => {
    
  886.         ReactNoop.render(
    
  887.           <App>
    
  888.             <AsyncText text="Async" ms={1000} />
    
  889.           </App>,
    
  890.         );
    
  891.         await waitFor([
    
  892.           'App render',
    
  893.           'Suspend:Async',
    
  894.           'Text:Fallback render',
    
  895.           'Text:Outer destroy layout',
    
  896.           'Text:Inner destroy layout',
    
  897.           'Text:Fallback create layout',
    
  898.         ]);
    
  899.         await waitForAll(['Text:Fallback create passive']);
    
  900.         expect(ReactNoop).toMatchRenderedOutput(
    
  901.           <>
    
  902.             <span hidden={true} prop="Outer">
    
  903.               <span prop="Inner" />
    
  904.             </span>
    
  905.             <span prop="Fallback" />
    
  906.           </>,
    
  907.         );
    
  908.       });
    
  909. 
    
  910.       // Resolving the suspended resource should re-create inner layout effects.
    
  911.       await act(async () => {
    
  912.         await resolveText('Async');
    
  913.       });
    
  914.       assertLog([
    
  915.         'AsyncText:Async render',
    
  916.         'Text:Outer render',
    
  917.         'Text:Inner render',
    
  918.         'Text:Fallback destroy layout',
    
  919.         'AsyncText:Async create layout',
    
  920.         'Text:Inner create layout',
    
  921.         'Text:Outer create layout',
    
  922.         'Text:Fallback destroy passive',
    
  923.         'AsyncText:Async create passive',
    
  924.       ]);
    
  925.       expect(ReactNoop).toMatchRenderedOutput(
    
  926.         <>
    
  927.           <span prop="Async" />
    
  928.           <span prop="Outer">
    
  929.             <span prop="Inner" />
    
  930.           </span>
    
  931.         </>,
    
  932.       );
    
  933. 
    
  934.       await act(() => {
    
  935.         ReactNoop.render(null);
    
  936.       });
    
  937.       assertLog([
    
  938.         'App destroy layout',
    
  939.         'AsyncText:Async destroy layout',
    
  940.         'Text:Outer destroy layout',
    
  941.         'Text:Inner destroy layout',
    
  942.         'App destroy passive',
    
  943.         'AsyncText:Async destroy passive',
    
  944.         'Text:Outer destroy passive',
    
  945.         'Text:Inner destroy passive',
    
  946.       ]);
    
  947.     });
    
  948. 
    
  949.     // @gate enableLegacyCache
    
  950.     it('should be destroyed and recreated even if there is a bailout because of memoization', async () => {
    
  951.       const MemoizedText = React.memo(Text, () => true);
    
  952. 
    
  953.       function App({children = null}) {
    
  954.         Scheduler.log('App render');
    
  955.         React.useLayoutEffect(() => {
    
  956.           Scheduler.log('App create layout');
    
  957.           return () => {
    
  958.             Scheduler.log('App destroy layout');
    
  959.           };
    
  960.         }, []);
    
  961.         React.useEffect(() => {
    
  962.           Scheduler.log('App create passive');
    
  963.           return () => {
    
  964.             Scheduler.log('App destroy passive');
    
  965.           };
    
  966.         }, []);
    
  967.         return (
    
  968.           <Suspense fallback={<Text text="Fallback" />}>
    
  969.             {children}
    
  970.             <Text text="Outer">
    
  971.               <MemoizedText text="MemoizedInner" />
    
  972.             </Text>
    
  973.           </Suspense>
    
  974.         );
    
  975.       }
    
  976. 
    
  977.       // Mount
    
  978.       await act(() => {
    
  979.         ReactNoop.render(<App />);
    
  980.       });
    
  981.       assertLog([
    
  982.         'App render',
    
  983.         'Text:Outer render',
    
  984.         'Text:MemoizedInner render',
    
  985.         'Text:MemoizedInner create layout',
    
  986.         'Text:Outer create layout',
    
  987.         'App create layout',
    
  988.         'Text:MemoizedInner create passive',
    
  989.         'Text:Outer create passive',
    
  990.         'App create passive',
    
  991.       ]);
    
  992.       expect(ReactNoop).toMatchRenderedOutput(
    
  993.         <span prop="Outer">
    
  994.           <span prop="MemoizedInner" />
    
  995.         </span>,
    
  996.       );
    
  997. 
    
  998.       // Schedule an update that causes React to suspend.
    
  999.       await act(async () => {
    
  1000.         ReactNoop.render(
    
  1001.           <App>
    
  1002.             <AsyncText text="Async" ms={1000} />
    
  1003.           </App>,
    
  1004.         );
    
  1005.         await waitFor([
    
  1006.           'App render',
    
  1007.           'Suspend:Async',
    
  1008.           // Text:MemoizedInner is memoized
    
  1009.           'Text:Fallback render',
    
  1010.           'Text:Outer destroy layout',
    
  1011.           'Text:MemoizedInner destroy layout',
    
  1012.           'Text:Fallback create layout',
    
  1013.         ]);
    
  1014.         await waitForAll(['Text:Fallback create passive']);
    
  1015.         expect(ReactNoop).toMatchRenderedOutput(
    
  1016.           <>
    
  1017.             <span hidden={true} prop="Outer">
    
  1018.               <span prop="MemoizedInner" />
    
  1019.             </span>
    
  1020.             <span prop="Fallback" />
    
  1021.           </>,
    
  1022.         );
    
  1023.       });
    
  1024. 
    
  1025.       // Resolving the suspended resource should re-create inner layout effects.
    
  1026.       await act(async () => {
    
  1027.         await resolveText('Async');
    
  1028.       });
    
  1029.       assertLog([
    
  1030.         'AsyncText:Async render',
    
  1031.         'Text:Outer render',
    
  1032.         'Text:Fallback destroy layout',
    
  1033.         'AsyncText:Async create layout',
    
  1034.         'Text:MemoizedInner create layout',
    
  1035.         'Text:Outer create layout',
    
  1036.         'Text:Fallback destroy passive',
    
  1037.         'AsyncText:Async create passive',
    
  1038.       ]);
    
  1039.       expect(ReactNoop).toMatchRenderedOutput(
    
  1040.         <>
    
  1041.           <span prop="Async" />
    
  1042.           <span prop="Outer">
    
  1043.             <span prop="MemoizedInner" />
    
  1044.           </span>
    
  1045.         </>,
    
  1046.       );
    
  1047. 
    
  1048.       await act(() => {
    
  1049.         ReactNoop.render(null);
    
  1050.       });
    
  1051.       assertLog([
    
  1052.         'App destroy layout',
    
  1053.         'AsyncText:Async destroy layout',
    
  1054.         'Text:Outer destroy layout',
    
  1055.         'Text:MemoizedInner destroy layout',
    
  1056.         'App destroy passive',
    
  1057.         'AsyncText:Async destroy passive',
    
  1058.         'Text:Outer destroy passive',
    
  1059.         'Text:MemoizedInner destroy passive',
    
  1060.       ]);
    
  1061.     });
    
  1062. 
    
  1063.     // @gate enableLegacyCache
    
  1064.     it('should respect nested suspense boundaries', async () => {
    
  1065.       function App({innerChildren = null, outerChildren = null}) {
    
  1066.         return (
    
  1067.           <Suspense fallback={<Text text="OuterFallback" />}>
    
  1068.             <Text text="Outer" />
    
  1069.             {outerChildren}
    
  1070.             <Suspense fallback={<Text text="InnerFallback" />}>
    
  1071.               <Text text="Inner" />
    
  1072.               {innerChildren}
    
  1073.             </Suspense>
    
  1074.           </Suspense>
    
  1075.         );
    
  1076.       }
    
  1077. 
    
  1078.       // Mount
    
  1079.       await act(() => {
    
  1080.         ReactNoop.render(<App />);
    
  1081.       });
    
  1082.       assertLog([
    
  1083.         'Text:Outer render',
    
  1084.         'Text:Inner render',
    
  1085.         'Text:Outer create layout',
    
  1086.         'Text:Inner create layout',
    
  1087.         'Text:Outer create passive',
    
  1088.         'Text:Inner create passive',
    
  1089.       ]);
    
  1090.       expect(ReactNoop).toMatchRenderedOutput(
    
  1091.         <>
    
  1092.           <span prop="Outer" />
    
  1093.           <span prop="Inner" />
    
  1094.         </>,
    
  1095.       );
    
  1096. 
    
  1097.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  1098.       await act(() => {
    
  1099.         ReactNoop.render(
    
  1100.           <App innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />} />,
    
  1101.         );
    
  1102.       });
    
  1103.       assertLog([
    
  1104.         'Text:Outer render',
    
  1105.         'Text:Inner render',
    
  1106.         'Suspend:InnerAsync_1',
    
  1107.         'Text:InnerFallback render',
    
  1108.         'Text:Inner destroy layout',
    
  1109.         'Text:InnerFallback create layout',
    
  1110.         'Text:InnerFallback create passive',
    
  1111.       ]);
    
  1112.       expect(ReactNoop).toMatchRenderedOutput(
    
  1113.         <>
    
  1114.           <span prop="Outer" />
    
  1115.           <span prop="Inner" hidden={true} />
    
  1116.           <span prop="InnerFallback" />
    
  1117.         </>,
    
  1118.       );
    
  1119. 
    
  1120.       // Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
    
  1121.       // (This check also ensures we don't destroy effects for mounted inner fallback.)
    
  1122.       await act(() => {
    
  1123.         ReactNoop.render(
    
  1124.           <App
    
  1125.             outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
    
  1126.             innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />}
    
  1127.           />,
    
  1128.         );
    
  1129.       });
    
  1130.       await advanceTimers(1000);
    
  1131.       assertLog([
    
  1132.         'Text:Outer render',
    
  1133.         'Suspend:OuterAsync_1',
    
  1134.         'Text:OuterFallback render',
    
  1135.         'Text:Outer destroy layout',
    
  1136.         'Text:InnerFallback destroy layout',
    
  1137.         'Text:OuterFallback create layout',
    
  1138.         'Text:OuterFallback create passive',
    
  1139.       ]);
    
  1140.       expect(ReactNoop).toMatchRenderedOutput(
    
  1141.         <>
    
  1142.           <span prop="Outer" hidden={true} />
    
  1143.           <span prop="Inner" hidden={true} />
    
  1144.           <span prop="InnerFallback" hidden={true} />
    
  1145.           <span prop="OuterFallback" />
    
  1146.         </>,
    
  1147.       );
    
  1148. 
    
  1149.       // Show the inner Suspense subtree (no effects should be recreated)
    
  1150.       await act(async () => {
    
  1151.         await resolveText('InnerAsync_1');
    
  1152.       });
    
  1153.       assertLog(['Text:Outer render', 'Suspend:OuterAsync_1']);
    
  1154.       expect(ReactNoop).toMatchRenderedOutput(
    
  1155.         <>
    
  1156.           <span prop="Outer" hidden={true} />
    
  1157.           <span prop="Inner" hidden={true} />
    
  1158.           <span prop="InnerFallback" hidden={true} />
    
  1159.           <span prop="OuterFallback" />
    
  1160.         </>,
    
  1161.       );
    
  1162. 
    
  1163.       // Suspend the inner Suspense subtree (no effects should be destroyed)
    
  1164.       await act(() => {
    
  1165.         ReactNoop.render(
    
  1166.           <App
    
  1167.             outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
    
  1168.             innerChildren={<AsyncText text="InnerAsync_2" ms={1000} />}
    
  1169.           />,
    
  1170.         );
    
  1171.       });
    
  1172.       await advanceTimers(1000);
    
  1173.       assertLog([
    
  1174.         'Text:Outer render',
    
  1175.         'Suspend:OuterAsync_1',
    
  1176.         'Text:OuterFallback render',
    
  1177.       ]);
    
  1178.       expect(ReactNoop).toMatchRenderedOutput(
    
  1179.         <>
    
  1180.           <span prop="Outer" hidden={true} />
    
  1181.           <span prop="Inner" hidden={true} />
    
  1182.           <span prop="InnerFallback" hidden={true} />
    
  1183.           <span prop="OuterFallback" />
    
  1184.         </>,
    
  1185.       );
    
  1186. 
    
  1187.       // Show the outer Suspense subtree (only outer effects should be recreated)
    
  1188.       await act(async () => {
    
  1189.         await resolveText('OuterAsync_1');
    
  1190.       });
    
  1191.       assertLog([
    
  1192.         'Text:Outer render',
    
  1193.         'AsyncText:OuterAsync_1 render',
    
  1194.         'Text:Inner render',
    
  1195.         'Suspend:InnerAsync_2',
    
  1196.         'Text:InnerFallback render',
    
  1197.         'Text:OuterFallback destroy layout',
    
  1198.         'Text:Outer create layout',
    
  1199.         'AsyncText:OuterAsync_1 create layout',
    
  1200.         'Text:InnerFallback create layout',
    
  1201.         'Text:OuterFallback destroy passive',
    
  1202.         'AsyncText:OuterAsync_1 create passive',
    
  1203.       ]);
    
  1204.       expect(ReactNoop).toMatchRenderedOutput(
    
  1205.         <>
    
  1206.           <span prop="Outer" />
    
  1207.           <span prop="OuterAsync_1" />
    
  1208.           <span prop="Inner" hidden={true} />
    
  1209.           <span prop="InnerFallback" />
    
  1210.         </>,
    
  1211.       );
    
  1212. 
    
  1213.       // Show the inner Suspense subtree (only inner effects should be recreated)
    
  1214.       await act(async () => {
    
  1215.         await resolveText('InnerAsync_2');
    
  1216.       });
    
  1217.       assertLog([
    
  1218.         'Text:Inner render',
    
  1219.         'AsyncText:InnerAsync_2 render',
    
  1220.         'Text:InnerFallback destroy layout',
    
  1221.         'Text:Inner create layout',
    
  1222.         'AsyncText:InnerAsync_2 create layout',
    
  1223.         'Text:InnerFallback destroy passive',
    
  1224.         'AsyncText:InnerAsync_2 create passive',
    
  1225.       ]);
    
  1226.       expect(ReactNoop).toMatchRenderedOutput(
    
  1227.         <>
    
  1228.           <span prop="Outer" />
    
  1229.           <span prop="OuterAsync_1" />
    
  1230.           <span prop="Inner" />
    
  1231.           <span prop="InnerAsync_2" />
    
  1232.         </>,
    
  1233.       );
    
  1234. 
    
  1235.       // Suspend the outer Suspense subtree (all effects should be destroyed)
    
  1236.       await act(() => {
    
  1237.         ReactNoop.render(
    
  1238.           <App
    
  1239.             outerChildren={<AsyncText text="OuterAsync_2" ms={1000} />}
    
  1240.             innerChildren={<AsyncText text="InnerAsync_2" ms={1000} />}
    
  1241.           />,
    
  1242.         );
    
  1243.       });
    
  1244.       assertLog([
    
  1245.         'Text:Outer render',
    
  1246.         'Suspend:OuterAsync_2',
    
  1247.         'Text:OuterFallback render',
    
  1248.         'Text:Outer destroy layout',
    
  1249.         'AsyncText:OuterAsync_1 destroy layout',
    
  1250.         'Text:Inner destroy layout',
    
  1251.         'AsyncText:InnerAsync_2 destroy layout',
    
  1252.         'Text:OuterFallback create layout',
    
  1253.         'Text:OuterFallback create passive',
    
  1254.       ]);
    
  1255.       expect(ReactNoop).toMatchRenderedOutput(
    
  1256.         <>
    
  1257.           <span prop="Outer" hidden={true} />
    
  1258.           <span prop="OuterAsync_1" hidden={true} />
    
  1259.           <span prop="Inner" hidden={true} />
    
  1260.           <span prop="InnerAsync_2" hidden={true} />
    
  1261.           <span prop="OuterFallback" />
    
  1262.         </>,
    
  1263.       );
    
  1264. 
    
  1265.       // Show the outer Suspense subtree (all effects should be recreated)
    
  1266.       await act(async () => {
    
  1267.         await resolveText('OuterAsync_2');
    
  1268.       });
    
  1269.       assertLog([
    
  1270.         'Text:Outer render',
    
  1271.         'AsyncText:OuterAsync_2 render',
    
  1272.         'Text:Inner render',
    
  1273.         'AsyncText:InnerAsync_2 render',
    
  1274.         'Text:OuterFallback destroy layout',
    
  1275.         'Text:Outer create layout',
    
  1276.         'AsyncText:OuterAsync_2 create layout',
    
  1277.         'Text:Inner create layout',
    
  1278.         'AsyncText:InnerAsync_2 create layout',
    
  1279.         'Text:OuterFallback destroy passive',
    
  1280.       ]);
    
  1281.       expect(ReactNoop).toMatchRenderedOutput(
    
  1282.         <>
    
  1283.           <span prop="Outer" />
    
  1284.           <span prop="OuterAsync_2" />
    
  1285.           <span prop="Inner" />
    
  1286.           <span prop="InnerAsync_2" />
    
  1287.         </>,
    
  1288.       );
    
  1289.     });
    
  1290. 
    
  1291.     // @gate enableLegacyCache
    
  1292.     it('should show nested host nodes if multiple boundaries resolve at the same time', async () => {
    
  1293.       function App({innerChildren = null, outerChildren = null}) {
    
  1294.         return (
    
  1295.           <Suspense fallback={<Text text="OuterFallback" />}>
    
  1296.             <Text text="Outer" />
    
  1297.             {outerChildren}
    
  1298.             <Suspense fallback={<Text text="InnerFallback" />}>
    
  1299.               <Text text="Inner" />
    
  1300.               {innerChildren}
    
  1301.             </Suspense>
    
  1302.           </Suspense>
    
  1303.         );
    
  1304.       }
    
  1305. 
    
  1306.       // Mount
    
  1307.       await act(() => {
    
  1308.         ReactNoop.render(<App />);
    
  1309.       });
    
  1310.       assertLog([
    
  1311.         'Text:Outer render',
    
  1312.         'Text:Inner render',
    
  1313.         'Text:Outer create layout',
    
  1314.         'Text:Inner create layout',
    
  1315.         'Text:Outer create passive',
    
  1316.         'Text:Inner create passive',
    
  1317.       ]);
    
  1318.       expect(ReactNoop).toMatchRenderedOutput(
    
  1319.         <>
    
  1320.           <span prop="Outer" />
    
  1321.           <span prop="Inner" />
    
  1322.         </>,
    
  1323.       );
    
  1324. 
    
  1325.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  1326.       await act(() => {
    
  1327.         ReactNoop.render(
    
  1328.           <App innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />} />,
    
  1329.         );
    
  1330.       });
    
  1331.       assertLog([
    
  1332.         'Text:Outer render',
    
  1333.         'Text:Inner render',
    
  1334.         'Suspend:InnerAsync_1',
    
  1335.         'Text:InnerFallback render',
    
  1336.         'Text:Inner destroy layout',
    
  1337.         'Text:InnerFallback create layout',
    
  1338.         'Text:InnerFallback create passive',
    
  1339.       ]);
    
  1340.       expect(ReactNoop).toMatchRenderedOutput(
    
  1341.         <>
    
  1342.           <span prop="Outer" />
    
  1343.           <span prop="Inner" hidden={true} />
    
  1344.           <span prop="InnerFallback" />
    
  1345.         </>,
    
  1346.       );
    
  1347. 
    
  1348.       // Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
    
  1349.       // (This check also ensures we don't destroy effects for mounted inner fallback.)
    
  1350.       await act(() => {
    
  1351.         ReactNoop.render(
    
  1352.           <App
    
  1353.             outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
    
  1354.             innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />}
    
  1355.           />,
    
  1356.         );
    
  1357.       });
    
  1358.       assertLog([
    
  1359.         'Text:Outer render',
    
  1360.         'Suspend:OuterAsync_1',
    
  1361.         'Text:OuterFallback render',
    
  1362.         'Text:Outer destroy layout',
    
  1363.         'Text:InnerFallback destroy layout',
    
  1364.         'Text:OuterFallback create layout',
    
  1365.         'Text:OuterFallback create passive',
    
  1366.       ]);
    
  1367.       expect(ReactNoop).toMatchRenderedOutput(
    
  1368.         <>
    
  1369.           <span prop="Outer" hidden={true} />
    
  1370.           <span prop="Inner" hidden={true} />
    
  1371.           <span prop="InnerFallback" hidden={true} />
    
  1372.           <span prop="OuterFallback" />
    
  1373.         </>,
    
  1374.       );
    
  1375. 
    
  1376.       // Resolve both suspended trees.
    
  1377.       await act(async () => {
    
  1378.         await resolveText('OuterAsync_1');
    
  1379.         await resolveText('InnerAsync_1');
    
  1380.       });
    
  1381.       assertLog([
    
  1382.         'Text:Outer render',
    
  1383.         'AsyncText:OuterAsync_1 render',
    
  1384.         'Text:Inner render',
    
  1385.         'AsyncText:InnerAsync_1 render',
    
  1386.         'Text:OuterFallback destroy layout',
    
  1387.         'Text:Outer create layout',
    
  1388.         'AsyncText:OuterAsync_1 create layout',
    
  1389.         'Text:Inner create layout',
    
  1390.         'AsyncText:InnerAsync_1 create layout',
    
  1391.         'Text:OuterFallback destroy passive',
    
  1392.         'Text:InnerFallback destroy passive',
    
  1393.         'AsyncText:OuterAsync_1 create passive',
    
  1394.         'AsyncText:InnerAsync_1 create passive',
    
  1395.       ]);
    
  1396.       expect(ReactNoop).toMatchRenderedOutput(
    
  1397.         <>
    
  1398.           <span prop="Outer" />
    
  1399.           <span prop="OuterAsync_1" />
    
  1400.           <span prop="Inner" />
    
  1401.           <span prop="InnerAsync_1" />
    
  1402.         </>,
    
  1403.       );
    
  1404.     });
    
  1405. 
    
  1406.     // @gate enableLegacyCache
    
  1407.     it('should be cleaned up inside of a fallback that suspends', async () => {
    
  1408.       function App({fallbackChildren = null, outerChildren = null}) {
    
  1409.         return (
    
  1410.           <>
    
  1411.             <Suspense
    
  1412.               fallback={
    
  1413.                 <>
    
  1414.                   <Suspense fallback={<Text text="Fallback:Fallback" />}>
    
  1415.                     <Text text="Fallback:Inside" />
    
  1416.                     {fallbackChildren}
    
  1417.                   </Suspense>
    
  1418.                   <Text text="Fallback:Outside" />
    
  1419.                 </>
    
  1420.               }>
    
  1421.               <Text text="Inside" />
    
  1422.               {outerChildren}
    
  1423.             </Suspense>
    
  1424.             <Text text="Outside" />
    
  1425.           </>
    
  1426.         );
    
  1427.       }
    
  1428. 
    
  1429.       // Mount
    
  1430.       await act(() => {
    
  1431.         ReactNoop.render(<App />);
    
  1432.       });
    
  1433.       assertLog([
    
  1434.         'Text:Inside render',
    
  1435.         'Text:Outside render',
    
  1436.         'Text:Inside create layout',
    
  1437.         'Text:Outside create layout',
    
  1438.         'Text:Inside create passive',
    
  1439.         'Text:Outside create passive',
    
  1440.       ]);
    
  1441.       expect(ReactNoop).toMatchRenderedOutput(
    
  1442.         <>
    
  1443.           <span prop="Inside" />
    
  1444.           <span prop="Outside" />
    
  1445.         </>,
    
  1446.       );
    
  1447. 
    
  1448.       // Suspend the outer shell
    
  1449.       await act(async () => {
    
  1450.         ReactNoop.render(
    
  1451.           <App outerChildren={<AsyncText text="OutsideAsync" ms={1000} />} />,
    
  1452.         );
    
  1453.         await waitFor([
    
  1454.           'Text:Inside render',
    
  1455.           'Suspend:OutsideAsync',
    
  1456.           'Text:Fallback:Inside render',
    
  1457.           'Text:Fallback:Outside render',
    
  1458.           'Text:Outside render',
    
  1459.           'Text:Inside destroy layout',
    
  1460.           'Text:Fallback:Inside create layout',
    
  1461.           'Text:Fallback:Outside create layout',
    
  1462.         ]);
    
  1463.         await waitForAll([
    
  1464.           'Text:Fallback:Inside create passive',
    
  1465.           'Text:Fallback:Outside create passive',
    
  1466.         ]);
    
  1467.         expect(ReactNoop).toMatchRenderedOutput(
    
  1468.           <>
    
  1469.             <span prop="Inside" hidden={true} />
    
  1470.             <span prop="Fallback:Inside" />
    
  1471.             <span prop="Fallback:Outside" />
    
  1472.             <span prop="Outside" />
    
  1473.           </>,
    
  1474.         );
    
  1475.       });
    
  1476. 
    
  1477.       // Suspend the fallback and verify that it's effects get cleaned up as well
    
  1478.       await act(async () => {
    
  1479.         ReactNoop.render(
    
  1480.           <App
    
  1481.             fallbackChildren={<AsyncText text="FallbackAsync" ms={1000} />}
    
  1482.             outerChildren={<AsyncText text="OutsideAsync" ms={1000} />}
    
  1483.           />,
    
  1484.         );
    
  1485.         await waitFor([
    
  1486.           'Text:Inside render',
    
  1487.           'Suspend:OutsideAsync',
    
  1488.           'Text:Fallback:Inside render',
    
  1489.           'Suspend:FallbackAsync',
    
  1490.           'Text:Fallback:Fallback render',
    
  1491.           'Text:Fallback:Outside render',
    
  1492.           'Text:Outside render',
    
  1493.           'Text:Fallback:Inside destroy layout',
    
  1494.           'Text:Fallback:Fallback create layout',
    
  1495.         ]);
    
  1496.         await waitForAll(['Text:Fallback:Fallback create passive']);
    
  1497.         expect(ReactNoop).toMatchRenderedOutput(
    
  1498.           <>
    
  1499.             <span prop="Inside" hidden={true} />
    
  1500.             <span prop="Fallback:Inside" hidden={true} />
    
  1501.             <span prop="Fallback:Fallback" />
    
  1502.             <span prop="Fallback:Outside" />
    
  1503.             <span prop="Outside" />
    
  1504.           </>,
    
  1505.         );
    
  1506.       });
    
  1507. 
    
  1508.       // Resolving both resources should cleanup fallback effects and recreate main effects
    
  1509.       await act(async () => {
    
  1510.         await resolveText('FallbackAsync');
    
  1511.         await resolveText('OutsideAsync');
    
  1512.       });
    
  1513.       assertLog([
    
  1514.         'Text:Inside render',
    
  1515.         'AsyncText:OutsideAsync render',
    
  1516.         'Text:Fallback:Fallback destroy layout',
    
  1517.         'Text:Fallback:Outside destroy layout',
    
  1518.         'Text:Inside create layout',
    
  1519.         'AsyncText:OutsideAsync create layout',
    
  1520.         'Text:Fallback:Inside destroy passive',
    
  1521.         'Text:Fallback:Fallback destroy passive',
    
  1522.         'Text:Fallback:Outside destroy passive',
    
  1523.         'AsyncText:OutsideAsync create passive',
    
  1524.       ]);
    
  1525.       expect(ReactNoop).toMatchRenderedOutput(
    
  1526.         <>
    
  1527.           <span prop="Inside" />
    
  1528.           <span prop="OutsideAsync" />
    
  1529.           <span prop="Outside" />
    
  1530.         </>,
    
  1531.       );
    
  1532.     });
    
  1533. 
    
  1534.     // @gate enableLegacyCache
    
  1535.     it('should be cleaned up inside of a fallback that suspends (alternate)', async () => {
    
  1536.       function App({fallbackChildren = null, outerChildren = null}) {
    
  1537.         return (
    
  1538.           <>
    
  1539.             <Suspense
    
  1540.               fallback={
    
  1541.                 <>
    
  1542.                   <Suspense fallback={<Text text="Fallback:Fallback" />}>
    
  1543.                     <Text text="Fallback:Inside" />
    
  1544.                     {fallbackChildren}
    
  1545.                   </Suspense>
    
  1546.                   <Text text="Fallback:Outside" />
    
  1547.                 </>
    
  1548.               }>
    
  1549.               <Text text="Inside" />
    
  1550.               {outerChildren}
    
  1551.             </Suspense>
    
  1552.             <Text text="Outside" />
    
  1553.           </>
    
  1554.         );
    
  1555.       }
    
  1556. 
    
  1557.       // Mount
    
  1558.       await act(() => {
    
  1559.         ReactNoop.render(<App />);
    
  1560.       });
    
  1561.       assertLog([
    
  1562.         'Text:Inside render',
    
  1563.         'Text:Outside render',
    
  1564.         'Text:Inside create layout',
    
  1565.         'Text:Outside create layout',
    
  1566.         'Text:Inside create passive',
    
  1567.         'Text:Outside create passive',
    
  1568.       ]);
    
  1569.       expect(ReactNoop).toMatchRenderedOutput(
    
  1570.         <>
    
  1571.           <span prop="Inside" />
    
  1572.           <span prop="Outside" />
    
  1573.         </>,
    
  1574.       );
    
  1575. 
    
  1576.       // Suspend both the outer boundary and the fallback
    
  1577.       await act(() => {
    
  1578.         ReactNoop.render(
    
  1579.           <App
    
  1580.             outerChildren={<AsyncText text="OutsideAsync" ms={1000} />}
    
  1581.             fallbackChildren={<AsyncText text="FallbackAsync" ms={1000} />}
    
  1582.           />,
    
  1583.         );
    
  1584.       });
    
  1585.       assertLog([
    
  1586.         'Text:Inside render',
    
  1587.         'Suspend:OutsideAsync',
    
  1588.         'Text:Fallback:Inside render',
    
  1589.         'Suspend:FallbackAsync',
    
  1590.         'Text:Fallback:Fallback render',
    
  1591.         'Text:Fallback:Outside render',
    
  1592.         'Text:Outside render',
    
  1593.         'Text:Inside destroy layout',
    
  1594.         'Text:Fallback:Fallback create layout',
    
  1595.         'Text:Fallback:Outside create layout',
    
  1596.         'Text:Fallback:Fallback create passive',
    
  1597.         'Text:Fallback:Outside create passive',
    
  1598.       ]);
    
  1599.       expect(ReactNoop).toMatchRenderedOutput(
    
  1600.         <>
    
  1601.           <span prop="Inside" hidden={true} />
    
  1602.           <span prop="Fallback:Fallback" />
    
  1603.           <span prop="Fallback:Outside" />
    
  1604.           <span prop="Outside" />
    
  1605.         </>,
    
  1606.       );
    
  1607. 
    
  1608.       // Resolving the inside fallback
    
  1609.       await act(async () => {
    
  1610.         await resolveText('FallbackAsync');
    
  1611.       });
    
  1612.       assertLog([
    
  1613.         'Text:Fallback:Inside render',
    
  1614.         'AsyncText:FallbackAsync render',
    
  1615.         'Text:Fallback:Fallback destroy layout',
    
  1616.         'Text:Fallback:Inside create layout',
    
  1617.         'AsyncText:FallbackAsync create layout',
    
  1618.         'Text:Fallback:Fallback destroy passive',
    
  1619.         'Text:Fallback:Inside create passive',
    
  1620.         'AsyncText:FallbackAsync create passive',
    
  1621.       ]);
    
  1622.       expect(ReactNoop).toMatchRenderedOutput(
    
  1623.         <>
    
  1624.           <span prop="Inside" hidden={true} />
    
  1625.           <span prop="Fallback:Inside" />
    
  1626.           <span prop="FallbackAsync" />
    
  1627.           <span prop="Fallback:Outside" />
    
  1628.           <span prop="Outside" />
    
  1629.         </>,
    
  1630.       );
    
  1631. 
    
  1632.       // Resolving the outer fallback only
    
  1633.       await act(async () => {
    
  1634.         await resolveText('OutsideAsync');
    
  1635.       });
    
  1636.       assertLog([
    
  1637.         'Text:Inside render',
    
  1638.         'AsyncText:OutsideAsync render',
    
  1639.         'Text:Fallback:Inside destroy layout',
    
  1640.         'AsyncText:FallbackAsync destroy layout',
    
  1641.         'Text:Fallback:Outside destroy layout',
    
  1642.         'Text:Inside create layout',
    
  1643.         'AsyncText:OutsideAsync create layout',
    
  1644.         'Text:Fallback:Inside destroy passive',
    
  1645.         'AsyncText:FallbackAsync destroy passive',
    
  1646.         'Text:Fallback:Outside destroy passive',
    
  1647.         'AsyncText:OutsideAsync create passive',
    
  1648.       ]);
    
  1649.       expect(ReactNoop).toMatchRenderedOutput(
    
  1650.         <>
    
  1651.           <span prop="Inside" />
    
  1652.           <span prop="OutsideAsync" />
    
  1653.           <span prop="Outside" />
    
  1654.         </>,
    
  1655.       );
    
  1656.     });
    
  1657. 
    
  1658.     // @gate enableLegacyCache
    
  1659.     it('should be cleaned up deeper inside of a subtree that suspends', async () => {
    
  1660.       function ConditionalSuspense({shouldSuspend}) {
    
  1661.         if (shouldSuspend) {
    
  1662.           readText('Suspend');
    
  1663.         }
    
  1664.         return <Text text="Inside" />;
    
  1665.       }
    
  1666. 
    
  1667.       function App({children = null, shouldSuspend}) {
    
  1668.         return (
    
  1669.           <>
    
  1670.             <Suspense fallback={<Text text="Fallback" />}>
    
  1671.               <ConditionalSuspense shouldSuspend={shouldSuspend} />
    
  1672.             </Suspense>
    
  1673.             <Text text="Outside" />
    
  1674.           </>
    
  1675.         );
    
  1676.       }
    
  1677. 
    
  1678.       // Mount
    
  1679.       await act(() => {
    
  1680.         ReactNoop.render(<App shouldSuspend={false} />);
    
  1681.       });
    
  1682.       assertLog([
    
  1683.         'Text:Inside render',
    
  1684.         'Text:Outside render',
    
  1685.         'Text:Inside create layout',
    
  1686.         'Text:Outside create layout',
    
  1687.         'Text:Inside create passive',
    
  1688.         'Text:Outside create passive',
    
  1689.       ]);
    
  1690.       expect(ReactNoop).toMatchRenderedOutput(
    
  1691.         <>
    
  1692.           <span prop="Inside" />
    
  1693.           <span prop="Outside" />
    
  1694.         </>,
    
  1695.       );
    
  1696. 
    
  1697.       // Suspending a component in the middle of the tree
    
  1698.       // should still properly cleanup effects deeper in the tree
    
  1699.       await act(async () => {
    
  1700.         ReactNoop.render(<App shouldSuspend={true} />);
    
  1701.         await waitFor([
    
  1702.           'Suspend:Suspend',
    
  1703.           'Text:Fallback render',
    
  1704.           'Text:Outside render',
    
  1705.           'Text:Inside destroy layout',
    
  1706.           'Text:Fallback create layout',
    
  1707.         ]);
    
  1708.         await waitForAll(['Text:Fallback create passive']);
    
  1709.         expect(ReactNoop).toMatchRenderedOutput(
    
  1710.           <>
    
  1711.             <span prop="Inside" hidden={true} />
    
  1712.             <span prop="Fallback" />
    
  1713.             <span prop="Outside" />
    
  1714.           </>,
    
  1715.         );
    
  1716.       });
    
  1717. 
    
  1718.       // Resolving should cleanup.
    
  1719.       await act(async () => {
    
  1720.         await resolveText('Suspend');
    
  1721.       });
    
  1722.       assertLog([
    
  1723.         'Text:Inside render',
    
  1724.         'Text:Fallback destroy layout',
    
  1725.         'Text:Inside create layout',
    
  1726.         'Text:Fallback destroy passive',
    
  1727.       ]);
    
  1728.       expect(ReactNoop).toMatchRenderedOutput(
    
  1729.         <>
    
  1730.           <span prop="Inside" />
    
  1731.           <span prop="Outside" />
    
  1732.         </>,
    
  1733.       );
    
  1734.     });
    
  1735. 
    
  1736.     describe('that throw errors', () => {
    
  1737.       // @gate enableLegacyCache
    
  1738.       it('are properly handled for componentDidMount', async () => {
    
  1739.         let componentDidMountShouldThrow = false;
    
  1740. 
    
  1741.         class ThrowsInDidMount extends React.Component {
    
  1742.           componentWillUnmount() {
    
  1743.             Scheduler.log('ThrowsInDidMount componentWillUnmount');
    
  1744.           }
    
  1745.           componentDidMount() {
    
  1746.             Scheduler.log('ThrowsInDidMount componentDidMount');
    
  1747.             if (componentDidMountShouldThrow) {
    
  1748.               throw Error('expected');
    
  1749.             }
    
  1750.           }
    
  1751.           render() {
    
  1752.             Scheduler.log('ThrowsInDidMount render');
    
  1753.             return <span prop="ThrowsInDidMount" />;
    
  1754.           }
    
  1755.         }
    
  1756. 
    
  1757.         function App({children = null}) {
    
  1758.           Scheduler.log('App render');
    
  1759.           React.useLayoutEffect(() => {
    
  1760.             Scheduler.log('App create layout');
    
  1761.             return () => {
    
  1762.               Scheduler.log('App destroy layout');
    
  1763.             };
    
  1764.           }, []);
    
  1765.           return (
    
  1766.             <>
    
  1767.               <Suspense fallback={<Text text="Fallback" />}>
    
  1768.                 {children}
    
  1769.                 <ThrowsInDidMount />
    
  1770.                 <Text text="Inside" />
    
  1771.               </Suspense>
    
  1772.               <Text text="Outside" />
    
  1773.             </>
    
  1774.           );
    
  1775.         }
    
  1776. 
    
  1777.         await act(() => {
    
  1778.           ReactNoop.render(
    
  1779.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  1780.               <App />
    
  1781.             </ErrorBoundary>,
    
  1782.           );
    
  1783.         });
    
  1784.         assertLog([
    
  1785.           'ErrorBoundary render: try',
    
  1786.           'App render',
    
  1787.           'ThrowsInDidMount render',
    
  1788.           'Text:Inside render',
    
  1789.           'Text:Outside render',
    
  1790.           'ThrowsInDidMount componentDidMount',
    
  1791.           'Text:Inside create layout',
    
  1792.           'Text:Outside create layout',
    
  1793.           'App create layout',
    
  1794.           'Text:Inside create passive',
    
  1795.           'Text:Outside create passive',
    
  1796.         ]);
    
  1797.         expect(ReactNoop).toMatchRenderedOutput(
    
  1798.           <>
    
  1799.             <span prop="ThrowsInDidMount" />
    
  1800.             <span prop="Inside" />
    
  1801.             <span prop="Outside" />
    
  1802.           </>,
    
  1803.         );
    
  1804. 
    
  1805.         // Schedule an update that causes React to suspend.
    
  1806.         await act(() => {
    
  1807.           ReactNoop.render(
    
  1808.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  1809.               <App>
    
  1810.                 <AsyncText text="Async" ms={1000} />
    
  1811.               </App>
    
  1812.             </ErrorBoundary>,
    
  1813.           );
    
  1814.         });
    
  1815.         assertLog([
    
  1816.           'ErrorBoundary render: try',
    
  1817.           'App render',
    
  1818.           'Suspend:Async',
    
  1819.           'Text:Fallback render',
    
  1820.           'Text:Outside render',
    
  1821.           'ThrowsInDidMount componentWillUnmount',
    
  1822.           'Text:Inside destroy layout',
    
  1823.           'Text:Fallback create layout',
    
  1824.           'Text:Fallback create passive',
    
  1825.         ]);
    
  1826.         expect(ReactNoop).toMatchRenderedOutput(
    
  1827.           <>
    
  1828.             <span prop="ThrowsInDidMount" hidden={true} />
    
  1829.             <span prop="Inside" hidden={true} />
    
  1830.             <span prop="Fallback" />
    
  1831.             <span prop="Outside" />
    
  1832.           </>,
    
  1833.         );
    
  1834. 
    
  1835.         // Resolve the pending suspense and throw
    
  1836.         componentDidMountShouldThrow = true;
    
  1837.         await act(async () => {
    
  1838.           await resolveText('Async');
    
  1839.         });
    
  1840.         assertLog([
    
  1841.           'AsyncText:Async render',
    
  1842.           'ThrowsInDidMount render',
    
  1843.           'Text:Inside render',
    
  1844.           'Text:Fallback destroy layout',
    
  1845.           'AsyncText:Async create layout',
    
  1846. 
    
  1847.           // Even though an error was thrown in componentDidMount,
    
  1848.           // subsequent layout effects should still be destroyed.
    
  1849.           'ThrowsInDidMount componentDidMount',
    
  1850.           'Text:Inside create layout',
    
  1851. 
    
  1852.           // Finish the in-progress commit
    
  1853.           'Text:Fallback destroy passive',
    
  1854.           'AsyncText:Async create passive',
    
  1855. 
    
  1856.           // Destroy layout and passive effects in the errored tree.
    
  1857.           'App destroy layout',
    
  1858.           'AsyncText:Async destroy layout',
    
  1859.           'ThrowsInDidMount componentWillUnmount',
    
  1860.           'Text:Inside destroy layout',
    
  1861.           'Text:Outside destroy layout',
    
  1862.           'AsyncText:Async destroy passive',
    
  1863.           'Text:Inside destroy passive',
    
  1864.           'Text:Outside destroy passive',
    
  1865. 
    
  1866.           // Render fallback
    
  1867.           'ErrorBoundary render: catch',
    
  1868.           'Text:Error render',
    
  1869.           'Text:Error create layout',
    
  1870.           'Text:Error create passive',
    
  1871.         ]);
    
  1872.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
    
  1873.       });
    
  1874. 
    
  1875.       // @gate enableLegacyCache
    
  1876.       it('are properly handled for componentWillUnmount', async () => {
    
  1877.         class ThrowsInWillUnmount extends React.Component {
    
  1878.           componentDidMount() {
    
  1879.             Scheduler.log('ThrowsInWillUnmount componentDidMount');
    
  1880.           }
    
  1881.           componentWillUnmount() {
    
  1882.             Scheduler.log('ThrowsInWillUnmount componentWillUnmount');
    
  1883.             throw Error('expected');
    
  1884.           }
    
  1885.           render() {
    
  1886.             Scheduler.log('ThrowsInWillUnmount render');
    
  1887.             return <span prop="ThrowsInWillUnmount" />;
    
  1888.           }
    
  1889.         }
    
  1890. 
    
  1891.         function App({children = null}) {
    
  1892.           Scheduler.log('App render');
    
  1893.           React.useLayoutEffect(() => {
    
  1894.             Scheduler.log('App create layout');
    
  1895.             return () => {
    
  1896.               Scheduler.log('App destroy layout');
    
  1897.             };
    
  1898.           }, []);
    
  1899.           return (
    
  1900.             <>
    
  1901.               <Suspense fallback={<Text text="Fallback" />}>
    
  1902.                 {children}
    
  1903.                 <ThrowsInWillUnmount />
    
  1904.                 <Text text="Inside" />
    
  1905.               </Suspense>
    
  1906.               <Text text="Outside" />
    
  1907.             </>
    
  1908.           );
    
  1909.         }
    
  1910. 
    
  1911.         await act(() => {
    
  1912.           ReactNoop.render(
    
  1913.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  1914.               <App />
    
  1915.             </ErrorBoundary>,
    
  1916.           );
    
  1917.         });
    
  1918.         assertLog([
    
  1919.           'ErrorBoundary render: try',
    
  1920.           'App render',
    
  1921.           'ThrowsInWillUnmount render',
    
  1922.           'Text:Inside render',
    
  1923.           'Text:Outside render',
    
  1924.           'ThrowsInWillUnmount componentDidMount',
    
  1925.           'Text:Inside create layout',
    
  1926.           'Text:Outside create layout',
    
  1927.           'App create layout',
    
  1928.           'Text:Inside create passive',
    
  1929.           'Text:Outside create passive',
    
  1930.         ]);
    
  1931.         expect(ReactNoop).toMatchRenderedOutput(
    
  1932.           <>
    
  1933.             <span prop="ThrowsInWillUnmount" />
    
  1934.             <span prop="Inside" />
    
  1935.             <span prop="Outside" />
    
  1936.           </>,
    
  1937.         );
    
  1938. 
    
  1939.         // Schedule an update that suspends and triggers our error code.
    
  1940.         await act(() => {
    
  1941.           ReactNoop.render(
    
  1942.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  1943.               <App>
    
  1944.                 <AsyncText text="Async" ms={1000} />
    
  1945.               </App>
    
  1946.             </ErrorBoundary>,
    
  1947.           );
    
  1948.         });
    
  1949.         assertLog([
    
  1950.           'ErrorBoundary render: try',
    
  1951.           'App render',
    
  1952.           'Suspend:Async',
    
  1953.           'Text:Fallback render',
    
  1954.           'Text:Outside render',
    
  1955. 
    
  1956.           // Even though an error was thrown in componentWillUnmount,
    
  1957.           // subsequent layout effects should still be destroyed.
    
  1958.           'ThrowsInWillUnmount componentWillUnmount',
    
  1959.           'Text:Inside destroy layout',
    
  1960. 
    
  1961.           // Finish the in-progress commit
    
  1962.           'Text:Fallback create layout',
    
  1963.           'Text:Fallback create passive',
    
  1964. 
    
  1965.           // Destroy layout and passive effects in the errored tree.
    
  1966.           'App destroy layout',
    
  1967.           'Text:Fallback destroy layout',
    
  1968.           'Text:Outside destroy layout',
    
  1969.           'Text:Inside destroy passive',
    
  1970.           'Text:Fallback destroy passive',
    
  1971.           'Text:Outside destroy passive',
    
  1972. 
    
  1973.           // Render fallback
    
  1974.           'ErrorBoundary render: catch',
    
  1975.           'Text:Error render',
    
  1976.           'Text:Error create layout',
    
  1977.           'Text:Error create passive',
    
  1978.         ]);
    
  1979.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
    
  1980.       });
    
  1981. 
    
  1982.       // @gate enableLegacyCache
    
  1983.       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
    
  1984.       it('are properly handled for layout effect creation', async () => {
    
  1985.         let useLayoutEffectShouldThrow = false;
    
  1986. 
    
  1987.         function ThrowsInLayoutEffect() {
    
  1988.           Scheduler.log('ThrowsInLayoutEffect render');
    
  1989.           React.useLayoutEffect(() => {
    
  1990.             Scheduler.log('ThrowsInLayoutEffect useLayoutEffect create');
    
  1991.             if (useLayoutEffectShouldThrow) {
    
  1992.               throw Error('expected');
    
  1993.             }
    
  1994.             return () => {
    
  1995.               Scheduler.log('ThrowsInLayoutEffect useLayoutEffect destroy');
    
  1996.             };
    
  1997.           }, []);
    
  1998.           return <span prop="ThrowsInLayoutEffect" />;
    
  1999.         }
    
  2000. 
    
  2001.         function App({children = null}) {
    
  2002.           Scheduler.log('App render');
    
  2003.           React.useLayoutEffect(() => {
    
  2004.             Scheduler.log('App create layout');
    
  2005.             return () => {
    
  2006.               Scheduler.log('App destroy layout');
    
  2007.             };
    
  2008.           }, []);
    
  2009.           return (
    
  2010.             <>
    
  2011.               <Suspense fallback={<Text text="Fallback" />}>
    
  2012.                 {children}
    
  2013.                 <ThrowsInLayoutEffect />
    
  2014.                 <Text text="Inside" />
    
  2015.               </Suspense>
    
  2016.               <Text text="Outside" />
    
  2017.             </>
    
  2018.           );
    
  2019.         }
    
  2020. 
    
  2021.         await act(() => {
    
  2022.           ReactNoop.render(
    
  2023.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  2024.               <App />
    
  2025.             </ErrorBoundary>,
    
  2026.           );
    
  2027.         });
    
  2028.         assertLog([
    
  2029.           'ErrorBoundary render: try',
    
  2030.           'App render',
    
  2031.           'ThrowsInLayoutEffect render',
    
  2032.           'Text:Inside render',
    
  2033.           'Text:Outside render',
    
  2034.           'ThrowsInLayoutEffect useLayoutEffect create',
    
  2035.           'Text:Inside create layout',
    
  2036.           'Text:Outside create layout',
    
  2037.           'App create layout',
    
  2038.           'Text:Inside create passive',
    
  2039.           'Text:Outside create passive',
    
  2040.         ]);
    
  2041.         expect(ReactNoop).toMatchRenderedOutput(
    
  2042.           <>
    
  2043.             <span prop="ThrowsInLayoutEffect" />
    
  2044.             <span prop="Inside" />
    
  2045.             <span prop="Outside" />
    
  2046.           </>,
    
  2047.         );
    
  2048. 
    
  2049.         // Schedule an update that causes React to suspend.
    
  2050.         await act(() => {
    
  2051.           ReactNoop.render(
    
  2052.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  2053.               <App>
    
  2054.                 <AsyncText text="Async" ms={1000} />
    
  2055.               </App>
    
  2056.             </ErrorBoundary>,
    
  2057.           );
    
  2058.         });
    
  2059.         assertLog([
    
  2060.           'ErrorBoundary render: try',
    
  2061.           'App render',
    
  2062.           'Suspend:Async',
    
  2063.           'Text:Fallback render',
    
  2064.           'Text:Outside render',
    
  2065.           'ThrowsInLayoutEffect useLayoutEffect destroy',
    
  2066.           'Text:Inside destroy layout',
    
  2067.           'Text:Fallback create layout',
    
  2068.           'Text:Fallback create passive',
    
  2069.         ]);
    
  2070.         expect(ReactNoop).toMatchRenderedOutput(
    
  2071.           <>
    
  2072.             <span prop="ThrowsInLayoutEffect" hidden={true} />
    
  2073.             <span prop="Inside" hidden={true} />
    
  2074.             <span prop="Fallback" />
    
  2075.             <span prop="Outside" />
    
  2076.           </>,
    
  2077.         );
    
  2078. 
    
  2079.         // Resolve the pending suspense and throw
    
  2080.         useLayoutEffectShouldThrow = true;
    
  2081.         await act(async () => {
    
  2082.           await resolveText('Async');
    
  2083.         });
    
  2084.         assertLog([
    
  2085.           'AsyncText:Async render',
    
  2086.           'ThrowsInLayoutEffect render',
    
  2087.           'Text:Inside render',
    
  2088. 
    
  2089.           'Text:Fallback destroy layout',
    
  2090. 
    
  2091.           // Even though an error was thrown in useLayoutEffect,
    
  2092.           // subsequent layout effects should still be created.
    
  2093.           'AsyncText:Async create layout',
    
  2094.           'ThrowsInLayoutEffect useLayoutEffect create',
    
  2095.           'Text:Inside create layout',
    
  2096. 
    
  2097.           // Finish the in-progress commit
    
  2098.           'Text:Fallback destroy passive',
    
  2099.           'AsyncText:Async create passive',
    
  2100. 
    
  2101.           // Destroy layout and passive effects in the errored tree.
    
  2102.           'App destroy layout',
    
  2103.           'AsyncText:Async destroy layout',
    
  2104.           'Text:Inside destroy layout',
    
  2105.           'Text:Outside destroy layout',
    
  2106.           'AsyncText:Async destroy passive',
    
  2107.           'Text:Inside destroy passive',
    
  2108.           'Text:Outside destroy passive',
    
  2109. 
    
  2110.           // Render fallback
    
  2111.           'ErrorBoundary render: catch',
    
  2112.           'Text:Error render',
    
  2113.           'Text:Error create layout',
    
  2114.           'Text:Error create passive',
    
  2115.         ]);
    
  2116.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
    
  2117.       });
    
  2118. 
    
  2119.       // @gate enableLegacyCache
    
  2120.       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
    
  2121.       it('are properly handled for layout effect destruction', async () => {
    
  2122.         function ThrowsInLayoutEffectDestroy() {
    
  2123.           Scheduler.log('ThrowsInLayoutEffectDestroy render');
    
  2124.           React.useLayoutEffect(() => {
    
  2125.             Scheduler.log('ThrowsInLayoutEffectDestroy useLayoutEffect create');
    
  2126.             return () => {
    
  2127.               Scheduler.log(
    
  2128.                 'ThrowsInLayoutEffectDestroy useLayoutEffect destroy',
    
  2129.               );
    
  2130.               throw Error('expected');
    
  2131.             };
    
  2132.           }, []);
    
  2133.           return <span prop="ThrowsInLayoutEffectDestroy" />;
    
  2134.         }
    
  2135. 
    
  2136.         function App({children = null}) {
    
  2137.           Scheduler.log('App render');
    
  2138.           React.useLayoutEffect(() => {
    
  2139.             Scheduler.log('App create layout');
    
  2140.             return () => {
    
  2141.               Scheduler.log('App destroy layout');
    
  2142.             };
    
  2143.           }, []);
    
  2144.           return (
    
  2145.             <>
    
  2146.               <Suspense fallback={<Text text="Fallback" />}>
    
  2147.                 {children}
    
  2148.                 <ThrowsInLayoutEffectDestroy />
    
  2149.                 <Text text="Inside" />
    
  2150.               </Suspense>
    
  2151.               <Text text="Outside" />
    
  2152.             </>
    
  2153.           );
    
  2154.         }
    
  2155. 
    
  2156.         await act(() => {
    
  2157.           ReactNoop.render(
    
  2158.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  2159.               <App />
    
  2160.             </ErrorBoundary>,
    
  2161.           );
    
  2162.         });
    
  2163.         assertLog([
    
  2164.           'ErrorBoundary render: try',
    
  2165.           'App render',
    
  2166.           'ThrowsInLayoutEffectDestroy render',
    
  2167.           'Text:Inside render',
    
  2168.           'Text:Outside render',
    
  2169.           'ThrowsInLayoutEffectDestroy useLayoutEffect create',
    
  2170.           'Text:Inside create layout',
    
  2171.           'Text:Outside create layout',
    
  2172.           'App create layout',
    
  2173.           'Text:Inside create passive',
    
  2174.           'Text:Outside create passive',
    
  2175.         ]);
    
  2176.         expect(ReactNoop).toMatchRenderedOutput(
    
  2177.           <>
    
  2178.             <span prop="ThrowsInLayoutEffectDestroy" />
    
  2179.             <span prop="Inside" />
    
  2180.             <span prop="Outside" />
    
  2181.           </>,
    
  2182.         );
    
  2183. 
    
  2184.         // Schedule an update that suspends and triggers our error code.
    
  2185.         await act(() => {
    
  2186.           ReactNoop.render(
    
  2187.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  2188.               <App>
    
  2189.                 <AsyncText text="Async" ms={1000} />
    
  2190.               </App>
    
  2191.             </ErrorBoundary>,
    
  2192.           );
    
  2193.         });
    
  2194.         assertLog([
    
  2195.           'ErrorBoundary render: try',
    
  2196.           'App render',
    
  2197.           'Suspend:Async',
    
  2198.           'Text:Fallback render',
    
  2199.           'Text:Outside render',
    
  2200. 
    
  2201.           // Even though an error was thrown in useLayoutEffect destroy,
    
  2202.           // subsequent layout effects should still be destroyed.
    
  2203.           'ThrowsInLayoutEffectDestroy useLayoutEffect destroy',
    
  2204.           'Text:Inside destroy layout',
    
  2205. 
    
  2206.           // Finish the in-progress commit
    
  2207.           'Text:Fallback create layout',
    
  2208.           'Text:Fallback create passive',
    
  2209. 
    
  2210.           // Destroy layout and passive effects in the errored tree.
    
  2211.           'App destroy layout',
    
  2212.           'Text:Fallback destroy layout',
    
  2213.           'Text:Outside destroy layout',
    
  2214.           'Text:Inside destroy passive',
    
  2215.           'Text:Fallback destroy passive',
    
  2216.           'Text:Outside destroy passive',
    
  2217. 
    
  2218.           // Render fallback
    
  2219.           'ErrorBoundary render: catch',
    
  2220.           'Text:Error render',
    
  2221.           'Text:Error create layout',
    
  2222.           'Text:Error create passive',
    
  2223.         ]);
    
  2224.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
    
  2225.       });
    
  2226.     });
    
  2227. 
    
  2228.     // @gate enableLegacyCache
    
  2229.     it('should be only destroy layout effects once if a tree suspends in multiple places', async () => {
    
  2230.       class ClassText extends React.Component {
    
  2231.         componentDidMount() {
    
  2232.           const {text} = this.props;
    
  2233.           Scheduler.log(`ClassText:${text} componentDidMount`);
    
  2234.         }
    
  2235.         componentDidUpdate() {
    
  2236.           const {text} = this.props;
    
  2237.           Scheduler.log(`ClassText:${text} componentDidUpdate`);
    
  2238.         }
    
  2239.         componentWillUnmount() {
    
  2240.           const {text} = this.props;
    
  2241.           Scheduler.log(`ClassText:${text} componentWillUnmount`);
    
  2242.         }
    
  2243.         render() {
    
  2244.           const {children, text} = this.props;
    
  2245.           Scheduler.log(`ClassText:${text} render`);
    
  2246.           return <span prop={text}>{children}</span>;
    
  2247.         }
    
  2248.       }
    
  2249. 
    
  2250.       function App({children = null}) {
    
  2251.         return (
    
  2252.           <Suspense fallback={<ClassText text="Fallback" />}>
    
  2253.             <Text text="Function" />
    
  2254.             {children}
    
  2255.             <ClassText text="Class" />
    
  2256.           </Suspense>
    
  2257.         );
    
  2258.       }
    
  2259. 
    
  2260.       await act(() => {
    
  2261.         ReactNoop.render(<App />);
    
  2262.       });
    
  2263.       assertLog([
    
  2264.         'Text:Function render',
    
  2265.         'ClassText:Class render',
    
  2266.         'Text:Function create layout',
    
  2267.         'ClassText:Class componentDidMount',
    
  2268.         'Text:Function create passive',
    
  2269.       ]);
    
  2270.       expect(ReactNoop).toMatchRenderedOutput(
    
  2271.         <>
    
  2272.           <span prop="Function" />
    
  2273.           <span prop="Class" />
    
  2274.         </>,
    
  2275.       );
    
  2276. 
    
  2277.       // Schedule an update that causes React to suspend.
    
  2278.       await act(async () => {
    
  2279.         ReactNoop.render(
    
  2280.           <App>
    
  2281.             <AsyncText text="Async_1" ms={1000} />
    
  2282.             <AsyncText text="Async_2" ms={2000} />
    
  2283.           </App>,
    
  2284.         );
    
  2285.         await waitFor([
    
  2286.           'Text:Function render',
    
  2287.           'Suspend:Async_1',
    
  2288.           'ClassText:Fallback render',
    
  2289.           'Text:Function destroy layout',
    
  2290.           'ClassText:Class componentWillUnmount',
    
  2291.           'ClassText:Fallback componentDidMount',
    
  2292.         ]);
    
  2293.         expect(ReactNoop).toMatchRenderedOutput(
    
  2294.           <>
    
  2295.             <span prop="Function" hidden={true} />
    
  2296.             <span prop="Class" hidden={true} />
    
  2297.             <span prop="Fallback" />
    
  2298.           </>,
    
  2299.         );
    
  2300.       });
    
  2301. 
    
  2302.       // Resolving the suspended resource should re-create inner layout effects.
    
  2303.       await act(async () => {
    
  2304.         await resolveText('Async_1');
    
  2305.       });
    
  2306.       assertLog([
    
  2307.         'Text:Function render',
    
  2308.         'AsyncText:Async_1 render',
    
  2309.         'Suspend:Async_2',
    
  2310.       ]);
    
  2311.       expect(ReactNoop).toMatchRenderedOutput(
    
  2312.         <>
    
  2313.           <span prop="Function" hidden={true} />
    
  2314.           <span prop="Class" hidden={true} />
    
  2315.           <span prop="Fallback" />
    
  2316.         </>,
    
  2317.       );
    
  2318. 
    
  2319.       // Resolving the suspended resource should re-create inner layout effects.
    
  2320.       await act(async () => {
    
  2321.         await resolveText('Async_2');
    
  2322.       });
    
  2323.       assertLog([
    
  2324.         'Text:Function render',
    
  2325.         'AsyncText:Async_1 render',
    
  2326.         'AsyncText:Async_2 render',
    
  2327.         'ClassText:Class render',
    
  2328.         'ClassText:Fallback componentWillUnmount',
    
  2329.         'Text:Function create layout',
    
  2330.         'AsyncText:Async_1 create layout',
    
  2331.         'AsyncText:Async_2 create layout',
    
  2332.         'ClassText:Class componentDidMount',
    
  2333.         'AsyncText:Async_1 create passive',
    
  2334.         'AsyncText:Async_2 create passive',
    
  2335.       ]);
    
  2336.       expect(ReactNoop).toMatchRenderedOutput(
    
  2337.         <>
    
  2338.           <span prop="Function" />
    
  2339.           <span prop="Async_1" />
    
  2340.           <span prop="Async_2" />
    
  2341.           <span prop="Class" />
    
  2342.         </>,
    
  2343.       );
    
  2344. 
    
  2345.       await act(() => {
    
  2346.         ReactNoop.render(null);
    
  2347.       });
    
  2348.       assertLog([
    
  2349.         'Text:Function destroy layout',
    
  2350.         'AsyncText:Async_1 destroy layout',
    
  2351.         'AsyncText:Async_2 destroy layout',
    
  2352.         'ClassText:Class componentWillUnmount',
    
  2353.         'Text:Function destroy passive',
    
  2354.         'AsyncText:Async_1 destroy passive',
    
  2355.         'AsyncText:Async_2 destroy passive',
    
  2356.       ]);
    
  2357.     });
    
  2358. 
    
  2359.     // @gate enableLegacyCache
    
  2360.     it('should be only destroy layout effects once if a component suspends multiple times', async () => {
    
  2361.       class ClassText extends React.Component {
    
  2362.         componentDidMount() {
    
  2363.           const {text} = this.props;
    
  2364.           Scheduler.log(`ClassText:${text} componentDidMount`);
    
  2365.         }
    
  2366.         componentDidUpdate() {
    
  2367.           const {text} = this.props;
    
  2368.           Scheduler.log(`ClassText:${text} componentDidUpdate`);
    
  2369.         }
    
  2370.         componentWillUnmount() {
    
  2371.           const {text} = this.props;
    
  2372.           Scheduler.log(`ClassText:${text} componentWillUnmount`);
    
  2373.         }
    
  2374.         render() {
    
  2375.           const {children, text} = this.props;
    
  2376.           Scheduler.log(`ClassText:${text} render`);
    
  2377.           return <span prop={text}>{children}</span>;
    
  2378.         }
    
  2379.       }
    
  2380. 
    
  2381.       let textToRead = null;
    
  2382. 
    
  2383.       function Suspender() {
    
  2384.         Scheduler.log(`Suspender "${textToRead}" render`);
    
  2385.         if (textToRead !== null) {
    
  2386.           readText(textToRead);
    
  2387.         }
    
  2388.         return <span prop="Suspender" />;
    
  2389.       }
    
  2390. 
    
  2391.       function App({children = null}) {
    
  2392.         return (
    
  2393.           <Suspense fallback={<ClassText text="Fallback" />}>
    
  2394.             <Text text="Function" />
    
  2395.             <Suspender />
    
  2396.             <ClassText text="Class" />
    
  2397.           </Suspense>
    
  2398.         );
    
  2399.       }
    
  2400. 
    
  2401.       await act(() => {
    
  2402.         ReactNoop.render(<App />);
    
  2403.       });
    
  2404.       assertLog([
    
  2405.         'Text:Function render',
    
  2406.         'Suspender "null" render',
    
  2407.         'ClassText:Class render',
    
  2408.         'Text:Function create layout',
    
  2409.         'ClassText:Class componentDidMount',
    
  2410.         'Text:Function create passive',
    
  2411.       ]);
    
  2412.       expect(ReactNoop).toMatchRenderedOutput(
    
  2413.         <>
    
  2414.           <span prop="Function" />
    
  2415.           <span prop="Suspender" />
    
  2416.           <span prop="Class" />
    
  2417.         </>,
    
  2418.       );
    
  2419. 
    
  2420.       // Schedule an update that causes React to suspend.
    
  2421.       textToRead = 'A';
    
  2422.       await act(async () => {
    
  2423.         ReactNoop.render(<App />);
    
  2424.         await waitFor([
    
  2425.           'Text:Function render',
    
  2426.           'Suspender "A" render',
    
  2427.           'Suspend:A',
    
  2428.           'ClassText:Fallback render',
    
  2429.           'Text:Function destroy layout',
    
  2430.           'ClassText:Class componentWillUnmount',
    
  2431.           'ClassText:Fallback componentDidMount',
    
  2432.         ]);
    
  2433.         expect(ReactNoop).toMatchRenderedOutput(
    
  2434.           <>
    
  2435.             <span prop="Function" hidden={true} />
    
  2436.             <span prop="Suspender" hidden={true} />
    
  2437.             <span prop="Class" hidden={true} />
    
  2438.             <span prop="Fallback" />
    
  2439.           </>,
    
  2440.         );
    
  2441.       });
    
  2442. 
    
  2443.       // Resolving the suspended resource should re-create inner layout effects.
    
  2444.       textToRead = 'B';
    
  2445.       await act(async () => {
    
  2446.         await resolveText('A');
    
  2447.       });
    
  2448.       assertLog(['Text:Function render', 'Suspender "B" render', 'Suspend:B']);
    
  2449.       expect(ReactNoop).toMatchRenderedOutput(
    
  2450.         <>
    
  2451.           <span prop="Function" hidden={true} />
    
  2452.           <span prop="Suspender" hidden={true} />
    
  2453.           <span prop="Class" hidden={true} />
    
  2454.           <span prop="Fallback" />
    
  2455.         </>,
    
  2456.       );
    
  2457. 
    
  2458.       // Resolving the suspended resource should re-create inner layout effects.
    
  2459.       await act(async () => {
    
  2460.         await resolveText('B');
    
  2461.       });
    
  2462.       assertLog([
    
  2463.         'Text:Function render',
    
  2464.         'Suspender "B" render',
    
  2465.         'ClassText:Class render',
    
  2466.         'ClassText:Fallback componentWillUnmount',
    
  2467.         'Text:Function create layout',
    
  2468.         'ClassText:Class componentDidMount',
    
  2469.       ]);
    
  2470.       expect(ReactNoop).toMatchRenderedOutput(
    
  2471.         <>
    
  2472.           <span prop="Function" />
    
  2473.           <span prop="Suspender" />
    
  2474.           <span prop="Class" />
    
  2475.         </>,
    
  2476.       );
    
  2477. 
    
  2478.       await act(() => {
    
  2479.         ReactNoop.render(null);
    
  2480.       });
    
  2481.       assertLog([
    
  2482.         'Text:Function destroy layout',
    
  2483.         'ClassText:Class componentWillUnmount',
    
  2484.         'Text:Function destroy passive',
    
  2485.       ]);
    
  2486.     });
    
  2487.   });
    
  2488. 
    
  2489.   describe('refs within a tree that re-suspends in an update', () => {
    
  2490.     function RefCheckerOuter({Component}) {
    
  2491.       const refObject = React.useRef(null);
    
  2492. 
    
  2493.       const manualRef = React.useMemo(() => ({current: null}), []);
    
  2494.       const refCallback = React.useCallback(value => {
    
  2495.         Scheduler.log(`RefCheckerOuter refCallback value? ${value != null}`);
    
  2496.         manualRef.current = value;
    
  2497.       }, []);
    
  2498. 
    
  2499.       Scheduler.log(`RefCheckerOuter render`);
    
  2500. 
    
  2501.       React.useLayoutEffect(() => {
    
  2502.         Scheduler.log(
    
  2503.           `RefCheckerOuter create layout refObject? ${
    
  2504.             refObject.current != null
    
  2505.           } refCallback? ${manualRef.current != null}`,
    
  2506.         );
    
  2507.         return () => {
    
  2508.           Scheduler.log(
    
  2509.             `RefCheckerOuter destroy layout refObject? ${
    
  2510.               refObject.current != null
    
  2511.             } refCallback? ${manualRef.current != null}`,
    
  2512.           );
    
  2513.         };
    
  2514.       }, []);
    
  2515. 
    
  2516.       return (
    
  2517.         <>
    
  2518.           <Component ref={refObject} prop="refObject">
    
  2519.             <RefCheckerInner forwardedRef={refObject} text="refObject" />
    
  2520.           </Component>
    
  2521.           <Component ref={refCallback} prop="refCallback">
    
  2522.             <RefCheckerInner forwardedRef={manualRef} text="refCallback" />
    
  2523.           </Component>
    
  2524.         </>
    
  2525.       );
    
  2526.     }
    
  2527. 
    
  2528.     function RefCheckerInner({forwardedRef, text}) {
    
  2529.       Scheduler.log(`RefCheckerInner:${text} render`);
    
  2530.       React.useLayoutEffect(() => {
    
  2531.         Scheduler.log(
    
  2532.           `RefCheckerInner:${text} create layout ref? ${
    
  2533.             forwardedRef.current != null
    
  2534.           }`,
    
  2535.         );
    
  2536.         return () => {
    
  2537.           Scheduler.log(
    
  2538.             `RefCheckerInner:${text} destroy layout ref? ${
    
  2539.               forwardedRef.current != null
    
  2540.             }`,
    
  2541.           );
    
  2542.         };
    
  2543.       }, []);
    
  2544.       return null;
    
  2545.     }
    
  2546. 
    
  2547.     // @gate enableLegacyCache
    
  2548.     it('should not be cleared within legacy roots', async () => {
    
  2549.       class ClassComponent extends React.Component {
    
  2550.         render() {
    
  2551.           Scheduler.log(`ClassComponent:${this.props.prop} render`);
    
  2552.           return this.props.children;
    
  2553.         }
    
  2554.       }
    
  2555. 
    
  2556.       function App({children}) {
    
  2557.         Scheduler.log(`App render`);
    
  2558.         return (
    
  2559.           <Suspense fallback={<Text text="Fallback" />}>
    
  2560.             {children}
    
  2561.             <RefCheckerOuter Component={ClassComponent} />
    
  2562.           </Suspense>
    
  2563.         );
    
  2564.       }
    
  2565. 
    
  2566.       await act(() => {
    
  2567.         ReactNoop.renderLegacySyncRoot(<App />);
    
  2568.       });
    
  2569.       assertLog([
    
  2570.         'App render',
    
  2571.         'RefCheckerOuter render',
    
  2572.         'ClassComponent:refObject render',
    
  2573.         'RefCheckerInner:refObject render',
    
  2574.         'ClassComponent:refCallback render',
    
  2575.         'RefCheckerInner:refCallback render',
    
  2576.         'RefCheckerInner:refObject create layout ref? false',
    
  2577.         'RefCheckerInner:refCallback create layout ref? false',
    
  2578.         'RefCheckerOuter refCallback value? true',
    
  2579.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2580.       ]);
    
  2581.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2582. 
    
  2583.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  2584.       await act(() => {
    
  2585.         ReactNoop.renderLegacySyncRoot(
    
  2586.           <App children={<AsyncText text="Async" ms={1000} />} />,
    
  2587.         );
    
  2588.       });
    
  2589.       await advanceTimers(1000);
    
  2590.       assertLog([
    
  2591.         'App render',
    
  2592.         'Suspend:Async',
    
  2593.         'RefCheckerOuter render',
    
  2594.         'ClassComponent:refObject render',
    
  2595.         'RefCheckerInner:refObject render',
    
  2596.         'ClassComponent:refCallback render',
    
  2597.         'RefCheckerInner:refCallback render',
    
  2598.         'Text:Fallback render',
    
  2599.         'Text:Fallback create layout',
    
  2600.         'Text:Fallback create passive',
    
  2601.       ]);
    
  2602.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
    
  2603. 
    
  2604.       // Resolving the suspended resource should re-create inner layout effects.
    
  2605.       await act(async () => {
    
  2606.         await resolveText('Async');
    
  2607.       });
    
  2608.       assertLog([
    
  2609.         'AsyncText:Async render',
    
  2610.         'Text:Fallback destroy layout',
    
  2611.         'AsyncText:Async create layout',
    
  2612.         'Text:Fallback destroy passive',
    
  2613.         'AsyncText:Async create passive',
    
  2614.       ]);
    
  2615.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Async" />);
    
  2616. 
    
  2617.       await act(() => {
    
  2618.         ReactNoop.renderLegacySyncRoot(null);
    
  2619.       });
    
  2620.       assertLog([
    
  2621.         'AsyncText:Async destroy layout',
    
  2622.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2623.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2624.         'RefCheckerOuter refCallback value? false',
    
  2625.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2626.         'AsyncText:Async destroy passive',
    
  2627.       ]);
    
  2628.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2629.     });
    
  2630. 
    
  2631.     // @gate enableLegacyCache
    
  2632.     it('should be cleared and reset for host components', async () => {
    
  2633.       function App({children}) {
    
  2634.         Scheduler.log(`App render`);
    
  2635.         return (
    
  2636.           <Suspense fallback={<Text text="Fallback" />}>
    
  2637.             {children}
    
  2638.             <RefCheckerOuter Component="span" />
    
  2639.           </Suspense>
    
  2640.         );
    
  2641.       }
    
  2642. 
    
  2643.       // Mount
    
  2644.       await act(() => {
    
  2645.         ReactNoop.render(<App />);
    
  2646.       });
    
  2647.       assertLog([
    
  2648.         'App render',
    
  2649.         'RefCheckerOuter render',
    
  2650.         'RefCheckerInner:refObject render',
    
  2651.         'RefCheckerInner:refCallback render',
    
  2652.         'RefCheckerInner:refObject create layout ref? false',
    
  2653.         'RefCheckerInner:refCallback create layout ref? false',
    
  2654.         'RefCheckerOuter refCallback value? true',
    
  2655.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2656.       ]);
    
  2657.       expect(ReactNoop).toMatchRenderedOutput(
    
  2658.         <>
    
  2659.           <span prop="refObject" />
    
  2660.           <span prop="refCallback" />
    
  2661.         </>,
    
  2662.       );
    
  2663. 
    
  2664.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  2665.       await act(() => {
    
  2666.         ReactNoop.render(
    
  2667.           <App children={<AsyncText text="Async" ms={1000} />} />,
    
  2668.         );
    
  2669.       });
    
  2670.       await advanceTimers(1000);
    
  2671.       assertLog([
    
  2672.         'App render',
    
  2673.         'Suspend:Async',
    
  2674.         'Text:Fallback render',
    
  2675.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2676.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2677.         'RefCheckerOuter refCallback value? false',
    
  2678.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2679.         'Text:Fallback create layout',
    
  2680.         'Text:Fallback create passive',
    
  2681.       ]);
    
  2682.       expect(ReactNoop).toMatchRenderedOutput(
    
  2683.         <>
    
  2684.           <span prop="refObject" hidden={true} />
    
  2685.           <span prop="refCallback" hidden={true} />
    
  2686.           <span prop="Fallback" />
    
  2687.         </>,
    
  2688.       );
    
  2689. 
    
  2690.       // Resolving the suspended resource should re-create inner layout effects.
    
  2691.       await act(async () => {
    
  2692.         await resolveText('Async');
    
  2693.       });
    
  2694.       assertLog([
    
  2695.         'AsyncText:Async render',
    
  2696.         'RefCheckerOuter render',
    
  2697.         'RefCheckerInner:refObject render',
    
  2698.         'RefCheckerInner:refCallback render',
    
  2699.         'Text:Fallback destroy layout',
    
  2700.         'AsyncText:Async create layout',
    
  2701.         'RefCheckerInner:refObject create layout ref? false',
    
  2702.         'RefCheckerInner:refCallback create layout ref? false',
    
  2703.         'RefCheckerOuter refCallback value? true',
    
  2704.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2705.         'Text:Fallback destroy passive',
    
  2706.         'AsyncText:Async create passive',
    
  2707.       ]);
    
  2708.       expect(ReactNoop).toMatchRenderedOutput(
    
  2709.         <>
    
  2710.           <span prop="Async" />
    
  2711.           <span prop="refObject" />
    
  2712.           <span prop="refCallback" />
    
  2713.         </>,
    
  2714.       );
    
  2715. 
    
  2716.       await act(() => {
    
  2717.         ReactNoop.render(null);
    
  2718.       });
    
  2719.       assertLog([
    
  2720.         'AsyncText:Async destroy layout',
    
  2721.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2722.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2723.         'RefCheckerOuter refCallback value? false',
    
  2724.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2725.         'AsyncText:Async destroy passive',
    
  2726.       ]);
    
  2727.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2728.     });
    
  2729. 
    
  2730.     // @gate enableLegacyCache
    
  2731.     it('should be cleared and reset for class components', async () => {
    
  2732.       class ClassComponent extends React.Component {
    
  2733.         render() {
    
  2734.           Scheduler.log(`ClassComponent:${this.props.prop} render`);
    
  2735.           return this.props.children;
    
  2736.         }
    
  2737.       }
    
  2738. 
    
  2739.       function App({children}) {
    
  2740.         Scheduler.log(`App render`);
    
  2741.         return (
    
  2742.           <Suspense fallback={<Text text="Fallback" />}>
    
  2743.             {children}
    
  2744.             <RefCheckerOuter Component={ClassComponent} />
    
  2745.           </Suspense>
    
  2746.         );
    
  2747.       }
    
  2748. 
    
  2749.       // Mount
    
  2750.       await act(() => {
    
  2751.         ReactNoop.render(<App />);
    
  2752.       });
    
  2753.       assertLog([
    
  2754.         'App render',
    
  2755.         'RefCheckerOuter render',
    
  2756.         'ClassComponent:refObject render',
    
  2757.         'RefCheckerInner:refObject render',
    
  2758.         'ClassComponent:refCallback render',
    
  2759.         'RefCheckerInner:refCallback render',
    
  2760.         'RefCheckerInner:refObject create layout ref? false',
    
  2761.         'RefCheckerInner:refCallback create layout ref? false',
    
  2762.         'RefCheckerOuter refCallback value? true',
    
  2763.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2764.       ]);
    
  2765.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2766. 
    
  2767.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  2768.       await act(() => {
    
  2769.         ReactNoop.render(
    
  2770.           <App children={<AsyncText text="Async" ms={1000} />} />,
    
  2771.         );
    
  2772.       });
    
  2773.       await advanceTimers(1000);
    
  2774.       assertLog([
    
  2775.         'App render',
    
  2776.         'Suspend:Async',
    
  2777.         'Text:Fallback render',
    
  2778.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2779.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2780.         'RefCheckerOuter refCallback value? false',
    
  2781.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2782.         'Text:Fallback create layout',
    
  2783.         'Text:Fallback create passive',
    
  2784.       ]);
    
  2785.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
    
  2786. 
    
  2787.       // Resolving the suspended resource should re-create inner layout effects.
    
  2788.       await act(async () => {
    
  2789.         await resolveText('Async');
    
  2790.       });
    
  2791.       assertLog([
    
  2792.         'AsyncText:Async render',
    
  2793.         'RefCheckerOuter render',
    
  2794.         'ClassComponent:refObject render',
    
  2795.         'RefCheckerInner:refObject render',
    
  2796.         'ClassComponent:refCallback render',
    
  2797.         'RefCheckerInner:refCallback render',
    
  2798.         'Text:Fallback destroy layout',
    
  2799.         'AsyncText:Async create layout',
    
  2800.         'RefCheckerInner:refObject create layout ref? false',
    
  2801.         'RefCheckerInner:refCallback create layout ref? false',
    
  2802.         'RefCheckerOuter refCallback value? true',
    
  2803.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2804.         'Text:Fallback destroy passive',
    
  2805.         'AsyncText:Async create passive',
    
  2806.       ]);
    
  2807.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Async" />);
    
  2808. 
    
  2809.       await act(() => {
    
  2810.         ReactNoop.render(null);
    
  2811.       });
    
  2812.       assertLog([
    
  2813.         'AsyncText:Async destroy layout',
    
  2814.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2815.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2816.         'RefCheckerOuter refCallback value? false',
    
  2817.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2818.         'AsyncText:Async destroy passive',
    
  2819.       ]);
    
  2820.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2821.     });
    
  2822. 
    
  2823.     // @gate enableLegacyCache
    
  2824.     it('should be cleared and reset for function components with useImperativeHandle', async () => {
    
  2825.       const FunctionComponent = React.forwardRef((props, ref) => {
    
  2826.         Scheduler.log('FunctionComponent render');
    
  2827.         React.useImperativeHandle(
    
  2828.           ref,
    
  2829.           () => ({
    
  2830.             // Noop
    
  2831.           }),
    
  2832.           [],
    
  2833.         );
    
  2834.         return props.children;
    
  2835.       });
    
  2836.       FunctionComponent.displayName = 'FunctionComponent';
    
  2837. 
    
  2838.       function App({children}) {
    
  2839.         Scheduler.log(`App render`);
    
  2840.         return (
    
  2841.           <Suspense fallback={<Text text="Fallback" />}>
    
  2842.             {children}
    
  2843.             <RefCheckerOuter Component={FunctionComponent} />
    
  2844.           </Suspense>
    
  2845.         );
    
  2846.       }
    
  2847. 
    
  2848.       // Mount
    
  2849.       await act(() => {
    
  2850.         ReactNoop.render(<App />);
    
  2851.       });
    
  2852.       assertLog([
    
  2853.         'App render',
    
  2854.         'RefCheckerOuter render',
    
  2855.         'FunctionComponent render',
    
  2856.         'RefCheckerInner:refObject render',
    
  2857.         'FunctionComponent render',
    
  2858.         'RefCheckerInner:refCallback render',
    
  2859.         'RefCheckerInner:refObject create layout ref? false',
    
  2860.         'RefCheckerInner:refCallback create layout ref? false',
    
  2861.         'RefCheckerOuter refCallback value? true',
    
  2862.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2863.       ]);
    
  2864.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2865. 
    
  2866.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  2867.       await act(() => {
    
  2868.         ReactNoop.render(
    
  2869.           <App children={<AsyncText text="Async" ms={1000} />} />,
    
  2870.         );
    
  2871.       });
    
  2872.       await advanceTimers(1000);
    
  2873.       assertLog([
    
  2874.         'App render',
    
  2875.         'Suspend:Async',
    
  2876.         'Text:Fallback render',
    
  2877.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2878.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2879.         'RefCheckerOuter refCallback value? false',
    
  2880.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2881.         'Text:Fallback create layout',
    
  2882.         'Text:Fallback create passive',
    
  2883.       ]);
    
  2884.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
    
  2885. 
    
  2886.       // Resolving the suspended resource should re-create inner layout effects.
    
  2887.       await act(async () => {
    
  2888.         await resolveText('Async');
    
  2889.       });
    
  2890.       assertLog([
    
  2891.         'AsyncText:Async render',
    
  2892.         'RefCheckerOuter render',
    
  2893.         'FunctionComponent render',
    
  2894.         'RefCheckerInner:refObject render',
    
  2895.         'FunctionComponent render',
    
  2896.         'RefCheckerInner:refCallback render',
    
  2897.         'Text:Fallback destroy layout',
    
  2898.         'AsyncText:Async create layout',
    
  2899.         'RefCheckerInner:refObject create layout ref? false',
    
  2900.         'RefCheckerInner:refCallback create layout ref? false',
    
  2901.         'RefCheckerOuter refCallback value? true',
    
  2902.         'RefCheckerOuter create layout refObject? true refCallback? true',
    
  2903.         'Text:Fallback destroy passive',
    
  2904.         'AsyncText:Async create passive',
    
  2905.       ]);
    
  2906.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Async" />);
    
  2907. 
    
  2908.       await act(() => {
    
  2909.         ReactNoop.render(null);
    
  2910.       });
    
  2911.       assertLog([
    
  2912.         'AsyncText:Async destroy layout',
    
  2913.         'RefCheckerOuter destroy layout refObject? true refCallback? true',
    
  2914.         'RefCheckerInner:refObject destroy layout ref? false',
    
  2915.         'RefCheckerOuter refCallback value? false',
    
  2916.         'RefCheckerInner:refCallback destroy layout ref? false',
    
  2917.         'AsyncText:Async destroy passive',
    
  2918.       ]);
    
  2919.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2920.     });
    
  2921. 
    
  2922.     // @gate enableLegacyCache
    
  2923.     it('should not reset for user-managed values', async () => {
    
  2924.       function RefChecker({forwardedRef}) {
    
  2925.         Scheduler.log(`RefChecker render`);
    
  2926.         React.useLayoutEffect(() => {
    
  2927.           Scheduler.log(
    
  2928.             `RefChecker create layout ref? ${forwardedRef.current === 'test'}`,
    
  2929.           );
    
  2930.           return () => {
    
  2931.             Scheduler.log(
    
  2932.               `RefChecker destroy layout ref? ${
    
  2933.                 forwardedRef.current === 'test'
    
  2934.               }`,
    
  2935.             );
    
  2936.           };
    
  2937.         }, []);
    
  2938.         return null;
    
  2939.       }
    
  2940. 
    
  2941.       function App({children = null}) {
    
  2942.         const ref = React.useRef('test');
    
  2943.         Scheduler.log(`App render`);
    
  2944.         React.useLayoutEffect(() => {
    
  2945.           Scheduler.log(`App create layout ref? ${ref.current === 'test'}`);
    
  2946.           return () => {
    
  2947.             Scheduler.log(`App destroy layout ref? ${ref.current === 'test'}`);
    
  2948.           };
    
  2949.         }, []);
    
  2950.         return (
    
  2951.           <Suspense fallback={<Text text="Fallback" />}>
    
  2952.             {children}
    
  2953.             <RefChecker forwardedRef={ref} />
    
  2954.           </Suspense>
    
  2955.         );
    
  2956.       }
    
  2957. 
    
  2958.       // Mount
    
  2959.       await act(() => {
    
  2960.         ReactNoop.render(<App />);
    
  2961.       });
    
  2962.       assertLog([
    
  2963.         'App render',
    
  2964.         'RefChecker render',
    
  2965.         'RefChecker create layout ref? true',
    
  2966.         'App create layout ref? true',
    
  2967.       ]);
    
  2968.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  2969. 
    
  2970.       // Suspend the inner Suspense subtree (only inner effects should be destroyed)
    
  2971.       await act(() => {
    
  2972.         ReactNoop.render(
    
  2973.           <App children={<AsyncText text="Async" ms={1000} />} />,
    
  2974.         );
    
  2975.       });
    
  2976.       await advanceTimers(1000);
    
  2977.       assertLog([
    
  2978.         'App render',
    
  2979.         'Suspend:Async',
    
  2980.         'Text:Fallback render',
    
  2981.         'RefChecker destroy layout ref? true',
    
  2982.         'Text:Fallback create layout',
    
  2983.         'Text:Fallback create passive',
    
  2984.       ]);
    
  2985.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
    
  2986. 
    
  2987.       // Resolving the suspended resource should re-create inner layout effects.
    
  2988.       await act(async () => {
    
  2989.         await resolveText('Async');
    
  2990.       });
    
  2991.       assertLog([
    
  2992.         'AsyncText:Async render',
    
  2993.         'RefChecker render',
    
  2994.         'Text:Fallback destroy layout',
    
  2995.         'AsyncText:Async create layout',
    
  2996.         'RefChecker create layout ref? true',
    
  2997.         'Text:Fallback destroy passive',
    
  2998.         'AsyncText:Async create passive',
    
  2999.       ]);
    
  3000.       expect(ReactNoop).toMatchRenderedOutput(<span prop="Async" />);
    
  3001. 
    
  3002.       await act(() => {
    
  3003.         ReactNoop.render(null);
    
  3004.       });
    
  3005.       assertLog([
    
  3006.         'App destroy layout ref? true',
    
  3007.         'AsyncText:Async destroy layout',
    
  3008.         'RefChecker destroy layout ref? true',
    
  3009.         'AsyncText:Async destroy passive',
    
  3010.       ]);
    
  3011.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  3012.     });
    
  3013. 
    
  3014.     describe('that throw errors', () => {
    
  3015.       // @gate enableLegacyCache
    
  3016.       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
    
  3017.       it('are properly handled in ref callbacks', async () => {
    
  3018.         let useRefCallbackShouldThrow = false;
    
  3019. 
    
  3020.         function ThrowsInRefCallback() {
    
  3021.           Scheduler.log('ThrowsInRefCallback render');
    
  3022.           const refCallback = React.useCallback(value => {
    
  3023.             Scheduler.log('ThrowsInRefCallback refCallback ref? ' + !!value);
    
  3024.             if (useRefCallbackShouldThrow) {
    
  3025.               throw Error('expected');
    
  3026.             }
    
  3027.           }, []);
    
  3028.           return <span ref={refCallback} prop="ThrowsInRefCallback" />;
    
  3029.         }
    
  3030. 
    
  3031.         function App({children = null}) {
    
  3032.           Scheduler.log('App render');
    
  3033.           React.useLayoutEffect(() => {
    
  3034.             Scheduler.log('App create layout');
    
  3035.             return () => {
    
  3036.               Scheduler.log('App destroy layout');
    
  3037.             };
    
  3038.           }, []);
    
  3039.           return (
    
  3040.             <>
    
  3041.               <Suspense fallback={<Text text="Fallback" />}>
    
  3042.                 {children}
    
  3043.                 <ThrowsInRefCallback />
    
  3044.                 <Text text="Inside" />
    
  3045.               </Suspense>
    
  3046.               <Text text="Outside" />
    
  3047.             </>
    
  3048.           );
    
  3049.         }
    
  3050. 
    
  3051.         await act(() => {
    
  3052.           ReactNoop.render(
    
  3053.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  3054.               <App />
    
  3055.             </ErrorBoundary>,
    
  3056.           );
    
  3057.         });
    
  3058.         assertLog([
    
  3059.           'ErrorBoundary render: try',
    
  3060.           'App render',
    
  3061.           'ThrowsInRefCallback render',
    
  3062.           'Text:Inside render',
    
  3063.           'Text:Outside render',
    
  3064.           'ThrowsInRefCallback refCallback ref? true',
    
  3065.           'Text:Inside create layout',
    
  3066.           'Text:Outside create layout',
    
  3067.           'App create layout',
    
  3068.           'Text:Inside create passive',
    
  3069.           'Text:Outside create passive',
    
  3070.         ]);
    
  3071.         expect(ReactNoop).toMatchRenderedOutput(
    
  3072.           <>
    
  3073.             <span prop="ThrowsInRefCallback" />
    
  3074.             <span prop="Inside" />
    
  3075.             <span prop="Outside" />
    
  3076.           </>,
    
  3077.         );
    
  3078. 
    
  3079.         // Schedule an update that causes React to suspend.
    
  3080.         await act(() => {
    
  3081.           ReactNoop.render(
    
  3082.             <ErrorBoundary fallback={<Text text="Error" />}>
    
  3083.               <App>
    
  3084.                 <AsyncText text="Async" ms={1000} />
    
  3085.               </App>
    
  3086.             </ErrorBoundary>,
    
  3087.           );
    
  3088.         });
    
  3089.         assertLog([
    
  3090.           'ErrorBoundary render: try',
    
  3091.           'App render',
    
  3092.           'Suspend:Async',
    
  3093.           'Text:Fallback render',
    
  3094.           'Text:Outside render',
    
  3095.           'ThrowsInRefCallback refCallback ref? false',
    
  3096.           'Text:Inside destroy layout',
    
  3097.           'Text:Fallback create layout',
    
  3098.           'Text:Fallback create passive',
    
  3099.         ]);
    
  3100.         expect(ReactNoop).toMatchRenderedOutput(
    
  3101.           <>
    
  3102.             <span prop="ThrowsInRefCallback" hidden={true} />
    
  3103.             <span prop="Inside" hidden={true} />
    
  3104.             <span prop="Fallback" />
    
  3105.             <span prop="Outside" />
    
  3106.           </>,
    
  3107.         );
    
  3108. 
    
  3109.         // Resolve the pending suspense and throw
    
  3110.         useRefCallbackShouldThrow = true;
    
  3111.         await act(async () => {
    
  3112.           await resolveText('Async');
    
  3113.         });
    
  3114.         assertLog([
    
  3115.           'AsyncText:Async render',
    
  3116.           'ThrowsInRefCallback render',
    
  3117.           'Text:Inside render',
    
  3118. 
    
  3119.           // Even though an error was thrown in refCallback,
    
  3120.           // subsequent layout effects should still be created.
    
  3121.           'Text:Fallback destroy layout',
    
  3122.           'AsyncText:Async create layout',
    
  3123.           'ThrowsInRefCallback refCallback ref? true',
    
  3124.           'Text:Inside create layout',
    
  3125. 
    
  3126.           // Finish the in-progress commit
    
  3127.           'Text:Fallback destroy passive',
    
  3128.           'AsyncText:Async create passive',
    
  3129. 
    
  3130.           // Destroy layout and passive effects in the errored tree.
    
  3131.           'App destroy layout',
    
  3132.           'AsyncText:Async destroy layout',
    
  3133.           'ThrowsInRefCallback refCallback ref? false',
    
  3134.           'Text:Inside destroy layout',
    
  3135.           'Text:Outside destroy layout',
    
  3136.           'AsyncText:Async destroy passive',
    
  3137.           'Text:Inside destroy passive',
    
  3138.           'Text:Outside destroy passive',
    
  3139. 
    
  3140.           // Render fallback
    
  3141.           'ErrorBoundary render: catch',
    
  3142.           'Text:Error render',
    
  3143.           'Text:Error create layout',
    
  3144.           'Text:Error create passive',
    
  3145.         ]);
    
  3146.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
    
  3147.       });
    
  3148.     });
    
  3149.   });
    
  3150. });