From 9119f26ca6d1bbb3a155a97cb501fde2ba885994 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 4 Mar 2026 10:43:55 -0800 Subject: [PATCH] Add fixYogaFlexBasisFitContentInMainAxis flag to avoid unnecessary re-measurement Summary: X-link: https://github.com/facebook/react-native/pull/55897 changelog: [internal] this change is gated. ## Problem 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, causing **unnecessary re-measurement and cascading ownership clones** when siblings change size. ### The re-measurement cascade (before fix) ``` ScrollView (overflow: scroll) +-------------------------------+ | Content Container (auto h) | | +---------------------------+ | | | Item A h=200 | | <-- Item A height changes | +---------------------------+ | | | Item B h=300 | | | +---------------------------+ | | | Item C h=150 | | | +---------------------------+ | +-------------------------------+ | v Content container height changes (200+300+150 = 650) | v FitContent(650) re-measures ALL items <-- PROBLEM | because their flex basis was FitContent(old_height) v Cascading clones of the entire subtree ``` With the legacy `FitContent` in the main axis, each item's flex basis is `min(content, parent_height)`. When Item A changes height, the content container's height changes, which invalidates the FitContent constraint for ALL items, triggering a full re-measurement cascade. ### After fix (MaxContent in main axis) ``` ScrollView (overflow: scroll) +-------------------------------+ | Content Container (auto h) | | +---------------------------+ | | | Item A h=200 -> 250 | | <-- Item A height changes | +---------------------------+ | | | Item B h=300 | | <-- NOT re-measured (basis unchanged) | +---------------------------+ | | | Item C h=150 | | <-- NOT re-measured (basis unchanged) | +---------------------------+ | +-------------------------------+ | v Content container height changes (250+300+150 = 700) | v Only Item A is re-measured. B and C keep their MaxContent flex basis (independent of parent height). ``` With `MaxContent`, each item's flex basis is its intrinsic content size, independent of the parent. Changing one item doesn't invalidate siblings. ## Solution This diff adds a `FlexBasisFitContentInMainAxis` errata bit gated by the `fixYogaFlexBasisFitContentInMainAxis` feature flag. When the fix is active, flex basis measurement uses `MaxContent` (unbounded) instead of `FitContent` for container children in the main axis. ### Three check points in `computeFlexBasisForChild` ``` computeFlexBasisForChild(parent, child) | |-- Check 1: Accept positive flex basis when mainAxisSize is NaN | (fixes flexBasis:200 items in ScrollView getting height 0) | |-- Check 2: FitContent vs MaxContent constraint | +--------------------------------------------------+ | | Parent type | Legacy (errata) | Fix | | |--------------------+-----------------+-----------| | | Auto height | FitContent | MaxContent| <-- key change | | Definite height | FitContent | FitContent| <-- preserved | | Scroll container | MaxContent | MaxContent| <-- unchanged | | Text child (any) | FitContent | FitContent| <-- preserved | +--------------------------------------------------+ | |-- Check 3: ownerHeightForChildren fallback (preserves percentage resolution when availableInnerHeight is NaN) ``` ### Why definite-height parents keep FitContent Yoga's default `flexShrink` is 0 (unlike CSS's default of 1). Without FitContent, a child measured at MaxContent would get a flex basis equal to its full content height and never shrink to fit: ``` View (height: 760) View (height: 760) +-------------------+ +-------------------+ | Wrapper (auto h) | | Wrapper (auto h) | | +-----------+ | | +-----------+-----|----+ | | ScrollView| | | | ScrollView| | | | | content: | | | | content: | | | | | 1800px | | | | 1800px | | | | +-----------+ | | | | | | | h=760 (bounded) | | +-----------+ | | +-------------------+ +---|---------+-----|----+ FitContent: wrapper=760 | h=1800 (overflows!) ScrollView can scroll MaxContent: wrapper=1800 flexShrink=0, no shrinking ScrollView frame=1800=content scrollable range = 0! ``` For **definite-height** parents, FitContent is safe (the parent's size is fixed, so no re-measurement cascade). For **auto-height** parents, MaxContent is used to avoid the cascade. ### Percentage resolution preservation (Check 3) When MaxContent is used, `availableInnerHeight` becomes NaN. This would break percentage-height grandchildren. Check 3 derives a definite `ownerHeightForChildren` from the parent-provided `ownerHeight`: ``` View (height: 844) +---------------------------+ | Wrapper (auto h) | availableInnerHeight = NaN (MaxContent) | ownerHeight = 844 | ownerHeightForChildren = 844 (from Check 3) | +-----+ +-----------+ | | |h:500| |h:'50%' | | 50% resolves against 844, not NaN | | | |= 422 | | | +-----+ +-----------+ | +---------------------------+ ``` Children of scroll containers skip this fallback (scroll content is intentionally unbounded). Reviewed By: javache Differential Revision: D94658492 --- enums.py | 3 + .../YGFlexBasisFitContentInMainAxisTest.html | 68 +++ java/com/facebook/yoga/YogaErrata.java | 2 + .../YGFlexBasisFitContentInMainAxisTest.java | 458 ++++++++++++++++ javascript/src/generated/YGEnums.ts | 2 + ...GFlexBasisFitContentInMainAxisTest.test.ts | 490 ++++++++++++++++++ tests/YGDefaultValuesTest.cpp | 5 +- tests/YGFlexBasisFitContentInMainAxisTest.cpp | 310 +++++++++++ tests/YGScaleChangeTest.cpp | 5 +- .../YGFlexBasisFitContentInMainAxisTest.cpp | 446 ++++++++++++++++ yoga/YGEnums.cpp | 2 + yoga/YGEnums.h | 1 + yoga/algorithm/CalculateLayout.cpp | 115 +++- yoga/enums/Errata.h | 1 + 14 files changed, 1892 insertions(+), 16 deletions(-) create mode 100644 gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.html create mode 100644 java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentInMainAxisTest.java create mode 100644 javascript/tests/generated/YGFlexBasisFitContentInMainAxisTest.test.ts create mode 100644 tests/YGFlexBasisFitContentInMainAxisTest.cpp create mode 100644 tests/generated/YGFlexBasisFitContentInMainAxisTest.cpp diff --git a/enums.py b/enums.py index d43dd6c147..76b1355120 100755 --- a/enums.py +++ b/enums.py @@ -76,6 +76,9 @@ # Absolute nodes will resolve percentages against the inner size of # their containing node, not the padding box ("AbsolutePercentAgainstInnerSize", 1 << 2), + # Applies a FitContent constraint in the main axis during flex basis + # computation for non-measure container nodes + ("FlexBasisFitContentInMainAxis", 1 << 3), # Enable all incorrect behavior (preserve compatibility) ("All", 0x7FFFFFFF), # Enable all errata except for "StretchFlexBasis" (Defaults behavior diff --git a/gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.html b/gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.html new file mode 100644 index 0000000000..56883e121f --- /dev/null +++ b/gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.html @@ -0,0 +1,68 @@ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
diff --git a/java/com/facebook/yoga/YogaErrata.java b/java/com/facebook/yoga/YogaErrata.java index e0521b3fbb..3bd711beb5 100644 --- a/java/com/facebook/yoga/YogaErrata.java +++ b/java/com/facebook/yoga/YogaErrata.java @@ -14,6 +14,7 @@ public enum YogaErrata { STRETCH_FLEX_BASIS(1), ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING(2), ABSOLUTE_PERCENT_AGAINST_INNER_SIZE(4), + FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS(8), ALL(2147483647), CLASSIC(2147483646); @@ -33,6 +34,7 @@ public static YogaErrata fromInt(int value) { case 1: return STRETCH_FLEX_BASIS; case 2: return ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING; case 4: return ABSOLUTE_PERCENT_AGAINST_INNER_SIZE; + case 8: return FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS; case 2147483647: return ALL; case 2147483646: return CLASSIC; default: throw new IllegalArgumentException("Unknown enum value: " + value); diff --git a/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentInMainAxisTest.java b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentInMainAxisTest.java new file mode 100644 index 0000000000..b41f3354b2 --- /dev/null +++ b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentInMainAxisTest.java @@ -0,0 +1,458 @@ +/* + * 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<<205adc3d98df4a065aef1c7cabfc7170>> + * generated by gentest/gentest-driver.ts from gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.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 YGFlexBasisFitContentInMainAxisTest { + @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.setWidth(50f); + 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(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.setFlexDirection(YogaFlexDirection.ROW); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setWidth(300f); + root.setHeight(200f); + + 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.setWidth(50f); + root_child0_child0.setHeight(100f); + 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.setOverflow(YogaOverflow.SCROLL); + 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.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(); + + final YogaNode root = createNode(config); + root.setPositionType(YogaPositionType.ABSOLUTE); + root.setOverflow(YogaOverflow.SCROLL); + 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.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 f389fe2fdf..3a870db8c2 100644 --- a/javascript/src/generated/YGEnums.ts +++ b/javascript/src/generated/YGEnums.ts @@ -58,6 +58,7 @@ export enum Errata { StretchFlexBasis = 1, AbsolutePositionWithoutInsetsExcludesPadding = 2, AbsolutePercentAgainstInnerSize = 4, + FlexBasisFitContentInMainAxis = 8, All = 2147483647, Classic = 2147483646, } @@ -169,6 +170,7 @@ const constants = { ERRATA_STRETCH_FLEX_BASIS: Errata.StretchFlexBasis, ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING: Errata.AbsolutePositionWithoutInsetsExcludesPadding, ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE: Errata.AbsolutePercentAgainstInnerSize, + ERRATA_FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS: Errata.FlexBasisFitContentInMainAxis, ERRATA_ALL: Errata.All, ERRATA_CLASSIC: Errata.Classic, EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS: ExperimentalFeature.WebFlexBasis, diff --git a/javascript/tests/generated/YGFlexBasisFitContentInMainAxisTest.test.ts b/javascript/tests/generated/YGFlexBasisFitContentInMainAxisTest.test.ts new file mode 100644 index 0000000000..85c657fed5 --- /dev/null +++ b/javascript/tests/generated/YGFlexBasisFitContentInMainAxisTest.test.ts @@ -0,0 +1,490 @@ +/** + * 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/gentest-driver.ts from gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.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(); + let root; + + try { + 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.setWidth(50); + 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(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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('container_child_overflows_definite_parent_row', () => { + const config = Yoga.Config.create(); + let root; + + try { + root = Yoga.Node.create(config); + root.setFlexDirection(FlexDirection.Row); + root.setPositionType(PositionType.Absolute); + root.setWidth(300); + root.setHeight(200); + + 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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('container_child_within_bounds_column', () => { + const config = Yoga.Config.create(); + let root; + + try { + 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.setWidth(50); + root_child0_child0.setHeight(100); + 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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('multiple_container_children_overflow_column', () => { + const config = Yoga.Config.create(); + let root; + + try { + 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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('scroll_container_column', () => { + const config = Yoga.Config.create(); + let root; + + try { + root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setOverflow(Overflow.Scroll); + 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.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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('explicit_and_container_children_column', () => { + const config = Yoga.Config.create(); + let root; + + try { + 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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); +test('flex_basis_in_scroll_content_container', () => { + const config = Yoga.Config.create(); + let root; + + try { + root = Yoga.Node.create(config); + root.setPositionType(PositionType.Absolute); + root.setOverflow(Overflow.Scroll); + 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.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); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); diff --git a/tests/YGDefaultValuesTest.cpp b/tests/YGDefaultValuesTest.cpp index 09c74ce8c6..d52f4878f3 100644 --- a/tests/YGDefaultValuesTest.cpp +++ b/tests/YGDefaultValuesTest.cpp @@ -118,7 +118,10 @@ TEST(YogaTest, assert_webdefault_values_reset) { TEST(YogaTest, assert_legacy_stretch_behaviour) { YGConfig* config = YGConfigNew(); - YGConfigSetErrata(config, YGErrataStretchFlexBasis); + YGConfigSetErrata( + config, + static_cast( + YGErrataStretchFlexBasis | YGErrataFlexBasisFitContentInMainAxis)); YGNodeRef root = YGNodeNewWithConfig(config); YGNodeStyleSetWidth(root, 500); YGNodeStyleSetHeight(root, 500); diff --git a/tests/YGFlexBasisFitContentInMainAxisTest.cpp b/tests/YGFlexBasisFitContentInMainAxisTest.cpp new file mode 100644 index 0000000000..3cbc6abc67 --- /dev/null +++ b/tests/YGFlexBasisFitContentInMainAxisTest.cpp @@ -0,0 +1,310 @@ +/* + * 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 + +// Tests for the FlexBasisFitContentInMainAxis errata. +// +// For non-scroll container nodes, FitContent and MaxContent produce the same +// final dimensions (both use content-based sizing). The errata primarily +// affects re-measurement/caching behavior (performance), not layout values. +// +// The one visible layout difference is Check 1: when mainAxisSize is NaN +// (e.g. inside a scroll content container), the errata causes flex-basis +// to be ignored, while the corrected behavior accepts positive flex-basis +// regardless of mainAxisSize. + +// Verify that container children produce the same layout regardless of errata +// when the child's content overflows a definite-height column parent. +// FitContent and MaxContent both resolve to content size for containers. +TEST(YogaTest, flex_basis_fit_content_errata_column_same_layout) { + // With errata + { + YGConfigRef config = YGConfigNew(); + YGConfigSetErrata(config, YGErrataFlexBasisFitContentInMainAxis); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0_child0, 50); + YGNodeStyleSetHeight(root_child0_child0, 500); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + // Container child gets content height (500) even with FitContent errata, + // because FitContent and MaxContent produce the same result for containers. + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } + + // Without errata (same result) + { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0_child0, 50); + YGNodeStyleSetHeight(root_child0_child0, 500); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } +} + +// Same test but for row direction. +TEST(YogaTest, flex_basis_fit_content_errata_row_same_layout) { + // With errata + { + YGConfigRef config = YGConfigNew(); + YGConfigSetErrata(config, YGErrataFlexBasisFitContentInMainAxis); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 300); + YGNodeStyleSetHeight(root, 200); + + 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(300, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } + + // Without errata (same result) + { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 300); + YGNodeStyleSetHeight(root, 200); + + 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(300, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } +} + +// Scroll containers use MaxContent in main axis regardless of errata. +TEST(YogaTest, flex_basis_fit_content_errata_scroll_same_layout) { + // With errata + { + YGConfigRef config = YGConfigNew(); + YGConfigSetErrata(config, YGErrataFlexBasisFitContentInMainAxis); + + YGNodeRef root = YGNodeNewWithConfig(config); + 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(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } + + // Without errata (same result) + { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + 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(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0_child0)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } +} + +// With errata: flex-basis is ignored when mainAxisSize is NaN, because the +// old condition requires isDefined(mainAxisSize). Items inside a scroll +// container's auto-height content container get height 0. +TEST(YogaTest, flex_basis_in_scroll_content_with_errata) { + YGConfigRef config = YGConfigNew(); + YGConfigSetErrata(config, YGErrataFlexBasisFitContentInMainAxis); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + YGNodeStyleSetOverflow(root, YGOverflowScroll); + + // Content container: auto height, measured with MaxContent by scroll parent + 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(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + // Content container has 0 height because items' flex-basis is ignored + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetHeight(root_child0_child1)); + + YGNodeFreeRecursive(root); + YGConfigFree(config); +} + +// Without errata: flex-basis is respected even when mainAxisSize is NaN. +// The corrected condition accepts positive resolvedFlexBasis regardless +// of mainAxisSize, so items get their specified flex-basis heights. +TEST(YogaTest, flex_basis_in_scroll_content_without_errata) { + YGConfigRef config = YGConfigNew(); + // No errata (default: Errata::None) + + YGNodeRef root = YGNodeNewWithConfig(config); + 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(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); + + // Content container has height 500 (200 + 300 from items' flex-basis) + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0_child0)); + + 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/tests/YGScaleChangeTest.cpp b/tests/YGScaleChangeTest.cpp index 11de9c079c..525cce7e27 100644 --- a/tests/YGScaleChangeTest.cpp +++ b/tests/YGScaleChangeTest.cpp @@ -42,7 +42,10 @@ TEST(YogaTest, scale_change_invalidates_layout) { TEST(YogaTest, errata_config_change_relayout) { YGConfig* config = YGConfigNew(); - YGConfigSetErrata(config, YGErrataStretchFlexBasis); + YGConfigSetErrata( + config, + static_cast( + YGErrataStretchFlexBasis | YGErrataFlexBasisFitContentInMainAxis)); YGNodeRef root = YGNodeNewWithConfig(config); YGNodeStyleSetWidth(root, 500); YGNodeStyleSetHeight(root, 500); diff --git a/tests/generated/YGFlexBasisFitContentInMainAxisTest.cpp b/tests/generated/YGFlexBasisFitContentInMainAxisTest.cpp new file mode 100644 index 0000000000..5f882b3483 --- /dev/null +++ b/tests/generated/YGFlexBasisFitContentInMainAxisTest.cpp @@ -0,0 +1,446 @@ +/* + * 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<<95958793a3aaa59510b88cf0cecf7676>> + * generated by gentest/gentest-driver.ts from gentest/fixtures/YGFlexBasisFitContentInMainAxisTest.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); + YGNodeStyleSetWidth(root_child0_child0, 50); + 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(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); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetWidth(root, 300); + YGNodeStyleSetHeight(root, 200); + + 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); + YGNodeStyleSetWidth(root_child0_child0, 50); + YGNodeStyleSetHeight(root_child0_child0, 100); + 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); + YGNodeStyleSetOverflow(root, YGOverflowScroll); + 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); + 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(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); + YGNodeStyleSetOverflow(root, YGOverflowScroll); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 300); + + 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 4bdace6b7a..3e63b509b7 100644 --- a/yoga/YGEnums.cpp +++ b/yoga/YGEnums.cpp @@ -111,6 +111,8 @@ const char* YGErrataToString(const YGErrata value) { return "absolute-position-without-insets-excludes-padding"; case YGErrataAbsolutePercentAgainstInnerSize: return "absolute-percent-against-inner-size"; + case YGErrataFlexBasisFitContentInMainAxis: + return "flex-basis-fit-content-in-main-axis"; case YGErrataAll: return "all"; case YGErrataClassic: diff --git a/yoga/YGEnums.h b/yoga/YGEnums.h index bb83bcfac9..c0bc7a04e4 100644 --- a/yoga/YGEnums.h +++ b/yoga/YGEnums.h @@ -64,6 +64,7 @@ YG_ENUM_DECL( YGErrataStretchFlexBasis = 1, YGErrataAbsolutePositionWithoutInsetsExcludesPadding = 2, YGErrataAbsolutePercentAgainstInnerSize = 4, + YGErrataFlexBasisFitContentInMainAxis = 8, YGErrataAll = 2147483647, YGErrataClassic = 2147483646) YG_DEFINE_ENUM_FLAG_OPERATORS(YGErrata) diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index 81a5f3b8a0..5e9350b2f9 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -94,7 +94,10 @@ static void computeFlexBasisForChild( const bool isColumnStyleDimDefined = child->hasDefiniteLength(Dimension::Height, ownerHeight); - if (resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize)) { + if (resolvedFlexBasis.isDefined() && + (yoga::isDefined(mainAxisSize) || + (!node->hasErrata(Errata::FlexBasisFitContentInMainAxis) && + resolvedFlexBasis.unwrap() > 0))) { if (child->getLayout().computedFlexBasis.isUndefined() || (child->getConfig()->isExperimentalFeatureEnabled( ExperimentalFeature::WebFlexBasis) && @@ -156,19 +159,57 @@ static void computeFlexBasisForChild( // The W3C spec doesn't say anything about the 'overflow' property, but all // major browsers appear to implement the following logic. - if ((!isMainAxisRow && node->style().overflow() == Overflow::Scroll) || - node->style().overflow() != Overflow::Scroll) { - if (yoga::isUndefined(childWidth) && yoga::isDefined(width)) { - childWidth = width; - childWidthSizingMode = SizingMode::FitContent; + // + // In the cross axis, children should always be bounded by the parent's + // available space (FitContent). In the main axis, the flex basis should + // be the child's intrinsic size under max-content constraint. + // + // The legacy behavior (FlexBasisFitContentInMainAxis errata) applies a + // FitContent constraint in the main axis for non-scroll containers, which + // causes unnecessary re-measurement and cascading clones when the parent's + // content-determined size changes (e.g. when a sibling's height changes + // in a ScrollView). + // + // The corrected behavior leaves the main axis unconstrained for container + // children (no measure function), and preserves the FitContent constraint + // for measure function nodes (e.g. text) to support text wrapping. + if (node->hasErrata(Errata::FlexBasisFitContentInMainAxis)) { + // Legacy behavior: apply FitContent in both axes (except main axis for + // scroll containers). + if ((!isMainAxisRow && node->style().overflow() == Overflow::Scroll) || + node->style().overflow() != Overflow::Scroll) { + if (yoga::isUndefined(childWidth) && yoga::isDefined(width)) { + childWidth = width; + childWidthSizingMode = SizingMode::FitContent; + } } - } - if ((isMainAxisRow && node->style().overflow() == Overflow::Scroll) || - node->style().overflow() != Overflow::Scroll) { - if (yoga::isUndefined(childHeight) && yoga::isDefined(height)) { - childHeight = height; - childHeightSizingMode = SizingMode::FitContent; + if ((isMainAxisRow && node->style().overflow() == Overflow::Scroll) || + node->style().overflow() != Overflow::Scroll) { + if (yoga::isUndefined(childHeight) && yoga::isDefined(height)) { + childHeight = height; + childHeightSizingMode = SizingMode::FitContent; + } + } + } else { + // Corrected behavior: only apply FitContent in the cross axis, or in + // the main axis for measure function nodes in non-scroll containers. + if (!isMainAxisRow || + (child->hasMeasureFunc() && + node->style().overflow() != Overflow::Scroll)) { + if (yoga::isUndefined(childWidth) && yoga::isDefined(width)) { + childWidth = width; + childWidthSizingMode = SizingMode::FitContent; + } + } + + if (isMainAxisRow || + (child->hasMeasureFunc() && + node->style().overflow() != Overflow::Scroll)) { + if (yoga::isUndefined(childHeight) && yoga::isDefined(height)) { + childHeight = height; + childHeightSizingMode = SizingMode::FitContent; + } } } @@ -537,6 +578,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 +641,8 @@ static float computeFlexBasisForChildren( availableInnerWidth, widthSizingMode, availableInnerHeight, - availableInnerWidth, - availableInnerHeight, + ownerWidth, + ownerHeight, heightSizingMode, direction, layoutMarkerData, @@ -1421,12 +1464,56 @@ static void calculateLayoutImpl( // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + // When this node is measured with MaxContent (corrected + // FlexBasisFitContentInMainAxis behavior), availableInnerHeight/Width is NaN. + // To preserve percentage resolution for descendants, derive a definite + // owner-size from the parent-provided ownerHeight/ownerWidth. + float ownerWidthForChildren = availableInnerWidth; + float ownerHeightForChildren = availableInnerHeight; + + if (!node->hasErrata(Errata::FlexBasisFitContentInMainAxis)) { + // Do not propagate the fallback when this node is a direct child of a + // scroll container. In scroll contexts, the scroll axis is intentionally + // indefinite and percentage-based children should not resolve against the + // viewport size. + 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/Errata.h b/yoga/enums/Errata.h index 2f47a94175..86d8409b15 100644 --- a/yoga/enums/Errata.h +++ b/yoga/enums/Errata.h @@ -20,6 +20,7 @@ enum class Errata : uint32_t { StretchFlexBasis = YGErrataStretchFlexBasis, AbsolutePositionWithoutInsetsExcludesPadding = YGErrataAbsolutePositionWithoutInsetsExcludesPadding, AbsolutePercentAgainstInnerSize = YGErrataAbsolutePercentAgainstInnerSize, + FlexBasisFitContentInMainAxis = YGErrataFlexBasisFitContentInMainAxis, All = YGErrataAll, Classic = YGErrataClassic, };