Skip to content

Commit 7e6e5b3

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
YGPersistentNodeCloningTest
Summary: 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
1 parent c935fd5 commit 7e6e5b3

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
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() {
72+
YGNodeFree(node);
73+
}
74+
75+
YGNodeRef node;
76+
std::vector<std::shared_ptr<NodeWrapper>> children;
77+
};
78+
79+
struct ConfigWrapper {
80+
ConfigWrapper() {
81+
YGConfigSetCloneNodeFunc(
82+
config,
83+
[](YGNodeConstRef oldNode, YGNodeConstRef owner, size_t childIndex) {
84+
onClone(oldNode, owner, childIndex);
85+
auto wrapper = static_cast<NodeWrapper*>(YGNodeGetContext(owner));
86+
auto old = static_cast<NodeWrapper*>(YGNodeGetContext(oldNode));
87+
88+
wrapper->children[childIndex] = std::make_shared<NodeWrapper>(*old);
89+
return wrapper->children[childIndex]->node;
90+
});
91+
}
92+
93+
~ConfigWrapper() {
94+
YGConfigFree(config);
95+
}
96+
97+
YGConfigRef config{YGConfigNew()};
98+
};
99+
100+
ConfigWrapper configWrapper;
101+
YGConfigRef config{configWrapper.config};
102+
103+
void SetUp() override {
104+
onClone = [](...) {};
105+
}
106+
107+
static inline std::function<void(YGNodeConstRef, YGNodeConstRef, size_t)>
108+
onClone;
109+
};
110+
111+
TEST_F(
112+
YGPersistentNodeCloningTest,
113+
changing_sibling_height_does_not_clone_neighbors) {
114+
// <ScrollView>
115+
// <View id="Sibling" style={{ height: 1 }} />
116+
// <View id="A" style={{ height: 1 }}>
117+
// <View id="B">
118+
// <View id="C">
119+
// <View id="D"/>
120+
// </View>
121+
// </View>
122+
// </View>
123+
// </ScrollView>
124+
125+
auto sibling = std::make_shared<NodeWrapper>(config);
126+
YGNodeStyleSetHeight(sibling->node, 1);
127+
128+
auto d = std::make_shared<NodeWrapper>(config);
129+
auto c = std::make_shared<NodeWrapper>(config, std::vector{d});
130+
auto b = std::make_shared<NodeWrapper>(config, std::vector{c});
131+
auto a = std::make_shared<NodeWrapper>(config, std::vector{b});
132+
YGNodeStyleSetHeight(a->node, 1);
133+
134+
auto scrollContentView =
135+
std::make_shared<NodeWrapper>(config, std::vector{sibling, a});
136+
YGNodeStyleSetPositionType(scrollContentView->node, YGPositionTypeAbsolute);
137+
138+
auto scrollView =
139+
std::make_shared<NodeWrapper>(config, std::vector{scrollContentView});
140+
YGNodeStyleSetWidth(scrollView->node, 100);
141+
YGNodeStyleSetHeight(scrollView->node, 100);
142+
143+
// We don't expect any cloning during the first layout
144+
onClone = [](...) { FAIL(); };
145+
146+
YGNodeCalculateLayout(
147+
scrollView->node, YGUndefined, YGUndefined, YGDirectionLTR);
148+
149+
auto siblingPrime = std::make_shared<NodeWrapper>(config);
150+
YGNodeStyleSetHeight(siblingPrime->node, 2);
151+
152+
auto scrollContentViewPrime = std::make_shared<NodeWrapper>(
153+
*scrollContentView, std::vector{siblingPrime, a});
154+
auto scrollViewPrime = std::make_shared<NodeWrapper>(
155+
*scrollView, std::vector{scrollContentViewPrime});
156+
157+
// We should only need to clone "A"
158+
onClone = [&](YGNodeConstRef oldNode,
159+
YGNodeConstRef /*owner*/,
160+
size_t /*childIndex*/) {
161+
EXPECT_EQ(YGNodeGetContext(oldNode), a.get());
162+
};
163+
164+
YGNodeCalculateLayout(
165+
scrollViewPrime->node, YGUndefined, YGUndefined, YGDirectionLTR);
166+
}
167+
168+
} // namespace facebook::yoga

0 commit comments

Comments
 (0)