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 ReactFeatureFlags;
    
  14. let ReactDOMServer;
    
  15. let act;
    
  16. 
    
  17. describe('ReactScope', () => {
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  21.     ReactFeatureFlags.enableScopeAPI = true;
    
  22.     React = require('react');
    
  23. 
    
  24.     const InternalTestUtils = require('internal-test-utils');
    
  25.     act = InternalTestUtils.act;
    
  26.   });
    
  27. 
    
  28.   describe('ReactDOM', () => {
    
  29.     let ReactDOM;
    
  30.     let ReactDOMClient;
    
  31.     let container;
    
  32. 
    
  33.     beforeEach(() => {
    
  34.       ReactDOM = require('react-dom');
    
  35.       ReactDOMClient = require('react-dom/client');
    
  36.       ReactDOMServer = require('react-dom/server');
    
  37.       container = document.createElement('div');
    
  38.       document.body.appendChild(container);
    
  39.     });
    
  40. 
    
  41.     afterEach(() => {
    
  42.       document.body.removeChild(container);
    
  43.       container = null;
    
  44.     });
    
  45. 
    
  46.     // @gate www
    
  47.     it('DO_NOT_USE_queryAllNodes() works as intended', () => {
    
  48.       const testScopeQuery = (type, props) => true;
    
  49.       const TestScope = React.unstable_Scope;
    
  50.       const scopeRef = React.createRef();
    
  51.       const divRef = React.createRef();
    
  52.       const spanRef = React.createRef();
    
  53.       const aRef = React.createRef();
    
  54. 
    
  55.       function Test({toggle}) {
    
  56.         return toggle ? (
    
  57.           <TestScope ref={scopeRef}>
    
  58.             <div ref={divRef}>DIV</div>
    
  59.             <span ref={spanRef}>SPAN</span>
    
  60.             <a ref={aRef}>A</a>
    
  61.           </TestScope>
    
  62.         ) : (
    
  63.           <TestScope ref={scopeRef}>
    
  64.             <a ref={aRef}>A</a>
    
  65.             <div ref={divRef}>DIV</div>
    
  66.             <span ref={spanRef}>SPAN</span>
    
  67.           </TestScope>
    
  68.         );
    
  69.       }
    
  70. 
    
  71.       ReactDOM.render(<Test toggle={true} />, container);
    
  72.       let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  73.       expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
    
  74.       ReactDOM.render(<Test toggle={false} />, container);
    
  75.       nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  76.       expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
    
  77.       ReactDOM.render(null, container);
    
  78.       expect(scopeRef.current).toBe(null);
    
  79.     });
    
  80. 
    
  81.     // @gate www
    
  82.     it('DO_NOT_USE_queryAllNodes() provides the correct host instance', () => {
    
  83.       const testScopeQuery = (type, props) => type === 'div';
    
  84.       const TestScope = React.unstable_Scope;
    
  85.       const scopeRef = React.createRef();
    
  86.       const divRef = React.createRef();
    
  87.       const spanRef = React.createRef();
    
  88.       const aRef = React.createRef();
    
  89. 
    
  90.       function Test({toggle}) {
    
  91.         return toggle ? (
    
  92.           <TestScope ref={scopeRef}>
    
  93.             <div ref={divRef}>DIV</div>
    
  94.             <span ref={spanRef}>SPAN</span>
    
  95.             <a ref={aRef}>A</a>
    
  96.           </TestScope>
    
  97.         ) : (
    
  98.           <TestScope ref={scopeRef}>
    
  99.             <a ref={aRef}>A</a>
    
  100.             <div ref={divRef}>DIV</div>
    
  101.             <span ref={spanRef}>SPAN</span>
    
  102.           </TestScope>
    
  103.         );
    
  104.       }
    
  105. 
    
  106.       ReactDOM.render(<Test toggle={true} />, container);
    
  107.       let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  108.       expect(nodes).toEqual([divRef.current]);
    
  109.       let filterQuery = (type, props, instance) =>
    
  110.         instance === spanRef.current || testScopeQuery(type, props);
    
  111.       nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
    
  112.       expect(nodes).toEqual([divRef.current, spanRef.current]);
    
  113.       filterQuery = (type, props, instance) =>
    
  114.         [spanRef.current, aRef.current].includes(instance) ||
    
  115.         testScopeQuery(type, props);
    
  116.       nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
    
  117.       expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
    
  118.       ReactDOM.render(<Test toggle={false} />, container);
    
  119.       filterQuery = (type, props, instance) =>
    
  120.         [spanRef.current, aRef.current].includes(instance) ||
    
  121.         testScopeQuery(type, props);
    
  122.       nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
    
  123.       expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
    
  124.       ReactDOM.render(null, container);
    
  125.       expect(scopeRef.current).toBe(null);
    
  126.     });
    
  127. 
    
  128.     // @gate www
    
  129.     it('DO_NOT_USE_queryFirstNode() works as intended', () => {
    
  130.       const testScopeQuery = (type, props) => true;
    
  131.       const TestScope = React.unstable_Scope;
    
  132.       const scopeRef = React.createRef();
    
  133.       const divRef = React.createRef();
    
  134.       const spanRef = React.createRef();
    
  135.       const aRef = React.createRef();
    
  136. 
    
  137.       function Test({toggle}) {
    
  138.         return toggle ? (
    
  139.           <TestScope ref={scopeRef}>
    
  140.             <div ref={divRef}>DIV</div>
    
  141.             <span ref={spanRef}>SPAN</span>
    
  142.             <a ref={aRef}>A</a>
    
  143.           </TestScope>
    
  144.         ) : (
    
  145.           <TestScope ref={scopeRef}>
    
  146.             <a ref={aRef}>A</a>
    
  147.             <div ref={divRef}>DIV</div>
    
  148.             <span ref={spanRef}>SPAN</span>
    
  149.           </TestScope>
    
  150.         );
    
  151.       }
    
  152. 
    
  153.       ReactDOM.render(<Test toggle={true} />, container);
    
  154.       let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
    
  155.       expect(node).toEqual(divRef.current);
    
  156.       ReactDOM.render(<Test toggle={false} />, container);
    
  157.       node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
    
  158.       expect(node).toEqual(aRef.current);
    
  159.       ReactDOM.render(null, container);
    
  160.       expect(scopeRef.current).toBe(null);
    
  161.     });
    
  162. 
    
  163.     // @gate www
    
  164.     it('containsNode() works as intended', () => {
    
  165.       const TestScope = React.unstable_Scope;
    
  166.       const scopeRef = React.createRef();
    
  167.       const divRef = React.createRef();
    
  168.       const spanRef = React.createRef();
    
  169.       const aRef = React.createRef();
    
  170.       const outerSpan = React.createRef();
    
  171.       const emRef = React.createRef();
    
  172. 
    
  173.       function Test({toggle}) {
    
  174.         return toggle ? (
    
  175.           <div>
    
  176.             <span ref={outerSpan}>SPAN</span>
    
  177.             <TestScope ref={scopeRef}>
    
  178.               <div ref={divRef}>DIV</div>
    
  179.               <span ref={spanRef}>SPAN</span>
    
  180.               <a ref={aRef}>A</a>
    
  181.             </TestScope>
    
  182.             <em ref={emRef}>EM</em>
    
  183.           </div>
    
  184.         ) : (
    
  185.           <div>
    
  186.             <TestScope ref={scopeRef}>
    
  187.               <a ref={aRef}>A</a>
    
  188.               <div ref={divRef}>DIV</div>
    
  189.               <span ref={spanRef}>SPAN</span>
    
  190.               <em ref={emRef}>EM</em>
    
  191.             </TestScope>
    
  192.             <span ref={outerSpan}>SPAN</span>
    
  193.           </div>
    
  194.         );
    
  195.       }
    
  196. 
    
  197.       ReactDOM.render(<Test toggle={true} />, container);
    
  198.       expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
    
  199.       expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
    
  200.       expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
    
  201.       expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
    
  202.       expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
    
  203.       ReactDOM.render(<Test toggle={false} />, container);
    
  204.       expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
    
  205.       expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
    
  206.       expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
    
  207.       expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
    
  208.       expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
    
  209.       ReactDOM.render(<Test toggle={true} />, container);
    
  210.       expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
    
  211.     });
    
  212. 
    
  213.     // @gate www
    
  214.     it('scopes support server-side rendering and hydration', () => {
    
  215.       const TestScope = React.unstable_Scope;
    
  216.       const scopeRef = React.createRef();
    
  217.       const divRef = React.createRef();
    
  218.       const spanRef = React.createRef();
    
  219.       const aRef = React.createRef();
    
  220. 
    
  221.       function Test({toggle}) {
    
  222.         return (
    
  223.           <div>
    
  224.             <TestScope ref={scopeRef}>
    
  225.               <div ref={divRef}>DIV</div>
    
  226.               <span ref={spanRef}>SPAN</span>
    
  227.               <a ref={aRef}>A</a>
    
  228.             </TestScope>
    
  229.             <div>Outside content!</div>
    
  230.           </div>
    
  231.         );
    
  232.       }
    
  233.       const html = ReactDOMServer.renderToString(<Test />);
    
  234.       expect(html).toBe(
    
  235.         '<div><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
    
  236.       );
    
  237.       container.innerHTML = html;
    
  238.       ReactDOM.hydrate(<Test />, container);
    
  239.       const testScopeQuery = (type, props) => true;
    
  240.       const nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  241.       expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
    
  242.     });
    
  243. 
    
  244.     // @gate www
    
  245.     it('getChildContextValues() works as intended', () => {
    
  246.       const TestContext = React.createContext();
    
  247.       const TestScope = React.unstable_Scope;
    
  248.       const scopeRef = React.createRef();
    
  249. 
    
  250.       function Test({toggle}) {
    
  251.         return toggle ? (
    
  252.           <TestScope ref={scopeRef}>
    
  253.             <TestContext.Provider value={1} />
    
  254.           </TestScope>
    
  255.         ) : (
    
  256.           <TestScope ref={scopeRef}>
    
  257.             <TestContext.Provider value={1} />
    
  258.             <TestContext.Provider value={2} />
    
  259.           </TestScope>
    
  260.         );
    
  261.       }
    
  262. 
    
  263.       ReactDOM.render(<Test toggle={true} />, container);
    
  264.       let nodes = scopeRef.current.getChildContextValues(TestContext);
    
  265.       expect(nodes).toEqual([1]);
    
  266.       ReactDOM.render(<Test toggle={false} />, container);
    
  267.       nodes = scopeRef.current.getChildContextValues(TestContext);
    
  268.       expect(nodes).toEqual([1, 2]);
    
  269.       ReactDOM.render(null, container);
    
  270.       expect(scopeRef.current).toBe(null);
    
  271.     });
    
  272. 
    
  273.     // @gate www
    
  274.     it('correctly works with suspended boundaries that are hydrated', async () => {
    
  275.       let suspend = false;
    
  276.       let resolve;
    
  277.       const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  278.       const ref = React.createRef();
    
  279.       const TestScope = React.unstable_Scope;
    
  280.       const scopeRef = React.createRef();
    
  281.       const testScopeQuery = (type, props) => true;
    
  282. 
    
  283.       function Child() {
    
  284.         if (suspend) {
    
  285.           throw promise;
    
  286.         } else {
    
  287.           return 'Hello';
    
  288.         }
    
  289.       }
    
  290. 
    
  291.       function App() {
    
  292.         return (
    
  293.           <div>
    
  294.             <TestScope ref={scopeRef}>
    
  295.               <React.Suspense fallback="Loading...">
    
  296.                 <span ref={ref}>
    
  297.                   <Child />
    
  298.                 </span>
    
  299.               </React.Suspense>
    
  300.             </TestScope>
    
  301.           </div>
    
  302.         );
    
  303.       }
    
  304. 
    
  305.       // First we render the final HTML. With the streaming renderer
    
  306.       // this may have suspense points on the server but here we want
    
  307.       // to test the completed HTML. Don't suspend on the server.
    
  308.       suspend = false;
    
  309.       const finalHTML = ReactDOMServer.renderToString(<App />);
    
  310. 
    
  311.       const container2 = document.createElement('div');
    
  312.       container2.innerHTML = finalHTML;
    
  313. 
    
  314.       const span = container2.getElementsByTagName('span')[0];
    
  315. 
    
  316.       // On the client we don't have all data yet but we want to start
    
  317.       // hydrating anyway.
    
  318.       suspend = true;
    
  319.       await act(() => ReactDOMClient.hydrateRoot(container2, <App />));
    
  320. 
    
  321.       // This should not cause a runtime exception, see:
    
  322.       // https://github.com/facebook/react/pull/18184
    
  323.       scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  324.       expect(ref.current).toBe(null);
    
  325. 
    
  326.       // Resolving the promise should continue hydration
    
  327.       suspend = false;
    
  328.       await act(async () => {
    
  329.         resolve();
    
  330.         await promise;
    
  331.       });
    
  332. 
    
  333.       // We should now have hydrated with a ref on the existing span.
    
  334.       expect(ref.current).toBe(span);
    
  335.     });
    
  336.   });
    
  337. 
    
  338.   describe('ReactTestRenderer', () => {
    
  339.     let ReactTestRenderer;
    
  340. 
    
  341.     beforeEach(() => {
    
  342.       ReactTestRenderer = require('react-test-renderer');
    
  343.     });
    
  344. 
    
  345.     // @gate www
    
  346.     it('DO_NOT_USE_queryAllNodes() works as intended', () => {
    
  347.       const testScopeQuery = (type, props) => true;
    
  348.       const TestScope = React.unstable_Scope;
    
  349.       const scopeRef = React.createRef();
    
  350.       const divRef = React.createRef();
    
  351.       const spanRef = React.createRef();
    
  352.       const aRef = React.createRef();
    
  353. 
    
  354.       function Test({toggle}) {
    
  355.         return toggle ? (
    
  356.           <TestScope ref={scopeRef}>
    
  357.             <div ref={divRef}>DIV</div>
    
  358.             <span ref={spanRef}>SPAN</span>
    
  359.             <a ref={aRef}>A</a>
    
  360.           </TestScope>
    
  361.         ) : (
    
  362.           <TestScope ref={scopeRef}>
    
  363.             <a ref={aRef}>A</a>
    
  364.             <div ref={divRef}>DIV</div>
    
  365.             <span ref={spanRef}>SPAN</span>
    
  366.           </TestScope>
    
  367.         );
    
  368.       }
    
  369. 
    
  370.       const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
    
  371.         createNodeMock: element => {
    
  372.           return element;
    
  373.         },
    
  374.       });
    
  375.       let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  376.       expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
    
  377.       renderer.update(<Test toggle={false} />);
    
  378.       nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
    
  379.       expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
    
  380.     });
    
  381. 
    
  382.     // @gate www
    
  383.     it('DO_NOT_USE_queryFirstNode() works as intended', () => {
    
  384.       const testScopeQuery = (type, props) => true;
    
  385.       const TestScope = React.unstable_Scope;
    
  386.       const scopeRef = React.createRef();
    
  387.       const divRef = React.createRef();
    
  388.       const spanRef = React.createRef();
    
  389.       const aRef = React.createRef();
    
  390. 
    
  391.       function Test({toggle}) {
    
  392.         return toggle ? (
    
  393.           <TestScope ref={scopeRef}>
    
  394.             <div ref={divRef}>DIV</div>
    
  395.             <span ref={spanRef}>SPAN</span>
    
  396.             <a ref={aRef}>A</a>
    
  397.           </TestScope>
    
  398.         ) : (
    
  399.           <TestScope ref={scopeRef}>
    
  400.             <a ref={aRef}>A</a>
    
  401.             <div ref={divRef}>DIV</div>
    
  402.             <span ref={spanRef}>SPAN</span>
    
  403.           </TestScope>
    
  404.         );
    
  405.       }
    
  406. 
    
  407.       const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
    
  408.         createNodeMock: element => {
    
  409.           return element;
    
  410.         },
    
  411.       });
    
  412.       let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
    
  413.       expect(node).toEqual(divRef.current);
    
  414.       renderer.update(<Test toggle={false} />);
    
  415.       node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
    
  416.       expect(node).toEqual(aRef.current);
    
  417.     });
    
  418. 
    
  419.     // @gate www
    
  420.     it('containsNode() works as intended', () => {
    
  421.       const TestScope = React.unstable_Scope;
    
  422.       const scopeRef = React.createRef();
    
  423.       const divRef = React.createRef();
    
  424.       const spanRef = React.createRef();
    
  425.       const aRef = React.createRef();
    
  426.       const outerSpan = React.createRef();
    
  427.       const emRef = React.createRef();
    
  428. 
    
  429.       function Test({toggle}) {
    
  430.         return toggle ? (
    
  431.           <div>
    
  432.             <span ref={outerSpan}>SPAN</span>
    
  433.             <TestScope ref={scopeRef}>
    
  434.               <div ref={divRef}>DIV</div>
    
  435.               <span ref={spanRef}>SPAN</span>
    
  436.               <a ref={aRef}>A</a>
    
  437.             </TestScope>
    
  438.             <em ref={emRef}>EM</em>
    
  439.           </div>
    
  440.         ) : (
    
  441.           <div>
    
  442.             <TestScope ref={scopeRef}>
    
  443.               <a ref={aRef}>A</a>
    
  444.               <div ref={divRef}>DIV</div>
    
  445.               <span ref={spanRef}>SPAN</span>
    
  446.               <em ref={emRef}>EM</em>
    
  447.             </TestScope>
    
  448.             <span ref={outerSpan}>SPAN</span>
    
  449.           </div>
    
  450.         );
    
  451.       }
    
  452. 
    
  453.       const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
    
  454.         createNodeMock: element => {
    
  455.           return element;
    
  456.         },
    
  457.       });
    
  458.       expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
    
  459.       expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
    
  460.       expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
    
  461.       expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
    
  462.       expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
    
  463.       renderer.update(<Test toggle={false} />);
    
  464.       expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
    
  465.       expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
    
  466.       expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
    
  467.       expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
    
  468.       expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
    
  469.       renderer.update(<Test toggle={true} />);
    
  470.       expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
    
  471.     });
    
  472.   });
    
  473. });