/* * 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 "Entropy.h" namespace facebook { namespace 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( ShadowNode::Shared const &parentShadowNode, std::function const &callback) { auto index = int{0}; for (auto const &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(ShadowNode::Shared const &rootShadowNode) { auto counter = int{0}; traverseShadowTree( rootShadowNode, [&](ShadowTreeEdge const &edge, bool &stop) { counter++; }); return counter; } static ShadowTreeEdge findShadowNodeWithIndex( ShadowNode::Shared const &rootNode, int index) { auto counter = int{0}; auto result = ShadowTreeEdge{}; traverseShadowTree(rootNode, [&](ShadowTreeEdge const &edge, bool &stop) { if (index == counter) { result = edge; } counter++; }); return result; } static ShadowTreeEdge findRandomShadowNode( Entropy const &entropy, ShadowNode::Shared const &rootShadowNode) { auto count = countShadowNodes(rootShadowNode); return findShadowNodeWithIndex( rootShadowNode, entropy.random(1 /* Excluding a root node */, count - 1)); } static ShadowNode::ListOfShared cloneSharedShadowNodeList( ShadowNode::ListOfShared const &list) { auto result = ShadowNode::ListOfShared{}; result.reserve(list.size()); for (auto const &shadowNode : list) { result.push_back(shadowNode->clone({})); } return result; } static inline ShadowNode::Unshared messWithChildren( Entropy const &entropy, ShadowNode const &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( Entropy const &entropy, ShadowNode const &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.foregroundColor = entropy.random() ? SharedColor() : blackColor(); } 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); } if (entropy.random(0.1)) { viewProps.elevation = entropy.random() ? 1 : 0; } 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( Entropy const &entropy, ShadowNode const &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.foregroundColor = SharedColor(); viewProps.shadowColor = SharedColor(); viewProps.accessible = false; viewProps.zIndex = {}; viewProps.pointerEvents = PointerEventsMode::Auto; viewProps.transform = Transform::Identity(); viewProps.elevation = 0; } else { viewProps.nativeId = "42"; viewProps.backgroundColor = whiteColor(); viewProps.foregroundColor = blackColor(); viewProps.shadowColor = blackColor(); viewProps.accessible = true; viewProps.zIndex = {entropy.random()}; viewProps.pointerEvents = PointerEventsMode::None; viewProps.transform = Transform::Perspective(entropy.random()); viewProps.elevation = entropy.random(); } return shadowNode.clone({newProps}); } static inline ShadowNode::Unshared messWithYogaStyles( Entropy const &entropy, ShadowNode const &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 (auto const &property : properties) { if (entropy.random(0.1)) { dynamic[property] = entropy.random(0, 1024); } } ContextContainer contextContainer{}; 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(Entropy const &entropy, ShadowNode const &shadowNode)>; static inline void alterShadowTree( Entropy const &entropy, RootShadowNode::Shared &rootShadowNode, ShadowNodeAlteration alteration) { auto edge = findRandomShadowNode(entropy, rootShadowNode); rootShadowNode = std::static_pointer_cast(rootShadowNode->cloneTree( edge.shadowNode->getFamily(), [&](ShadowNode const &oldShadowNode) { return alteration(entropy, oldShadowNode); })); } static inline void alterShadowTree( Entropy const &entropy, RootShadowNode::Shared &rootShadowNode, std::vector alterations) { auto i = entropy.random(0, alterations.size() - 1); alterShadowTree(entropy, rootShadowNode, alterations[i]); } static SharedViewProps generateDefaultProps( ComponentDescriptor const &componentDescriptor) { ContextContainer contextContainer{}; PropsParserContext parserContext{-1, contextContainer}; return std::static_pointer_cast( componentDescriptor.cloneProps(parserContext, nullptr, RawProps{})); } static inline ShadowNode::Shared generateShadowNodeTree( Entropy const &entropy, ComponentDescriptor const &componentDescriptor, int size, int deviation = 3) { if (size <= 1) { auto family = componentDescriptor.createFamily( {generateReactTag(), SurfaceId(1), nullptr}, 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 (auto const &chunk : chunks) { children.push_back( generateShadowNodeTree(entropy, componentDescriptor, chunk.size())); } auto family = componentDescriptor.createFamily( {generateReactTag(), SurfaceId(1), nullptr}, nullptr); return componentDescriptor.createShadowNode( ShadowNodeFragment{ generateDefaultProps(componentDescriptor), std::make_shared(children)}, family); } } // namespace react } // namespace facebook