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.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. const React = require('react');
    
  13. const PropTypes = require('prop-types');
    
  14. const ReactDOM = require('react-dom');
    
  15. const ReactTestUtils = require('react-dom/test-utils');
    
  16. const renderSubtreeIntoContainer =
    
  17.   require('react-dom').unstable_renderSubtreeIntoContainer;
    
  18. 
    
  19. describe('renderSubtreeIntoContainer', () => {
    
  20.   // @gate !disableLegacyContext
    
  21.   it('should pass context when rendering subtree elsewhere', () => {
    
  22.     const portal = document.createElement('div');
    
  23. 
    
  24.     class Component extends React.Component {
    
  25.       static contextTypes = {
    
  26.         foo: PropTypes.string.isRequired,
    
  27.       };
    
  28. 
    
  29.       render() {
    
  30.         return <div>{this.context.foo}</div>;
    
  31.       }
    
  32.     }
    
  33. 
    
  34.     class Parent extends React.Component {
    
  35.       static childContextTypes = {
    
  36.         foo: PropTypes.string.isRequired,
    
  37.       };
    
  38. 
    
  39.       getChildContext() {
    
  40.         return {
    
  41.           foo: 'bar',
    
  42.         };
    
  43.       }
    
  44. 
    
  45.       render() {
    
  46.         return null;
    
  47.       }
    
  48. 
    
  49.       componentDidMount() {
    
  50.         expect(
    
  51.           function () {
    
  52.             renderSubtreeIntoContainer(this, <Component />, portal);
    
  53.           }.bind(this),
    
  54.         ).toErrorDev(
    
  55.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  56.         );
    
  57.       }
    
  58.     }
    
  59. 
    
  60.     ReactTestUtils.renderIntoDocument(<Parent />);
    
  61.     expect(portal.firstChild.innerHTML).toBe('bar');
    
  62.   });
    
  63. 
    
  64.   it('should throw if parentComponent is invalid', () => {
    
  65.     const portal = document.createElement('div');
    
  66. 
    
  67.     class Component extends React.Component {
    
  68.       static contextTypes = {
    
  69.         foo: PropTypes.string.isRequired,
    
  70.       };
    
  71. 
    
  72.       render() {
    
  73.         return <div>{this.context.foo}</div>;
    
  74.       }
    
  75.     }
    
  76. 
    
  77.     // ESLint is confused here and thinks Parent is unused, presumably because
    
  78.     // it is only used inside of the class body?
    
  79.     // eslint-disable-next-line no-unused-vars
    
  80.     class Parent extends React.Component {
    
  81.       static childContextTypes = {
    
  82.         foo: PropTypes.string.isRequired,
    
  83.       };
    
  84. 
    
  85.       getChildContext() {
    
  86.         return {
    
  87.           foo: 'bar',
    
  88.         };
    
  89.       }
    
  90. 
    
  91.       render() {
    
  92.         return null;
    
  93.       }
    
  94. 
    
  95.       componentDidMount() {
    
  96.         expect(function () {
    
  97.           renderSubtreeIntoContainer(<Parent />, <Component />, portal);
    
  98.         }).toThrowError('parentComponentmust be a valid React Component');
    
  99.       }
    
  100.     }
    
  101.   });
    
  102. 
    
  103.   // @gate !disableLegacyContext
    
  104.   it('should update context if it changes due to setState', () => {
    
  105.     const container = document.createElement('div');
    
  106.     document.body.appendChild(container);
    
  107.     const portal = document.createElement('div');
    
  108. 
    
  109.     class Component extends React.Component {
    
  110.       static contextTypes = {
    
  111.         foo: PropTypes.string.isRequired,
    
  112.         getFoo: PropTypes.func.isRequired,
    
  113.       };
    
  114. 
    
  115.       render() {
    
  116.         return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
    
  117.       }
    
  118.     }
    
  119. 
    
  120.     class Parent extends React.Component {
    
  121.       static childContextTypes = {
    
  122.         foo: PropTypes.string.isRequired,
    
  123.         getFoo: PropTypes.func.isRequired,
    
  124.       };
    
  125. 
    
  126.       state = {
    
  127.         bar: 'initial',
    
  128.       };
    
  129. 
    
  130.       getChildContext() {
    
  131.         return {
    
  132.           foo: this.state.bar,
    
  133.           getFoo: () => this.state.bar,
    
  134.         };
    
  135.       }
    
  136. 
    
  137.       render() {
    
  138.         return null;
    
  139.       }
    
  140. 
    
  141.       componentDidMount() {
    
  142.         expect(() => {
    
  143.           renderSubtreeIntoContainer(this, <Component />, portal);
    
  144.         }).toErrorDev(
    
  145.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  146.         );
    
  147.       }
    
  148. 
    
  149.       componentDidUpdate() {
    
  150.         expect(() => {
    
  151.           renderSubtreeIntoContainer(this, <Component />, portal);
    
  152.         }).toErrorDev(
    
  153.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  154.         );
    
  155.       }
    
  156.     }
    
  157. 
    
  158.     const instance = ReactDOM.render(<Parent />, container);
    
  159.     expect(portal.firstChild.innerHTML).toBe('initial-initial');
    
  160.     instance.setState({bar: 'changed'});
    
  161.     expect(portal.firstChild.innerHTML).toBe('changed-changed');
    
  162.   });
    
  163. 
    
  164.   // @gate !disableLegacyContext
    
  165.   it('should update context if it changes due to re-render', () => {
    
  166.     const container = document.createElement('div');
    
  167.     document.body.appendChild(container);
    
  168.     const portal = document.createElement('div');
    
  169. 
    
  170.     class Component extends React.Component {
    
  171.       static contextTypes = {
    
  172.         foo: PropTypes.string.isRequired,
    
  173.         getFoo: PropTypes.func.isRequired,
    
  174.       };
    
  175. 
    
  176.       render() {
    
  177.         return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
    
  178.       }
    
  179.     }
    
  180. 
    
  181.     class Parent extends React.Component {
    
  182.       static childContextTypes = {
    
  183.         foo: PropTypes.string.isRequired,
    
  184.         getFoo: PropTypes.func.isRequired,
    
  185.       };
    
  186. 
    
  187.       getChildContext() {
    
  188.         return {
    
  189.           foo: this.props.bar,
    
  190.           getFoo: () => this.props.bar,
    
  191.         };
    
  192.       }
    
  193. 
    
  194.       render() {
    
  195.         return null;
    
  196.       }
    
  197. 
    
  198.       componentDidMount() {
    
  199.         expect(() => {
    
  200.           renderSubtreeIntoContainer(this, <Component />, portal);
    
  201.         }).toErrorDev(
    
  202.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  203.         );
    
  204.       }
    
  205. 
    
  206.       componentDidUpdate() {
    
  207.         expect(() => {
    
  208.           renderSubtreeIntoContainer(this, <Component />, portal);
    
  209.         }).toErrorDev(
    
  210.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  211.         );
    
  212.       }
    
  213.     }
    
  214. 
    
  215.     ReactDOM.render(<Parent bar="initial" />, container);
    
  216.     expect(portal.firstChild.innerHTML).toBe('initial-initial');
    
  217.     ReactDOM.render(<Parent bar="changed" />, container);
    
  218.     expect(portal.firstChild.innerHTML).toBe('changed-changed');
    
  219.   });
    
  220. 
    
  221.   it('should render portal with non-context-provider parent', () => {
    
  222.     const container = document.createElement('div');
    
  223.     document.body.appendChild(container);
    
  224.     const portal = document.createElement('div');
    
  225. 
    
  226.     class Parent extends React.Component {
    
  227.       render() {
    
  228.         return null;
    
  229.       }
    
  230. 
    
  231.       componentDidMount() {
    
  232.         expect(() => {
    
  233.           renderSubtreeIntoContainer(this, <div>hello</div>, portal);
    
  234.         }).toErrorDev(
    
  235.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  236.         );
    
  237.       }
    
  238.     }
    
  239. 
    
  240.     ReactDOM.render(<Parent bar="initial" />, container);
    
  241.     expect(portal.firstChild.innerHTML).toBe('hello');
    
  242.   });
    
  243. 
    
  244.   // @gate !disableLegacyContext
    
  245.   it('should get context through non-context-provider parent', () => {
    
  246.     const container = document.createElement('div');
    
  247.     document.body.appendChild(container);
    
  248.     const portal = document.createElement('div');
    
  249. 
    
  250.     class Parent extends React.Component {
    
  251.       render() {
    
  252.         return <Middle />;
    
  253.       }
    
  254.       getChildContext() {
    
  255.         return {value: this.props.value};
    
  256.       }
    
  257.       static childContextTypes = {
    
  258.         value: PropTypes.string.isRequired,
    
  259.       };
    
  260.     }
    
  261. 
    
  262.     class Middle extends React.Component {
    
  263.       render() {
    
  264.         return null;
    
  265.       }
    
  266.       componentDidMount() {
    
  267.         expect(() => {
    
  268.           renderSubtreeIntoContainer(this, <Child />, portal);
    
  269.         }).toErrorDev(
    
  270.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  271.         );
    
  272.       }
    
  273.     }
    
  274. 
    
  275.     class Child extends React.Component {
    
  276.       static contextTypes = {
    
  277.         value: PropTypes.string.isRequired,
    
  278.       };
    
  279.       render() {
    
  280.         return <div>{this.context.value}</div>;
    
  281.       }
    
  282.     }
    
  283. 
    
  284.     ReactDOM.render(<Parent value="foo" />, container);
    
  285.     expect(portal.textContent).toBe('foo');
    
  286.   });
    
  287. 
    
  288.   // @gate !disableLegacyContext
    
  289.   it('should get context through middle non-context-provider layer', () => {
    
  290.     const container = document.createElement('div');
    
  291.     document.body.appendChild(container);
    
  292.     const portal1 = document.createElement('div');
    
  293.     const portal2 = document.createElement('div');
    
  294. 
    
  295.     class Parent extends React.Component {
    
  296.       render() {
    
  297.         return null;
    
  298.       }
    
  299.       getChildContext() {
    
  300.         return {value: this.props.value};
    
  301.       }
    
  302.       componentDidMount() {
    
  303.         expect(() => {
    
  304.           renderSubtreeIntoContainer(this, <Middle />, portal1);
    
  305.         }).toErrorDev(
    
  306.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  307.         );
    
  308.       }
    
  309.       static childContextTypes = {
    
  310.         value: PropTypes.string.isRequired,
    
  311.       };
    
  312.     }
    
  313. 
    
  314.     class Middle extends React.Component {
    
  315.       render() {
    
  316.         return null;
    
  317.       }
    
  318.       componentDidMount() {
    
  319.         expect(() => {
    
  320.           renderSubtreeIntoContainer(this, <Child />, portal2);
    
  321.         }).toErrorDev(
    
  322.           'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported',
    
  323.         );
    
  324.       }
    
  325.     }
    
  326. 
    
  327.     class Child extends React.Component {
    
  328.       static contextTypes = {
    
  329.         value: PropTypes.string.isRequired,
    
  330.       };
    
  331.       render() {
    
  332.         return <div>{this.context.value}</div>;
    
  333.       }
    
  334.     }
    
  335. 
    
  336.     ReactDOM.render(<Parent value="foo" />, container);
    
  337.     expect(portal2.textContent).toBe('foo');
    
  338.   });
    
  339. 
    
  340.   it('fails gracefully when mixing React 15 and 16', () => {
    
  341.     class C extends React.Component {
    
  342.       render() {
    
  343.         return <div />;
    
  344.       }
    
  345.     }
    
  346.     const c = ReactDOM.render(<C />, document.createElement('div'));
    
  347.     // React 15 calls this:
    
  348.     // https://github.com/facebook/react/blob/77b71fc3c4/src/renderers/dom/client/ReactMount.js#L478-L479
    
  349.     expect(() => {
    
  350.       c._reactInternalInstance._processChildContext({});
    
  351.     }).toThrow(
    
  352.       __DEV__
    
  353.         ? '_processChildContext is not available in React 16+. This likely ' +
    
  354.             'means you have multiple copies of React and are attempting to nest ' +
    
  355.             'a React 15 tree inside a React 16 tree using ' +
    
  356.             "unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
    
  357.             'make sure you have only one copy of React (and ideally, switch to ' +
    
  358.             'ReactDOM.createPortal).'
    
  359.         : "Cannot read property '_processChildContext' of undefined",
    
  360.     );
    
  361.   });
    
  362. });