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 ./scripts/jest/ReactDOMServerIntegrationEnvironment
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
    
  14. 
    
  15. let React;
    
  16. let ReactDOM;
    
  17. let ReactDOMServer;
    
  18. let ReactTestUtils;
    
  19. 
    
  20. function initModules() {
    
  21.   // Reset warning cache.
    
  22.   jest.resetModules();
    
  23.   React = require('react');
    
  24.   ReactDOM = require('react-dom');
    
  25.   ReactDOMServer = require('react-dom/server');
    
  26.   ReactTestUtils = require('react-dom/test-utils');
    
  27. 
    
  28.   // Make them available to the helpers.
    
  29.   return {
    
  30.     ReactDOM,
    
  31.     ReactDOMServer,
    
  32.     ReactTestUtils,
    
  33.   };
    
  34. }
    
  35. 
    
  36. const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
    
  37. 
    
  38. describe('ReactDOMServerIntegration', () => {
    
  39.   beforeEach(() => {
    
  40.     resetModules();
    
  41.   });
    
  42. 
    
  43.   describe('context', function () {
    
  44.     let Context, PurpleContextProvider, RedContextProvider, Consumer;
    
  45.     beforeEach(() => {
    
  46.       Context = React.createContext('none');
    
  47. 
    
  48.       class Parent extends React.Component {
    
  49.         render() {
    
  50.           return (
    
  51.             <Context.Provider value={this.props.text}>
    
  52.               {this.props.children}
    
  53.             </Context.Provider>
    
  54.           );
    
  55.         }
    
  56.       }
    
  57.       Consumer = Context.Consumer;
    
  58.       PurpleContextProvider = props => (
    
  59.         <Parent text="purple">{props.children}</Parent>
    
  60.       );
    
  61.       RedContextProvider = props => (
    
  62.         <Parent text="red">{props.children}</Parent>
    
  63.       );
    
  64.     });
    
  65. 
    
  66.     itRenders('class child with context', async render => {
    
  67.       class ClassChildWithContext extends React.Component {
    
  68.         render() {
    
  69.           return (
    
  70.             <div>
    
  71.               <Consumer>{text => text}</Consumer>
    
  72.             </div>
    
  73.           );
    
  74.         }
    
  75.       }
    
  76. 
    
  77.       const e = await render(
    
  78.         <PurpleContextProvider>
    
  79.           <ClassChildWithContext />
    
  80.         </PurpleContextProvider>,
    
  81.       );
    
  82.       expect(e.textContent).toBe('purple');
    
  83.     });
    
  84. 
    
  85.     itRenders('stateless child with context', async render => {
    
  86.       function FunctionChildWithContext(props) {
    
  87.         return <Consumer>{text => text}</Consumer>;
    
  88.       }
    
  89. 
    
  90.       const e = await render(
    
  91.         <PurpleContextProvider>
    
  92.           <FunctionChildWithContext />
    
  93.         </PurpleContextProvider>,
    
  94.       );
    
  95.       expect(e.textContent).toBe('purple');
    
  96.     });
    
  97. 
    
  98.     itRenders('class child with default context', async render => {
    
  99.       class ClassChildWithWrongContext extends React.Component {
    
  100.         render() {
    
  101.           return (
    
  102.             <div id="classWrongChild">
    
  103.               <Consumer>{text => text}</Consumer>
    
  104.             </div>
    
  105.           );
    
  106.         }
    
  107.       }
    
  108. 
    
  109.       const e = await render(<ClassChildWithWrongContext />);
    
  110.       expect(e.textContent).toBe('none');
    
  111.     });
    
  112. 
    
  113.     itRenders('stateless child with wrong context', async render => {
    
  114.       function FunctionChildWithWrongContext(props) {
    
  115.         return (
    
  116.           <div id="statelessWrongChild">
    
  117.             <Consumer>{text => text}</Consumer>
    
  118.           </div>
    
  119.         );
    
  120.       }
    
  121. 
    
  122.       const e = await render(<FunctionChildWithWrongContext />);
    
  123.       expect(e.textContent).toBe('none');
    
  124.     });
    
  125. 
    
  126.     itRenders('with context passed through to a grandchild', async render => {
    
  127.       function Grandchild(props) {
    
  128.         return (
    
  129.           <div>
    
  130.             <Consumer>{text => text}</Consumer>
    
  131.           </div>
    
  132.         );
    
  133.       }
    
  134. 
    
  135.       const Child = props => <Grandchild />;
    
  136. 
    
  137.       const e = await render(
    
  138.         <PurpleContextProvider>
    
  139.           <Child />
    
  140.         </PurpleContextProvider>,
    
  141.       );
    
  142.       expect(e.textContent).toBe('purple');
    
  143.     });
    
  144. 
    
  145.     itRenders('a child context overriding a parent context', async render => {
    
  146.       const Grandchild = props => {
    
  147.         return (
    
  148.           <div>
    
  149.             <Consumer>{text => text}</Consumer>
    
  150.           </div>
    
  151.         );
    
  152.       };
    
  153. 
    
  154.       const e = await render(
    
  155.         <PurpleContextProvider>
    
  156.           <RedContextProvider>
    
  157.             <Grandchild />
    
  158.           </RedContextProvider>
    
  159.         </PurpleContextProvider>,
    
  160.       );
    
  161.       expect(e.textContent).toBe('red');
    
  162.     });
    
  163. 
    
  164.     itRenders('readContext() in different components', async render => {
    
  165.       function readContext(Ctx) {
    
  166.         const dispatcher =
    
  167.           React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
    
  168.             .ReactCurrentDispatcher.current;
    
  169.         return dispatcher.readContext(Ctx);
    
  170.       }
    
  171. 
    
  172.       class Cls extends React.Component {
    
  173.         render() {
    
  174.           return readContext(Context);
    
  175.         }
    
  176.       }
    
  177.       function Fn() {
    
  178.         return readContext(Context);
    
  179.       }
    
  180.       const Memo = React.memo(() => {
    
  181.         return readContext(Context);
    
  182.       });
    
  183.       const FwdRef = React.forwardRef((props, ref) => {
    
  184.         return readContext(Context);
    
  185.       });
    
  186. 
    
  187.       const e = await render(
    
  188.         <PurpleContextProvider>
    
  189.           <RedContextProvider>
    
  190.             <span>
    
  191.               <Fn />
    
  192.               <Cls />
    
  193.               <Memo />
    
  194.               <FwdRef />
    
  195.               <Consumer>{() => readContext(Context)}</Consumer>
    
  196.             </span>
    
  197.           </RedContextProvider>
    
  198.         </PurpleContextProvider>,
    
  199.       );
    
  200.       expect(e.textContent).toBe('redredredredred');
    
  201.     });
    
  202. 
    
  203.     itRenders('multiple contexts', async render => {
    
  204.       const Theme = React.createContext('dark');
    
  205.       const Language = React.createContext('french');
    
  206.       class Parent extends React.Component {
    
  207.         render() {
    
  208.           return (
    
  209.             <Theme.Provider value="light">
    
  210.               <Child />
    
  211.             </Theme.Provider>
    
  212.           );
    
  213.         }
    
  214.       }
    
  215. 
    
  216.       function Child() {
    
  217.         return (
    
  218.           <Language.Provider value="english">
    
  219.             <Grandchild />
    
  220.           </Language.Provider>
    
  221.         );
    
  222.       }
    
  223. 
    
  224.       const Grandchild = props => {
    
  225.         return (
    
  226.           <div>
    
  227.             <Theme.Consumer>
    
  228.               {theme => <div id="theme">{theme}</div>}
    
  229.             </Theme.Consumer>
    
  230.             <Language.Consumer>
    
  231.               {language => <div id="language">{language}</div>}
    
  232.             </Language.Consumer>
    
  233.           </div>
    
  234.         );
    
  235.       };
    
  236. 
    
  237.       const e = await render(<Parent />);
    
  238.       expect(e.querySelector('#theme').textContent).toBe('light');
    
  239.       expect(e.querySelector('#language').textContent).toBe('english');
    
  240.     });
    
  241. 
    
  242.     itRenders('nested context unwinding', async render => {
    
  243.       const Theme = React.createContext('dark');
    
  244.       const Language = React.createContext('french');
    
  245. 
    
  246.       const App = () => (
    
  247.         <div>
    
  248.           <Theme.Provider value="light">
    
  249.             <Language.Provider value="english">
    
  250.               <Theme.Provider value="dark">
    
  251.                 <Theme.Consumer>
    
  252.                   {theme => <div id="theme1">{theme}</div>}
    
  253.                 </Theme.Consumer>
    
  254.               </Theme.Provider>
    
  255.               <Theme.Consumer>
    
  256.                 {theme => <div id="theme2">{theme}</div>}
    
  257.               </Theme.Consumer>
    
  258.               <Language.Provider value="sanskrit">
    
  259.                 <Theme.Provider value="blue">
    
  260.                   <Theme.Provider value="red">
    
  261.                     <Language.Consumer>
    
  262.                       {() => (
    
  263.                         <Language.Provider value="chinese">
    
  264.                           <Language.Provider value="hungarian" />
    
  265.                           <Language.Consumer>
    
  266.                             {language => <div id="language1">{language}</div>}
    
  267.                           </Language.Consumer>
    
  268.                         </Language.Provider>
    
  269.                       )}
    
  270.                     </Language.Consumer>
    
  271.                   </Theme.Provider>
    
  272.                   <Language.Consumer>
    
  273.                     {language => (
    
  274.                       <>
    
  275.                         <Theme.Consumer>
    
  276.                           {theme => <div id="theme3">{theme}</div>}
    
  277.                         </Theme.Consumer>
    
  278.                         <div id="language2">{language}</div>
    
  279.                       </>
    
  280.                     )}
    
  281.                   </Language.Consumer>
    
  282.                 </Theme.Provider>
    
  283.               </Language.Provider>
    
  284.             </Language.Provider>
    
  285.           </Theme.Provider>
    
  286.           <Language.Consumer>
    
  287.             {language => <div id="language3">{language}</div>}
    
  288.           </Language.Consumer>
    
  289.         </div>
    
  290.       );
    
  291.       const e = await render(<App />);
    
  292.       expect(e.querySelector('#theme1').textContent).toBe('dark');
    
  293.       expect(e.querySelector('#theme2').textContent).toBe('light');
    
  294.       expect(e.querySelector('#theme3').textContent).toBe('blue');
    
  295.       expect(e.querySelector('#language1').textContent).toBe('chinese');
    
  296.       expect(e.querySelector('#language2').textContent).toBe('sanskrit');
    
  297.       expect(e.querySelector('#language3').textContent).toBe('french');
    
  298.     });
    
  299. 
    
  300.     itRenders(
    
  301.       'should warn with an error message when using Context as consumer in DEV',
    
  302.       async render => {
    
  303.         const Theme = React.createContext('dark');
    
  304.         const Language = React.createContext('french');
    
  305. 
    
  306.         const App = () => (
    
  307.           <div>
    
  308.             <Theme.Provider value="light">
    
  309.               <Language.Provider value="english">
    
  310.                 <Theme.Provider value="dark">
    
  311.                   <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
    
  312.                 </Theme.Provider>
    
  313.               </Language.Provider>
    
  314.             </Theme.Provider>
    
  315.           </div>
    
  316.         );
    
  317.         // We expect 1 error.
    
  318.         await render(<App />, 1);
    
  319.       },
    
  320.     );
    
  321. 
    
  322.     // False positive regression test.
    
  323.     itRenders(
    
  324.       'should not warn when using Consumer from React < 16.6 with newer renderer',
    
  325.       async render => {
    
  326.         const Theme = React.createContext('dark');
    
  327.         const Language = React.createContext('french');
    
  328.         // React 16.5 and earlier didn't have a separate object.
    
  329.         Theme.Consumer = Theme;
    
  330. 
    
  331.         const App = () => (
    
  332.           <div>
    
  333.             <Theme.Provider value="light">
    
  334.               <Language.Provider value="english">
    
  335.                 <Theme.Provider value="dark">
    
  336.                   <Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
    
  337.                 </Theme.Provider>
    
  338.               </Language.Provider>
    
  339.             </Theme.Provider>
    
  340.           </div>
    
  341.         );
    
  342.         // We expect 0 errors.
    
  343.         await render(<App />, 0);
    
  344.       },
    
  345.     );
    
  346. 
    
  347.     itRenders(
    
  348.       'should warn with an error message when using nested context consumers in DEV',
    
  349.       async render => {
    
  350.         const App = () => {
    
  351.           const Theme = React.createContext('dark');
    
  352.           const Language = React.createContext('french');
    
  353. 
    
  354.           return (
    
  355.             <div>
    
  356.               <Theme.Provider value="light">
    
  357.                 <Language.Provider value="english">
    
  358.                   <Theme.Provider value="dark">
    
  359.                     <Theme.Consumer.Consumer>
    
  360.                       {theme => <div id="theme1">{theme}</div>}
    
  361.                     </Theme.Consumer.Consumer>
    
  362.                   </Theme.Provider>
    
  363.                 </Language.Provider>
    
  364.               </Theme.Provider>
    
  365.             </div>
    
  366.           );
    
  367.         };
    
  368.         // We expect 1 error.
    
  369.         await render(<App />, 1);
    
  370.       },
    
  371.     );
    
  372. 
    
  373.     itRenders(
    
  374.       'should warn with an error message when using Context.Consumer.Provider DEV',
    
  375.       async render => {
    
  376.         const App = () => {
    
  377.           const Theme = React.createContext('dark');
    
  378.           const Language = React.createContext('french');
    
  379. 
    
  380.           return (
    
  381.             <div>
    
  382.               <Theme.Provider value="light">
    
  383.                 <Language.Provider value="english">
    
  384.                   <Theme.Consumer.Provider value="dark">
    
  385.                     <Theme.Consumer>
    
  386.                       {theme => <div id="theme1">{theme}</div>}
    
  387.                     </Theme.Consumer>
    
  388.                   </Theme.Consumer.Provider>
    
  389.                 </Language.Provider>
    
  390.               </Theme.Provider>
    
  391.             </div>
    
  392.           );
    
  393.         };
    
  394.         // We expect 1 error.
    
  395.         await render(<App />, 1);
    
  396.       },
    
  397.     );
    
  398. 
    
  399.     it('does not pollute parallel node streams', () => {
    
  400.       const LoggedInUser = React.createContext();
    
  401. 
    
  402.       const AppWithUser = user => (
    
  403.         <LoggedInUser.Provider value={user}>
    
  404.           <header>
    
  405.             <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
    
  406.           </header>
    
  407.           <footer>
    
  408.             <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
    
  409.           </footer>
    
  410.         </LoggedInUser.Provider>
    
  411.       );
    
  412. 
    
  413.       let streamAmy;
    
  414.       let streamBob;
    
  415.       expect(() => {
    
  416.         streamAmy = ReactDOMServer.renderToNodeStream(
    
  417.           AppWithUser('Amy'),
    
  418.         ).setEncoding('utf8');
    
  419.       }).toErrorDev(
    
  420.         'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  421.         {withoutStack: true},
    
  422.       );
    
  423.       expect(() => {
    
  424.         streamBob = ReactDOMServer.renderToNodeStream(
    
  425.           AppWithUser('Bob'),
    
  426.         ).setEncoding('utf8');
    
  427.       }).toErrorDev(
    
  428.         'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  429.         {withoutStack: true},
    
  430.       );
    
  431. 
    
  432.       // Testing by filling the buffer using internal _read() with a small
    
  433.       // number of bytes to avoid a test case which needs to align to a
    
  434.       // highWaterMark boundary of 2^14 chars.
    
  435.       streamAmy._read(20);
    
  436.       streamBob._read(20);
    
  437.       streamAmy._read(20);
    
  438.       streamBob._read(20);
    
  439. 
    
  440.       expect(streamAmy.read()).toBe('<header>Amy</header><footer>Amy</footer>');
    
  441.       expect(streamBob.read()).toBe('<header>Bob</header><footer>Bob</footer>');
    
  442.     });
    
  443. 
    
  444.     it('does not pollute parallel node streams when many are used', () => {
    
  445.       const CurrentIndex = React.createContext();
    
  446. 
    
  447.       const NthRender = index => (
    
  448.         <CurrentIndex.Provider value={index}>
    
  449.           <header>
    
  450.             <CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
    
  451.           </header>
    
  452.           <footer>
    
  453.             <CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
    
  454.           </footer>
    
  455.         </CurrentIndex.Provider>
    
  456.       );
    
  457. 
    
  458.       const streams = [];
    
  459. 
    
  460.       // Test with more than 32 streams to test that growing the thread count
    
  461.       // works properly.
    
  462.       const streamCount = 34;
    
  463. 
    
  464.       for (let i = 0; i < streamCount; i++) {
    
  465.         expect(() => {
    
  466.           streams[i] = ReactDOMServer.renderToNodeStream(
    
  467.             NthRender(i % 2 === 0 ? 'Expected to be recreated' : i),
    
  468.           ).setEncoding('utf8');
    
  469.         }).toErrorDev(
    
  470.           'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  471.           {withoutStack: true},
    
  472.         );
    
  473.       }
    
  474. 
    
  475.       // Testing by filling the buffer using internal _read() with a small
    
  476.       // number of bytes to avoid a test case which needs to align to a
    
  477.       // highWaterMark boundary of 2^14 chars.
    
  478.       for (let i = 0; i < streamCount; i++) {
    
  479.         streams[i]._read(20);
    
  480.       }
    
  481. 
    
  482.       // Early destroy every other stream
    
  483.       for (let i = 0; i < streamCount; i += 2) {
    
  484.         streams[i].destroy();
    
  485.       }
    
  486. 
    
  487.       // Recreate those same streams.
    
  488.       for (let i = 0; i < streamCount; i += 2) {
    
  489.         expect(() => {
    
  490.           streams[i] = ReactDOMServer.renderToNodeStream(
    
  491.             NthRender(i),
    
  492.           ).setEncoding('utf8');
    
  493.         }).toErrorDev(
    
  494.           'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  495.           {withoutStack: true},
    
  496.         );
    
  497.       }
    
  498. 
    
  499.       // Read a bit from all streams again.
    
  500.       for (let i = 0; i < streamCount; i++) {
    
  501.         streams[i]._read(20);
    
  502.       }
    
  503. 
    
  504.       // Assert that all stream rendered the expected output.
    
  505.       for (let i = 0; i < streamCount; i++) {
    
  506.         expect(streams[i].read()).toBe(
    
  507.           '<header>' + i + '</header><footer>' + i + '</footer>',
    
  508.         );
    
  509.       }
    
  510.     });
    
  511. 
    
  512.     it('does not pollute sync renders after an error', () => {
    
  513.       const LoggedInUser = React.createContext('default');
    
  514.       const Crash = () => {
    
  515.         throw new Error('Boo!');
    
  516.       };
    
  517.       const AppWithUser = user => (
    
  518.         <LoggedInUser.Provider value={user}>
    
  519.           <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
    
  520.           <Crash />
    
  521.         </LoggedInUser.Provider>
    
  522.       );
    
  523. 
    
  524.       expect(() => {
    
  525.         ReactDOMServer.renderToString(AppWithUser('Casper'));
    
  526.       }).toThrow('Boo');
    
  527. 
    
  528.       // Should not report a value from failed render
    
  529.       expect(
    
  530.         ReactDOMServer.renderToString(
    
  531.           <LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>,
    
  532.         ),
    
  533.       ).toBe('default');
    
  534.     });
    
  535.   });
    
  536. });