[Layout] Treat flex factors as true factors (#2349)

* Treat flex factors as factors

* Add snapshot tests

* Address comments
This commit is contained in:
Michael Schneider 2016-10-06 13:53:26 -07:00 committed by Adlai Holler
parent 124234fef8
commit 94f958c4a3
44 changed files with 256 additions and 44 deletions

View File

@ -131,8 +131,8 @@ static const CGFloat kViolationEpsilon = 0.01;
*/
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViolationDirection(const CGFloat violation)
{
if (fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &item) { return 0; };
if (std::fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &item) { return 0.0; };
} else if (violation > 0) {
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
} else {
@ -140,9 +140,11 @@ static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViola
}
}
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item, const ASStackLayoutSpecStyle &style)
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item,
const ASStackLayoutSpecStyle &style,
const CGFloat flexFactorSum)
{
return stackDimension(style.direction, item.layout.size) * item.child.style.flexShrink;
return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum);
}
/**
@ -150,20 +152,22 @@ static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item
@param items The unpositioned items from the original unconstrained layout pass.
@param style The layout style to be applied to all children.
@param violation The amount that the stack layout violates its size range.
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
@return A lambda capable of computing the flex shrink adjustment, if any, for a particular item.
*/
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
{
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x + scaledFlexShrinkFactor(item, style);
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x + scaledFlexShrinkFactor(item, style, flexFactorSum);
});
return [style, scaledFlexShrinkFactorSum, violation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style) / scaledFlexShrinkFactorSum;
return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackUnpositionedItem &item) {
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum;
// The item should shrink proportionally to the scaled flex shrink factor ratio computed above.
// Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account.
return -fabs(scaledFlexShrinkFactorRatio * violation);
return -std::fabs(scaledFlexShrinkFactorRatio * violation);
};
}
@ -174,17 +178,13 @@ static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexShrinkA
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
@return A lambda capable of computing the flex grow adjustment, if any, for a particular item.
*/
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const CGFloat violation,
const CGFloat flexFactorSum)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const CGFloat violation,
const CGFloat flexFactorSum)
{
const CGFloat violationPerFlexFactor = floorf(violation / flexFactorSum);
const CGFloat remainingViolation = violation - (violationPerFlexFactor * flexFactorSum);
// To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor.
// If there happens to be a violation remaining make sure it is allocated to the first flexible child.
return [violationPerFlexFactor, remainingViolation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
// Only apply the remaining violation for the first flexible child that has a flex grow factor.
return violationPerFlexFactor * item.child.style.flexGrow + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0);
return [violation, flexFactorSum](const ASStackUnpositionedItem &item) {
return std::floor(violation * (item.child.style.flexGrow / flexFactorSum));
};
}
@ -196,15 +196,15 @@ static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexGrowAdj
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
@return A lambda capable of computing the flex adjustment for a particular item.
*/
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
{
if (violation > 0) {
return flexGrowAdjustment(items, violation, flexFactorSum);
} else {
return flexShrinkAdjustment(items, style, violation);
return flexShrinkAdjustment(items, style, violation, flexFactorSum);
}
}
@ -352,7 +352,7 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactor = flexFactorInViolationDirection(violation);
// The flex factor sum is needed to determine if flexing is necessary.
// This value is also needed if the violation is positive and flexible children need to grow, so keep it around.
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x + flexFactor(item);
});
// If no children are able to flex then there is nothing left to do. Bail.
@ -363,17 +363,23 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
}
return;
}
std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustment = flexAdjustmentInViolationDirection(items,
style,
violation,
flexFactorSum);
std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustment = flexAdjustmentInViolationDirection(items,
style,
violation,
flexFactorSum);
// Compute any remaining violation to the first flexible child.
const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x - flexAdjustment(item);
});
BOOL isFirstFlex = YES;
for (ASStackUnpositionedItem &item : items) {
const CGFloat currentFlexAdjustment = flexAdjustment(item, isFirstFlex);
const CGFloat currentFlexAdjustment = flexAdjustment(item);
// Children are consider inflexible if they do not need to make a flex adjustment.
if (currentFlexAdjustment != 0) {
const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size);
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment;
// Only apply the remaining violation for the first flexible child that has a flex grow factor.
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0);
item.layout = crossChildLayout(item.child,
style,
MAX(flexedStackSize, 0),

View File

@ -21,6 +21,15 @@
@implementation ASStackLayoutSpecSnapshotTests
#pragma mark - XCTestCase
- (void)setUp
{
[super setUp];
self.recordMode = NO;
}
#pragma mark - Utility methods
static NSArray<ASDisplayNode *> *defaultSubnodes()
@ -597,13 +606,13 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedEquallyAmongFlexibleChildren
- (void)testPositiveViolationIsDistributedEqually
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
subnodes[0].style.flexGrow = 0;
subnodes[2].style.flexGrow = 0;
subnodes[0].style.flexGrow = 1;
subnodes[2].style.flexGrow = 1;
// In this scenario a width of 350 results in a positive violation of 200.
// Due to each flexible subnode specifying a flex grow factor of 1 the violation will be distributed evenly.
@ -611,7 +620,21 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedProportionallyAmongFlexibleChildren
- (void)testPositiveViolationIsDistributedEquallyWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
subnodes[0].style.flexGrow = 0.5;
subnodes[2].style.flexGrow = 0.5;
// In this scenario a width of 350 results in a positive violation of 200.
// Due to each flexible child component specifying a flex grow factor of 0.5 the violation will be distributed evenly.
static ASSizeRange kSize = {{350, 350}, {350, 350}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedProportionally
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
@ -627,7 +650,23 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedEquallyAmongGrowingAndShrinkingFlexibleChildren
- (void)testPositiveViolationIsDistributedProportionallyWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
subnodes[0].style.flexGrow = 0.25;
subnodes[1].style.flexGrow = 0.50;
subnodes[2].style.flexGrow = 0.25;
// In this scenario a width of 350 results in a positive violation of 200.
// The first and third child components specify a flex grow factor of 0.25 and will flex by 50.
// The second child component specifies a flex grow factor of 0.25 and will flex by 100.
static ASSizeRange kSize = {{350, 350}, {350, 350}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedEquallyAmongMixedChildren
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@ -647,7 +686,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedProportionallyAmongGrowingAndShrinkingFlexibleChildren
- (void)testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
const CGSize kSubnodeSize = {50, 50};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
subnodes[0].style.flexShrink = 1.0;
subnodes[1].style.flexGrow = 0.5;
subnodes[2].style.flexShrink = 0.0;
subnodes[3].style.flexGrow = 0.5;
// In this scenario a width of 400 results in a positive violation of 200.
// The first and third child components specify a flex shrink factor of 1 and 0, respectively. They won't flex.
// The second and fourth child components specify a flex grow factor of 0.5 and will flex by 100.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedProportionallyAmongMixedChildren
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
@ -668,6 +727,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
const CGSize kSubnodeSize = {50, 50};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
subnodes[0].style.flexShrink = 1.0;
subnodes[1].style.flexGrow = 0.75;
subnodes[2].style.flexShrink = 0.0;
subnodes[3].style.flexGrow = 0.25;
// In this scenario a width of 400 results in a positive violation of 200.
// The first and third child components specify a flex shrink factor of 1 and 0, respectively. They won't flex.
// The second child component specifies a flex grow factor of 0.75 and will flex by 150.
// The fourth child component specifies a flex grow factor of 0.25 and will flex by 50.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChild
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
@ -688,7 +768,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongFlexibleChildren
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 25}),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 0}),
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 100})
];
subnodes[0].style.flexGrow = 0.0;
subnodes[1].style.flexGrow = 0.5;
subnodes[2].style.flexGrow = 0.5;
// In this scenario a width of 300 results in a positive violation of 175.
// The second and third child components specify a flex grow factor of 0.5 and will flex by 88 and 87, respectively.
static ASSizeRange kSize = {{300, 300}, {300, 300}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSize
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@ -708,7 +808,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongFlexibleChildren
- (void)testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {300, 50}),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {100, 50}),
ASDisplayNodeWithBackgroundColor([UIColor redColor], {200, 50})
];
subnodes[0].style.flexShrink = 0.5;
subnodes[1].style.flexShrink = 0.0;
subnodes[2].style.flexShrink = 0.5;
// In this scenario a width of 400 results in a negative violation of 200.
// The first and third child components specify a flex shrink factor of 0.5 and will flex by -120 and -80, respectively.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
@ -729,7 +849,28 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongGrowingAndShrinkingFlexibleChildren
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 300}),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 100}),
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 200})
];
subnodes[0].style.flexShrink = 0.4;
subnodes[1].style.flexShrink = 0.2;
subnodes[2].style.flexShrink = 0.4;
// In this scenario a width of 400 results in a negative violation of 200.
// The first and third child components specify a flex shrink factor of 0.4 and will flex by -109 and -72, respectively.
// The second child component specifies a flex shrink factor of 0.2 and will flex by -18.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@ -749,7 +890,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongGrowingAndShrinkingFlexibleChildren
- (void)testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
const CGSize kSubnodeSize = {150, 50};
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
subnodes[0].style.flexGrow = 1.0;
subnodes[1].style.flexShrink = 0.5;
subnodes[2].style.flexGrow = 0.0;
subnodes[3].style.flexShrink = 0.5;
// In this scenario a width of 400 results in a negative violation of 200.
// The first and third child components specify a flex grow factor of 1 and 0, respectively. They won't flex.
// The second and fourth child components specify a flex shrink factor of 0.5 and will flex by -100.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
@ -773,7 +934,31 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWidth
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 150}),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 100}),
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 150}),
ASDisplayNodeWithBackgroundColor([UIColor yellowColor], {50, 200})
];
subnodes[0].style.flexGrow = 1.0;
subnodes[1].style.flexShrink = 0.25;
subnodes[2].style.flexGrow = 0.0;
subnodes[3].style.flexShrink = 0.75;
// In this scenario a width of 400 results in a negative violation of 200.
// The first and third child components specify a flex grow factor of 1 and 0, respectively. They won't flex.
// The second child component specifies a flex shrink factor of 0.25 and will flex by -28.
// The fourth child component specifies a flex shrink factor of 0.75 and will flex by -171.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@ -794,6 +979,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {300, 50}),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {100, 50}),
ASDisplayNodeWithBackgroundColor([UIColor redColor], {200, 50})
];
subnodes[0].style.flexShrink = 0.25;
subnodes[1].style.flexShrink = 0.50;
subnodes[2].style.flexShrink = 0.25;
// In this scenario a width of 400 results in a negative violation of 200.
// The first and third child components specify a flex shrink factor of 0.25 and will flex by 50.
// The second child component specifies a flex shrink factor of 0.50 and will flex by -57. It will have a width of 43.
static ASSizeRange kSize = {{400, 400}, {400, 400}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testNestedStackLayoutStretchDoesNotViolateWidth
{
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal