/* * 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. */ #pragma once #include <glog/logging.h> #include <gtest/gtest.h> #include <algorithm> #include <iostream> #include <memory> #include <random> #include <react/config/ReactNativeConfig.h> #include <react/renderer/core/PropsParserContext.h> #include <react/renderer/mounting/Differentiator.h> #include <react/renderer/mounting/stubs.h> #include <react/renderer/components/root/RootComponentDescriptor.h> #include <react/renderer/components/view/ViewComponentDescriptor.h> #include "Entropy.h" namespace facebook::react { static Tag generateReactTag() { static Tag tag = 1000; return tag++; } class ShadowTreeEdge final { public: ShadowNode::Shared shadowNode{nullptr}; ShadowNode::Shared parentShadowNode{nullptr}; int index{0}; }; static bool traverseShadowTree( const ShadowNode::Shared& parentShadowNode, const std::function<void(ShadowTreeEdge const& edge, bool& stop)>& callback) { auto index = int{0}; for (const auto& childNode : parentShadowNode->getChildren()) { auto stop = bool{false}; callback(ShadowTreeEdge{childNode, parentShadowNode, index}, stop); if (stop) { return true; } if (traverseShadowTree(childNode, callback)) { return true; } index++; } return false; } static int countShadowNodes(const ShadowNode::Shared& rootShadowNode) { auto counter = int{0}; traverseShadowTree( rootShadowNode, [&](const ShadowTreeEdge& edge, bool& stop) { counter++; }); return counter; } static ShadowTreeEdge findShadowNodeWithIndex( const ShadowNode::Shared& rootNode, int index) { auto counter = int{0}; auto result = ShadowTreeEdge{}; traverseShadowTree(rootNode, [&](const ShadowTreeEdge& edge, bool& stop) { if (index == counter) { result = edge; } counter++; }); return result; } static ShadowTreeEdge findRandomShadowNode( const Entropy& entropy, const ShadowNode::Shared& rootShadowNode) { auto count = countShadowNodes(rootShadowNode); return findShadowNodeWithIndex( rootShadowNode, entropy.random<int>(1 /* Excluding a root node */, count - 1)); } static ShadowNode::ListOfShared cloneSharedShadowNodeList( const ShadowNode::ListOfShared& list) { auto result = ShadowNode::ListOfShared{}; result.reserve(list.size()); for (const auto& shadowNode : list) { result.push_back(shadowNode->clone({})); } return result; } static inline ShadowNode::Unshared messWithChildren( const Entropy& entropy, const ShadowNode& shadowNode) { auto children = shadowNode.getChildren(); children = cloneSharedShadowNodeList(children); entropy.shuffle(children); return shadowNode.clone( {ShadowNodeFragment::propsPlaceholder(), std::make_shared<ShadowNode::ListOfShared const>(children)}); } static inline ShadowNode::Unshared messWithLayoutableOnlyFlag( const Entropy& entropy, const ShadowNode& shadowNode) { auto oldProps = shadowNode.getProps(); ContextContainer contextContainer{}; PropsParserContext parserContext{-1, contextContainer}; auto newProps = shadowNode.getComponentDescriptor().cloneProps( parserContext, oldProps, RawProps(folly::dynamic::object())); auto& viewProps = const_cast<ViewProps&>(static_cast<const ViewProps&>(*newProps)); if (entropy.random<bool>(0.1)) { viewProps.nativeId = entropy.random<bool>() ? "42" : ""; } if (entropy.random<bool>(0.1)) { viewProps.backgroundColor = entropy.random<bool>() ? SharedColor() : whiteColor(); } if (entropy.random<bool>(0.1)) { viewProps.shadowColor = entropy.random<bool>() ? SharedColor() : blackColor(); } if (entropy.random<bool>(0.1)) { viewProps.accessible = entropy.random<bool>(); } if (entropy.random<bool>(0.1)) { viewProps.zIndex = entropy.random<int>(); } if (entropy.random<bool>(0.1)) { viewProps.pointerEvents = entropy.random<bool>() ? PointerEventsMode::Auto : PointerEventsMode::None; } if (entropy.random<bool>(0.1)) { viewProps.transform = entropy.random<bool>() ? Transform::Identity() : Transform::Perspective(42); } #ifdef ANDROID if (entropy.random<bool>(0.1)) { viewProps.elevation = entropy.random<bool>() ? 1 : 0; } #endif return shadowNode.clone({newProps}); } // Similar to `messWithLayoutableOnlyFlag` but has a 50/50 chance of flattening // (or unflattening) a node's children. static inline ShadowNode::Unshared messWithNodeFlattenednessFlags( const Entropy& entropy, const ShadowNode& shadowNode) { ContextContainer contextContainer{}; PropsParserContext parserContext{-1, contextContainer}; auto oldProps = shadowNode.getProps(); auto newProps = shadowNode.getComponentDescriptor().cloneProps( parserContext, oldProps, RawProps(folly::dynamic::object())); auto& viewProps = const_cast<ViewProps&>(static_cast<const ViewProps&>(*newProps)); if (entropy.random<bool>(0.5)) { viewProps.nativeId = ""; viewProps.collapsable = true; viewProps.backgroundColor = SharedColor(); viewProps.shadowColor = SharedColor(); viewProps.accessible = false; viewProps.zIndex = {}; viewProps.pointerEvents = PointerEventsMode::Auto; viewProps.transform = Transform::Identity(); #ifdef ANDROID viewProps.elevation = 0; #endif } else { viewProps.nativeId = "42"; viewProps.backgroundColor = whiteColor(); viewProps.shadowColor = blackColor(); viewProps.accessible = true; viewProps.zIndex = {entropy.random<int>()}; viewProps.pointerEvents = PointerEventsMode::None; viewProps.transform = Transform::Perspective(entropy.random<int>()); #ifdef ANDROID viewProps.elevation = entropy.random<int>(); #endif } return shadowNode.clone({newProps}); } static inline ShadowNode::Unshared messWithYogaStyles( const Entropy& entropy, const ShadowNode& shadowNode) { folly::dynamic dynamic = folly::dynamic::object(); if (entropy.random<bool>()) { dynamic["flexDirection"] = entropy.random<bool>() ? "row" : "column"; } std::vector<std::string> properties = { "flex", "flexGrow", "flexShrink", "flexBasis", "left", "top", "marginLeft", "marginTop", "marginRight", "marginBottom", "paddingLeft", "paddingTop", "paddingRight", "paddingBottom", "width", "height", "maxWidth", "maxHeight", "minWidth", "minHeight", }; // It is not safe to add new Yoga properties to this list. Unit tests // validate specific seeds, and what they test may change and cause unrelated // failures if the size of properties also changes. EXPECT_EQ(properties.size(), 20); for (const auto& property : properties) { if (entropy.random<bool>(0.1)) { dynamic[property] = entropy.random<int>(0, 1024); } } ContextContainer contextContainer; contextContainer.insert( "ReactNativeConfig", std::make_shared<EmptyReactNativeConfig>()); PropsParserContext parserContext{-1, contextContainer}; auto oldProps = shadowNode.getProps(); auto newProps = shadowNode.getComponentDescriptor().cloneProps( parserContext, oldProps, RawProps(dynamic)); return shadowNode.clone({newProps}); } using ShadowNodeAlteration = std::function< ShadowNode::Unshared(const Entropy& entropy, const ShadowNode& shadowNode)>; static inline void alterShadowTree( const Entropy& entropy, RootShadowNode::Shared& rootShadowNode, ShadowNodeAlteration alteration) { auto edge = findRandomShadowNode(entropy, rootShadowNode); rootShadowNode = std::static_pointer_cast<RootShadowNode>(rootShadowNode->cloneTree( edge.shadowNode->getFamily(), [&](const ShadowNode& oldShadowNode) { return alteration(entropy, oldShadowNode); })); } static inline void alterShadowTree( const Entropy& entropy, RootShadowNode::Shared& rootShadowNode, std::vector<ShadowNodeAlteration> alterations) { auto i = entropy.random<int>(0, alterations.size() - 1); alterShadowTree(entropy, rootShadowNode, alterations[i]); } static SharedViewProps generateDefaultProps( const ComponentDescriptor& componentDescriptor) { ContextContainer contextContainer{}; PropsParserContext parserContext{-1, contextContainer}; return std::static_pointer_cast<const ViewProps>( componentDescriptor.cloneProps(parserContext, nullptr, RawProps{})); } static inline ShadowNode::Shared generateShadowNodeTree( const Entropy& entropy, const ComponentDescriptor& componentDescriptor, int size, int deviation = 3) { if (size <= 1) { auto family = componentDescriptor.createFamily( {generateReactTag(), SurfaceId(1), nullptr}); return componentDescriptor.createShadowNode( ShadowNodeFragment{generateDefaultProps(componentDescriptor)}, family); } auto items = std::vector<int>(size); std::fill(items.begin(), items.end(), 1); auto chunks = entropy.distribute(items, deviation); auto children = ShadowNode::ListOfShared{}; for (const auto& chunk : chunks) { children.push_back( generateShadowNodeTree(entropy, componentDescriptor, chunk.size())); } auto family = componentDescriptor.createFamily( {generateReactTag(), SurfaceId(1), nullptr}); return componentDescriptor.createShadowNode( ShadowNodeFragment{ generateDefaultProps(componentDescriptor), std::make_shared<ShadowNode::ListOfShared>(children)}, family); } } // namespace facebook::react