From 1310dc8d1e44ef0853f19734a0924fbdf4264380 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 12 Mar 2026 09:38:28 -0700 Subject: [PATCH] Re-land: fixYogaFlexBasisFitContentInMainAxis (#1917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: X-link: https://github.com/facebook/react-native/pull/56064 Re-land of D94658492 with fixes, which was reverted in D95669495. ## Context D94658492 added the `fixYogaFlexBasisFitContentInMainAxis` flag to avoid unnecessary re-measurement cascades in Yoga. When Yoga computes flex basis for container children, the legacy behavior applies a `FitContent` constraint in the main axis, bounding the child's measurement by the parent's available space. This creates a dependency between the child's flex basis and the parent's content-determined size — when one sibling changes size, all siblings get re-measured and their shadow nodes get cloned unnecessarily. The fix switches from `FitContent` to `MaxContent` for non-measure container children under auto-height parents, making each child's flex basis independent of the parent's size. ## What went wrong D94658492 modeled the fix as a `YogaErrata` bit (`FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS`). Errata flags are bitmasks, and apps that opt into `ALL` or `CLASSIC` errata (like IGVR and Airwave) inadvertently picked up the new behavior without explicitly enabling the feature flag, causing breakages. ## What changed in this re-land This diff models the fix as a `YogaExperimentalFeature` (`FIX_FLEX_BASIS_FIT_CONTENT`) instead of a `YogaErrata` bit. Experimental features are individually opt-in, so existing apps won't accidentally pick up the change. This diff only wires up the RN feature flag infrastructure (flag defaults to `false`). The iOS MobileConfig override and the Yoga layout logic will be landed in follow-up diffs. changelog: [internal] Reviewed By: javache, NickGerleman Differential Revision: D95852922 --- enums.py | 5 +- .../fixtures/YGFlexBasisFitContentTest.html | 68 +++ .../yoga/YogaExperimentalFeature.java | 4 +- .../yoga/YGFlexBasisFitContentTest.java | 459 ++++++++++++++++++ javascript/src/generated/YGEnums.ts | 2 + .../YGFlexBasisFitContentTest.test.ts | 429 ++++++++++++++++ tests/YGFlexBasisFitContentTest.cpp | 241 +++++++++ tests/generated/YGFlexBasisFitContentTest.cpp | 447 +++++++++++++++++ yoga/YGEnums.cpp | 2 + yoga/YGEnums.h | 3 +- yoga/algorithm/CalculateLayout.cpp | 87 +++- yoga/enums/ExperimentalFeature.h | 3 +- 12 files changed, 1737 insertions(+), 13 deletions(-) create mode 100644 gentest/fixtures/YGFlexBasisFitContentTest.html create mode 100644 java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java create mode 100644 javascript/tests/generated/YGFlexBasisFitContentTest.test.ts create mode 100644 tests/YGFlexBasisFitContentTest.cpp create mode 100644 tests/generated/YGFlexBasisFitContentTest.cpp diff --git a/enums.py b/enums.py index 2c53672a2e..ceef4deed3 100755 --- a/enums.py +++ b/enums.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env fbpython # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the @@ -66,6 +66,9 @@ "ExperimentalFeature": [ # Mimic web flex-basis behavior (experiment may be broken) "WebFlexBasis", + # Fix flex basis computation to not apply FitContent constraint in the + # main axis for non-measure container nodes + "FixFlexBasisFitContent", ], "Gutter": ["Column", "Row", "All"], "GridTrackType": ["Auto", "Points", "Percent", "Fr", "Minmax"], diff --git a/gentest/fixtures/YGFlexBasisFitContentTest.html b/gentest/fixtures/YGFlexBasisFitContentTest.html new file mode 100644 index 0000000000..a9176e4ce6 --- /dev/null +++ b/gentest/fixtures/YGFlexBasisFitContentTest.html @@ -0,0 +1,68 @@ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
diff --git a/java/com/facebook/yoga/YogaExperimentalFeature.java b/java/com/facebook/yoga/YogaExperimentalFeature.java index 3fabbb9172..32e643439e 100644 --- a/java/com/facebook/yoga/YogaExperimentalFeature.java +++ b/java/com/facebook/yoga/YogaExperimentalFeature.java @@ -10,7 +10,8 @@ package com.facebook.yoga; public enum YogaExperimentalFeature { - WEB_FLEX_BASIS(0); + WEB_FLEX_BASIS(0), + FIX_FLEX_BASIS_FIT_CONTENT(1); private final int mIntValue; @@ -25,6 +26,7 @@ public int intValue() { public static YogaExperimentalFeature fromInt(int value) { switch (value) { case 0: return WEB_FLEX_BASIS; + case 1: return FIX_FLEX_BASIS_FIT_CONTENT; default: throw new IllegalArgumentException("Unknown enum value: " + value); } } diff --git a/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java new file mode 100644 index 0000000000..3941362bb5 --- /dev/null +++ b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java @@ -0,0 +1,459 @@ +/* + * 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. + * + * @generated SignedSource<<43053b7b4f9b08ef644e0bb63c44e78f>> + * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html + */ + +package com.facebook.yoga; + +import static org.junit.Assert.assertEquals; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import com.facebook.yoga.utils.TestUtils; + +@RunWith(Parameterized.class) +public class YGFlexBasisFitContentTest { + @Parameterized.Parameters(name = "{0}") + public static Iterable nodeFactories() { + return TestParametrization.nodeFactories(); + } + + @Parameterized.Parameter public TestParametrization.NodeFactory mNodeFactory; + + @Test + public void test_container_child_overflows_definite_parent_column() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setHeight(500f); + root_child0_child0.setWidth(50f); + root_child0.addChildAt(root_child0_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(150f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_container_child_overflows_definite_parent_row() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(300f); + root.setHeight(200f); + root.setFlexDirection(YogaFlexDirection.ROW); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setWidth(500f); + root_child0_child0.setHeight(50f); + root_child0.addChildAt(root_child0_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(300f, root.getLayoutWidth(), 0.0f); + assertEquals(200f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(500f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(200f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(300f, root.getLayoutWidth(), 0.0f); + assertEquals(200f, root.getLayoutHeight(), 0.0f); + + assertEquals(-200f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(500f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(200f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_container_child_within_bounds_column() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setHeight(100f); + root_child0_child0.setWidth(50f); + root_child0.addChildAt(root_child0_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(150f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_multiple_container_children_overflow_column() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setHeight(400f); + root_child0.addChildAt(root_child0_child0, 0); + + final YogaNode root_child1 = createNode(config); + root.addChildAt(root_child1, 1); + + final YogaNode root_child1_child0 = createNode(config); + root_child1_child0.setHeight(500f); + root_child1.addChildAt(root_child1_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(400f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(400f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(400f, root_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child1_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child1_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(400f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(400f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(400f, root_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child1_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child1_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_scroll_container_column() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + root.setOverflow(YogaOverflow.SCROLL); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setHeight(500f); + root_child0.addChildAt(root_child0_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_explicit_and_container_children_column() { + YogaConfig config = YogaConfigFactory.create(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + + final YogaNode root_child0 = createNode(config); + root_child0.setHeight(100f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child1 = createNode(config); + root.addChildAt(root_child1, 1); + + final YogaNode root_child1_child0 = createNode(config); + root_child1_child0.setHeight(500f); + root_child1.addChildAt(root_child1_child0, 0); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(100f, root_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child1_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child1_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1_child0.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(100f, root_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child1_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child1_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child1_child0.getLayoutHeight(), 0.0f); + } + + @Test + public void test_flex_basis_in_scroll_content_container() { + YogaConfig config = YogaConfigFactory.create(); + config.setExperimentalFeatureEnabled(YogaExperimentalFeature.FIX_FLEX_BASIS_FIT_CONTENT, true); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(200f); + root.setHeight(300f); + root.setOverflow(YogaOverflow.SCROLL); + + final YogaNode root_child0 = createNode(config); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = createNode(config); + root_child0_child0.setFlexBasis(200f); + root_child0.addChildAt(root_child0_child0, 0); + + final YogaNode root_child0_child1 = createNode(config); + root_child0_child1.setFlexBasis(300f); + root_child0.addChildAt(root_child0_child1, 1); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child1.getLayoutX(), 0.0f); + assertEquals(200f, root_child0_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child1.getLayoutWidth(), 0.0f); + assertEquals(300f, root_child0_child1.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(300f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(200f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child1.getLayoutX(), 0.0f); + assertEquals(200f, root_child0_child1.getLayoutY(), 0.0f); + assertEquals(200f, root_child0_child1.getLayoutWidth(), 0.0f); + assertEquals(300f, root_child0_child1.getLayoutHeight(), 0.0f); + } + + private YogaNode createNode(YogaConfig config) { + return mNodeFactory.create(config); + } +} diff --git a/javascript/src/generated/YGEnums.ts b/javascript/src/generated/YGEnums.ts index f50aa88a73..17878ab0bb 100644 --- a/javascript/src/generated/YGEnums.ts +++ b/javascript/src/generated/YGEnums.ts @@ -67,6 +67,7 @@ export enum Errata { export enum ExperimentalFeature { WebFlexBasis = 0, + FixFlexBasisFitContent = 1, } export enum FlexDirection { @@ -190,6 +191,7 @@ const constants = { ERRATA_ALL: Errata.All, ERRATA_CLASSIC: Errata.Classic, EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS: ExperimentalFeature.WebFlexBasis, + EXPERIMENTAL_FEATURE_FIX_FLEX_BASIS_FIT_CONTENT: ExperimentalFeature.FixFlexBasisFitContent, FLEX_DIRECTION_COLUMN: FlexDirection.Column, FLEX_DIRECTION_COLUMN_REVERSE: FlexDirection.ColumnReverse, FLEX_DIRECTION_ROW: FlexDirection.Row, diff --git a/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts b/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts new file mode 100644 index 0000000000..1e5f8c7c44 --- /dev/null +++ b/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts @@ -0,0 +1,429 @@ +/** + * 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. + * + * @generated SignedSource<> + * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html + */ + +import { instrinsicSizeMeasureFunc } from '../tools/utils.ts' +import Yoga from 'yoga-layout'; +import { + Align, + BoxSizing, + Direction, + Display, + Edge, + Errata, + ExperimentalFeature, + FlexDirection, + Gutter, + Justify, + MeasureMode, + Overflow, + PositionType, + Unit, + Wrap, +} from 'yoga-layout'; + +test('container_child_overflows_definite_parent_column', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setHeight(500); + root_child0_child0.setWidth(50); + root_child0.insertChild(root_child0_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(50); + expect(root_child0_child0.getComputedHeight()).toBe(500); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(150); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(50); + expect(root_child0_child0.getComputedHeight()).toBe(500); +}); +test('container_child_overflows_definite_parent_row', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(300); + root.setHeight(200); + root.setFlexDirection(FlexDirection.Row); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setWidth(500); + root_child0_child0.setHeight(50); + root_child0.insertChild(root_child0_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(300); + expect(root.getComputedHeight()).toBe(200); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(500); + expect(root_child0.getComputedHeight()).toBe(200); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(500); + expect(root_child0_child0.getComputedHeight()).toBe(50); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(300); + expect(root.getComputedHeight()).toBe(200); + + expect(root_child0.getComputedLeft()).toBe(-200); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(500); + expect(root_child0.getComputedHeight()).toBe(200); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(500); + expect(root_child0_child0.getComputedHeight()).toBe(50); +}); +test('container_child_within_bounds_column', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setHeight(100); + root_child0_child0.setWidth(50); + root_child0.insertChild(root_child0_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(100); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(50); + expect(root_child0_child0.getComputedHeight()).toBe(100); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(100); + + expect(root_child0_child0.getComputedLeft()).toBe(150); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(50); + expect(root_child0_child0.getComputedHeight()).toBe(100); +}); +test('multiple_container_children_overflow_column', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setHeight(400); + root_child0.insertChild(root_child0_child0, 0); + + const root_child1 = Yoga.Node.create(config); + root.insertChild(root_child1, 1); + + const root_child1_child0 = Yoga.Node.create(config); + root_child1_child0.setHeight(500); + root_child1.insertChild(root_child1_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(400); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(400); + + expect(root_child1.getComputedLeft()).toBe(0); + expect(root_child1.getComputedTop()).toBe(400); + expect(root_child1.getComputedWidth()).toBe(200); + expect(root_child1.getComputedHeight()).toBe(500); + + expect(root_child1_child0.getComputedLeft()).toBe(0); + expect(root_child1_child0.getComputedTop()).toBe(0); + expect(root_child1_child0.getComputedWidth()).toBe(200); + expect(root_child1_child0.getComputedHeight()).toBe(500); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(400); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(400); + + expect(root_child1.getComputedLeft()).toBe(0); + expect(root_child1.getComputedTop()).toBe(400); + expect(root_child1.getComputedWidth()).toBe(200); + expect(root_child1.getComputedHeight()).toBe(500); + + expect(root_child1_child0.getComputedLeft()).toBe(0); + expect(root_child1_child0.getComputedTop()).toBe(0); + expect(root_child1_child0.getComputedWidth()).toBe(200); + expect(root_child1_child0.getComputedHeight()).toBe(500); +}); +test('scroll_container_column', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + root.setOverflow(Overflow.Scroll); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setHeight(500); + root_child0.insertChild(root_child0_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(500); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(500); +}); +test('explicit_and_container_children_column', () => { + const config = Yoga.Config.create(); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + + const root_child0 = Yoga.Node.create(config); + root_child0.setHeight(100); + root.insertChild(root_child0, 0); + + const root_child1 = Yoga.Node.create(config); + root.insertChild(root_child1, 1); + + const root_child1_child0 = Yoga.Node.create(config); + root_child1_child0.setHeight(500); + root_child1.insertChild(root_child1_child0, 0); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(100); + + expect(root_child1.getComputedLeft()).toBe(0); + expect(root_child1.getComputedTop()).toBe(100); + expect(root_child1.getComputedWidth()).toBe(200); + expect(root_child1.getComputedHeight()).toBe(500); + + expect(root_child1_child0.getComputedLeft()).toBe(0); + expect(root_child1_child0.getComputedTop()).toBe(0); + expect(root_child1_child0.getComputedWidth()).toBe(200); + expect(root_child1_child0.getComputedHeight()).toBe(500); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(100); + + expect(root_child1.getComputedLeft()).toBe(0); + expect(root_child1.getComputedTop()).toBe(100); + expect(root_child1.getComputedWidth()).toBe(200); + expect(root_child1.getComputedHeight()).toBe(500); + + expect(root_child1_child0.getComputedLeft()).toBe(0); + expect(root_child1_child0.getComputedTop()).toBe(0); + expect(root_child1_child0.getComputedWidth()).toBe(200); + expect(root_child1_child0.getComputedHeight()).toBe(500); +}); +test('flex_basis_in_scroll_content_container', () => { + const config = Yoga.Config.create(); + + config.setExperimentalFeatureEnabled(ExperimentalFeature.FixFlexBasisFitContent, true); + + const root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setWidth(200); + root.setHeight(300); + root.setOverflow(Overflow.Scroll); + + const root_child0 = Yoga.Node.create(config); + root.insertChild(root_child0, 0); + + const root_child0_child0 = Yoga.Node.create(config); + root_child0_child0.setFlexBasis(200); + root_child0.insertChild(root_child0_child0, 0); + + const root_child0_child1 = Yoga.Node.create(config); + root_child0_child1.setFlexBasis(300); + root_child0.insertChild(root_child0_child1, 1); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(200); + + expect(root_child0_child1.getComputedLeft()).toBe(0); + expect(root_child0_child1.getComputedTop()).toBe(200); + expect(root_child0_child1.getComputedWidth()).toBe(200); + expect(root_child0_child1.getComputedHeight()).toBe(300); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(200); + expect(root.getComputedHeight()).toBe(300); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(200); + expect(root_child0.getComputedHeight()).toBe(500); + + expect(root_child0_child0.getComputedLeft()).toBe(0); + expect(root_child0_child0.getComputedTop()).toBe(0); + expect(root_child0_child0.getComputedWidth()).toBe(200); + expect(root_child0_child0.getComputedHeight()).toBe(200); + + expect(root_child0_child1.getComputedLeft()).toBe(0); + expect(root_child0_child1.getComputedTop()).toBe(200); + expect(root_child0_child1.getComputedWidth()).toBe(200); + expect(root_child0_child1.getComputedHeight()).toBe(300); +}); diff --git a/tests/YGFlexBasisFitContentTest.cpp b/tests/YGFlexBasisFitContentTest.cpp new file mode 100644 index 0000000000..7ce7873497 --- /dev/null +++ b/tests/YGFlexBasisFitContentTest.cpp @@ -0,0 +1,241 @@ +/* + * 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 +#include +#include + +static YGSize measureTextLike( + YGNodeConstRef /*node*/, + float width, + YGMeasureMode widthMode, + float /*height*/, + YGMeasureMode /*heightMode*/) { + float measuredWidth = 200.0f; + if (widthMode == YGMeasureModeAtMost) { + measuredWidth = std::min(measuredWidth, width); + } + return YGSize{.width = measuredWidth, .height = 20.0f}; +} + +class YGFlexBasisFitContentTest : public testing::TestWithParam { + protected: + void SetUp() override { + config_ = YGConfigNew(); + YGConfigSetExperimentalFeatureEnabled( + config_, YGExperimentalFeatureFixFlexBasisFitContent, GetParam()); + } + + void TearDown() override { + if (root_ != nullptr) { + YGNodeFreeRecursive(root_); + } + YGConfigFree(config_); + } + + YGConfigRef config_ = nullptr; + YGNodeRef root_ = nullptr; +}; + +// Auto-height container with a percentage-height child produces the same +// layout regardless of feature state, because Check 3 preserves percentage +// resolution when availableInnerHeight is NaN. +TEST_P(YGFlexBasisFitContentTest, percentage_height_converges) { + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeight(root_, 300); + YGNodeStyleSetWidth(root_, 100); + + YGNodeRef container = YGNodeNewWithConfig(config_); + YGNodeInsertChild(root_, container, 0); + + YGNodeRef child = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeightPercent(child, 50); + YGNodeInsertChild(container, child, 0); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(75, YGNodeLayoutGetHeight(child)); + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetHeight(container)); +} + +// Two auto-height containers with percentage children and flexGrow:1 produce +// the same layout regardless of feature state. +TEST_P(YGFlexBasisFitContentTest, percentage_with_flex_grow_converges) { + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeight(root_, 400); + YGNodeStyleSetWidth(root_, 100); + + YGNodeRef containerA = YGNodeNewWithConfig(config_); + YGNodeStyleSetFlexGrow(containerA, 1); + YGNodeInsertChild(root_, containerA, 0); + + YGNodeRef childA = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeightPercent(childA, 25); + YGNodeInsertChild(containerA, childA, 0); + + YGNodeRef containerB = YGNodeNewWithConfig(config_); + YGNodeStyleSetFlexGrow(containerB, 1); + YGNodeInsertChild(root_, containerB, 1); + + YGNodeRef childB = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeightPercent(childB, 50); + YGNodeInsertChild(containerB, childB, 0); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetHeight(containerA)); + ASSERT_FLOAT_EQ(250, YGNodeLayoutGetHeight(containerB)); +} + +// Auto-height container with flexShrink and a percentage child causing +// overflow produces the same layout regardless of feature state. +TEST_P(YGFlexBasisFitContentTest, flex_shrink_overflow_converges) { + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeight(root_, 200); + YGNodeStyleSetWidth(root_, 100); + + YGNodeRef container = YGNodeNewWithConfig(config_); + YGNodeStyleSetFlexShrink(container, 1); + YGNodeInsertChild(root_, container, 0); + + YGNodeRef child = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeightPercent(child, 100); + YGNodeInsertChild(container, child, 0); + + YGNodeRef fixed = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeight(fixed, 150); + YGNodeInsertChild(root_, fixed, 1); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(container)); + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetHeight(fixed)); +} + +// In a scroll container (column), changing a sibling's height does not cause +// re-measurement of unaffected subtrees when the feature is enabled. +TEST_P(YGFlexBasisFitContentTest, scroll_avoids_remeasure) { + static uint32_t measureCount = 0; + auto measureFunc = [](YGNodeConstRef /*node*/, + float /*width*/, + YGMeasureMode /*widthMode*/, + float /*height*/, + YGMeasureMode /*heightMode*/) { + measureCount++; + return YGSize{.width = 50.0f, .height = 50.0f}; + }; + + measureCount = 0; + + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetOverflow(root_, YGOverflowScroll); + YGNodeStyleSetWidth(root_, 100); + YGNodeStyleSetHeight(root_, 500); + + YGNodeRef sibling = YGNodeNewWithConfig(config_); + YGNodeStyleSetHeight(sibling, 100); + YGNodeInsertChild(root_, sibling, 0); + + YGNodeRef wrapper = YGNodeNewWithConfig(config_); + YGNodeInsertChild(root_, wrapper, 1); + + YGNodeRef inner = YGNodeNewWithConfig(config_); + YGNodeInsertChild(wrapper, inner, 0); + + YGNodeRef leaf = YGNodeNewWithConfig(config_); + YGNodeSetMeasureFunc(leaf, measureFunc); + YGNodeInsertChild(inner, leaf, 0); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + uint32_t firstPassCount = measureCount; + + YGNodeStyleSetHeight(sibling, 200); + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + uint32_t secondPassCount = measureCount - firstPassCount; + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(leaf)); + + EXPECT_EQ(0, secondPassCount); +} + +// Row direction is unaffected by the optimization. Width FitContent is always +// preserved to support text wrapping through container nodes. +TEST_P(YGFlexBasisFitContentTest, row_direction_unchanged) { + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetWidth(root_, 100); + YGNodeStyleSetHeight(root_, 100); + + YGNodeRef container = YGNodeNewWithConfig(config_); + YGNodeInsertChild(root_, container, 0); + + YGNodeRef text = YGNodeNewWithConfig(config_); + YGNodeSetMeasureFunc(text, measureTextLike); + YGNodeInsertChild(container, text, 0); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(text)); +} + +// Scroll container in row direction: width FitContent is skipped for the +// main axis (row) in scroll containers, matching legacy behavior. +TEST_P(YGFlexBasisFitContentTest, row_scroll_skips_width) { + root_ = YGNodeNewWithConfig(config_); + YGNodeStyleSetFlexDirection(root_, YGFlexDirectionRow); + YGNodeStyleSetOverflow(root_, YGOverflowScroll); + YGNodeStyleSetWidth(root_, 100); + YGNodeStyleSetHeight(root_, 100); + + YGNodeRef text = YGNodeNewWithConfig(config_); + YGNodeSetMeasureFunc(text, measureTextLike); + YGNodeInsertChild(root_, text, 0); + + YGNodeCalculateLayout(root_, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(text)); +} + +INSTANTIATE_TEST_SUITE_P( + YogaTest, + YGFlexBasisFitContentTest, + testing::Values(false, true)); + +// Feature toggle invalidates layout cache. +TEST(YogaTest, flex_basis_fit_content_feature_change_invalidates_cache) { + YGConfigRef config = YGConfigNew(); + YGConfigSetExperimentalFeatureEnabled( + config, YGExperimentalFeatureFixFlexBasisFitContent, false); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root, 300); + YGNodeStyleSetWidth(root, 100); + + YGNodeRef container = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(container, 1); + YGNodeInsertChild(root, container, 0); + + YGNodeRef child = YGNodeNewWithConfig(config); + YGNodeStyleSetHeightPercent(child, 50); + YGNodeInsertChild(container, child, 0); + + YGNodeRef fixed = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(fixed, 100); + YGNodeInsertChild(root, fixed, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + float heightBefore = YGNodeLayoutGetHeight(container); + + YGConfigSetExperimentalFeatureEnabled( + config, YGExperimentalFeatureFixFlexBasisFitContent, true); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + float heightAfter = YGNodeLayoutGetHeight(container); + + ASSERT_FLOAT_EQ(heightBefore, heightAfter); + + YGNodeFreeRecursive(root); + YGConfigFree(config); +} diff --git a/tests/generated/YGFlexBasisFitContentTest.cpp b/tests/generated/YGFlexBasisFitContentTest.cpp new file mode 100644 index 0000000000..b1dea09665 --- /dev/null +++ b/tests/generated/YGFlexBasisFitContentTest.cpp @@ -0,0 +1,447 @@ +/* + * 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. + * + * clang-format off + * @generated SignedSource<> + * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html + */ + +#include +#include +#include "../util/TestUtil.h" + +TEST(YogaTest, container_child_overflows_definite_parent_column) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0, 500); + YGNodeStyleSetWidth(root_child0_child0, 50); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, container_child_overflows_definite_parent_row) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 300); + YGNodeStyleSetHeight(root, 200); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0_child0, 500); + YGNodeStyleSetHeight(root_child0_child0, 50); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(-200, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, container_child_within_bounds_column) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0, 100); + YGNodeStyleSetWidth(root_child0_child0, 50); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(150, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, multiple_container_children_overflow_column) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0, 400); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeRef root_child1_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child1_child0, 500); + YGNodeInsertChild(root_child1, root_child1_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(400, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, scroll_container_column) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + YGNodeStyleSetOverflow(root, YGOverflowScroll); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0, 500); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, explicit_and_container_children_column) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0, 100); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeRef root_child1_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child1_child0, 500); + YGNodeInsertChild(root_child1, root_child1_child0, 0); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1_child0)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child1_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child1_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, flex_basis_in_scroll_content_container) { + YGConfigRef config = YGConfigNew(); + YGConfigSetExperimentalFeatureEnabled(config, YGExperimentalFeatureFixFlexBasisFitContent, true); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + YGNodeStyleSetOverflow(root, YGOverflowScroll); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexBasis(root_child0_child0, 200); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeRef root_child0_child1 = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexBasis(root_child0_child1, 300); + YGNodeInsertChild(root_child0, root_child0_child1, 1); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetTop(root_child0_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child1)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root_child0_child1)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetTop(root_child0_child1)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child1)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root_child0_child1)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} diff --git a/yoga/YGEnums.cpp b/yoga/YGEnums.cpp index 9fc4a83a82..538b0b0847 100644 --- a/yoga/YGEnums.cpp +++ b/yoga/YGEnums.cpp @@ -129,6 +129,8 @@ const char* YGExperimentalFeatureToString(const YGExperimentalFeature value) { switch (value) { case YGExperimentalFeatureWebFlexBasis: return "web-flex-basis"; + case YGExperimentalFeatureFixFlexBasisFitContent: + return "fix-flex-basis-fit-content"; } return "unknown"; } diff --git a/yoga/YGEnums.h b/yoga/YGEnums.h index 1b69f09318..aa0b1d4350 100644 --- a/yoga/YGEnums.h +++ b/yoga/YGEnums.h @@ -73,7 +73,8 @@ YG_DEFINE_ENUM_FLAG_OPERATORS(YGErrata) YG_ENUM_DECL( YGExperimentalFeature, - YGExperimentalFeatureWebFlexBasis) + YGExperimentalFeatureWebFlexBasis, + YGExperimentalFeatureFixFlexBasisFitContent) YG_ENUM_DECL( YGFlexDirection, diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index 0a7224cc0b..d25435071b 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -94,7 +94,18 @@ static void computeFlexBasisForChild( const bool isColumnStyleDimDefined = child->hasDefiniteLength(Dimension::Height, ownerHeight); - if (resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize)) { + const bool fixFlexBasisFitContent = + node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent); + + bool useResolvedFlexBasis = + resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize); + if (fixFlexBasisFitContent && resolvedFlexBasis.isDefined() && + resolvedFlexBasis.unwrap() > 0) { + useResolvedFlexBasis = true; + } + + if (useResolvedFlexBasis) { if (child->getLayout().computedFlexBasis.isUndefined() || (child->getConfig()->isExperimentalFeatureEnabled( ExperimentalFeature::WebFlexBasis) && @@ -164,12 +175,27 @@ static void computeFlexBasisForChild( } } - if ((isMainAxisRow && node->style().overflow() == Overflow::Scroll) || - node->style().overflow() != Overflow::Scroll) { - if (yoga::isUndefined(childHeight) && yoga::isDefined(height)) { - childHeight = height; - childHeightSizingMode = SizingMode::FitContent; - } + // For height in the main axis (column direction): when the + // FixFlexBasisFitContent feature is enabled, skip FitContent for + // non-measure container children. This makes the flex basis independent + // of the parent's content-determined height, preventing unnecessary + // re-measurement cascades when a sibling changes size in a ScrollView. + // + // We only optimize the height (column) axis because text wrapping depends + // on width constraints propagating through container nodes. Removing + // FitContent from the width axis would cause text inside nested + // containers to stop wrapping. + bool applyHeightFitContent = + isMainAxisRow || node->style().overflow() != Overflow::Scroll; + if (fixFlexBasisFitContent) { + applyHeightFitContent = isMainAxisRow || + (child->hasMeasureFunc() && + node->style().overflow() != Overflow::Scroll); + } + if (applyHeightFitContent && yoga::isUndefined(childHeight) && + yoga::isDefined(height)) { + childHeight = height; + childHeightSizingMode = SizingMode::FitContent; } const auto& childStyle = child->style(); @@ -537,6 +563,8 @@ static float computeFlexBasisForChildren( yoga::Node* const node, const float availableInnerWidth, const float availableInnerHeight, + const float ownerWidth, + const float ownerHeight, SizingMode widthSizingMode, SizingMode heightSizingMode, Direction direction, @@ -598,8 +626,8 @@ static float computeFlexBasisForChildren( availableInnerWidth, widthSizingMode, availableInnerHeight, - availableInnerWidth, - availableInnerHeight, + ownerWidth, + ownerHeight, heightSizingMode, direction, layoutMarkerData, @@ -1429,12 +1457,53 @@ static void calculateLayoutImpl( // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + // When this node is measured with MaxContent (FixFlexBasisFitContent + // behavior), availableInnerHeight is NaN. + // To preserve percentage resolution for descendants, derive a definite + // owner-size from the parent-provided ownerHeight. + float ownerWidthForChildren = availableInnerWidth; + float ownerHeightForChildren = availableInnerHeight; + + if (node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent)) { + const auto* owner = node->getOwner(); + const bool isChildOfScrollContainer = + owner != nullptr && owner->style().overflow() == Overflow::Scroll; + + if (!isChildOfScrollContainer) { + if (yoga::isUndefined(ownerWidthForChildren) && + yoga::isDefined(ownerWidth)) { + ownerWidthForChildren = calculateAvailableInnerDimension( + node, + direction, + Dimension::Width, + ownerWidth - marginAxisRow, + paddingAndBorderAxisRow, + ownerWidth, + ownerWidth); + } + if (yoga::isUndefined(ownerHeightForChildren) && + yoga::isDefined(ownerHeight)) { + ownerHeightForChildren = calculateAvailableInnerDimension( + node, + direction, + Dimension::Height, + ownerHeight - marginAxisColumn, + paddingAndBorderAxisColumn, + ownerHeight, + ownerWidth); + } + } + } + // Computed basis + margins + gap float totalMainDim = 0; totalMainDim += computeFlexBasisForChildren( node, availableInnerWidth, availableInnerHeight, + ownerWidthForChildren, + ownerHeightForChildren, widthSizingMode, heightSizingMode, direction, diff --git a/yoga/enums/ExperimentalFeature.h b/yoga/enums/ExperimentalFeature.h index bbbf9cda43..7e6c5eb8b6 100644 --- a/yoga/enums/ExperimentalFeature.h +++ b/yoga/enums/ExperimentalFeature.h @@ -17,11 +17,12 @@ namespace facebook::yoga { enum class ExperimentalFeature : uint8_t { WebFlexBasis = YGExperimentalFeatureWebFlexBasis, + FixFlexBasisFitContent = YGExperimentalFeatureFixFlexBasisFitContent, }; template <> constexpr int32_t ordinalCount() { - return 1; + return 2; } constexpr ExperimentalFeature scopedEnum(YGExperimentalFeature unscoped) {