/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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& 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(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(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(static_cast(*newProps)); if (entropy.random(0.1)) { viewProps.nativeId = entropy.random() ? "42" : ""; } if (entropy.random(0.1)) { viewProps.backgroundColor = entropy.random() ? SharedColor() : whiteColor(); } if (entropy.random(0.1)) { viewProps.shadowColor = entropy.random() ? SharedColor() : blackColor(); } if (entropy.random(0.1)) { viewProps.accessible = entropy.random(); } if (entropy.random(0.1)) { viewProps.zIndex = entropy.random(); } if (entropy.random(0.1)) { viewProps.pointerEvents = entropy.random() ? PointerEventsMode::Auto : PointerEventsMode::None; } if (entropy.random(0.1)) { viewProps.transform = entropy.random() ? Transform::Identity() : Transform::Perspective(42); } #ifdef ANDROID if (entropy.random(0.1)) { viewProps.elevation = entropy.random() ? 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(static_cast(*newProps)); if (entropy.random(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()}; viewProps.pointerEvents = PointerEventsMode::None; viewProps.transform = Transform::Perspective(entropy.random()); #ifdef ANDROID viewProps.elevation = entropy.random(); #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()) { dynamic["flexDirection"] = entropy.random() ? "row" : "column"; } std::vector 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(0.1)) { dynamic[property] = entropy.random(0, 1024); } } ContextContainer contextContainer; contextContainer.insert( "ReactNativeConfig", std::make_shared()); 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->cloneTree( edge.shadowNode->getFamily(), [&](const ShadowNode& oldShadowNode) { return alteration(entropy, oldShadowNode); })); } static inline void alterShadowTree( const Entropy& entropy, RootShadowNode::Shared& rootShadowNode, std::vector alterations) { auto i = entropy.random(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( 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(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(children)}, family); } } // namespace facebook::react