@@ -232,6 +232,100 @@ TEST_F(YogaDirtyFlagTest, updatingStateForScrollViewMistNotDirtyYogaNode) {
232232 static_cast <RootShadowNode&>(*newRootShadowNode).layoutIfNeeded ());
233233}
234234
235+ TEST_F (YogaDirtyFlagTest, dirtyChildOfHiddenNodeMustNotDirtyIt) {
236+ /*
237+ * A `display: none` node is laid out with zero size whatever its children
238+ * are, and Yoga never cleans the descendants of a hidden node. A child that
239+ * changed while its parent was hidden must *not* dirty the parent, otherwise
240+ * every commit that clones the parent re-runs layout from the root.
241+ */
242+ auto & childFamily = innerShadowNode_->getChildren ().at (0 )->getFamily ();
243+
244+ auto hiddenRootShadowNode = rootShadowNode_->cloneTree (
245+ innerShadowNode_->getFamily (), [](const ShadowNode& oldShadowNode) {
246+ auto viewProps = std::make_shared<ViewProps>();
247+ viewProps->yogaStyle .setDisplay (yoga::Display::None);
248+ return oldShadowNode.clone (ShadowNodeFragment{.props = viewProps});
249+ });
250+
251+ /*
252+ * Hiding the node *must* dirty it.
253+ */
254+ EXPECT_TRUE (
255+ static_cast <RootShadowNode&>(*hiddenRootShadowNode).layoutIfNeeded ());
256+
257+ /*
258+ * Changing the layout of a child of the hidden node dirties the child, but
259+ * not the hidden node.
260+ */
261+ auto changedRootShadowNode = hiddenRootShadowNode->cloneTree (
262+ childFamily, [](const ShadowNode& oldShadowNode) {
263+ auto viewProps = std::make_shared<ViewProps>();
264+ viewProps->yogaStyle .setDimension (
265+ yoga::Dimension::Width, yoga::StyleSizeLength::points (42 ));
266+ return oldShadowNode.clone (ShadowNodeFragment{.props = viewProps});
267+ });
268+
269+ EXPECT_FALSE (
270+ static_cast <RootShadowNode&>(*changedRootShadowNode).layoutIfNeeded ());
271+
272+ /*
273+ * The child is still dirty, because the layout pass didn't visit it. A
274+ * later change to its non-layout props must not dirty the hidden node
275+ * either.
276+ */
277+ auto newRootShadowNode = changedRootShadowNode->cloneTree (
278+ childFamily, [](const ShadowNode& oldShadowNode) {
279+ auto viewProps = std::make_shared<ViewProps>();
280+ viewProps->yogaStyle .setDimension (
281+ yoga::Dimension::Width, yoga::StyleSizeLength::points (42 ));
282+ viewProps->opacity = 0.5 ;
283+ return oldShadowNode.clone (ShadowNodeFragment{.props = viewProps});
284+ });
285+
286+ EXPECT_FALSE (
287+ static_cast <RootShadowNode&>(*newRootShadowNode).layoutIfNeeded ());
288+ }
289+
290+ TEST_F (YogaDirtyFlagTest, showingHiddenNodeMustDirtyItAndLayOutItsChildren) {
291+ /*
292+ * Showing a hidden node again *must* dirty it, and the children that
293+ * changed while it was hidden must be laid out.
294+ */
295+ auto & childFamily = innerShadowNode_->getChildren ().at (0 )->getFamily ();
296+
297+ auto hiddenRootShadowNode = rootShadowNode_->cloneTree (
298+ innerShadowNode_->getFamily (), [](const ShadowNode& oldShadowNode) {
299+ auto viewProps = std::make_shared<ViewProps>();
300+ viewProps->yogaStyle .setDisplay (yoga::Display::None);
301+ return oldShadowNode.clone (ShadowNodeFragment{.props = viewProps});
302+ });
303+ static_cast <RootShadowNode&>(*hiddenRootShadowNode).layoutIfNeeded ();
304+
305+ auto changedRootShadowNode = hiddenRootShadowNode->cloneTree (
306+ childFamily, [](const ShadowNode& oldShadowNode) {
307+ auto viewProps = std::make_shared<ViewProps>();
308+ viewProps->yogaStyle .setDimension (
309+ yoga::Dimension::Width, yoga::StyleSizeLength::points (42 ));
310+ return oldShadowNode.clone (ShadowNodeFragment{.props = viewProps});
311+ });
312+ static_cast <RootShadowNode&>(*changedRootShadowNode).layoutIfNeeded ();
313+
314+ auto shownRootShadowNode = changedRootShadowNode->cloneTree (
315+ innerShadowNode_->getFamily (), [](const ShadowNode& oldShadowNode) {
316+ return oldShadowNode.clone (
317+ ShadowNodeFragment{.props = std::make_shared<ViewProps>()});
318+ });
319+
320+ EXPECT_TRUE (
321+ static_cast <RootShadowNode&>(*shownRootShadowNode).layoutIfNeeded ());
322+
323+ auto child = std::static_pointer_cast<const ViewShadowNode>(
324+ shownRootShadowNode->getChildren ().at (1 )->getChildren ().at (0 ));
325+ EXPECT_EQ (child->getLayoutMetrics ().displayType , DisplayType::Flex);
326+ EXPECT_EQ (child->getLayoutMetrics ().frame .size .width , 42 );
327+ }
328+
235329TEST_F (YogaDirtyFlagTest, clonedPropsPreserveAspectRatio) {
236330 ContextContainer contextContainer{};
237331 PropsParserContext parserContext{-1 , contextContainer};
0 commit comments