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. 'use strict';
    
  12. 
    
  13. let React;
    
  14. let ReactNoopPersistent;
    
  15. let waitForAll;
    
  16. 
    
  17. describe('ReactPersistent', () => {
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20. 
    
  21.     React = require('react');
    
  22.     ReactNoopPersistent = require('react-noop-renderer/persistent');
    
  23.     const InternalTestUtils = require('internal-test-utils');
    
  24.     waitForAll = InternalTestUtils.waitForAll;
    
  25.   });
    
  26. 
    
  27.   // Inlined from shared folder so we can run this test on a bundle.
    
  28.   function createPortal(children, containerInfo, implementation, key) {
    
  29.     return {
    
  30.       $$typeof: Symbol.for('react.portal'),
    
  31.       key: key == null ? null : String(key),
    
  32.       children,
    
  33.       containerInfo,
    
  34.       implementation,
    
  35.     };
    
  36.   }
    
  37. 
    
  38.   function render(element) {
    
  39.     ReactNoopPersistent.render(element);
    
  40.   }
    
  41. 
    
  42.   function div(...children) {
    
  43.     children = children.map(c =>
    
  44.       typeof c === 'string' ? {text: c, hidden: false} : c,
    
  45.     );
    
  46.     return {type: 'div', children, prop: undefined, hidden: false};
    
  47.   }
    
  48. 
    
  49.   function span(prop) {
    
  50.     return {type: 'span', children: [], prop, hidden: false};
    
  51.   }
    
  52. 
    
  53.   // For persistent renderers we have to mix deep equality and reference equality checks
    
  54.   //  for which we need the actual children.
    
  55.   //  None of the tests are gated and the underlying implementation is rarely touch
    
  56.   //  so it's unlikely we deal with failing `toEqual` checks which cause bad performance.
    
  57.   function dangerouslyGetChildren() {
    
  58.     return ReactNoopPersistent.dangerouslyGetChildren();
    
  59.   }
    
  60. 
    
  61.   it('can update child nodes of a host instance', async () => {
    
  62.     function Bar(props) {
    
  63.       return <span>{props.text}</span>;
    
  64.     }
    
  65. 
    
  66.     function Foo(props) {
    
  67.       return (
    
  68.         <div>
    
  69.           <Bar text={props.text} />
    
  70.           {props.text === 'World' ? <Bar text={props.text} /> : null}
    
  71.         </div>
    
  72.       );
    
  73.     }
    
  74. 
    
  75.     render(<Foo text="Hello" />);
    
  76.     await waitForAll([]);
    
  77.     const originalChildren = dangerouslyGetChildren();
    
  78.     expect(originalChildren).toEqual([div(span())]);
    
  79. 
    
  80.     render(<Foo text="World" />);
    
  81.     await waitForAll([]);
    
  82.     const newChildren = dangerouslyGetChildren();
    
  83.     expect(newChildren).toEqual([div(span(), span())]);
    
  84. 
    
  85.     expect(originalChildren).toEqual([div(span())]);
    
  86.   });
    
  87. 
    
  88.   it('can reuse child nodes between updates', async () => {
    
  89.     function Baz(props) {
    
  90.       return <span prop={props.text} />;
    
  91.     }
    
  92.     class Bar extends React.Component {
    
  93.       shouldComponentUpdate(newProps) {
    
  94.         return false;
    
  95.       }
    
  96.       render() {
    
  97.         return <Baz text={this.props.text} />;
    
  98.       }
    
  99.     }
    
  100.     function Foo(props) {
    
  101.       return (
    
  102.         <div>
    
  103.           <Bar text={props.text} />
    
  104.           {props.text === 'World' ? <Bar text={props.text} /> : null}
    
  105.         </div>
    
  106.       );
    
  107.     }
    
  108. 
    
  109.     render(<Foo text="Hello" />);
    
  110.     await waitForAll([]);
    
  111.     const originalChildren = dangerouslyGetChildren();
    
  112.     expect(originalChildren).toEqual([div(span('Hello'))]);
    
  113. 
    
  114.     render(<Foo text="World" />);
    
  115.     await waitForAll([]);
    
  116.     const newChildren = dangerouslyGetChildren();
    
  117.     expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
    
  118. 
    
  119.     expect(originalChildren).toEqual([div(span('Hello'))]);
    
  120. 
    
  121.     // Reused node should have reference equality
    
  122.     expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
    
  123.   });
    
  124. 
    
  125.   it('can update child text nodes', async () => {
    
  126.     function Foo(props) {
    
  127.       return (
    
  128.         <div>
    
  129.           {props.text}
    
  130.           <span />
    
  131.         </div>
    
  132.       );
    
  133.     }
    
  134. 
    
  135.     render(<Foo text="Hello" />);
    
  136.     await waitForAll([]);
    
  137.     const originalChildren = dangerouslyGetChildren();
    
  138.     expect(originalChildren).toEqual([div('Hello', span())]);
    
  139. 
    
  140.     render(<Foo text="World" />);
    
  141.     await waitForAll([]);
    
  142.     const newChildren = dangerouslyGetChildren();
    
  143.     expect(newChildren).toEqual([div('World', span())]);
    
  144. 
    
  145.     expect(originalChildren).toEqual([div('Hello', span())]);
    
  146.   });
    
  147. 
    
  148.   it('supports portals', async () => {
    
  149.     function Parent(props) {
    
  150.       return <div>{props.children}</div>;
    
  151.     }
    
  152. 
    
  153.     function BailoutSpan() {
    
  154.       return <span />;
    
  155.     }
    
  156. 
    
  157.     class BailoutTest extends React.Component {
    
  158.       shouldComponentUpdate() {
    
  159.         return false;
    
  160.       }
    
  161.       render() {
    
  162.         return <BailoutSpan />;
    
  163.       }
    
  164.     }
    
  165. 
    
  166.     function Child(props) {
    
  167.       return (
    
  168.         <div>
    
  169.           <BailoutTest />
    
  170.           {props.children}
    
  171.         </div>
    
  172.       );
    
  173.     }
    
  174.     const portalContainer = {rootID: 'persistent-portal-test', children: []};
    
  175.     const emptyPortalChildSet = portalContainer.children;
    
  176.     render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
    
  177.     await waitForAll([]);
    
  178. 
    
  179.     expect(emptyPortalChildSet).toEqual([]);
    
  180. 
    
  181.     const originalChildren = dangerouslyGetChildren();
    
  182.     expect(originalChildren).toEqual([div()]);
    
  183.     const originalPortalChildren = portalContainer.children;
    
  184.     expect(originalPortalChildren).toEqual([div(span())]);
    
  185. 
    
  186.     render(
    
  187.       <Parent>
    
  188.         {createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
    
  189.       </Parent>,
    
  190.     );
    
  191.     await waitForAll([]);
    
  192. 
    
  193.     const newChildren = dangerouslyGetChildren();
    
  194.     expect(newChildren).toEqual([div()]);
    
  195.     const newPortalChildren = portalContainer.children;
    
  196.     expect(newPortalChildren).toEqual([div(span(), 'Hello ', 'World')]);
    
  197. 
    
  198.     expect(originalChildren).toEqual([div()]);
    
  199.     expect(originalPortalChildren).toEqual([div(span())]);
    
  200. 
    
  201.     // Reused portal children should have reference equality
    
  202.     expect(newPortalChildren[0].children[0]).toBe(
    
  203.       originalPortalChildren[0].children[0],
    
  204.     );
    
  205. 
    
  206.     // Deleting the Portal, should clear its children
    
  207.     render(<Parent />);
    
  208.     await waitForAll([]);
    
  209. 
    
  210.     const clearedPortalChildren = portalContainer.children;
    
  211.     expect(clearedPortalChildren).toEqual([]);
    
  212. 
    
  213.     // The original is unchanged.
    
  214.     expect(newPortalChildren).toEqual([div(span(), 'Hello ', 'World')]);
    
  215.   });
    
  216. });