/* * 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 StackingContextTest : public ::testing::Test { protected: ComponentBuilder builder_; std::shared_ptr rootShadowNode_; std::shared_ptr nodeA_; std::shared_ptr nodeAA_; std::shared_ptr nodeB_; std::shared_ptr nodeBA_; std::shared_ptr nodeBB_; std::shared_ptr nodeBBA_; std::shared_ptr nodeBBB_; std::shared_ptr nodeBC_; std::shared_ptr nodeBD_; std::shared_ptr currentRootShadowNode_; StubViewTree currentStubViewTree_; StackingContextTest() : builder_(simpleComponentBuilder()) { // ┌────────────── (Root) ──────────────┐ // │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┃ │ // │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ // │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // └────────────────────────────────────┘ // clang-format off auto element = Element() .reference(rootShadowNode_) .tag(1) .children({ Element() .tag(2) .reference(nodeA_) .children({ Element() .tag(3) .reference(nodeAA_) }), Element() .tag(4) .reference(nodeB_) .children({ Element() .tag(5) .reference(nodeBA_), Element() .tag(6) .reference(nodeBB_) .children({ Element() .tag(7) .reference(nodeBBA_), Element() .tag(8) .reference(nodeBBB_) }), Element() .tag(9) .reference(nodeBC_), Element() .tag(10) .reference(nodeBD_) }) }); // clang-format on builder_.build(element); currentRootShadowNode_ = rootShadowNode_; currentRootShadowNode_->layoutIfNeeded(); currentStubViewTree_ = buildStubViewTreeWithoutUsingDifferentiator(*currentRootShadowNode_); } void mutateViewShadowNodeProps_( std::shared_ptr const &node, std::function callback) { rootShadowNode_ = std::static_pointer_cast(rootShadowNode_->cloneTree( node->getFamily(), [&](ShadowNode const &oldShadowNode) { auto viewProps = std::make_shared(); callback(*viewProps); return oldShadowNode.clone(ShadowNodeFragment{viewProps}); })); } void testViewTree_( std::function const &callback) { rootShadowNode_->layoutIfNeeded(); callback(buildStubViewTreeUsingDifferentiator(*rootShadowNode_)); callback(buildStubViewTreeWithoutUsingDifferentiator(*rootShadowNode_)); auto mutations = calculateShadowViewMutations(*currentRootShadowNode_, *rootShadowNode_); currentRootShadowNode_ = rootShadowNode_; currentStubViewTree_.mutate(mutations); callback(currentStubViewTree_); } }; TEST_F(StackingContextTest, defaultPropsMakeEverythingFlattened) { testViewTree_([](StubViewTree const &viewTree) { // 1 view in total. EXPECT_EQ(viewTree.size(), 1); // The root view has no subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 0); }); } TEST_F(StackingContextTest, mostPropsDoNotForceViewsToMaterialize) { // ┌────────────── (Root) ──────────────┐ ┌────────── (Root) ───────────┐ // │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ padding: 10; ┃ ┃ │ │ │ // │ ┃ ┃ margin: 9001; ┃ ┃ │ │ │ // │ ┃ ┃ position: absolute; ┃ ┃ │ │ │ // │ ┃ ┃ shadowRadius: 10; ┃ ┃ │ │ │ // │ ┃ ┃ shadowOffset: [42, 42]; ┃ ┃ │ │ │ // │ ┃ ┃ backgroundColor: clear; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ // │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ zIndex: 42; ┃ ┃ │ │ │ // │ ┃ ┃ margin: 42; ┃ ┃ │ │ │ // │ ┃ ┃ shadowColor: clear; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ No observable side-effects. │ // │ ┃ ┃ ┃ ┃ │━━━▶│ No views are generated. │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ position: relative; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ borderRadii: 42; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ borderColor: black; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ onLayout: true; ┃ ┃ │ │ │ // │ ┃ ┃ hitSlop: 42; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ // └────────────────────────────────────┘ └─────────────────────────────┘ mutateViewShadowNodeProps_(nodeAA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.padding()[YGEdgeAll] = YGValue{42, YGUnitPoint}; yogaStyle.margin()[YGEdgeAll] = YGValue{42, YGUnitPoint}; yogaStyle.positionType() = YGPositionTypeAbsolute; props.shadowRadius = 42; props.shadowOffset = Size{42, 42}; props.backgroundColor = clearColor(); }); mutateViewShadowNodeProps_(nodeBA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; props.zIndex = 42; yogaStyle.margin()[YGEdgeAll] = YGValue{42, YGUnitPoint}; props.shadowColor = clearColor(); props.shadowOpacity = 0.42; }); mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.borderRadii.all = 42; props.borderColors.all = blackColor(); }); mutateViewShadowNodeProps_(nodeBD_, [](ViewProps &props) { props.onLayout = true; props.hitSlop = EdgeInsets{42, 42, 42, 42}; }); testViewTree_([](StubViewTree const &viewTree) { // 1 view in total. EXPECT_EQ(viewTree.size(), 1); // The root view has no subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 0); }); } TEST_F(StackingContextTest, somePropsForceViewsToMaterialize1) { // ┌────────────── (Root) ──────────────┐ ┌─────────── (Root) ──────────┐ // │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ AA (tag: 3) ━━━━━━━━━━━┓ │ // │ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BA (tag: 5) ━━━━━━━━━━━┓ │ // │ ┃ ┃ backgroundColor: black; ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ │ │ ┏━ BBA (tag: 7) ━━━━━━━━━━┓ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┃ ┃ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┃ │ │ │ // │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ backgroundColor: white; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │━━━▶│ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ shadowColor: black; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ // └────────────────────────────────────┘ └─────────────────────────────┘ mutateViewShadowNodeProps_( nodeAA_, [](ViewProps &props) { props.backgroundColor = blackColor(); }); mutateViewShadowNodeProps_( nodeBA_, [](ViewProps &props) { props.backgroundColor = whiteColor(); }); mutateViewShadowNodeProps_( nodeBBA_, [](ViewProps &props) { props.shadowColor = blackColor(); }); testViewTree_([](StubViewTree const &viewTree) { // 4 views in total. EXPECT_EQ(viewTree.size(), 4); // The root view has all 3 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 3); // The root view subviews are [3, 5, 7]. EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 3); EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 5); EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 7); }); } TEST_F(StackingContextTest, somePropsForceViewsToMaterialize2) { // ┌────────────── (Root) ──────────────┐ ┌─────────── (Root) ──────────┐ // │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ A (tag: 2) ━━━━━━━━━━━━┓ │ // │ ┃ backgroundColor: black; ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ AA (tag: 3) ━━━━━━━━━━━┓ │ // │ ┃ ┃ pointerEvents: none; ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ │ │ ┏━ B (tag: 4) ━━━━━━━━━━━━┓ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┃ ┃ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BA (tag: 5) ━━━━━━━━━━━┓ │ // │ ┃ testId: "42" ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BB (tag: 6) ━━━━━━━━━━━┓ │ // │ ┃ ┃ nativeId: "42" ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┏━ BBA (tag: 7) ━━━━━━━━━━┓ │ // │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ foregroundColor: black; ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ │━━━▶│ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ │ │ ┏━ BBB (tag: 8) ━━━━━━━━━━┓ │ // │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ transform: scale(2); ┃ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ ┏━ BC (tag: 9) ━━━━━━━━━━━┓ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ position: relative; ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ zIndex: 42; ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ ┏━ BD (tag: 10) ━━━━━━━━━━┓ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┃ ┃ │ // │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ shadowColor: black; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ opacity: 0.42; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ // └────────────────────────────────────┘ └─────────────────────────────┘ mutateViewShadowNodeProps_( nodeA_, [](ViewProps &props) { props.backgroundColor = blackColor(); }); mutateViewShadowNodeProps_(nodeAA_, [](ViewProps &props) { props.pointerEvents = PointerEventsMode::None; }); mutateViewShadowNodeProps_( nodeB_, [](ViewProps &props) { props.testId = "42"; }); mutateViewShadowNodeProps_( nodeBA_, [](ViewProps &props) { props.nativeId = "42"; }); mutateViewShadowNodeProps_( nodeBB_, [](ViewProps &props) { props.foregroundColor = blackColor(); }); mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps &props) { props.transform = Transform::Scale(2, 2, 2); }); mutateViewShadowNodeProps_(nodeBBB_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 42; }); mutateViewShadowNodeProps_( nodeBC_, [](ViewProps &props) { props.shadowColor = blackColor(); }); mutateViewShadowNodeProps_( nodeBD_, [](ViewProps &props) { props.opacity = 0.42; }); testViewTree_([](StubViewTree const &viewTree) { // 10 views in total. EXPECT_EQ(viewTree.size(), 10); // The root view has all 9 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 9); }); } TEST_F(StackingContextTest, zIndexAndFlattenedNodes) { // ┌────────────── (Root) ──────────────┐ ┌────────── (Root) ───────────┐ // │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BD (tag: 10) ━━━━━━━━━━┓ │ // │ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BC (tag: 9) ━━━━━━━━━━━┓ │ // │ ┃ ┃ position: relative; ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ zIndex: 9001; ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ │ │ ┏━ BBB (tag: 8) ━━━━━━━━━━┓ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┃ ┃ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BBA (tag: 7) ━━━━━━━━━━┓ │ // │ ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BA (tag: 5) ━━━━━━━━━━━┓ │ // │ ┃ ┃ position: relative; ┃ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ zIndex: 9000; ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┏━ AA (tag: 3) ━━━━━━━━━━━┓ │ // │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ ┃ #FormsView ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┃ #FormsStackingContext ┃ │ // │ ┃ ┃ ┃ ┃ │━━━▶│ ┃ ┃ │ // │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ position: relative; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ zIndex: 8999; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ position: relative; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ zIndex: 8998; ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ position: relative; ┃ ┃ │ │ │ // │ ┃ ┃ zIndex: 8997; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │ // │ ┃ ┃ position: relative; ┃ ┃ │ │ │ // │ ┃ ┃ zIndex: 8996; ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┃ ┃ ┃ │ │ │ // │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ // │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │ // └────────────────────────────────────┘ └─────────────────────────────┘ mutateViewShadowNodeProps_(nodeAA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 9001; }); mutateViewShadowNodeProps_(nodeBA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 9000; }); mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 8999; }); mutateViewShadowNodeProps_(nodeBBB_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 8998; }); mutateViewShadowNodeProps_(nodeBC_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 8997; }); mutateViewShadowNodeProps_(nodeBD_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 8996; }); testViewTree_([](StubViewTree const &viewTree) { // 7 views in total. EXPECT_EQ(viewTree.size(), 7); // The root view has all 6 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 6); // The root view subviews are [10, 9, 8, 7, 5, 3]. EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 10); EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 9); EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 8); EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 7); EXPECT_EQ(viewTree.getRootStubView().children.at(4)->tag, 5); EXPECT_EQ(viewTree.getRootStubView().children.at(5)->tag, 3); }); // And now let's make BB to form a Stacking Context with small order-index. // ┌────────────── (Root) ──────────────┐ ┌────────── (Root) ──────────┐ // │ ┌─ A (tag: 2) ───────────────────┐ │ │ ┏━ BB (tag: 6) ━━━━━━━━━━┓ │ // │ │ │ │ │ ┃ #View ┃ │ // │ │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ ┃ ┏━ BBB (tag: 8) ━━━━━┓ ┃ │ // │ │ ┌─ AA (tag: 3) ──────────────┐ │ │ │ ┃ ┃ #View ┃ ┃ │ // │ │ │ position: relative; │ │ │ │ ┃ ┃ #StackingContext ┃ ┃ │ // │ │ │ zIndex: 9001; │ │ │ │ ┃ ┃ ┃ ┃ │ // │ │ │ │ │ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ │ │ │ │ │ │ ┃ ┏━ BBA (tag: 7) ━━━━━┓ ┃ │ // │ │ │ │ │ │ │ ┃ ┃ #View ┃ ┃ │ // │ │ │ │ │ │ │ ┃ ┃ #StackingContext ┃ ┃ │ // │ │ │ │ │ │ │ ┃ ┃ ┃ ┃ │ // │ │ └────────────────────────────┘ │ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━┛ ┃ │ // │ └────────────────────────────────┘ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┌─ B (tag: 4) ───────────────────┐ │ │ ┏━ BD (tag: 10) ━━━━━━━━━┓ │ // │ │ │ │ │ ┃ #View ┃ │ // │ │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ ┌─ BA (tag: 5) ──────────────┐ │ │ │ ┏━ BC (tag: 9) ━━━━━━━━━━┓ │ // │ │ │ position: relative; │ │ │ │ ┃ #View ┃ │ // │ │ │ zIndex: 9000; │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ └────────────────────────────┘ │ │ │ ┏━ BA (tag: 5) ━━━━━━━━━━┓ │ // │ │ ╔═ BB (tag: 6) ══════════════╗ │ │ │ ┃ #View ┃ │ // │ │ ║ *** position: relative; ║ │ │ │ ┃ #StackingContext ┃ │ // │ │ ║ *** zIndex: 42; ║ │ │━━━━▶│ ┃ ┃ │ // │ │ ║ ║ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ ║ ║ │ │ │ ┏━ AA (tag: 3) ━━━━━━━━━━┓ │ // │ │ ║ ┌─ BBA (tag: 7) ─────────┐ ║ │ │ │ ┃ #View ┃ │ // │ │ ║ │ position: relative; │ ║ │ │ │ ┃ #StackingContext ┃ │ // │ │ ║ │ zIndex: 8999; │ ║ │ │ │ ┃ ┃ │ // │ │ ║ │ │ ║ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ └────────────────────────┘ ║ │ │ │ │ // │ │ ║ ┌─ BBB (tag: 8) ─────────┐ ║ │ │ │ │ // │ │ ║ │ position: relative; │ ║ │ │ │ │ // │ │ ║ │ zIndex: 8998; │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ └────────────────────────┘ ║ │ │ │ │ // │ │ ╚════════════════════════════╝ │ │ │ │ // │ │ ┌─ BC (tag: 9) ──────────────┐ │ │ │ │ // │ │ │ position: relative; │ │ │ │ │ // │ │ │ zIndex: 8997; │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ └────────────────────────────┘ │ │ │ │ // │ │ ┌─ BD (tag: 10) ─────────────┐ │ │ │ │ // │ │ │ position: relative; │ │ │ │ │ // │ │ │ zIndex: 8996; │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ └────────────────────────────┘ │ │ │ │ // │ └────────────────────────────────┘ │ │ │ // └────────────────────────────────────┘ └────────────────────────────┘ mutateViewShadowNodeProps_(nodeBB_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeRelative; props.zIndex = 42; }); testViewTree_([](StubViewTree const &viewTree) { // 8 views in total. EXPECT_EQ(viewTree.size(), 8); // The root view has 5 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 5); // The root view subviews are [6, 10, 9, 5, 3]. EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 6); EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 10); EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 9); EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 5); EXPECT_EQ(viewTree.getRootStubView().children.at(4)->tag, 3); auto &view6 = viewTree.getStubView(6); EXPECT_EQ(view6.children.size(), 2); EXPECT_EQ(view6.children.at(0)->tag, 8); EXPECT_EQ(view6.children.at(1)->tag, 7); }); // And now, let's revert it back. mutateViewShadowNodeProps_(nodeBB_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.positionType() = YGPositionTypeStatic; props.zIndex = {}; }); testViewTree_([](StubViewTree const &viewTree) { // 7 views in total. EXPECT_EQ(viewTree.size(), 7); // The root view has all 6 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 6); // The root view subviews are [10, 9, 8, 7, 5, 3]. EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 10); EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 9); EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 8); EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 7); EXPECT_EQ(viewTree.getRootStubView().children.at(4)->tag, 5); EXPECT_EQ(viewTree.getRootStubView().children.at(5)->tag, 3); }); // And now, let's hide BB completety. // ┌────────────── (Root) ──────────────┐ ┌────────── (Root) ──────────┐ // │ ┌─ A (tag: 2) ───────────────────┐ │ │ ┏━ BD (tag: 10) ━━━━━━━━━┓ │ // │ │ │ │ │ ┃ #View ┃ │ // │ │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ ┌─ AA (tag: 3) ──────────────┐ │ │ │ ┏━ BC (tag: 9) ━━━━━━━━━━┓ │ // │ │ │ position: relative; │ │ │ │ ┃ #View ┃ │ // │ │ │ zIndex: 9001; │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ │ │ │ │ │ ┏━ BA (tag: 5) ━━━━━━━━━━┓ │ // │ │ │ │ │ │ │ ┃ #View ┃ │ // │ │ │ │ │ │ │ ┃ #StackingContext ┃ │ // │ │ └────────────────────────────┘ │ │ │ ┃ ┃ │ // │ └────────────────────────────────┘ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ ┌─ B (tag: 4) ───────────────────┐ │ │ ┏━ AA (tag: 3) ━━━━━━━━━━┓ │ // │ │ │ │ │ ┃ #View ┃ │ // │ │ │ │ │ ┃ #StackingContext ┃ │ // │ │ │ │ │ ┃ ┃ │ // │ │ │ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ │ // │ │ ┌─ BA (tag: 5) ──────────────┐ │ │ │ │ // │ │ │ position: relative; │ │ │ │ │ // │ │ │ zIndex: 9000; │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ └────────────────────────────┘ │ │ │ │ // │ │ ╔═ BB (tag: 6) ══════════════╗ │ │ │ │ // │ │ ║ *** display: none; ║ │ │ │ │ // │ │ ║ ║ │ │━━━━▶│ │ // │ │ ║ ║ │ │ │ │ // │ │ ║ ║ │ │ │ │ // │ │ ║ ┌─ BBA (tag: 7) ─────────┐ ║ │ │ │ │ // │ │ ║ │ position: relative; │ ║ │ │ │ │ // │ │ ║ │ zIndex: 8999; │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ └────────────────────────┘ ║ │ │ │ │ // │ │ ║ ┌─ BBB (tag: 8) ─────────┐ ║ │ │ │ │ // │ │ ║ │ position: relative; │ ║ │ │ │ │ // │ │ ║ │ zIndex: 8998; │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ │ │ ║ │ │ │ │ // │ │ ║ └────────────────────────┘ ║ │ │ │ │ // │ │ ╚════════════════════════════╝ │ │ │ │ // │ │ ┌─ BC (tag: 9) ──────────────┐ │ │ │ │ // │ │ │ position: relative; │ │ │ │ │ // │ │ │ zIndex: 8997; │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ └────────────────────────────┘ │ │ │ │ // │ │ ┌─ BD (tag: 10) ─────────────┐ │ │ │ │ // │ │ │ position: relative; │ │ │ │ │ // │ │ │ zIndex: 8996; │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ │ │ │ │ │ │ // │ │ └────────────────────────────┘ │ │ │ │ // │ └────────────────────────────────┘ │ │ │ // └────────────────────────────────────┘ └────────────────────────────┘ mutateViewShadowNodeProps_(nodeBB_, [](ViewProps &props) { auto &yogaStyle = props.yogaStyle; yogaStyle.display() = YGDisplayNone; }); testViewTree_([](StubViewTree const &viewTree) { // 5 views in total. EXPECT_EQ(viewTree.size(), 8); // The root view has all 5 subviews. EXPECT_EQ(viewTree.getRootStubView().children.size(), 5); // The root view subviews are [6, 10, 9, 5]. EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 6); EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 10); EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 9); EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 5); }); } } // namespace facebook::react