Skip to content

Commit 1232761

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
YGPersistentNodeCloningTest (#1813)
Summary: Pull Request resolved: #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
1 parent c935fd5 commit 1232761

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <gtest/gtest.h>
9+
#include <yoga/Yoga.h>
10+
#include <yoga/config/Config.h>
11+
#include <yoga/node/Node.h>
12+
13+
#include <functional>
14+
#include <memory>
15+
#include <vector>
16+
17+
namespace facebook::yoga {
18+
19+
struct YGPersistentNodeCloningTest : public ::testing::Test {
20+
struct NodeWrapper {
21+
explicit NodeWrapper(
22+
YGConfigRef config,
23+
std::vector<std::shared_ptr<NodeWrapper>> children = {})
24+
: node{YGNodeNewWithConfig(config)}, children{std::move(children)} {
25+
YGNodeSetContext(node, this);
26+
27+
auto privateNode = resolveRef(node);
28+
for (const auto& child : this->children) {
29+
auto privateChild = resolveRef(child->node);
30+
// Claim first ownership of not yet owned nodes, to avoid immediately
31+
// cloning them
32+
if (YGNodeGetOwner(child->node) == nullptr) {
33+
privateChild->setOwner(privateNode);
34+
}
35+
privateNode->insertChild(privateChild, privateNode->getChildCount());
36+
}
37+
}
38+
39+
// Clone, with current children, for mutation
40+
NodeWrapper(const NodeWrapper& other)
41+
: node{YGNodeClone(other.node)}, children{other.children} {
42+
YGNodeSetContext(node, this);
43+
44+
auto privateNode = resolveRef(node);
45+
privateNode->setOwner(nullptr);
46+
}
47+
48+
// Clone, with new children
49+
NodeWrapper(
50+
const NodeWrapper& other,
51+
std::vector<std::shared_ptr<NodeWrapper>> children)
52+
: node{YGNodeClone(other.node)}, children{std::move(children)} {
53+
YGNodeSetContext(node, this);
54+
55+
auto privateNode = resolveRef(node);
56+
privateNode->setOwner(nullptr);
57+
privateNode->setChildren({});
58+
privateNode->setDirty(true);
59+
60+
for (const auto& child : this->children) {
61+
auto privateChild = resolveRef(child->node);
62+
// Claim first ownership of not yet owned nodes, to avoid immediately
63+
// cloning them
64+
if (YGNodeGetOwner(child->node) == nullptr) {
65+
privateChild->setOwner(privateNode);
66+
}
67+
privateNode->insertChild(privateChild, privateNode->getChildCount());
68+
}
69+
}
70+
71+
NodeWrapper(NodeWrapper&&) = delete;
72+
73+
~NodeWrapper() {
74+
YGNodeFree(node);
75+
}
76+
77+
NodeWrapper& operator=(const NodeWrapper& other) = delete;
78+
NodeWrapper& operator=(NodeWrapper&& other) = delete;
79+
80+
YGNodeRef node;
81+
std::vector<std::shared_ptr<NodeWrapper>> children;
82+
};
83+
84+
struct ConfigWrapper {
85+
ConfigWrapper() {
86+
YGConfigSetCloneNodeFunc(
87+
config,
88+
[](YGNodeConstRef oldNode, YGNodeConstRef owner, size_t childIndex) {
89+
onClone(oldNode, owner, childIndex);
90+
auto wrapper = static_cast<NodeWrapper*>(YGNodeGetContext(owner));
91+
auto old = static_cast<NodeWrapper*>(YGNodeGetContext(oldNode));
92+
93+
wrapper->children[childIndex] = std::make_shared<NodeWrapper>(*old);
94+
return wrapper->children[childIndex]->node;
95+
});
96+
}
97+
98+
ConfigWrapper(const ConfigWrapper&) = delete;
99+
ConfigWrapper(ConfigWrapper&&) = delete;
100+
101+
~ConfigWrapper() {
102+
YGConfigFree(config);
103+
}
104+
105+
ConfigWrapper& operator=(const ConfigWrapper&) = delete;
106+
ConfigWrapper& operator=(ConfigWrapper&&) = delete;
107+
108+
YGConfigRef config{YGConfigNew()};
109+
};
110+
111+
ConfigWrapper configWrapper;
112+
YGConfigRef config{configWrapper.config};
113+
114+
void SetUp() override {
115+
onClone = [](...) {};
116+
}
117+
118+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
119+
static inline std::function<void(YGNodeConstRef, YGNodeConstRef, size_t)>
120+
onClone;
121+
};
122+
123+
TEST_F(
124+
YGPersistentNodeCloningTest,
125+
changing_sibling_height_does_not_clone_neighbors) {
126+
// <ScrollView>
127+
// <View id="Sibling" style={{ height: 1 }} />
128+
// <View id="A" style={{ height: 1 }}>
129+
// <View id="B">
130+
// <View id="C">
131+
// <View id="D"/>
132+
// </View>
133+
// </View>
134+
// </View>
135+
// </ScrollView>
136+
137+
auto sibling = std::make_shared<NodeWrapper>(config);
138+
YGNodeStyleSetHeight(sibling->node, 1);
139+
140+
auto d = std::make_shared<NodeWrapper>(config);
141+
auto c = std::make_shared<NodeWrapper>(config, std::vector{d});
142+
auto b = std::make_shared<NodeWrapper>(config, std::vector{c});
143+
auto a = std::make_shared<NodeWrapper>(config, std::vector{b});
144+
YGNodeStyleSetHeight(a->node, 1);
145+
146+
auto scrollContentView =
147+
std::make_shared<NodeWrapper>(config, std::vector{sibling, a});
148+
YGNodeStyleSetPositionType(scrollContentView->node, YGPositionTypeAbsolute);
149+
150+
auto scrollView =
151+
std::make_shared<NodeWrapper>(config, std::vector{scrollContentView});
152+
YGNodeStyleSetWidth(scrollView->node, 100);
153+
YGNodeStyleSetHeight(scrollView->node, 100);
154+
155+
// We don't expect any cloning during the first layout
156+
onClone = [](...) { FAIL(); };
157+
158+
YGNodeCalculateLayout(
159+
scrollView->node, YGUndefined, YGUndefined, YGDirectionLTR);
160+
161+
auto siblingPrime = std::make_shared<NodeWrapper>(config);
162+
YGNodeStyleSetHeight(siblingPrime->node, 2);
163+
164+
auto scrollContentViewPrime = std::make_shared<NodeWrapper>(
165+
*scrollContentView, std::vector{siblingPrime, a});
166+
auto scrollViewPrime = std::make_shared<NodeWrapper>(
167+
*scrollView, std::vector{scrollContentViewPrime});
168+
169+
std::vector<NodeWrapper*> nodesCloned;
170+
// We should only need to clone "A"
171+
onClone = [&](YGNodeConstRef oldNode,
172+
YGNodeConstRef /*owner*/,
173+
size_t /*childIndex*/) {
174+
nodesCloned.push_back(static_cast<NodeWrapper*>(YGNodeGetContext(oldNode)));
175+
};
176+
177+
YGNodeCalculateLayout(
178+
scrollViewPrime->node, YGUndefined, YGUndefined, YGDirectionLTR);
179+
180+
EXPECT_EQ(nodesCloned.size(), 1);
181+
EXPECT_EQ(nodesCloned[0], a.get());
182+
}
183+
184+
} // namespace facebook::yoga

0 commit comments

Comments
 (0)