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. 
    
  8. 'use strict';
    
  9. 
    
  10. // Mock of the Native Hooks
    
  11. 
    
  12. // Map of viewTag -> {children: [childTag], parent: ?parentTag}
    
  13. const roots = [];
    
  14. const views = new Map();
    
  15. 
    
  16. function autoCreateRoot(tag) {
    
  17.   // Seriously, this is how we distinguish roots in RN.
    
  18.   if (!views.has(tag) && tag % 10 === 1) {
    
  19.     roots.push(tag);
    
  20.     views.set(tag, {
    
  21.       children: [],
    
  22.       parent: null,
    
  23.       props: {},
    
  24.       viewName: '<native root>',
    
  25.     });
    
  26.   }
    
  27. }
    
  28. 
    
  29. function insertSubviewAtIndex(parent, child, index) {
    
  30.   const parentInfo = views.get(parent);
    
  31.   const childInfo = views.get(child);
    
  32. 
    
  33.   if (childInfo.parent !== null) {
    
  34.     throw new Error(
    
  35.       `Inserting view ${child} ${JSON.stringify(
    
  36.         childInfo.props,
    
  37.       )} which already has parent`,
    
  38.     );
    
  39.   }
    
  40. 
    
  41.   if (0 > index || index > parentInfo.children.length) {
    
  42.     throw new Error(
    
  43.       `Invalid index ${index} for children ${parentInfo.children}`,
    
  44.     );
    
  45.   }
    
  46. 
    
  47.   parentInfo.children.splice(index, 0, child);
    
  48.   childInfo.parent = parent;
    
  49. }
    
  50. 
    
  51. function removeChild(parent, child) {
    
  52.   const parentInfo = views.get(parent);
    
  53.   const childInfo = views.get(child);
    
  54.   const index = parentInfo.children.indexOf(child);
    
  55. 
    
  56.   if (index < 0) {
    
  57.     throw new Error(`Missing view ${child} during removal`);
    
  58.   }
    
  59. 
    
  60.   parentInfo.children.splice(index, 1);
    
  61.   childInfo.parent = null;
    
  62. }
    
  63. 
    
  64. const RCTUIManager = {
    
  65.   __dumpHierarchyForJestTestsOnly: function () {
    
  66.     function dumpSubtree(tag, indent) {
    
  67.       const info = views.get(tag);
    
  68.       let out = '';
    
  69.       out +=
    
  70.         ' '.repeat(indent) + info.viewName + ' ' + JSON.stringify(info.props);
    
  71.       // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    
  72.       for (const child of info.children) {
    
  73.         out += '\n' + dumpSubtree(child, indent + 2);
    
  74.       }
    
  75.       return out;
    
  76.     }
    
  77.     return roots.map(tag => dumpSubtree(tag, 0)).join('\n');
    
  78.   },
    
  79.   clearJSResponder: jest.fn(),
    
  80.   createView: jest.fn(function createView(reactTag, viewName, rootTag, props) {
    
  81.     if (views.has(reactTag)) {
    
  82.       throw new Error(`Created two native views with tag ${reactTag}`);
    
  83.     }
    
  84. 
    
  85.     views.set(reactTag, {
    
  86.       children: [],
    
  87.       parent: null,
    
  88.       props: props,
    
  89.       viewName: viewName,
    
  90.     });
    
  91.   }),
    
  92.   dispatchViewManagerCommand: jest.fn(),
    
  93.   sendAccessibilityEvent: jest.fn(),
    
  94.   setJSResponder: jest.fn(),
    
  95.   setChildren: jest.fn(function setChildren(parentTag, reactTags) {
    
  96.     autoCreateRoot(parentTag);
    
  97. 
    
  98.     // Native doesn't actually check this but it seems like a good idea
    
  99.     if (views.get(parentTag).children.length !== 0) {
    
  100.       throw new Error(`Calling .setChildren on nonempty view ${parentTag}`);
    
  101.     }
    
  102. 
    
  103.     // This logic ported from iOS (RCTUIManager.m)
    
  104.     reactTags.forEach((tag, i) => {
    
  105.       insertSubviewAtIndex(parentTag, tag, i);
    
  106.     });
    
  107.   }),
    
  108.   manageChildren: jest.fn(function manageChildren(
    
  109.     parentTag,
    
  110.     moveFromIndices = [],
    
  111.     moveToIndices = [],
    
  112.     addChildReactTags = [],
    
  113.     addAtIndices = [],
    
  114.     removeAtIndices = [],
    
  115.   ) {
    
  116.     autoCreateRoot(parentTag);
    
  117. 
    
  118.     // This logic ported from iOS (RCTUIManager.m)
    
  119.     if (moveFromIndices.length !== moveToIndices.length) {
    
  120.       throw new Error(
    
  121.         `Mismatched move indices ${moveFromIndices} and ${moveToIndices}`,
    
  122.       );
    
  123.     }
    
  124. 
    
  125.     if (addChildReactTags.length !== addAtIndices.length) {
    
  126.       throw new Error(
    
  127.         `Mismatched add indices ${addChildReactTags} and ${addAtIndices}`,
    
  128.       );
    
  129.     }
    
  130. 
    
  131.     const parentInfo = views.get(parentTag);
    
  132.     const permanentlyRemovedChildren = removeAtIndices.map(
    
  133.       index => parentInfo.children[index],
    
  134.     );
    
  135.     const temporarilyRemovedChildren = moveFromIndices.map(
    
  136.       index => parentInfo.children[index],
    
  137.     );
    
  138.     permanentlyRemovedChildren.forEach(tag => removeChild(parentTag, tag));
    
  139.     temporarilyRemovedChildren.forEach(tag => removeChild(parentTag, tag));
    
  140.     permanentlyRemovedChildren.forEach(tag => {
    
  141.       views.delete(tag);
    
  142.     });
    
  143.     // List of [index, tag]
    
  144.     const indicesToInsert = [];
    
  145.     temporarilyRemovedChildren.forEach((tag, i) => {
    
  146.       indicesToInsert.push([moveToIndices[i], temporarilyRemovedChildren[i]]);
    
  147.     });
    
  148.     addChildReactTags.forEach((tag, i) => {
    
  149.       indicesToInsert.push([addAtIndices[i], addChildReactTags[i]]);
    
  150.     });
    
  151.     indicesToInsert.sort((a, b) => a[0] - b[0]);
    
  152.     // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    
  153.     for (const [i, tag] of indicesToInsert) {
    
  154.       insertSubviewAtIndex(parentTag, tag, i);
    
  155.     }
    
  156.   }),
    
  157.   updateView: jest.fn(),
    
  158.   removeSubviewsFromContainerWithID: jest.fn(function (parentTag) {
    
  159.     views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
    
  160.   }),
    
  161.   replaceExistingNonRootView: jest.fn(),
    
  162.   measure: jest.fn(function measure(tag, callback) {
    
  163.     if (typeof tag !== 'number') {
    
  164.       throw new Error(`Expected tag to be a number, was passed ${tag}`);
    
  165.     }
    
  166. 
    
  167.     callback(10, 10, 100, 100, 0, 0);
    
  168.   }),
    
  169.   measureInWindow: jest.fn(function measureInWindow(tag, callback) {
    
  170.     if (typeof tag !== 'number') {
    
  171.       throw new Error(`Expected tag to be a number, was passed ${tag}`);
    
  172.     }
    
  173. 
    
  174.     callback(10, 10, 100, 100);
    
  175.   }),
    
  176.   measureLayout: jest.fn(
    
  177.     function measureLayout(tag, relativeTag, fail, success) {
    
  178.       if (typeof tag !== 'number') {
    
  179.         throw new Error(`Expected tag to be a number, was passed ${tag}`);
    
  180.       }
    
  181. 
    
  182.       if (typeof relativeTag !== 'number') {
    
  183.         throw new Error(
    
  184.           `Expected relativeTag to be a number, was passed ${relativeTag}`,
    
  185.         );
    
  186.       }
    
  187. 
    
  188.       success(1, 1, 100, 100);
    
  189.     },
    
  190.   ),
    
  191.   __takeSnapshot: jest.fn(),
    
  192. };
    
  193. 
    
  194. module.exports = RCTUIManager;