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.  * @emails react-core
    
  8.  * @jest-environment node
    
  9.  */
    
  10. 
    
  11. /* eslint-disable no-func-assign */
    
  12. 
    
  13. 'use strict';
    
  14. 
    
  15. let PropTypes;
    
  16. let React;
    
  17. let ReactNoop;
    
  18. let Suspense;
    
  19. let Scheduler;
    
  20. let act;
    
  21. let waitForAll;
    
  22. let assertLog;
    
  23. 
    
  24. describe('memo', () => {
    
  25.   beforeEach(() => {
    
  26.     jest.resetModules();
    
  27. 
    
  28.     PropTypes = require('prop-types');
    
  29.     React = require('react');
    
  30.     ReactNoop = require('react-noop-renderer');
    
  31.     Scheduler = require('scheduler');
    
  32.     act = require('internal-test-utils').act;
    
  33.     ({Suspense} = React);
    
  34. 
    
  35.     const InternalTestUtils = require('internal-test-utils');
    
  36.     waitForAll = InternalTestUtils.waitForAll;
    
  37.     assertLog = InternalTestUtils.assertLog;
    
  38.   });
    
  39. 
    
  40.   function Text(props) {
    
  41.     Scheduler.log(props.text);
    
  42.     return <span prop={props.text} />;
    
  43.   }
    
  44. 
    
  45.   async function fakeImport(result) {
    
  46.     return {default: result};
    
  47.   }
    
  48. 
    
  49.   it('warns when giving a ref (simple)', async () => {
    
  50.     // This test lives outside sharedTests because the wrappers don't forward
    
  51.     // refs properly, and they end up affecting the current owner which is used
    
  52.     // by the warning (making the messages not line up).
    
  53.     function App() {
    
  54.       return null;
    
  55.     }
    
  56.     App = React.memo(App);
    
  57.     function Outer() {
    
  58.       return <App ref={() => {}} />;
    
  59.     }
    
  60.     ReactNoop.render(<Outer />);
    
  61.     await expect(async () => await waitForAll([])).toErrorDev([
    
  62.       'Warning: Function components cannot be given refs. Attempts to access ' +
    
  63.         'this ref will fail.',
    
  64.     ]);
    
  65.   });
    
  66. 
    
  67.   it('warns when giving a ref (complex)', async () => {
    
  68.     // defaultProps means this won't use SimpleMemoComponent (as of this writing)
    
  69.     // SimpleMemoComponent is unobservable tho, so we can't check :)
    
  70.     function App() {
    
  71.       return null;
    
  72.     }
    
  73.     App.defaultProps = {};
    
  74.     App = React.memo(App);
    
  75.     function Outer() {
    
  76.       return <App ref={() => {}} />;
    
  77.     }
    
  78.     ReactNoop.render(<Outer />);
    
  79.     await expect(async () => await waitForAll([])).toErrorDev([
    
  80.       'App: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
    
  81.       'Warning: Function components cannot be given refs. Attempts to access ' +
    
  82.         'this ref will fail.',
    
  83.     ]);
    
  84.   });
    
  85. 
    
  86.   // Tests should run against both the lazy and non-lazy versions of `memo`.
    
  87.   // To make the tests work for both versions, we wrap the non-lazy version in
    
  88.   // a lazy function component.
    
  89.   sharedTests('normal', (...args) => {
    
  90.     const Memo = React.memo(...args);
    
  91.     function Indirection(props) {
    
  92.       return <Memo {...props} />;
    
  93.     }
    
  94.     return React.lazy(() => fakeImport(Indirection));
    
  95.   });
    
  96.   sharedTests('lazy', (...args) => {
    
  97.     const Memo = React.memo(...args);
    
  98.     return React.lazy(() => fakeImport(Memo));
    
  99.   });
    
  100. 
    
  101.   function sharedTests(label, memo) {
    
  102.     describe(`${label}`, () => {
    
  103.       it('bails out on props equality', async () => {
    
  104.         function Counter({count}) {
    
  105.           return <Text text={count} />;
    
  106.         }
    
  107.         Counter = memo(Counter);
    
  108. 
    
  109.         await act(() =>
    
  110.           ReactNoop.render(
    
  111.             <Suspense fallback={<Text text="Loading..." />}>
    
  112.               <Counter count={0} />
    
  113.             </Suspense>,
    
  114.           ),
    
  115.         );
    
  116.         assertLog(['Loading...', 0]);
    
  117.         expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
    
  118. 
    
  119.         // Should bail out because props have not changed
    
  120.         ReactNoop.render(
    
  121.           <Suspense>
    
  122.             <Counter count={0} />
    
  123.           </Suspense>,
    
  124.         );
    
  125.         await waitForAll([]);
    
  126.         expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
    
  127. 
    
  128.         // Should update because count prop changed
    
  129.         ReactNoop.render(
    
  130.           <Suspense>
    
  131.             <Counter count={1} />
    
  132.           </Suspense>,
    
  133.         );
    
  134.         await waitForAll([1]);
    
  135.         expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
    
  136.       });
    
  137. 
    
  138.       it("does not bail out if there's a context change", async () => {
    
  139.         const CountContext = React.createContext(0);
    
  140. 
    
  141.         function readContext(Context) {
    
  142.           const dispatcher =
    
  143.             React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
    
  144.               .ReactCurrentDispatcher.current;
    
  145.           return dispatcher.readContext(Context);
    
  146.         }
    
  147. 
    
  148.         function Counter(props) {
    
  149.           const count = readContext(CountContext);
    
  150.           return <Text text={`${props.label}: ${count}`} />;
    
  151.         }
    
  152.         Counter = memo(Counter);
    
  153. 
    
  154.         class Parent extends React.Component {
    
  155.           state = {count: 0};
    
  156.           render() {
    
  157.             return (
    
  158.               <Suspense fallback={<Text text="Loading..." />}>
    
  159.                 <CountContext.Provider value={this.state.count}>
    
  160.                   <Counter label="Count" />
    
  161.                 </CountContext.Provider>
    
  162.               </Suspense>
    
  163.             );
    
  164.           }
    
  165.         }
    
  166. 
    
  167.         const parent = React.createRef(null);
    
  168.         await act(() => ReactNoop.render(<Parent ref={parent} />));
    
  169.         assertLog(['Loading...', 'Count: 0']);
    
  170.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
    
  171. 
    
  172.         // Should bail out because props have not changed
    
  173.         ReactNoop.render(<Parent ref={parent} />);
    
  174.         await waitForAll([]);
    
  175.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
    
  176. 
    
  177.         // Should update because there was a context change
    
  178.         parent.current.setState({count: 1});
    
  179.         await waitForAll(['Count: 1']);
    
  180.         expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
    
  181.       });
    
  182. 
    
  183.       it('consistent behavior for reusing props object across different function component types', async () => {
    
  184.         // This test is a bit complicated because it relates to an
    
  185.         // implementation detail. We don't have strong guarantees that the props
    
  186.         // object is referentially equal during updates where we can't bail
    
  187.         // out anyway — like if the props are shallowly equal, but there's a
    
  188.         // local state or context update in the same batch.
    
  189.         //
    
  190.         // However, as a principle, we should aim to make the behavior
    
  191.         // consistent across different ways of memoizing a component. For
    
  192.         // example, React.memo has a different internal Fiber layout if you pass
    
  193.         // a normal function component (SimpleMemoComponent) versus if you pass
    
  194.         // a different type like forwardRef (MemoComponent). But this is an
    
  195.         // implementation detail. Wrapping a component in forwardRef (or
    
  196.         // React.lazy, etc) shouldn't affect whether the props object is reused
    
  197.         // during a bailout.
    
  198.         //
    
  199.         // So this test isn't primarily about asserting a particular behavior
    
  200.         // for reusing the props object; it's about making sure the behavior
    
  201.         // is consistent.
    
  202. 
    
  203.         const {useEffect, useState} = React;
    
  204. 
    
  205.         let setSimpleMemoStep;
    
  206.         const SimpleMemo = React.memo(props => {
    
  207.           const [step, setStep] = useState(0);
    
  208.           setSimpleMemoStep = setStep;
    
  209. 
    
  210.           const prevProps = React.useRef(props);
    
  211.           useEffect(() => {
    
  212.             if (props !== prevProps.current) {
    
  213.               prevProps.current = props;
    
  214.               Scheduler.log('Props changed [SimpleMemo]');
    
  215.             }
    
  216.           }, [props]);
    
  217. 
    
  218.           return <Text text={`SimpleMemo [${props.prop}${step}]`} />;
    
  219.         });
    
  220. 
    
  221.         let setComplexMemo;
    
  222.         const ComplexMemo = React.memo(
    
  223.           React.forwardRef((props, ref) => {
    
  224.             const [step, setStep] = useState(0);
    
  225.             setComplexMemo = setStep;
    
  226. 
    
  227.             const prevProps = React.useRef(props);
    
  228.             useEffect(() => {
    
  229.               if (props !== prevProps.current) {
    
  230.                 prevProps.current = props;
    
  231.                 Scheduler.log('Props changed [ComplexMemo]');
    
  232.               }
    
  233.             }, [props]);
    
  234. 
    
  235.             return <Text text={`ComplexMemo [${props.prop}${step}]`} />;
    
  236.           }),
    
  237.         );
    
  238. 
    
  239.         let setMemoWithIndirectionStep;
    
  240.         const MemoWithIndirection = React.memo(props => {
    
  241.           return <Indirection props={props} />;
    
  242.         });
    
  243.         function Indirection({props}) {
    
  244.           const [step, setStep] = useState(0);
    
  245.           setMemoWithIndirectionStep = setStep;
    
  246. 
    
  247.           const prevProps = React.useRef(props);
    
  248.           useEffect(() => {
    
  249.             if (props !== prevProps.current) {
    
  250.               prevProps.current = props;
    
  251.               Scheduler.log('Props changed [MemoWithIndirection]');
    
  252.             }
    
  253.           }, [props]);
    
  254. 
    
  255.           return <Text text={`MemoWithIndirection [${props.prop}${step}]`} />;
    
  256.         }
    
  257. 
    
  258.         function setLocalUpdateOnChildren(step) {
    
  259.           setSimpleMemoStep(step);
    
  260.           setMemoWithIndirectionStep(step);
    
  261.           setComplexMemo(step);
    
  262.         }
    
  263. 
    
  264.         function App({prop}) {
    
  265.           return (
    
  266.             <>
    
  267.               <SimpleMemo prop={prop} />
    
  268.               <ComplexMemo prop={prop} />
    
  269.               <MemoWithIndirection prop={prop} />
    
  270.             </>
    
  271.           );
    
  272.         }
    
  273. 
    
  274.         const root = ReactNoop.createRoot();
    
  275.         await act(() => {
    
  276.           root.render(<App prop="A" />);
    
  277.         });
    
  278.         assertLog([
    
  279.           'SimpleMemo [A0]',
    
  280.           'ComplexMemo [A0]',
    
  281.           'MemoWithIndirection [A0]',
    
  282.         ]);
    
  283. 
    
  284.         // Demonstrate what happens when the props change
    
  285.         await act(() => {
    
  286.           root.render(<App prop="B" />);
    
  287.         });
    
  288.         assertLog([
    
  289.           'SimpleMemo [B0]',
    
  290.           'ComplexMemo [B0]',
    
  291.           'MemoWithIndirection [B0]',
    
  292.           'Props changed [SimpleMemo]',
    
  293.           'Props changed [ComplexMemo]',
    
  294.           'Props changed [MemoWithIndirection]',
    
  295.         ]);
    
  296. 
    
  297.         // Demonstrate what happens when the prop object changes but there's a
    
  298.         // bailout because all the individual props are the same.
    
  299.         await act(() => {
    
  300.           root.render(<App prop="B" />);
    
  301.         });
    
  302.         // Nothing re-renders
    
  303.         assertLog([]);
    
  304. 
    
  305.         // Demonstrate what happens when the prop object changes, it bails out
    
  306.         // because all the props are the same, but we still render the
    
  307.         // children because there's a local update in the same batch.
    
  308.         await act(() => {
    
  309.           root.render(<App prop="B" />);
    
  310.           setLocalUpdateOnChildren(1);
    
  311.         });
    
  312.         // The components should re-render with the new local state, but none
    
  313.         // of the props objects should have changed
    
  314.         assertLog([
    
  315.           'SimpleMemo [B1]',
    
  316.           'ComplexMemo [B1]',
    
  317.           'MemoWithIndirection [B1]',
    
  318.         ]);
    
  319. 
    
  320.         // Do the same thing again. We should still reuse the props object.
    
  321.         await act(() => {
    
  322.           root.render(<App prop="B" />);
    
  323.           setLocalUpdateOnChildren(2);
    
  324.         });
    
  325.         // The components should re-render with the new local state, but none
    
  326.         // of the props objects should have changed
    
  327.         assertLog([
    
  328.           'SimpleMemo [B2]',
    
  329.           'ComplexMemo [B2]',
    
  330.           'MemoWithIndirection [B2]',
    
  331.         ]);
    
  332.       });
    
  333. 
    
  334.       it('accepts custom comparison function', async () => {
    
  335.         function Counter({count}) {
    
  336.           return <Text text={count} />;
    
  337.         }
    
  338.         Counter = memo(Counter, (oldProps, newProps) => {
    
  339.           Scheduler.log(
    
  340.             `Old count: ${oldProps.count}, New count: ${newProps.count}`,
    
  341.           );
    
  342.           return oldProps.count === newProps.count;
    
  343.         });
    
  344. 
    
  345.         await act(() =>
    
  346.           ReactNoop.render(
    
  347.             <Suspense fallback={<Text text="Loading..." />}>
    
  348.               <Counter count={0} />
    
  349.             </Suspense>,
    
  350.           ),
    
  351.         );
    
  352.         assertLog(['Loading...', 0]);
    
  353.         expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
    
  354. 
    
  355.         // Should bail out because props have not changed
    
  356.         ReactNoop.render(
    
  357.           <Suspense>
    
  358.             <Counter count={0} />
    
  359.           </Suspense>,
    
  360.         );
    
  361.         await waitForAll(['Old count: 0, New count: 0']);
    
  362.         expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
    
  363. 
    
  364.         // Should update because count prop changed
    
  365.         ReactNoop.render(
    
  366.           <Suspense>
    
  367.             <Counter count={1} />
    
  368.           </Suspense>,
    
  369.         );
    
  370.         await waitForAll(['Old count: 0, New count: 1', 1]);
    
  371.         expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
    
  372.       });
    
  373. 
    
  374.       it('supports non-pure class components', async () => {
    
  375.         class CounterInner extends React.Component {
    
  376.           static defaultProps = {suffix: '!'};
    
  377.           render() {
    
  378.             return <Text text={this.props.count + String(this.props.suffix)} />;
    
  379.           }
    
  380.         }
    
  381.         const Counter = memo(CounterInner);
    
  382. 
    
  383.         await act(() =>
    
  384.           ReactNoop.render(
    
  385.             <Suspense fallback={<Text text="Loading..." />}>
    
  386.               <Counter count={0} />
    
  387.             </Suspense>,
    
  388.           ),
    
  389.         );
    
  390.         assertLog(['Loading...', '0!']);
    
  391.         expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
    
  392. 
    
  393.         // Should bail out because props have not changed
    
  394.         ReactNoop.render(
    
  395.           <Suspense>
    
  396.             <Counter count={0} />
    
  397.           </Suspense>,
    
  398.         );
    
  399.         await waitForAll([]);
    
  400.         expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
    
  401. 
    
  402.         // Should update because count prop changed
    
  403.         ReactNoop.render(
    
  404.           <Suspense>
    
  405.             <Counter count={1} />
    
  406.           </Suspense>,
    
  407.         );
    
  408.         await waitForAll(['1!']);
    
  409.         expect(ReactNoop).toMatchRenderedOutput(<span prop="1!" />);
    
  410.       });
    
  411. 
    
  412.       it('supports defaultProps defined on the memo() return value', async () => {
    
  413.         function Counter({a, b, c, d, e}) {
    
  414.           return <Text text={a + b + c + d + e} />;
    
  415.         }
    
  416.         Counter.defaultProps = {
    
  417.           a: 1,
    
  418.         };
    
  419.         // Note! We intentionally use React.memo() rather than the injected memo().
    
  420.         // This tests a synchronous chain of React.memo() without lazy() in the middle.
    
  421.         Counter = React.memo(Counter);
    
  422.         Counter.defaultProps = {
    
  423.           b: 2,
    
  424.         };
    
  425.         Counter = React.memo(Counter);
    
  426.         Counter = React.memo(Counter); // Layer without defaultProps
    
  427.         Counter.defaultProps = {
    
  428.           c: 3,
    
  429.         };
    
  430.         Counter = React.memo(Counter);
    
  431.         Counter.defaultProps = {
    
  432.           d: 4,
    
  433.         };
    
  434.         // The final layer uses memo() from test fixture (which might be lazy).
    
  435.         Counter = memo(Counter);
    
  436. 
    
  437.         await expect(async () => {
    
  438.           await act(() => {
    
  439.             ReactNoop.render(
    
  440.               <Suspense fallback={<Text text="Loading..." />}>
    
  441.                 <Counter e={5} />
    
  442.               </Suspense>,
    
  443.             );
    
  444.           });
    
  445.           assertLog(['Loading...', 15]);
    
  446.         }).toErrorDev([
    
  447.           'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
    
  448.         ]);
    
  449.         expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
    
  450. 
    
  451.         // Should bail out because props have not changed
    
  452.         ReactNoop.render(
    
  453.           <Suspense>
    
  454.             <Counter e={5} />
    
  455.           </Suspense>,
    
  456.         );
    
  457.         await waitForAll([]);
    
  458.         expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
    
  459. 
    
  460.         // Should update because count prop changed
    
  461.         ReactNoop.render(
    
  462.           <Suspense>
    
  463.             <Counter e={10} />
    
  464.           </Suspense>,
    
  465.         );
    
  466.         await waitForAll([20]);
    
  467.         expect(ReactNoop).toMatchRenderedOutput(<span prop={20} />);
    
  468.       });
    
  469. 
    
  470.       it('warns if the first argument is undefined', () => {
    
  471.         expect(() => memo()).toErrorDev(
    
  472.           'memo: The first argument must be a component. Instead ' +
    
  473.             'received: undefined',
    
  474.           {withoutStack: true},
    
  475.         );
    
  476.       });
    
  477. 
    
  478.       it('warns if the first argument is null', () => {
    
  479.         expect(() => memo(null)).toErrorDev(
    
  480.           'memo: The first argument must be a component. Instead ' +
    
  481.             'received: null',
    
  482.           {withoutStack: true},
    
  483.         );
    
  484.       });
    
  485. 
    
  486.       it('validates propTypes declared on the inner component', async () => {
    
  487.         function FnInner(props) {
    
  488.           return props.inner;
    
  489.         }
    
  490.         FnInner.propTypes = {inner: PropTypes.number.isRequired};
    
  491.         const Fn = React.memo(FnInner);
    
  492. 
    
  493.         // Mount
    
  494.         await expect(async () => {
    
  495.           ReactNoop.render(<Fn inner="2" />);
    
  496.           await waitForAll([]);
    
  497.         }).toErrorDev(
    
  498.           'Invalid prop `inner` of type `string` supplied to `FnInner`, expected `number`.',
    
  499.         );
    
  500. 
    
  501.         // Update
    
  502.         await expect(async () => {
    
  503.           ReactNoop.render(<Fn inner={false} />);
    
  504.           await waitForAll([]);
    
  505.         }).toErrorDev(
    
  506.           'Invalid prop `inner` of type `boolean` supplied to `FnInner`, expected `number`.',
    
  507.         );
    
  508.       });
    
  509. 
    
  510.       it('validates propTypes declared on the outer component', async () => {
    
  511.         function FnInner(props) {
    
  512.           return props.outer;
    
  513.         }
    
  514.         const Fn = React.memo(FnInner);
    
  515.         Fn.propTypes = {outer: PropTypes.number.isRequired};
    
  516. 
    
  517.         // Mount
    
  518.         await expect(async () => {
    
  519.           ReactNoop.render(<Fn outer="3" />);
    
  520.           await waitForAll([]);
    
  521.         }).toErrorDev(
    
  522.           // Outer props are checked in createElement
    
  523.           'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.',
    
  524.         );
    
  525. 
    
  526.         // Update
    
  527.         await expect(async () => {
    
  528.           ReactNoop.render(<Fn outer={false} />);
    
  529.           await waitForAll([]);
    
  530.         }).toErrorDev(
    
  531.           // Outer props are checked in createElement
    
  532.           'Invalid prop `outer` of type `boolean` supplied to `FnInner`, expected `number`.',
    
  533.         );
    
  534.       });
    
  535. 
    
  536.       it('validates nested propTypes declarations', async () => {
    
  537.         function Inner(props) {
    
  538.           return props.inner + props.middle + props.outer;
    
  539.         }
    
  540.         Inner.propTypes = {inner: PropTypes.number.isRequired};
    
  541.         Inner.defaultProps = {inner: 0};
    
  542.         const Middle = React.memo(Inner);
    
  543.         Middle.propTypes = {middle: PropTypes.number.isRequired};
    
  544.         Middle.defaultProps = {middle: 0};
    
  545.         const Outer = React.memo(Middle);
    
  546.         Outer.propTypes = {outer: PropTypes.number.isRequired};
    
  547.         Outer.defaultProps = {outer: 0};
    
  548. 
    
  549.         // No warning expected because defaultProps satisfy both.
    
  550.         ReactNoop.render(
    
  551.           <div>
    
  552.             <Outer />
    
  553.           </div>,
    
  554.         );
    
  555.         await expect(async () => {
    
  556.           await waitForAll([]);
    
  557.         }).toErrorDev([
    
  558.           'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
    
  559.         ]);
    
  560. 
    
  561.         // Mount
    
  562.         await expect(async () => {
    
  563.           ReactNoop.render(
    
  564.             <div>
    
  565.               <Outer inner="2" middle="3" outer="4" />
    
  566.             </div>,
    
  567.           );
    
  568.           await waitForAll([]);
    
  569.         }).toErrorDev([
    
  570.           'Invalid prop `outer` of type `string` supplied to `Inner`, expected `number`.',
    
  571.           'Invalid prop `middle` of type `string` supplied to `Inner`, expected `number`.',
    
  572.           'Invalid prop `inner` of type `string` supplied to `Inner`, expected `number`.',
    
  573.         ]);
    
  574. 
    
  575.         // Update
    
  576.         await expect(async () => {
    
  577.           ReactNoop.render(
    
  578.             <div>
    
  579.               <Outer inner={false} middle={false} outer={false} />
    
  580.             </div>,
    
  581.           );
    
  582.           await waitForAll([]);
    
  583.         }).toErrorDev([
    
  584.           'Invalid prop `outer` of type `boolean` supplied to `Inner`, expected `number`.',
    
  585.           'Invalid prop `middle` of type `boolean` supplied to `Inner`, expected `number`.',
    
  586.           'Invalid prop `inner` of type `boolean` supplied to `Inner`, expected `number`.',
    
  587.         ]);
    
  588.       });
    
  589. 
    
  590.       it('does not drop lower priority state updates when bailing out at higher pri (simple)', async () => {
    
  591.         const {useState} = React;
    
  592. 
    
  593.         let setCounter;
    
  594.         const Counter = memo(() => {
    
  595.           const [counter, _setCounter] = useState(0);
    
  596.           setCounter = _setCounter;
    
  597.           return counter;
    
  598.         });
    
  599. 
    
  600.         function App() {
    
  601.           return (
    
  602.             <Suspense fallback="Loading...">
    
  603.               <Counter />
    
  604.             </Suspense>
    
  605.           );
    
  606.         }
    
  607. 
    
  608.         const root = ReactNoop.createRoot();
    
  609.         await act(() => {
    
  610.           root.render(<App />);
    
  611.         });
    
  612.         expect(root).toMatchRenderedOutput('0');
    
  613. 
    
  614.         await act(() => {
    
  615.           setCounter(1);
    
  616.           ReactNoop.discreteUpdates(() => {
    
  617.             root.render(<App />);
    
  618.           });
    
  619.         });
    
  620.         expect(root).toMatchRenderedOutput('1');
    
  621.       });
    
  622. 
    
  623.       it('does not drop lower priority state updates when bailing out at higher pri (complex)', async () => {
    
  624.         const {useState} = React;
    
  625. 
    
  626.         let setCounter;
    
  627.         const Counter = memo(
    
  628.           () => {
    
  629.             const [counter, _setCounter] = useState(0);
    
  630.             setCounter = _setCounter;
    
  631.             return counter;
    
  632.           },
    
  633.           (a, b) => a.complexProp.val === b.complexProp.val,
    
  634.         );
    
  635. 
    
  636.         function App() {
    
  637.           return (
    
  638.             <Suspense fallback="Loading...">
    
  639.               <Counter complexProp={{val: 1}} />
    
  640.             </Suspense>
    
  641.           );
    
  642.         }
    
  643. 
    
  644.         const root = ReactNoop.createRoot();
    
  645.         await act(() => {
    
  646.           root.render(<App />);
    
  647.         });
    
  648.         expect(root).toMatchRenderedOutput('0');
    
  649. 
    
  650.         await act(() => {
    
  651.           setCounter(1);
    
  652.           ReactNoop.discreteUpdates(() => {
    
  653.             root.render(<App />);
    
  654.           });
    
  655.         });
    
  656.         expect(root).toMatchRenderedOutput('1');
    
  657.       });
    
  658.     });
    
  659. 
    
  660.     it('should fall back to showing something meaningful if no displayName or name are present', () => {
    
  661.       const MemoComponent = React.memo(props => <div {...props} />);
    
  662.       MemoComponent.propTypes = {
    
  663.         required: PropTypes.string.isRequired,
    
  664.       };
    
  665. 
    
  666.       expect(() =>
    
  667.         ReactNoop.render(<MemoComponent optional="foo" />),
    
  668.       ).toErrorDev(
    
  669.         'Warning: Failed prop type: The prop `required` is marked as required in ' +
    
  670.           '`Memo`, but its value is `undefined`.',
    
  671.         // There's no component stack in this warning because the inner function is anonymous.
    
  672.         // If we wanted to support this (for the Error frames / source location)
    
  673.         // we could do this by updating ReactComponentStackFrame.
    
  674.         {withoutStack: true},
    
  675.       );
    
  676.     });
    
  677. 
    
  678.     it('should honor a displayName if set on the inner component in warnings', () => {
    
  679.       function Component(props) {
    
  680.         return <div {...props} />;
    
  681.       }
    
  682.       Component.displayName = 'Inner';
    
  683.       const MemoComponent = React.memo(Component);
    
  684.       MemoComponent.propTypes = {
    
  685.         required: PropTypes.string.isRequired,
    
  686.       };
    
  687. 
    
  688.       expect(() =>
    
  689.         ReactNoop.render(<MemoComponent optional="foo" />),
    
  690.       ).toErrorDev(
    
  691.         'Warning: Failed prop type: The prop `required` is marked as required in ' +
    
  692.           '`Inner`, but its value is `undefined`.\n' +
    
  693.           '    in Inner (at **)',
    
  694.       );
    
  695.     });
    
  696. 
    
  697.     it('should honor a displayName if set on the memo wrapper in warnings', () => {
    
  698.       const MemoComponent = React.memo(function Component(props) {
    
  699.         return <div {...props} />;
    
  700.       });
    
  701.       MemoComponent.displayName = 'Outer';
    
  702.       MemoComponent.propTypes = {
    
  703.         required: PropTypes.string.isRequired,
    
  704.       };
    
  705. 
    
  706.       expect(() =>
    
  707.         ReactNoop.render(<MemoComponent optional="foo" />),
    
  708.       ).toErrorDev(
    
  709.         'Warning: Failed prop type: The prop `required` is marked as required in ' +
    
  710.           '`Outer`, but its value is `undefined`.\n' +
    
  711.           '    in Component (at **)',
    
  712.       );
    
  713.     });
    
  714. 
    
  715.     it('should pass displayName to an anonymous inner component so it shows up in component stacks', () => {
    
  716.       const MemoComponent = React.memo(props => {
    
  717.         return <div {...props} />;
    
  718.       });
    
  719.       MemoComponent.displayName = 'Memo';
    
  720.       MemoComponent.propTypes = {
    
  721.         required: PropTypes.string.isRequired,
    
  722.       };
    
  723. 
    
  724.       expect(() =>
    
  725.         ReactNoop.render(<MemoComponent optional="foo" />),
    
  726.       ).toErrorDev(
    
  727.         'Warning: Failed prop type: The prop `required` is marked as required in ' +
    
  728.           '`Memo`, but its value is `undefined`.\n' +
    
  729.           '    in Memo (at **)',
    
  730.       );
    
  731.     });
    
  732. 
    
  733.     it('should honor a outer displayName when wrapped component and memo component set displayName at the same time.', () => {
    
  734.       function Component(props) {
    
  735.         return <div {...props} />;
    
  736.       }
    
  737.       Component.displayName = 'Inner';
    
  738. 
    
  739.       const MemoComponent = React.memo(Component);
    
  740.       MemoComponent.displayName = 'Outer';
    
  741.       MemoComponent.propTypes = {
    
  742.         required: PropTypes.string.isRequired,
    
  743.       };
    
  744. 
    
  745.       expect(() =>
    
  746.         ReactNoop.render(<MemoComponent optional="foo" />),
    
  747.       ).toErrorDev(
    
  748.         'Warning: Failed prop type: The prop `required` is marked as required in ' +
    
  749.           '`Outer`, but its value is `undefined`.\n' +
    
  750.           '    in Inner (at **)',
    
  751.       );
    
  752.     });
    
  753.   }
    
  754. });