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 ReactDOMSelection;
    
  15. 
    
  16. let getModernOffsetsFromPoints;
    
  17. 
    
  18. describe('ReactDOMSelection', () => {
    
  19.   beforeEach(() => {
    
  20.     React = require('react');
    
  21.     ReactDOM = require('react-dom');
    
  22.     ReactDOMSelection = require('react-dom-bindings/src/client/ReactDOMSelection');
    
  23. 
    
  24.     ({getModernOffsetsFromPoints} = ReactDOMSelection);
    
  25.   });
    
  26. 
    
  27.   // Simple implementation to compare correctness. React's old implementation of
    
  28.   // this logic used DOM Range objects and is available for manual testing at
    
  29.   // https://gist.github.com/sophiebits/2e6d571f4f10f33b62ea138a6e9c265c.
    
  30.   function simpleModernOffsetsFromPoints(
    
  31.     outerNode,
    
  32.     anchorNode,
    
  33.     anchorOffset,
    
  34.     focusNode,
    
  35.     focusOffset,
    
  36.   ) {
    
  37.     let start;
    
  38.     let end;
    
  39.     let length = 0;
    
  40. 
    
  41.     function traverse(node) {
    
  42.       if (node.nodeType === Node.TEXT_NODE) {
    
  43.         if (node === anchorNode) {
    
  44.           start = length + anchorOffset;
    
  45.         }
    
  46.         if (node === focusNode) {
    
  47.           end = length + focusOffset;
    
  48.         }
    
  49.         length += node.nodeValue.length;
    
  50.         return;
    
  51.       }
    
  52. 
    
  53.       for (let i = 0; true; i++) {
    
  54.         if (node === anchorNode && i === anchorOffset) {
    
  55.           start = length;
    
  56.         }
    
  57.         if (node === focusNode && i === focusOffset) {
    
  58.           end = length;
    
  59.         }
    
  60.         if (i === node.childNodes.length) {
    
  61.           break;
    
  62.         }
    
  63.         const n = node.childNodes[i];
    
  64.         traverse(n);
    
  65.       }
    
  66.     }
    
  67.     traverse(outerNode);
    
  68. 
    
  69.     if (start === null || end === null) {
    
  70.       throw new Error('Provided anchor/focus nodes were outside of root.');
    
  71.     }
    
  72.     return {start, end};
    
  73.   }
    
  74. 
    
  75.   // Complicated example derived from a real-world DOM tree. Has a bit of
    
  76.   // everything.
    
  77.   function getFixture() {
    
  78.     return ReactDOM.render(
    
  79.       <div>
    
  80.         <div>
    
  81.           <div>
    
  82.             <div>xxxxxxxxxxxxxxxxxxxx</div>
    
  83.           </div>
    
  84.           x
    
  85.           <div>
    
  86.             <div>
    
  87.               x
    
  88.               <div>
    
  89.                 <div>
    
  90.                   <div>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</div>
    
  91.                   <div />
    
  92.                   <div />
    
  93.                   <div>xxxxxxxxxxxxxxxxxx</div>
    
  94.                 </div>
    
  95.               </div>
    
  96.             </div>
    
  97.           </div>
    
  98.           <div />
    
  99.         </div>
    
  100.         <div>
    
  101.           <div>
    
  102.             <div>
    
  103.               <div>xxxx</div>
    
  104.               <div>xxxxxxxxxxxxxxxxxxx</div>
    
  105.             </div>
    
  106.           </div>
    
  107.           <div>xxx</div>
    
  108.           <div>xxxxx</div>
    
  109.           <div>xxx</div>
    
  110.           <div>
    
  111.             <div>
    
  112.               <div>
    
  113.                 <div>{['x', 'x', 'xxx']}</div>
    
  114.               </div>
    
  115.             </div>
    
  116.           </div>
    
  117.         </div>
    
  118.         <div>
    
  119.           <div>xxxxxx</div>
    
  120.         </div>
    
  121.       </div>,
    
  122.       document.createElement('div'),
    
  123.     );
    
  124.   }
    
  125. 
    
  126.   it('returns correctly for base case', () => {
    
  127.     const node = document.createElement('div');
    
  128.     expect(getModernOffsetsFromPoints(node, node, 0, node, 0)).toEqual({
    
  129.       start: 0,
    
  130.       end: 0,
    
  131.     });
    
  132.     expect(simpleModernOffsetsFromPoints(node, node, 0, node, 0)).toEqual({
    
  133.       start: 0,
    
  134.       end: 0,
    
  135.     });
    
  136.   });
    
  137. 
    
  138.   it('returns correctly for fuzz test', () => {
    
  139.     const fixtureRoot = getFixture();
    
  140.     const allNodes = [fixtureRoot].concat(
    
  141.       Array.from(fixtureRoot.querySelectorAll('*')),
    
  142.     );
    
  143.     expect(allNodes.length).toBe(27);
    
  144.     allNodes.slice().forEach(element => {
    
  145.       // Add text nodes.
    
  146.       allNodes.push(
    
  147.         ...Array.from(element.childNodes).filter(n => n.nodeType === 3),
    
  148.       );
    
  149.     });
    
  150.     expect(allNodes.length).toBe(41);
    
  151. 
    
  152.     function randomNode() {
    
  153.       return allNodes[(Math.random() * allNodes.length) | 0];
    
  154.     }
    
  155.     function randomOffset(node) {
    
  156.       return (
    
  157.         (Math.random() *
    
  158.           (1 +
    
  159.             (node.nodeType === 3 ? node.nodeValue : node.childNodes).length)) |
    
  160.         0
    
  161.       );
    
  162.     }
    
  163. 
    
  164.     for (let i = 0; i < 2000; i++) {
    
  165.       const anchorNode = randomNode();
    
  166.       const anchorOffset = randomOffset(anchorNode);
    
  167.       const focusNode = randomNode();
    
  168.       const focusOffset = randomOffset(focusNode);
    
  169. 
    
  170.       const offsets1 = getModernOffsetsFromPoints(
    
  171.         fixtureRoot,
    
  172.         anchorNode,
    
  173.         anchorOffset,
    
  174.         focusNode,
    
  175.         focusOffset,
    
  176.       );
    
  177.       const offsets2 = simpleModernOffsetsFromPoints(
    
  178.         fixtureRoot,
    
  179.         anchorNode,
    
  180.         anchorOffset,
    
  181.         focusNode,
    
  182.         focusOffset,
    
  183.       );
    
  184.       if (JSON.stringify(offsets1) !== JSON.stringify(offsets2)) {
    
  185.         throw new Error(
    
  186.           JSON.stringify(offsets1) +
    
  187.             ' does not match ' +
    
  188.             JSON.stringify(offsets2) +
    
  189.             ' for anchorNode=allNodes[' +
    
  190.             allNodes.indexOf(anchorNode) +
    
  191.             '], anchorOffset=' +
    
  192.             anchorOffset +
    
  193.             ', focusNode=allNodes[' +
    
  194.             allNodes.indexOf(focusNode) +
    
  195.             '], focusOffset=' +
    
  196.             focusOffset,
    
  197.         );
    
  198.       }
    
  199.     }
    
  200.   });
    
  201. });