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. let React;
    
  13. let ReactDOM;
    
  14. let ReactDOMServer;
    
  15. let ReactFeatureFlags;
    
  16. 
    
  17. describe('ReactLegacyContextDisabled', () => {
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20. 
    
  21.     React = require('react');
    
  22.     ReactDOM = require('react-dom');
    
  23.     ReactDOMServer = require('react-dom/server');
    
  24.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  25.     ReactFeatureFlags.disableLegacyContext = true;
    
  26.   });
    
  27. 
    
  28.   function formatValue(val) {
    
  29.     if (val === null) {
    
  30.       return 'null';
    
  31.     }
    
  32.     if (val === undefined) {
    
  33.       return 'undefined';
    
  34.     }
    
  35.     if (typeof val === 'string') {
    
  36.       return val;
    
  37.     }
    
  38.     return JSON.stringify(val);
    
  39.   }
    
  40. 
    
  41.   it('warns for legacy context', () => {
    
  42.     class LegacyProvider extends React.Component {
    
  43.       static childContextTypes = {
    
  44.         foo() {},
    
  45.       };
    
  46.       getChildContext() {
    
  47.         return {foo: 10};
    
  48.       }
    
  49.       render() {
    
  50.         return this.props.children;
    
  51.       }
    
  52.     }
    
  53. 
    
  54.     const lifecycleContextLog = [];
    
  55.     class LegacyClsConsumer extends React.Component {
    
  56.       static contextTypes = {
    
  57.         foo() {},
    
  58.       };
    
  59.       shouldComponentUpdate(nextProps, nextState, nextContext) {
    
  60.         lifecycleContextLog.push(nextContext);
    
  61.         return true;
    
  62.       }
    
  63.       UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
    
  64.         lifecycleContextLog.push(nextContext);
    
  65.       }
    
  66.       UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
    
  67.         lifecycleContextLog.push(nextContext);
    
  68.       }
    
  69.       render() {
    
  70.         return formatValue(this.context);
    
  71.       }
    
  72.     }
    
  73. 
    
  74.     function LegacyFnConsumer(props, context) {
    
  75.       return formatValue(context);
    
  76.     }
    
  77.     LegacyFnConsumer.contextTypes = {foo() {}};
    
  78. 
    
  79.     function RegularFn(props, context) {
    
  80.       return formatValue(context);
    
  81.     }
    
  82. 
    
  83.     const container = document.createElement('div');
    
  84.     expect(() => {
    
  85.       ReactDOM.render(
    
  86.         <LegacyProvider>
    
  87.           <span>
    
  88.             <LegacyClsConsumer />
    
  89.             <LegacyFnConsumer />
    
  90.             <RegularFn />
    
  91.           </span>
    
  92.         </LegacyProvider>,
    
  93.         container,
    
  94.       );
    
  95.     }).toErrorDev([
    
  96.       'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' +
    
  97.         'Use React.createContext() instead.',
    
  98.       'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' +
    
  99.         'Use React.createContext() with static contextType instead.',
    
  100.       'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
    
  101.         'Use React.createContext() with React.useContext() instead.',
    
  102.     ]);
    
  103.     expect(container.textContent).toBe('{}undefinedundefined');
    
  104.     expect(lifecycleContextLog).toEqual([]);
    
  105. 
    
  106.     // Test update path.
    
  107.     ReactDOM.render(
    
  108.       <LegacyProvider>
    
  109.         <span>
    
  110.           <LegacyClsConsumer />
    
  111.           <LegacyFnConsumer />
    
  112.           <RegularFn />
    
  113.         </span>
    
  114.       </LegacyProvider>,
    
  115.       container,
    
  116.     );
    
  117.     expect(container.textContent).toBe('{}undefinedundefined');
    
  118.     expect(lifecycleContextLog).toEqual([{}, {}, {}]);
    
  119.     ReactDOM.unmountComponentAtNode(container);
    
  120. 
    
  121.     // test server path.
    
  122.     let text;
    
  123.     expect(() => {
    
  124.       text = ReactDOMServer.renderToString(
    
  125.         <LegacyProvider>
    
  126.           <span>
    
  127.             <LegacyClsConsumer />
    
  128.             <LegacyFnConsumer />
    
  129.             <RegularFn />
    
  130.           </span>
    
  131.         </LegacyProvider>,
    
  132.         container,
    
  133.       );
    
  134.     }).toErrorDev([
    
  135.       'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' +
    
  136.         'Use React.createContext() instead.',
    
  137.       'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' +
    
  138.         'Use React.createContext() with static contextType instead.',
    
  139.       'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' +
    
  140.         'Use React.createContext() with React.useContext() instead.',
    
  141.     ]);
    
  142.     expect(text).toBe('<span>{}<!-- -->undefined<!-- -->undefined</span>');
    
  143.     expect(lifecycleContextLog).toEqual([{}, {}, {}]);
    
  144.   });
    
  145. 
    
  146.   it('renders a tree with modern context', () => {
    
  147.     const Ctx = React.createContext();
    
  148. 
    
  149.     class Provider extends React.Component {
    
  150.       render() {
    
  151.         return (
    
  152.           <Ctx.Provider value={this.props.value}>
    
  153.             {this.props.children}
    
  154.           </Ctx.Provider>
    
  155.         );
    
  156.       }
    
  157.     }
    
  158. 
    
  159.     class RenderPropConsumer extends React.Component {
    
  160.       render() {
    
  161.         return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>;
    
  162.       }
    
  163.     }
    
  164. 
    
  165.     const lifecycleContextLog = [];
    
  166.     class ContextTypeConsumer extends React.Component {
    
  167.       static contextType = Ctx;
    
  168.       shouldComponentUpdate(nextProps, nextState, nextContext) {
    
  169.         lifecycleContextLog.push(nextContext);
    
  170.         return true;
    
  171.       }
    
  172.       UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
    
  173.         lifecycleContextLog.push(nextContext);
    
  174.       }
    
  175.       UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
    
  176.         lifecycleContextLog.push(nextContext);
    
  177.       }
    
  178.       render() {
    
  179.         return formatValue(this.context);
    
  180.       }
    
  181.     }
    
  182. 
    
  183.     function FnConsumer() {
    
  184.       return formatValue(React.useContext(Ctx));
    
  185.     }
    
  186. 
    
  187.     const container = document.createElement('div');
    
  188.     ReactDOM.render(
    
  189.       <Provider value="a">
    
  190.         <span>
    
  191.           <RenderPropConsumer />
    
  192.           <ContextTypeConsumer />
    
  193.           <FnConsumer />
    
  194.         </span>
    
  195.       </Provider>,
    
  196.       container,
    
  197.     );
    
  198.     expect(container.textContent).toBe('aaa');
    
  199.     expect(lifecycleContextLog).toEqual([]);
    
  200. 
    
  201.     // Test update path
    
  202.     ReactDOM.render(
    
  203.       <Provider value="a">
    
  204.         <span>
    
  205.           <RenderPropConsumer />
    
  206.           <ContextTypeConsumer />
    
  207.           <FnConsumer />
    
  208.         </span>
    
  209.       </Provider>,
    
  210.       container,
    
  211.     );
    
  212.     expect(container.textContent).toBe('aaa');
    
  213.     expect(lifecycleContextLog).toEqual(['a', 'a', 'a']);
    
  214.     lifecycleContextLog.length = 0;
    
  215. 
    
  216.     ReactDOM.render(
    
  217.       <Provider value="b">
    
  218.         <span>
    
  219.           <RenderPropConsumer />
    
  220.           <ContextTypeConsumer />
    
  221.           <FnConsumer />
    
  222.         </span>
    
  223.       </Provider>,
    
  224.       container,
    
  225.     );
    
  226.     expect(container.textContent).toBe('bbb');
    
  227.     if (gate(flags => flags.enableLazyContextPropagation)) {
    
  228.       // In the lazy propagation implementation, we don't check if context
    
  229.       // changed until after shouldComponentUpdate is run.
    
  230.       expect(lifecycleContextLog).toEqual(['b', 'b', 'b']);
    
  231.     } else {
    
  232.       // In the eager implementation, a dirty flag was set when the parent
    
  233.       // changed, so we skipped sCU.
    
  234.       expect(lifecycleContextLog).toEqual(['b', 'b']);
    
  235.     }
    
  236.     ReactDOM.unmountComponentAtNode(container);
    
  237.   });
    
  238. });