/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.*/'use strict';
// Mock of the Native Hooks// Map of viewTag -> {children: [childTag], parent: ?parentTag}const roots = [];
const views = new Map();
function autoCreateRoot(tag) {
// Seriously, this is how we distinguish roots in RN.
if (!views.has(tag) && tag % 10 === 1) {
roots.push(tag);
views.set(tag, {
children: [],parent: null,
props: {},viewName: '<native root>',
});}}function insertSubviewAtIndex(parent, child, index) {
const parentInfo = views.get(parent);
const childInfo = views.get(child);
if (childInfo.parent !== null) {
throw new Error(
`Inserting view ${child} ${JSON.stringify(
childInfo.props,
)} which already has parent`,
);}if (0 > index || index > parentInfo.children.length) {
throw new Error(
`Invalid index ${index} for children ${parentInfo.children}`,
);}parentInfo.children.splice(index, 0, child);
childInfo.parent = parent;
}function removeChild(parent, child) {
const parentInfo = views.get(parent);
const childInfo = views.get(child);
const index = parentInfo.children.indexOf(child);
if (index < 0) {
throw new Error(`Missing view ${child} during removal`);
}parentInfo.children.splice(index, 1);
childInfo.parent = null;
}const RCTUIManager = {
__dumpHierarchyForJestTestsOnly: function () {
function dumpSubtree(tag, indent) {
const info = views.get(tag);
let out = '';
out +=
' '.repeat(indent) + info.viewName + ' ' + JSON.stringify(info.props);
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const child of info.children) {
out += '\n' + dumpSubtree(child, indent + 2);
}return out;
}return roots.map(tag => dumpSubtree(tag, 0)).join('\n');
},clearJSResponder: jest.fn(),
createView: jest.fn(function createView(reactTag, viewName, rootTag, props) {
if (views.has(reactTag)) {
throw new Error(`Created two native views with tag ${reactTag}`);
}views.set(reactTag, {
children: [],parent: null,
props: props,
viewName: viewName,
});}),dispatchViewManagerCommand: jest.fn(),
sendAccessibilityEvent: jest.fn(),
setJSResponder: jest.fn(),
setChildren: jest.fn(function setChildren(parentTag, reactTags) {
autoCreateRoot(parentTag);
// Native doesn't actually check this but it seems like a good idea
if (views.get(parentTag).children.length !== 0) {
throw new Error(`Calling .setChildren on nonempty view ${parentTag}`);
}// This logic ported from iOS (RCTUIManager.m)
reactTags.forEach((tag, i) => {
insertSubviewAtIndex(parentTag, tag, i);
});}),manageChildren: jest.fn(function manageChildren(
parentTag,moveFromIndices = [],moveToIndices = [],addChildReactTags = [],addAtIndices = [],removeAtIndices = [],) {autoCreateRoot(parentTag);
// This logic ported from iOS (RCTUIManager.m)
if (moveFromIndices.length !== moveToIndices.length) {
throw new Error(
`Mismatched move indices ${moveFromIndices} and ${moveToIndices}`,
);}if (addChildReactTags.length !== addAtIndices.length) {
throw new Error(
`Mismatched add indices ${addChildReactTags} and ${addAtIndices}`,
);}const parentInfo = views.get(parentTag);
const permanentlyRemovedChildren = removeAtIndices.map(
index => parentInfo.children[index],
);const temporarilyRemovedChildren = moveFromIndices.map(
index => parentInfo.children[index],
);permanentlyRemovedChildren.forEach(tag => removeChild(parentTag, tag));
temporarilyRemovedChildren.forEach(tag => removeChild(parentTag, tag));
permanentlyRemovedChildren.forEach(tag => {
views.delete(tag);
});// List of [index, tag]
const indicesToInsert = [];
temporarilyRemovedChildren.forEach((tag, i) => {
indicesToInsert.push([moveToIndices[i], temporarilyRemovedChildren[i]]);
});addChildReactTags.forEach((tag, i) => {
indicesToInsert.push([addAtIndices[i], addChildReactTags[i]]);
});indicesToInsert.sort((a, b) => a[0] - b[0]);
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const [i, tag] of indicesToInsert) {
insertSubviewAtIndex(parentTag, tag, i);
}}),updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(function (parentTag) {
views.get(parentTag).children.forEach(tag => removeChild(parentTag, tag));
}),replaceExistingNonRootView: jest.fn(),
measure: jest.fn(function measure(tag, callback) {
if (typeof tag !== 'number') {
throw new Error(`Expected tag to be a number, was passed ${tag}`);
}callback(10, 10, 100, 100, 0, 0);
}),measureInWindow: jest.fn(function measureInWindow(tag, callback) {
if (typeof tag !== 'number') {
throw new Error(`Expected tag to be a number, was passed ${tag}`);
}callback(10, 10, 100, 100);
}),measureLayout: jest.fn(
function measureLayout(tag, relativeTag, fail, success) {
if (typeof tag !== 'number') {
throw new Error(`Expected tag to be a number, was passed ${tag}`);
}if (typeof relativeTag !== 'number') {
throw new Error(
`Expected relativeTag to be a number, was passed ${relativeTag}`,
);}success(1, 1, 100, 100);
},),__takeSnapshot: jest.fn(),
};module.exports = RCTUIManager;