first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
/*
* 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 <memory>
#include <gtest/gtest.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs/stubs.h>
namespace facebook::react {
class OrderIndexTest : public ::testing::Test {
protected:
std::unique_ptr<ComponentBuilder> builder_;
std::shared_ptr<RootShadowNode> rootShadowNode_;
std::shared_ptr<ViewShadowNode> nodeA_;
std::shared_ptr<ViewShadowNode> nodeB_;
std::shared_ptr<ViewShadowNode> nodeC_;
std::shared_ptr<ViewShadowNode> nodeD_;
std::shared_ptr<RootShadowNode> currentRootShadowNode_;
StubViewTree currentStubViewTree_;
void SetUp() override {
builder_ = std::make_unique<ComponentBuilder>(simpleComponentBuilder());
auto element = Element<RootShadowNode>()
.reference(rootShadowNode_)
.tag(1)
.children({
Element<ViewShadowNode>().tag(2).reference(nodeA_),
Element<ViewShadowNode>().tag(3).reference(nodeB_),
Element<ViewShadowNode>().tag(4).reference(nodeC_),
Element<ViewShadowNode>().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<ViewShadowNode>& node,
std::function<void(ViewProps& props)> callback) {
rootShadowNode_ =
std::static_pointer_cast<RootShadowNode>(rootShadowNode_->cloneTree(
node->getFamily(), [&](const ShadowNode& oldShadowNode) {
auto viewProps = std::make_shared<ViewShadowNodeProps>();
callback(*viewProps);
return oldShadowNode.clone(
ShadowNodeFragment{.props = viewProps});
}));
}
void testViewTree_(
const std::function<void(const StubViewTree& viewTree)>& 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

View File

@@ -0,0 +1,437 @@
/*
* 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 <vector>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs/stubs.h>
#include <react/test_utils/Entropy.h>
#include <react/test_utils/shadowTreeGeneration.h>
// Uncomment when random test blocks are uncommented below.
// #include <algorithm>
// #include <random>
namespace facebook::react {
static void testShadowNodeTreeLifeCycle(
uint_fast32_t seed,
int treeSize,
int repeats,
int stages) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<ContextContainer>();
auto componentDescriptorParameters = ComponentDescriptorParameters{
.eventDispatcher = eventDispatcher,
.contextContainer = contextContainer,
.flavor = nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
PropsParserContext parserContext{-1, *contextContainer};
auto allNodes = std::vector<std::shared_ptr<const ShadowNode>>{};
for (int i = 0; i < repeats; i++) {
allNodes.clear();
auto family = rootComponentDescriptor.createFamily(
{.tag = Tag(1), .surfaceId = SurfaceId(1), .instanceHandle = nullptr});
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<const RootShadowNode>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{
.props = RootShadowNode::defaultSharedProps()},
family)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
parserContext,
LayoutConstraints{
.minimumSize = Size{.width = 512, .height = 0},
.maximumSize =
Size{
.width = 512,
.height = std::numeric_limits<Float>::infinity()}},
LayoutContext{});
// Generation of a random tree.
auto singleRootChildNode =
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
// Injecting a tree into the root node.
auto currentRootNode = std::static_pointer_cast<const RootShadowNode>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = std::make_shared<
std::vector<std::shared_ptr<const ShadowNode>>>(
std::vector<std::shared_ptr<const ShadowNode>>{
singleRootChildNode})}));
// Building an initial view hierarchy.
auto viewTree = StubViewTree(ShadowView(*emptyRootNode));
viewTree.mutate(
calculateShadowViewMutations(*emptyRootNode, *currentRootNode));
for (int j = 0; j < stages; j++) {
auto nextRootNode = currentRootNode;
// Mutating the tree.
alterShadowTree(
entropy,
nextRootNode,
{
&messWithChildren,
&messWithYogaStyles,
&messWithLayoutableOnlyFlag,
});
std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);
nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);
// Calculating mutations.
auto mutations =
calculateShadowViewMutations(*currentRootNode, *nextRootNode);
// Make sure that in a single frame, a DELETE for a
// view is not followed by a CREATE for the same view.
{
std::vector<int> deletedTags{};
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Delete) {
deletedTags.push_back(mutation.oldChildShadowView.tag);
}
}
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Create) {
if (std::find(
deletedTags.begin(),
deletedTags.end(),
mutation.newChildShadowView.tag) != deletedTags.end()) {
LOG(ERROR) << "Deleted tag was recreated in mutations list: ["
<< mutation.newChildShadowView.tag << "]";
react_native_assert(false);
}
}
}
}
// Mutating the view tree.
viewTree.mutate(mutations);
// Building a view tree to compare with.
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR) << "Entropy seed: " << entropy.getSeed() << "\n";
// There are some issues getting `getDebugDescription` to compile
// under test on Android for now.
#if RN_DEBUG_STRING_CONVERTIBLE
LOG(ERROR) << "Shadow Tree before: \n"
<< currentRootNode->getDebugDescription();
LOG(ERROR) << "Shadow Tree after: \n"
<< nextRootNode->getDebugDescription();
LOG(ERROR) << "View Tree before: \n"
<< getDebugDescription(viewTree.getRootStubView(), {});
LOG(ERROR) << "View Tree after: \n"
<< getDebugDescription(
rebuiltViewTree.getRootStubView(), {});
LOG(ERROR) << "Mutations:" << "\n"
<< getDebugDescription(mutations, {});
#endif
react_native_assert(false);
}
currentRootNode = nextRootNode;
}
}
SUCCEED();
}
static void testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
uint_fast32_t seed,
int treeSize,
int repeats,
int stages) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<ContextContainer>();
auto componentDescriptorParameters = ComponentDescriptorParameters{
.eventDispatcher = eventDispatcher,
.contextContainer = contextContainer,
.flavor = nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
PropsParserContext parserContext{-1, *contextContainer};
auto allNodes = std::vector<std::shared_ptr<const ShadowNode>>{};
for (int i = 0; i < repeats; i++) {
allNodes.clear();
auto family = rootComponentDescriptor.createFamily(
{.tag = Tag(1), .surfaceId = SurfaceId(1), .instanceHandle = nullptr});
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<const RootShadowNode>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{
.props = RootShadowNode::defaultSharedProps()},
family)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
parserContext,
LayoutConstraints{
.minimumSize = Size{.width = 512, .height = 0},
.maximumSize =
Size{
.width = 512,
.height = std::numeric_limits<Float>::infinity()}},
LayoutContext{});
// Generation of a random tree.
auto singleRootChildNode =
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
// Injecting a tree into the root node.
auto currentRootNode = std::static_pointer_cast<const RootShadowNode>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = std::make_shared<
std::vector<std::shared_ptr<const ShadowNode>>>(
std::vector<std::shared_ptr<const ShadowNode>>{
singleRootChildNode})}));
// Building an initial view hierarchy.
auto viewTree = buildStubViewTreeWithoutUsingDifferentiator(*emptyRootNode);
viewTree.mutate(
calculateShadowViewMutations(*emptyRootNode, *currentRootNode));
for (int j = 0; j < stages; j++) {
auto nextRootNode = currentRootNode;
// Mutating the tree.
alterShadowTree(
entropy,
nextRootNode,
{
&messWithYogaStyles,
&messWithLayoutableOnlyFlag,
});
alterShadowTree(entropy, nextRootNode, &messWithNodeFlattenednessFlags);
alterShadowTree(entropy, nextRootNode, &messWithChildren);
std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);
nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);
// Calculating mutations.
auto mutations =
calculateShadowViewMutations(*currentRootNode, *nextRootNode);
// Make sure that in a single frame, a DELETE for a
// view is not followed by a CREATE for the same view.
{
std::vector<int> deletedTags{};
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Delete) {
deletedTags.push_back(mutation.oldChildShadowView.tag);
}
}
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Create) {
if (std::find(
deletedTags.begin(),
deletedTags.end(),
mutation.newChildShadowView.tag) != deletedTags.end()) {
LOG(ERROR) << "Deleted tag was recreated in mutations list: ["
<< mutation.newChildShadowView.tag << "]";
react_native_assert(false);
}
}
}
}
// Mutating the view tree.
viewTree.mutate(mutations);
// Building a view tree to compare with.
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR) << "Entropy seed: " << entropy.getSeed() << "\n";
// There are some issues getting `getDebugDescription` to compile
// under test on Android for now.
#if RN_DEBUG_STRING_CONVERTIBLE
LOG(ERROR) << "Shadow Tree before: \n"
<< currentRootNode->getDebugDescription();
LOG(ERROR) << "Shadow Tree after: \n"
<< nextRootNode->getDebugDescription();
LOG(ERROR) << "View Tree before: \n"
<< getDebugDescription(viewTree.getRootStubView(), {});
LOG(ERROR) << "View Tree after: \n"
<< getDebugDescription(
rebuiltViewTree.getRootStubView(), {});
LOG(ERROR) << "Mutations:" << "\n"
<< getDebugDescription(mutations, {});
#endif
react_native_assert(false);
}
currentRootNode = nextRootNode;
}
}
SUCCEED();
}
} // namespace facebook::react
using namespace facebook::react;
TEST(
ShadowTreeLifecycleTest,
stableBiggerTreeFewerIterationsOptimizedMovesFlattener) {
testShadowNodeTreeLifeCycle(
/* seed */ 0,
/* size */ 512,
/* repeats */ 32,
/* stages */ 32);
}
TEST(
ShadowTreeLifecycleTest,
stableBiggerTreeFewerIterationsOptimizedMovesFlattener2) {
testShadowNodeTreeLifeCycle(
/* seed */ 1,
/* size */ 512,
/* repeats */ 32,
/* stages */ 32);
}
TEST(
ShadowTreeLifecycleTest,
stableSmallerTreeMoreIterationsOptimizedMovesFlattener) {
testShadowNodeTreeLifeCycle(
/* seed */ 0,
/* size */ 16,
/* repeats */ 512,
/* stages */ 32);
}
TEST(
ShadowTreeLifecycleTest,
unstableSmallerTreeFewerIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 32,
/* repeats */ 32,
/* stages */ 32);
}
TEST(
ShadowTreeLifecycleTest,
unstableBiggerTreeFewerIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 256,
/* repeats */ 32,
/* stages */ 32);
}
TEST(
ShadowTreeLifecycleTest,
unstableSmallerTreeMoreIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 32,
/* repeats */ 512,
/* stages */ 32);
}
// failing test case found 4-25-2021
// TODO: T213669056
// TEST(
// ShadowTreeLifecycleTest,
// unstableSmallerTreeMoreIterationsExtensiveFlatteningUnflattening_1167342011)
// {
// testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
// /* seed */ 1167342011,
// /* size */ 32,
// /* repeats */ 512,
// /* stages */ 32);
// }
// You may uncomment this - locally only! - to generate failing seeds.
// TEST(
// ShadowTreeLifecycleTest,
// unstableSmallerTreeMoreIterationsExtensiveFlatteningUnflatteningManyRandom)
// {
// std::random_device device;
// for (int i = 0; i < 10; i++) {
// uint_fast32_t seed = device();
// LOG(ERROR) << "Seed: " << seed;
// testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
// /* seed */ seed,
// /* size */ 32,
// /* repeats */ 512,
// /* stages */ 32);
// }
// }

View File

@@ -0,0 +1,978 @@
/*
* 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 <memory>
#include <gtest/gtest.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs/stubs.h>
namespace facebook::react {
class StackingContextTest : public ::testing::Test {
protected:
std::unique_ptr<ComponentBuilder> builder_;
std::shared_ptr<RootShadowNode> rootShadowNode_;
std::shared_ptr<ViewShadowNode> nodeA_;
std::shared_ptr<ViewShadowNode> nodeAA_;
std::shared_ptr<ViewShadowNode> nodeB_;
std::shared_ptr<ViewShadowNode> nodeBA_;
std::shared_ptr<ViewShadowNode> nodeBB_;
std::shared_ptr<ViewShadowNode> nodeBBA_;
std::shared_ptr<ViewShadowNode> nodeBBB_;
std::shared_ptr<ViewShadowNode> nodeBC_;
std::shared_ptr<ViewShadowNode> nodeBD_;
std::shared_ptr<RootShadowNode> currentRootShadowNode_;
StubViewTree currentStubViewTree_;
void SetUp() override {
// ┌────────────── (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<RootShadowNode>()
.reference(rootShadowNode_)
.tag(1)
.children({
Element<ViewShadowNode>()
.tag(2)
.reference(nodeA_)
.children({
Element<ViewShadowNode>()
.tag(3)
.reference(nodeAA_)
}),
Element<ViewShadowNode>()
.tag(4)
.reference(nodeB_)
.children({
Element<ViewShadowNode>()
.tag(5)
.reference(nodeBA_),
Element<ViewShadowNode>()
.tag(6)
.reference(nodeBB_)
.children({
Element<ViewShadowNode>()
.tag(7)
.reference(nodeBBA_),
Element<ViewShadowNode>()
.tag(8)
.reference(nodeBBB_)
}),
Element<ViewShadowNode>()
.tag(9)
.reference(nodeBC_),
Element<ViewShadowNode>()
.tag(10)
.reference(nodeBD_)
})
});
// clang-format on
builder_ = std::make_unique<ComponentBuilder>(simpleComponentBuilder());
builder_->build(element);
currentRootShadowNode_ = rootShadowNode_;
currentRootShadowNode_->layoutIfNeeded();
currentStubViewTree_ =
buildStubViewTreeWithoutUsingDifferentiator(*currentRootShadowNode_);
}
void TearDown() override {
ReactNativeFeatureFlags::dangerouslyReset();
}
void mutateViewShadowNodeProps_(
const std::shared_ptr<ViewShadowNode>& node,
std::function<void(ViewProps& props)> callback) {
rootShadowNode_ =
std::static_pointer_cast<RootShadowNode>(rootShadowNode_->cloneTree(
node->getFamily(), [&](const ShadowNode& oldShadowNode) {
auto viewProps = std::make_shared<ViewShadowNodeProps>();
callback(*viewProps);
return oldShadowNode.clone(
ShadowNodeFragment{.props = viewProps});
}));
}
void testViewTree_(
const std::function<void(const StubViewTree& viewTree)>& 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_([](const StubViewTree& 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.setPadding(yoga::Edge::All, yoga::StyleLength::points(42));
yogaStyle.setMargin(yoga::Edge::All, yoga::StyleLength::points(42));
yogaStyle.setPositionType(yoga::PositionType::Absolute);
props.shadowRadius = 42;
props.shadowOffset = Size{.width = 42, .height = 42};
props.backgroundColor = clearColor();
});
mutateViewShadowNodeProps_(nodeBA_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
props.zIndex = 42;
yogaStyle.setPositionType(yoga::PositionType::Static);
yogaStyle.setMargin(yoga::Edge::All, yoga::StyleLength::points(42));
props.shadowColor = clearColor();
props.shadowOpacity = 0.42;
});
mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.borderRadii.all = ValueUnit{42, UnitType::Point};
props.borderColors.all = blackColor();
});
mutateViewShadowNodeProps_(nodeBD_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
props.onLayout = true;
props.hitSlop = EdgeInsets{42, 42, 42, 42};
yogaStyle.setPositionType(yoga::PositionType::Static);
});
testViewTree_([](const StubViewTree& 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_([](const StubViewTree& 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 ┃ │
// │ ┃ ┃ backgroundColor: 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.backgroundColor = blackColor(); });
mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps& props) {
props.transform = Transform::Scale(2, 2, 2);
});
mutateViewShadowNodeProps_(nodeBBB_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 42;
});
mutateViewShadowNodeProps_(
nodeBC_, [](ViewProps& props) { props.shadowColor = blackColor(); });
mutateViewShadowNodeProps_(
nodeBD_, [](ViewProps& props) { props.opacity = 0.42; });
testViewTree_([](const StubViewTree& 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, nonCollapsableChildren) {
// ┌────────────── (Root) ──────────────┐ ┌─────────── (Root) ──────────┐
// │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BBA (tag: 7) ━━━━━━━━━━┓ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BBB (tag: 8) ━━━━━━━━━━┓ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┃ ┃ ┃ ┃ │ │ |
// │ ┃ ┃ ┃ ┃ │ │ |
// │ ┃ ┃ ┃ ┃ │ │ |
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ |
// │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ |
// │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ │
// │ ┃ ┃ │ │ │
// │ ┃ ┃ │ │ │
// │ ┃ ┃ │ │ │
// │ ┃ ┃ │ │ │
// │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ collapsableChildren: false ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │━━━▶│ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │
// │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │
// └────────────────────────────────────┘ └─────────────────────────────┘
mutateViewShadowNodeProps_(
nodeBB_, [](ViewProps& props) { props.collapsableChildren = false; });
testViewTree_([](const StubViewTree& viewTree) {
// 3 views in total.
EXPECT_EQ(viewTree.size(), 3);
// The root view has all 2 subviews.
EXPECT_EQ(viewTree.getRootStubView().children.size(), 2);
// The root view subviews are [7,8].
EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 7);
EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 8);
});
}
TEST_F(StackingContextTest, nonCollapsableChildrenMixed) {
// ┌────────────── (Root) ──────────────┐ ┌─────────── (Root) ──────────┐
// │ ┏━ A (tag: 2) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BA (tag: 5) ━━ ━━━━━━━━┓ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┃ ┏━ AA (tag: 3) ━━━━━━━━━━━━━━┓ ┃ │ │ ┏━ BB (tag: 6) ━━━━━━━━━━┓ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┏━ BBA (tag: 7) ━━━-━━┓ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃FormsView ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃FormsStackingContext ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━-┛ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┃ ┃ ┃ ┃ │ │ ┏━ BC (tag: 9) ━━━━━━━━━━┓ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ ┃ ┃ │
// │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┏━ B (tag: 4) ━━━━━━━━━━━━━━━━━━━┓ │ │ ┏━ BD (tag: 10) ━━━━━━━━━━┓ │
// │ ┃ collapsableChildren: false ┃ │ │ ┃FormsView ┃ │
// │ ┃ ┃ │ │ ┃FormsStackingContext ┃ │
// │ ┃ ┃ │ │ ┃ ┃ │
// │ ┃ ┃ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
// │ ┃ ┏━ BA (tag: 5) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BB (tag: 6) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │━━━▶│ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┏━ BBA (tag: 7) ━━━━━━━━━┓ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ testId: "42" ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │
// │ ┃ ┃ ┏━ BBB (tag: 8) ━━━━━━━━━┓ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BC (tag: 9) ━━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ collapsable: true ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┃ ┏━ BD (tag: 10) ━━━━━━━━━━━━━┓ ┃ │ │ │
// │ ┃ ┃ testId: "123" ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┃ ┃ ┃ │ │ │
// │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │
// │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ │
// └────────────────────────────────────┘ └─────────────────────────────┘
mutateViewShadowNodeProps_(
nodeB_, [](ViewProps& props) { props.collapsableChildren = false; });
mutateViewShadowNodeProps_(
nodeBBA_, [](ViewProps& props) { props.testId = "42"; });
mutateViewShadowNodeProps_(
nodeBC_, [](ViewProps& props) { props.collapsable = true; });
mutateViewShadowNodeProps_(
nodeBD_, [](ViewProps& props) { props.testId = "43"; });
testViewTree_([](const StubViewTree& viewTree) {
// 6 views in total.
EXPECT_EQ(viewTree.size(), 6);
// The root view has four of the subviews.
EXPECT_EQ(viewTree.getRootStubView().children.size(), 4);
// The root view subviews are [5, 6, 9, 10].
EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 5);
EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 6);
EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 9);
EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 10);
EXPECT_EQ(viewTree.getRootStubView().children.at(1)->children.size(), 1);
EXPECT_EQ(
viewTree.getRootStubView().children.at(1)->children.at(0)->tag, 7);
});
}
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.setPositionType(yoga::PositionType::Relative);
props.zIndex = 9001;
});
mutateViewShadowNodeProps_(nodeBA_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 9000;
});
mutateViewShadowNodeProps_(nodeBBA_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 8999;
});
mutateViewShadowNodeProps_(nodeBBB_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 8998;
});
mutateViewShadowNodeProps_(nodeBC_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 8997;
});
mutateViewShadowNodeProps_(nodeBD_, [](ViewProps& props) {
auto& yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Relative);
props.zIndex = 8996;
});
testViewTree_([](const StubViewTree& 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.setPositionType(yoga::PositionType::Relative);
props.zIndex = 42;
});
testViewTree_([](const StubViewTree& 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.setPositionType(yoga::PositionType::Static);
props.zIndex = {};
});
testViewTree_([](const StubViewTree& 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.setDisplay(yoga::Display::None);
});
testViewTree_([](const StubViewTree& viewTree) {
#ifdef ANDROID
// T153547836: Android still mounts views with
// ShadowNodeTraits::Trait::Hidden
EXPECT_EQ(viewTree.size(), 8);
// nodeBB_ forms a stacking context
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);
EXPECT_EQ(viewTree.getRootStubView().children.at(4)->tag, 3);
#else
EXPECT_EQ(viewTree.size(), 5);
// The root view has all 4 subviews.
EXPECT_EQ(viewTree.getRootStubView().children.size(), 4);
// The root view subviews are [6, 10, 9, 5].
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, 5);
EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 3);
#endif
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,856 @@
/*
* 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 <memory>
#include <gtest/gtest.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/mounting/MountingCoordinator.h>
#include <react/renderer/mounting/ShadowTree.h>
#include <react/renderer/mounting/ShadowTreeDelegate.h>
#include <react/renderer/element/testUtils.h>
using namespace facebook::react;
class DummyShadowTreeDelegate : public ShadowTreeDelegate {
public:
RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree& /*shadowTree*/,
const RootShadowNode::Shared& /*oldRootShadowNode*/,
const RootShadowNode::Unshared& newRootShadowNode,
const ShadowTree::CommitOptions& /*commitOptions*/) const override {
return newRootShadowNode;
}
void shadowTreeDidFinishTransaction(
std::shared_ptr<const MountingCoordinator> mountingCoordinator,
bool /*mountSynchronously*/) const override {}
};
namespace {
const ShadowNode* findDescendantNode(
const ShadowNode& shadowNode,
const ShadowNodeFamily& family) {
if (&shadowNode.getFamily() == &family) {
return &shadowNode;
}
for (const auto& childNode : shadowNode.getChildren()) {
auto descendant = findDescendantNode(*childNode, family);
if (descendant != nullptr) {
return descendant;
}
}
return nullptr;
}
const ShadowNode* findDescendantNode(
const ShadowTree& shadowTree,
const ShadowNodeFamily& family) {
return findDescendantNode(
*shadowTree.getCurrentRevision().rootShadowNode, family);
}
} // namespace
class StateReconciliationTest : public ::testing::TestWithParam<bool> {
public:
StateReconciliationTest() : builder_(simpleComponentBuilder()) {}
ComponentBuilder builder_;
};
TEST_F(StateReconciliationTest, testStateReconciliation) {
// ==== SETUP ====
/*
<Root>
<View>
<ScrollView />
</View>
</Root>
*/
auto parentShadowNode = std::shared_ptr<ViewShadowNode>{};
auto scrollViewInitialShadowNode = std::shared_ptr<ScrollViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ViewShadowNode>()
.reference(parentShadowNode).children({
Element<ScrollViewShadowNode>()
.reference(scrollViewInitialShadowNode)
})
});
// clang-format on
ContextContainer contextContainer{};
auto initialRootShadowNode = builder_.build(element);
auto rootShadowNodeState1 = initialRootShadowNode->ShadowNode::clone({});
auto& scrollViewComponentDescriptor =
scrollViewInitialShadowNode->getComponentDescriptor();
auto& scrollViewFamily = scrollViewInitialShadowNode->getFamily();
auto initialState = scrollViewInitialShadowNode->getState();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== INITIAL COMMIT ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNodeState1);
},
{.enableStateReconciliation = true});
EXPECT_EQ(initialState->getMostRecentState(), initialState);
EXPECT_EQ(
findDescendantNode(*rootShadowNodeState1, scrollViewFamily)->getState(),
initialState);
// ==== COMMIT with new State 2 ====
auto state2 = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeState2 = initialRootShadowNode->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = state2});
});
EXPECT_EQ(
findDescendantNode(*initialRootShadowNode, scrollViewFamily)->getState(),
initialState);
EXPECT_EQ(
findDescendantNode(*rootShadowNodeState2, scrollViewFamily)->getState(),
state2);
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNodeState2);
},
{.enableStateReconciliation = false});
EXPECT_EQ(initialState->getMostRecentState(), state2);
EXPECT_EQ(state2->getMostRecentState(), state2);
// ==== COMMIT with new State 3 ====
auto state3 = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeState3 = rootShadowNodeState2->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = state3});
});
EXPECT_EQ(
findDescendantNode(*rootShadowNodeState3, scrollViewFamily)->getState(),
state3);
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNodeState3);
},
{.enableStateReconciliation = false});
EXPECT_EQ(
findDescendantNode(shadowTree, scrollViewFamily)->getState(), state3);
EXPECT_EQ(initialState->getMostRecentState(), state3);
EXPECT_EQ(state2->getMostRecentState(), state3);
EXPECT_EQ(state3->getMostRecentState(), state3);
// ==== COMMIT from React ====
auto rootShadowNode = rootShadowNodeState2->cloneTree(
parentShadowNode->getFamily(),
[&](const ShadowNode& oldShadowNode) { return oldShadowNode.clone({}); });
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNode);
},
{.enableStateReconciliation = true});
EXPECT_EQ(
findDescendantNode(shadowTree, scrollViewFamily)
->getState()
->getRevision(),
state3->getRevision());
}
TEST_F(StateReconciliationTest, testCloneslessStateReconciliationDoesntClone) {
// ==== SETUP ====
/*
<Root>
<ScrollView />
</Root>
*/
auto initialScrollViewShadowNode = std::shared_ptr<ScrollViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ScrollViewShadowNode>()
.reference(initialScrollViewShadowNode)
});
// clang-format on
ContextContainer contextContainer{};
auto rootShadowNode1 = builder_.build(element);
auto& scrollViewComponentDescriptor =
initialScrollViewShadowNode->getComponentDescriptor();
auto& scrollViewFamily = initialScrollViewShadowNode->getFamily();
auto initialState = initialScrollViewShadowNode->getState();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== Initial commit ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNode1);
},
{.enableStateReconciliation = true});
EXPECT_EQ(initialState->getMostRecentState(), initialState);
EXPECT_EQ(
findDescendantNode(*rootShadowNode1, scrollViewFamily)->getState(),
initialState);
// ==== C++ state update commit ====
auto state2 = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNode2 = rootShadowNode1->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = state2});
});
EXPECT_EQ(
findDescendantNode(*rootShadowNode2, scrollViewFamily)->getState(),
state2);
EXPECT_EQ(initialState->getMostRecentState(), initialState);
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNode2);
},
{.enableStateReconciliation = false});
EXPECT_EQ(initialState->getMostRecentState(), state2);
EXPECT_EQ(state2->getMostRecentState(), state2);
// ==== Creact clones tree ====
std::shared_ptr<ShadowNode> newlyClonedShadowNode;
auto rootShadowNodeClonedFromReact = rootShadowNode2->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
newlyClonedShadowNode = oldShadowNode.clone({});
return newlyClonedShadowNode;
});
auto state3 = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeClonedFromStateUpdate = rootShadowNode2->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = state3});
});
// ==== State update ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromStateUpdate);
},
{.enableStateReconciliation = false});
// ==== React commit ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
auto scrollViewShadowNode = findDescendantNode(shadowTree, scrollViewFamily);
EXPECT_EQ(scrollViewShadowNode->getState(), state3);
}
TEST_F(StateReconciliationTest, testStateReconciliationScrollViewChildUpdate) {
// ==== SETUP ====
/*
<Root>
<ScrollView>
<View />
</ScrollView>
</Root>
*/
auto initialScrollViewShadowNode = std::shared_ptr<ScrollViewShadowNode>{};
auto initialChildViewShadowNode = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ScrollViewShadowNode>()
.reference(initialScrollViewShadowNode)
.children({
Element<ViewShadowNode>()
.reference(initialChildViewShadowNode)
})
});
// clang-format on
ContextContainer contextContainer{};
auto initialRootShadowNode = builder_.build(element);
auto& scrollViewComponentDescriptor =
initialScrollViewShadowNode->getComponentDescriptor();
auto& scrollViewFamily = initialScrollViewShadowNode->getFamily();
auto initialState = initialScrollViewShadowNode->getState();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== Initial commit ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(initialRootShadowNode);
},
{.enableStateReconciliation = true});
// ==== React starts cloning but does not commit ====
std::shared_ptr<ShadowNode> newlyClonedViewShadowNode;
auto rootShadowNodeClonedFromReact = initialRootShadowNode->cloneTree(
initialChildViewShadowNode->getFamily(),
[&](const ShadowNode& oldShadowNode) {
auto& viewComponentDescriptor =
initialChildViewShadowNode->getComponentDescriptor();
PropsParserContext parserContext{-1, contextContainer};
auto props =
viewComponentDescriptor.cloneProps(parserContext, nullptr, {});
newlyClonedViewShadowNode = oldShadowNode.clone({});
return newlyClonedViewShadowNode;
});
// ==== State update ====
auto state2 = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNode2 = initialRootShadowNode->cloneTree(
scrollViewFamily, [&](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = state2});
});
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootShadowNode2);
},
{.enableStateReconciliation = false});
// ==== React commits its tree ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
auto scrollViewShadowNode = findDescendantNode(shadowTree, scrollViewFamily);
EXPECT_EQ(scrollViewShadowNode->getState(), state2);
EXPECT_EQ(
findDescendantNode(shadowTree, initialChildViewShadowNode->getFamily()),
newlyClonedViewShadowNode.get());
}
TEST_F(StateReconciliationTest, testScrollViewWithChildrenDeletion) {
// ==== SETUP ====
/*
<Root>
<View> - parent
<ScrollView /> - child A - will be deleted.
<ScrollView /> - child B - will remain and its props are updated.
</View>
</Root>
*/
auto parentView = std::shared_ptr<ViewShadowNode>{};
auto childA = std::shared_ptr<ScrollViewShadowNode>{};
auto childB = std::shared_ptr<ScrollViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ViewShadowNode>()
.reference(parentView)
.children({
Element<ScrollViewShadowNode>()
.reference(childA),
Element<ScrollViewShadowNode>()
.reference(childB),
})
});
// clang-format on
ContextContainer contextContainer{};
auto rootNode = builder_.build(element);
auto& scrollViewComponentDescriptor = childB->getComponentDescriptor();
auto& childBFamily = childB->getFamily();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== INITIAL COMMIT ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{.enableStateReconciliation = true});
// ==== Tree without childA and childB has new props ====
auto rootShadowNodeClonedFromReact = rootNode->cloneTree(
parentView->getFamily(),
[&childB, &scrollViewComponentDescriptor, &contextContainer](
const ShadowNode& oldShadowNode) {
PropsParserContext parserContext{-1, contextContainer};
auto clonedChildB = childB->clone({
.props = scrollViewComponentDescriptor.cloneProps(
parserContext, nullptr, {}),
});
std::shared_ptr<const ShadowNode> shadowNode = clonedChildB;
std::vector<std::shared_ptr<const ShadowNode>> children =
std::vector({shadowNode});
const auto childrenShared =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
children);
return oldShadowNode.clone({.children = childrenShared});
});
// ==== State update ====
auto newState = scrollViewComponentDescriptor.createState(
childBFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeClonedFromStateUpdate = rootNode->cloneTree(
childBFamily, [&newState](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = newState});
});
shadowTree.commit(
[&rootShadowNodeClonedFromStateUpdate](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromStateUpdate);
},
{.enableStateReconciliation = false});
EXPECT_NE(findDescendantNode(shadowTree, childA->getFamily()), nullptr);
// ==== Now the react commit happens. ====
shadowTree.commit(
[&rootShadowNodeClonedFromReact](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
EXPECT_EQ(findDescendantNode(shadowTree, childA->getFamily()), nullptr);
EXPECT_EQ(
findDescendantNode(shadowTree, childB->getFamily())->getState(),
newState);
}
TEST_F(StateReconciliationTest, testScrollViewWithComplexChildrenReorder) {
// ==== SETUP ====
/*
<Root>
<View> - grandparent
<View> - parent A
<ScrollView /> - child A
</View>
<View> - parent B
<ScrollView /> - child B
</View>
</View>
</Root>
*/
auto grandParent = std::shared_ptr<ViewShadowNode>{};
auto childA = std::shared_ptr<ScrollViewShadowNode>{};
auto childB = std::shared_ptr<ScrollViewShadowNode>{};
auto parentA = std::shared_ptr<ViewShadowNode>{};
auto parentB = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ViewShadowNode>()
.reference(grandParent)
.children({
Element<ViewShadowNode>()
.reference(parentA)
.children({
Element<ScrollViewShadowNode>()
.reference(childA)
}),
Element<ViewShadowNode>()
.reference(parentB)
.children({
Element<ScrollViewShadowNode>()
.reference(childB)
}),
})
});
// clang-format on
ContextContainer contextContainer{};
auto rootNode = builder_.build(element);
auto& scrollViewComponentDescriptor = childB->getComponentDescriptor();
auto& childAFamily = childA->getFamily();
auto initialState = childA->getState();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== INITIAL COMMIT ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{.enableStateReconciliation = true});
// ==== Tree swapping childA and childB. ChildB has new props ====
auto rootShadowNodeClonedFromReact = rootNode->cloneTree(
grandParent->getFamily(),
[&parentA, &parentB](const ShadowNode& oldShadowNode) {
auto children =
std::vector<std::shared_ptr<const ShadowNode>>({parentB, parentA});
const auto childrenShared =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
children);
return oldShadowNode.clone({.children = childrenShared});
});
// ==== State update ====
auto newState = scrollViewComponentDescriptor.createState(
childAFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeClonedFromStateUpdate = rootNode->cloneTree(
childAFamily, [&newState](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = newState});
});
shadowTree.commit(
[&rootShadowNodeClonedFromStateUpdate](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromStateUpdate);
},
{.enableStateReconciliation = false});
// ==== Now the react commit happens. ====
shadowTree.commit(
[&rootShadowNodeClonedFromReact](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
EXPECT_NE(findDescendantNode(shadowTree, childA->getFamily()), nullptr);
EXPECT_NE(findDescendantNode(shadowTree, childB->getFamily()), nullptr);
EXPECT_EQ(findDescendantNode(shadowTree, childAFamily)->getState(), newState);
}
TEST_F(StateReconciliationTest, testScrollViewWithChildrenReorder) {
// ==== SETUP ====
/*
<Root>
<View> - parent
<ScrollView /> - child A - will be moved to 2nd position.
<ScrollView /> - child B - will will be moved to 1st position.
</View>
</Root>
*/
auto parentView = std::shared_ptr<ViewShadowNode>{};
auto childA = std::shared_ptr<ScrollViewShadowNode>{};
auto childB = std::shared_ptr<ScrollViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ViewShadowNode>()
.reference(parentView)
.children({
Element<ScrollViewShadowNode>()
.reference(childA),
Element<ScrollViewShadowNode>()
.reference(childB),
})
});
// clang-format on
ContextContainer contextContainer{};
auto rootNode = builder_.build(element);
auto& scrollViewComponentDescriptor = childB->getComponentDescriptor();
auto& childAFamily = childA->getFamily();
auto initialState = childA->getState();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{11},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== INITIAL COMMIT ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{.enableStateReconciliation = true});
// ==== Tree swapping childA and childB. ChildB has new props ====
auto rootShadowNodeClonedFromReact = rootNode->cloneTree(
parentView->getFamily(),
[&childB, &childA](const ShadowNode& oldShadowNode) {
auto children =
std::vector<std::shared_ptr<const ShadowNode>>({childB, childA});
const auto childrenShared =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
children);
return oldShadowNode.clone({.children = childrenShared});
});
// ==== State update ====
auto newState = scrollViewComponentDescriptor.createState(
childAFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeClonedFromStateUpdate = rootNode->cloneTree(
childAFamily, [&newState](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = newState});
});
shadowTree.commit(
[&rootShadowNodeClonedFromStateUpdate](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromStateUpdate);
},
{.enableStateReconciliation = false});
// ==== Now the react commit happens. ====
shadowTree.commit(
[&rootShadowNodeClonedFromReact](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
EXPECT_NE(findDescendantNode(shadowTree, childA->getFamily()), nullptr);
EXPECT_NE(findDescendantNode(shadowTree, childB->getFamily()), nullptr);
EXPECT_EQ(findDescendantNode(shadowTree, childAFamily)->getState(), newState);
}
TEST_F(StateReconciliationTest, testScrollViewWithChildrenAddition) {
// ==== SETUP ====
/*
<Root>
<View> - parent
<ScrollView /> - child A - will be added.
<ScrollView /> - child B - will stay
</View>
</Root>
*/
auto parentView = std::shared_ptr<ViewShadowNode>{};
auto childA = std::shared_ptr<const ShadowNode>{};
auto childB = std::shared_ptr<ScrollViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.children({
Element<ViewShadowNode>()
.reference(parentView)
.children({
Element<ScrollViewShadowNode>()
.reference(childB),
})
});
// clang-format on
ContextContainer contextContainer{};
auto rootNode = builder_.build(element);
auto& scrollViewComponentDescriptor = childB->getComponentDescriptor();
auto& scrollViewFamily = childB->getFamily();
auto shadowTreeDelegate = DummyShadowTreeDelegate{};
ShadowTree shadowTree{
SurfaceId{1},
LayoutConstraints{},
LayoutContext{},
shadowTreeDelegate,
contextContainer};
// ==== INITIAL COMMIT ====
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{.enableStateReconciliation = true});
// ==== State update ====
auto newState = scrollViewComponentDescriptor.createState(
scrollViewFamily, std::make_shared<const ScrollViewState>());
auto rootShadowNodeClonedFromStateUpdate = rootNode->cloneTree(
scrollViewFamily, [&newState](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone({.state = newState});
});
// ==== Tree with new child ====
auto rootShadowNodeClonedFromReact = rootNode->cloneTree(
parentView->getFamily(),
[&childA, &childB, &contextContainer](const ShadowNode& oldShadowNode) {
auto& viewComponentDescriptor = childB->getComponentDescriptor();
auto childAFamily = viewComponentDescriptor.createFamily(
{.tag = 13, .surfaceId = 1, .instanceHandle = nullptr});
PropsParserContext parserContext{-1, contextContainer};
auto props =
viewComponentDescriptor.cloneProps(parserContext, nullptr, {});
childA = viewComponentDescriptor.createShadowNode(
{.props =
viewComponentDescriptor.cloneProps(parserContext, nullptr, {}),
.state = viewComponentDescriptor.createInitialState(
props, childAFamily)},
childAFamily);
std::shared_ptr<const ShadowNode> shadowNode = childA;
auto children = std::vector<std::shared_ptr<const ShadowNode>>(
{shadowNode, childB});
const auto childrenShared =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
children);
return oldShadowNode.clone({.children = childrenShared});
});
// ==== State update happens ====
shadowTree.commit(
[&rootShadowNodeClonedFromStateUpdate](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromStateUpdate);
},
{.enableStateReconciliation = false});
// ==== React commits tree ====
shadowTree.commit(
[&rootShadowNodeClonedFromReact](
const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(
rootShadowNodeClonedFromReact);
},
{.enableStateReconciliation = true});
EXPECT_NE(findDescendantNode(shadowTree, childA->getFamily()), nullptr);
EXPECT_EQ(
findDescendantNode(shadowTree, childB->getFamily())->getState(),
newState);
}