diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/OrderIndexTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/OrderIndexTest.cpp new file mode 100644 index 00000000000..804287cc133 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/OrderIndexTest.cpp @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +class OrderIndexTest : public ::testing::Test { + protected: + ComponentBuilder builder_; + std::shared_ptr rootShadowNode_; + std::shared_ptr nodeA_; + std::shared_ptr nodeB_; + std::shared_ptr nodeC_; + std::shared_ptr nodeD_; + + std::shared_ptr currentRootShadowNode_; + StubViewTree currentStubViewTree_; + + OrderIndexTest() : builder_(simpleComponentBuilder()) { + auto element = Element() + .reference(rootShadowNode_) + .tag(1) + .children({ + Element().tag(2).reference(nodeA_), + Element().tag(3).reference(nodeB_), + Element().tag(4).reference(nodeC_), + Element().tag(5).reference(nodeD_), + }); + + builder_.build(element); + + mutateViewShadowNodeProps_(nodeA_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Relative); + props.backgroundColor = blackColor(); // to ensure it won't get flattened + }); + mutateViewShadowNodeProps_(nodeB_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Relative); + props.backgroundColor = blackColor(); // to ensure it won't get flattened + }); + mutateViewShadowNodeProps_(nodeC_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Relative); + props.backgroundColor = blackColor(); // to ensure it won't get flattened + }); + mutateViewShadowNodeProps_(nodeD_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Relative); + props.backgroundColor = blackColor(); // to ensure it won't get flattened + }); + + currentRootShadowNode_ = rootShadowNode_; + currentRootShadowNode_->layoutIfNeeded(); + currentStubViewTree_ = + buildStubViewTreeWithoutUsingDifferentiator(*currentRootShadowNode_); + } + + void mutateViewShadowNodeProps_( + const std::shared_ptr& node, + std::function callback) { + rootShadowNode_ = + std::static_pointer_cast(rootShadowNode_->cloneTree( + node->getFamily(), [&](const ShadowNode& oldShadowNode) { + auto viewProps = std::make_shared(); + callback(*viewProps); + return oldShadowNode.clone(ShadowNodeFragment{viewProps}); + })); + } + + void testViewTree_( + const std::function& callback) { + rootShadowNode_->layoutIfNeeded(); + + callback(buildStubViewTreeUsingDifferentiator(*rootShadowNode_)); + + auto mutations = + calculateShadowViewMutations(*currentRootShadowNode_, *rootShadowNode_); + currentRootShadowNode_ = rootShadowNode_; + currentStubViewTree_.mutate(mutations); + callback(currentStubViewTree_); + } +}; + +TEST_F(OrderIndexTest, defaultOrderIsDocumentOrder) { + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeB_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeC_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeD_->getTag()); + }); +} + +TEST_F(OrderIndexTest, basicZIndex) { + mutateViewShadowNodeProps_( + nodeA_, [](ViewProps& props) { props.zIndex = 5; }); + mutateViewShadowNodeProps_( + nodeB_, [](ViewProps& props) { props.zIndex = 10; }); + mutateViewShadowNodeProps_( + nodeC_, [](ViewProps& props) { props.zIndex = 1; }); + mutateViewShadowNodeProps_( + nodeD_, [](ViewProps& props) { props.zIndex = 2; }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeC_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeD_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeB_->getTag()); + }); +} + +TEST_F(OrderIndexTest, negativeZIndex) { + mutateViewShadowNodeProps_( + nodeA_, [](ViewProps& props) { props.zIndex = 5; }); + mutateViewShadowNodeProps_( + nodeB_, [](ViewProps& props) { props.zIndex = -10; }); + mutateViewShadowNodeProps_( + nodeC_, [](ViewProps& props) { props.zIndex = -1; }); + mutateViewShadowNodeProps_( + nodeD_, [](ViewProps& props) { props.zIndex = 2; }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeB_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeC_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeD_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeA_->getTag()); + }); +} + +TEST_F(OrderIndexTest, zeroZIndex) { + mutateViewShadowNodeProps_( + nodeC_, [](ViewProps& props) { props.zIndex = 0; }); + mutateViewShadowNodeProps_( + nodeD_, [](ViewProps& props) { props.zIndex = 0; }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeB_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeC_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeD_->getTag()); + }); +} + +TEST_F(OrderIndexTest, staticBehindNonStatic) { + mutateViewShadowNodeProps_(nodeB_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Static); + props.backgroundColor = blackColor(); + }); + mutateViewShadowNodeProps_(nodeD_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Static); + props.backgroundColor = blackColor(); + }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeB_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeD_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeC_->getTag()); + }); +} + +TEST_F(OrderIndexTest, zIndexStaticBehindNonStatic) { + mutateViewShadowNodeProps_( + nodeB_, [](ViewProps& props) { props.zIndex = 5; }); + mutateViewShadowNodeProps_( + nodeC_, [](ViewProps& props) { props.zIndex = -1; }); + mutateViewShadowNodeProps_(nodeD_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Static); + props.backgroundColor = blackColor(); + }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeC_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeD_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeB_->getTag()); + }); +} + +TEST_F(OrderIndexTest, staticDoesNotGetZIndex) { + mutateViewShadowNodeProps_(nodeB_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Static); + props.backgroundColor = blackColor(); + props.zIndex = 5; + }); + mutateViewShadowNodeProps_(nodeD_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setPositionType(yoga::PositionType::Static); + props.backgroundColor = blackColor(); + props.zIndex = -5; + }); + + testViewTree_([this](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 4); + + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, nodeB_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, nodeD_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, nodeA_->getTag()); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, nodeC_->getTag()); + }); +} +} // namespace facebook::react