YGPersistentNodeCloningTest (#1813)
Summary: Pull Request resolved: https://github.com/facebook/yoga/pull/1813 This adds a unit test to Yoga, which emulates the model of "persistent Yoga nodes" and cloning used by React Fabric, including the private (but relied on) Yoga APIs. It models the over-invalidation exposed in D75287261, which reproduces (due to Yoga incorrectly measuring flex-basis under fit-content, and that constraint changing when sibling changes) but this test for now sets a definite height on A, to show that we only clone what is neccesary, when measure constraints do not have to change. Having a minimal version of Fabric's model in Yoga unit tests should make some of these interesting interactions a bit easier to debug. Changelog: [Internal] Reviewed By: javache Differential Revision: D75572762 fbshipit-source-id: cda8b3fdd6e538a55dd100494518688c864bd233
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c935fd5e10
commit
1232761571
184
tests/YGPersistentNodeCloningTest.cpp
Normal file
184
tests/YGPersistentNodeCloningTest.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* 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 <gtest/gtest.h>
|
||||||
|
#include <yoga/Yoga.h>
|
||||||
|
#include <yoga/config/Config.h>
|
||||||
|
#include <yoga/node/Node.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace facebook::yoga {
|
||||||
|
|
||||||
|
struct YGPersistentNodeCloningTest : public ::testing::Test {
|
||||||
|
struct NodeWrapper {
|
||||||
|
explicit NodeWrapper(
|
||||||
|
YGConfigRef config,
|
||||||
|
std::vector<std::shared_ptr<NodeWrapper>> children = {})
|
||||||
|
: node{YGNodeNewWithConfig(config)}, children{std::move(children)} {
|
||||||
|
YGNodeSetContext(node, this);
|
||||||
|
|
||||||
|
auto privateNode = resolveRef(node);
|
||||||
|
for (const auto& child : this->children) {
|
||||||
|
auto privateChild = resolveRef(child->node);
|
||||||
|
// Claim first ownership of not yet owned nodes, to avoid immediately
|
||||||
|
// cloning them
|
||||||
|
if (YGNodeGetOwner(child->node) == nullptr) {
|
||||||
|
privateChild->setOwner(privateNode);
|
||||||
|
}
|
||||||
|
privateNode->insertChild(privateChild, privateNode->getChildCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone, with current children, for mutation
|
||||||
|
NodeWrapper(const NodeWrapper& other)
|
||||||
|
: node{YGNodeClone(other.node)}, children{other.children} {
|
||||||
|
YGNodeSetContext(node, this);
|
||||||
|
|
||||||
|
auto privateNode = resolveRef(node);
|
||||||
|
privateNode->setOwner(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone, with new children
|
||||||
|
NodeWrapper(
|
||||||
|
const NodeWrapper& other,
|
||||||
|
std::vector<std::shared_ptr<NodeWrapper>> children)
|
||||||
|
: node{YGNodeClone(other.node)}, children{std::move(children)} {
|
||||||
|
YGNodeSetContext(node, this);
|
||||||
|
|
||||||
|
auto privateNode = resolveRef(node);
|
||||||
|
privateNode->setOwner(nullptr);
|
||||||
|
privateNode->setChildren({});
|
||||||
|
privateNode->setDirty(true);
|
||||||
|
|
||||||
|
for (const auto& child : this->children) {
|
||||||
|
auto privateChild = resolveRef(child->node);
|
||||||
|
// Claim first ownership of not yet owned nodes, to avoid immediately
|
||||||
|
// cloning them
|
||||||
|
if (YGNodeGetOwner(child->node) == nullptr) {
|
||||||
|
privateChild->setOwner(privateNode);
|
||||||
|
}
|
||||||
|
privateNode->insertChild(privateChild, privateNode->getChildCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeWrapper(NodeWrapper&&) = delete;
|
||||||
|
|
||||||
|
~NodeWrapper() {
|
||||||
|
YGNodeFree(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeWrapper& operator=(const NodeWrapper& other) = delete;
|
||||||
|
NodeWrapper& operator=(NodeWrapper&& other) = delete;
|
||||||
|
|
||||||
|
YGNodeRef node;
|
||||||
|
std::vector<std::shared_ptr<NodeWrapper>> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConfigWrapper {
|
||||||
|
ConfigWrapper() {
|
||||||
|
YGConfigSetCloneNodeFunc(
|
||||||
|
config,
|
||||||
|
[](YGNodeConstRef oldNode, YGNodeConstRef owner, size_t childIndex) {
|
||||||
|
onClone(oldNode, owner, childIndex);
|
||||||
|
auto wrapper = static_cast<NodeWrapper*>(YGNodeGetContext(owner));
|
||||||
|
auto old = static_cast<NodeWrapper*>(YGNodeGetContext(oldNode));
|
||||||
|
|
||||||
|
wrapper->children[childIndex] = std::make_shared<NodeWrapper>(*old);
|
||||||
|
return wrapper->children[childIndex]->node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigWrapper(const ConfigWrapper&) = delete;
|
||||||
|
ConfigWrapper(ConfigWrapper&&) = delete;
|
||||||
|
|
||||||
|
~ConfigWrapper() {
|
||||||
|
YGConfigFree(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigWrapper& operator=(const ConfigWrapper&) = delete;
|
||||||
|
ConfigWrapper& operator=(ConfigWrapper&&) = delete;
|
||||||
|
|
||||||
|
YGConfigRef config{YGConfigNew()};
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigWrapper configWrapper;
|
||||||
|
YGConfigRef config{configWrapper.config};
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
onClone = [](...) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static inline std::function<void(YGNodeConstRef, YGNodeConstRef, size_t)>
|
||||||
|
onClone;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
YGPersistentNodeCloningTest,
|
||||||
|
changing_sibling_height_does_not_clone_neighbors) {
|
||||||
|
// <ScrollView>
|
||||||
|
// <View id="Sibling" style={{ height: 1 }} />
|
||||||
|
// <View id="A" style={{ height: 1 }}>
|
||||||
|
// <View id="B">
|
||||||
|
// <View id="C">
|
||||||
|
// <View id="D"/>
|
||||||
|
// </View>
|
||||||
|
// </View>
|
||||||
|
// </View>
|
||||||
|
// </ScrollView>
|
||||||
|
|
||||||
|
auto sibling = std::make_shared<NodeWrapper>(config);
|
||||||
|
YGNodeStyleSetHeight(sibling->node, 1);
|
||||||
|
|
||||||
|
auto d = std::make_shared<NodeWrapper>(config);
|
||||||
|
auto c = std::make_shared<NodeWrapper>(config, std::vector{d});
|
||||||
|
auto b = std::make_shared<NodeWrapper>(config, std::vector{c});
|
||||||
|
auto a = std::make_shared<NodeWrapper>(config, std::vector{b});
|
||||||
|
YGNodeStyleSetHeight(a->node, 1);
|
||||||
|
|
||||||
|
auto scrollContentView =
|
||||||
|
std::make_shared<NodeWrapper>(config, std::vector{sibling, a});
|
||||||
|
YGNodeStyleSetPositionType(scrollContentView->node, YGPositionTypeAbsolute);
|
||||||
|
|
||||||
|
auto scrollView =
|
||||||
|
std::make_shared<NodeWrapper>(config, std::vector{scrollContentView});
|
||||||
|
YGNodeStyleSetWidth(scrollView->node, 100);
|
||||||
|
YGNodeStyleSetHeight(scrollView->node, 100);
|
||||||
|
|
||||||
|
// We don't expect any cloning during the first layout
|
||||||
|
onClone = [](...) { FAIL(); };
|
||||||
|
|
||||||
|
YGNodeCalculateLayout(
|
||||||
|
scrollView->node, YGUndefined, YGUndefined, YGDirectionLTR);
|
||||||
|
|
||||||
|
auto siblingPrime = std::make_shared<NodeWrapper>(config);
|
||||||
|
YGNodeStyleSetHeight(siblingPrime->node, 2);
|
||||||
|
|
||||||
|
auto scrollContentViewPrime = std::make_shared<NodeWrapper>(
|
||||||
|
*scrollContentView, std::vector{siblingPrime, a});
|
||||||
|
auto scrollViewPrime = std::make_shared<NodeWrapper>(
|
||||||
|
*scrollView, std::vector{scrollContentViewPrime});
|
||||||
|
|
||||||
|
std::vector<NodeWrapper*> nodesCloned;
|
||||||
|
// We should only need to clone "A"
|
||||||
|
onClone = [&](YGNodeConstRef oldNode,
|
||||||
|
YGNodeConstRef /*owner*/,
|
||||||
|
size_t /*childIndex*/) {
|
||||||
|
nodesCloned.push_back(static_cast<NodeWrapper*>(YGNodeGetContext(oldNode)));
|
||||||
|
};
|
||||||
|
|
||||||
|
YGNodeCalculateLayout(
|
||||||
|
scrollViewPrime->node, YGUndefined, YGUndefined, YGDirectionLTR);
|
||||||
|
|
||||||
|
EXPECT_EQ(nodesCloned.size(), 1);
|
||||||
|
EXPECT_EQ(nodesCloned[0], a.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace facebook::yoga
|
Reference in New Issue
Block a user