ComponentKitTests/CKFlexboxComponentTests.mm (444 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import <XCTest/XCTest.h> #import <ComponentKit/CKFlexboxComponent.h> #import <ComponentKit/CKCompositeComponent.h> #import <ComponentKit/CKComponentLayout.h> #import <ComponentKit/CKComponent+Yoga.h> #import "yoga/Yoga.h" #import "CKComponentTestCase.h" @interface CKFlexboxComponent (Test) - (YGNodeRef)ygNode:(CKSizeRange)constrainedSize; - (RCLayout)layoutThatFits:(CKSizeRange)constrainedSize parentSize:(CGSize)parentSize; @end @interface CKFlexboxComponentTests : CKComponentTestCase @end @implementation CKFlexboxComponentTests - (void)testSizeTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .child(CK::ComponentBuilder() .viewClass([UIView class]) .width(50) .height(50) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .width(50) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .height(50) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .build(); YGNodeRef node = [component ygNode:{{300, 0}, {300, 300}}]; XCTAssertEqual(YGNodeStyleGetWidth(node).value, 300); XCTAssertEqual(YGNodeStyleGetMinHeight(node).value, 0); XCTAssertEqual(YGNodeStyleGetMaxHeight(node).value, 300); YGNodeRef childNode = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetWidth(childNode).value, 50); XCTAssertEqual(YGNodeStyleGetHeight(childNode).value, 50); childNode = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetWidth(childNode).value, 50); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetHeight(childNode).value)); childNode = YGNodeGetChild(node, 2); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetWidth(childNode).value)); XCTAssertEqual(YGNodeStyleGetHeight(childNode).value, 50); childNode = YGNodeGetChild(node, 3); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetWidth(childNode).value)); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetHeight(childNode).value)); } - (void)testDirectionTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .build(); YGNodeRef node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetFlexDirection(node), YGFlexDirectionColumn); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .direction(CKFlexboxDirectionColumn) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetFlexDirection(node), YGFlexDirectionColumn); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .direction(CKFlexboxDirectionRow) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetFlexDirection(node), YGFlexDirectionRow); } - (void)testJustifyTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .build(); YGNodeRef node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetJustifyContent(node), YGJustifyFlexStart); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .justifyContent(CKFlexboxJustifyContentStart) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetJustifyContent(node), YGJustifyFlexStart); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .justifyContent(CKFlexboxJustifyContentCenter) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetJustifyContent(node), YGJustifyCenter); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .justifyContent(CKFlexboxJustifyContentEnd) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetJustifyContent(node), YGJustifyFlexEnd); } - (void)testAlignItemsTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .build(); YGNodeRef node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetAlignItems(node), YGAlignFlexStart); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetAlignItems(node), YGAlignFlexStart); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStretch) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetAlignItems(node), YGAlignStretch); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsEnd) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetAlignItems(node), YGAlignFlexEnd); component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsCenter) .build(); node = [component ygNode:{{0, 0}, {0, 0}}]; XCTAssertEqual(YGNodeStyleGetAlignItems(node), YGAlignCenter); } - (void)testAlignChildTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .alignSelf(CKFlexboxAlignSelfAuto) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .alignSelf(CKFlexboxAlignSelfStart) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .alignSelf(CKFlexboxAlignSelfEnd) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .alignSelf(CKFlexboxAlignSelfStretch) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .alignSelf(CKFlexboxAlignSelfCenter) .build(); YGNodeRef node = [component ygNode:{{0, 0}, {0, 0}}]; YGNodeRef childNode = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignAuto); childNode = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignAuto); childNode = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignFlexStart); childNode = YGNodeGetChild(node, 3); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignFlexEnd); childNode = YGNodeGetChild(node, 4); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignStretch); childNode = YGNodeGetChild(node, 5); XCTAssertEqual(YGNodeStyleGetAlignSelf(childNode), YGAlignCenter); } - (void)testFlexGrowShrinkTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexGrow(1) .flexShrink(0.5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexShrink(1) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexGrow(0.5) .build(); YGNodeRef node = [component ygNode:{{0, 0}, {0, 0}}]; YGNodeRef childNode = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetFlexGrow(childNode), 0); XCTAssertEqual(YGNodeStyleGetFlexShrink(childNode), 0); childNode = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetFlexGrow(childNode), 1); XCTAssertEqual(YGNodeStyleGetFlexShrink(childNode), 0.5); childNode = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetFlexGrow(childNode), 0); XCTAssertEqual(YGNodeStyleGetFlexShrink(childNode), 1); childNode = YGNodeGetChild(node, 3); XCTAssertEqual(YGNodeStyleGetFlexGrow(childNode), 0.5); XCTAssertEqual(YGNodeStyleGetFlexShrink(childNode), 0); } - (void)testFlexBasisTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexBasis(RCRelativeDimension::Auto()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexBasis(100) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .flexBasis(RCRelativeDimension::Percent(0.5)) .build(); YGNodeRef node = [component ygNode:{{0, 300}, {0, 300}}]; YGNodeRef childNode = YGNodeGetChild(node, 0); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetFlexBasis(childNode).value)); childNode = YGNodeGetChild(node, 1); XCTAssertTrue(YGFloatIsUndefined(YGNodeStyleGetFlexBasis(childNode).value)); childNode = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetFlexBasis(childNode).value, 100); childNode = YGNodeGetChild(node, 3); XCTAssertEqual(YGNodeStyleGetFlexBasis(childNode).value, 150); } - (void)testSpacingTranslation { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .spacing(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingBefore(15) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingBefore(-10) .spacingAfter(10) .build(); YGNodeRef node = [component ygNode:{{0, 300}, {0, 300}}]; YGNodeRef childNode = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetMargin(childNode, YGEdgeTop).value, 15); childNode = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetMargin(childNode, YGEdgeTop).value, 5); childNode = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetMargin(childNode, YGEdgeTop).value, 10); childNode = YGNodeGetChild(node, 3); XCTAssertEqual(YGNodeStyleGetMargin(childNode, YGEdgeTop).value, 0); XCTAssertEqual(YGNodeStyleGetMargin(childNode, YGEdgeBottom).value, 10); } - (void)testSpacingDoesNotApplyToAbsolutelyPositionedChildren { auto const c0 = CK::ComponentBuilder().build(); auto const c1 = CK::ComponentBuilder().build(); auto const c2 = CK::ComponentBuilder().build(); auto const c3 = CK::ComponentBuilder().build(); auto const c4 = CK::ComponentBuilder().build(); auto const flexbox = CK::FlexboxComponentBuilder() .viewClass([UIView class]) .spacing(1) // 0 .child(c0) .positionType(CKFlexboxPositionTypeAbsolute) // 0 .child(c1) // + 1 .child(c2) .spacingAfter(2) // + 0 .child(c3) .positionType(CKFlexboxPositionTypeAbsolute) // + 3 .child(c4) .spacingAfter(6) .build(); YGNodeRef node = [flexbox ygNode:{}]; auto child = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeAbsolute); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValueUndefined); child = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValueUndefined); child = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValue{1.0f}); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeEnd), YGValueUndefined); child = YGNodeGetChild(node, 3); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeAbsolute); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValueUndefined); child = YGNodeGetChild(node, 4); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValue{3.0f}); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeEnd), YGValue{6.0f}); } - (void)testSpacingDoesNotApplyToNilChildren { CKComponent *const c0 = CK::ComponentBuilder().build(); CKComponent *const c1 = CK::ComponentBuilder().build(); CKComponent *const c2 = CK::ComponentBuilder().build(); auto const flexbox = CK::FlexboxComponentBuilder() .viewClass([UIView class]) .spacing(1) // 0 .child(nil) .positionType(CKFlexboxPositionTypeAbsolute) // 0 .child(c0) // + 1 .child(c1) .spacingAfter(2) // + 0 .child(nil) .spacingAfter(20) // + 3 .child(c2) .spacingAfter(7) .child(nil) .build(); YGNodeRef node = [flexbox ygNode:{}]; const auto count = YGNodeGetChildCount(node); XCTAssertEqual(count, 3, @"Expected 3 nodes for non-nil children"); auto child = YGNodeGetChild(node, 0); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValueUndefined); child = YGNodeGetChild(node, 1); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValue{1.0f}); child = YGNodeGetChild(node, 2); XCTAssertEqual(YGNodeStyleGetPositionType(child), YGPositionTypeRelative); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeStart), YGValue{3.0f}); XCTAssertEqual(YGNodeStyleGetMargin(child, YGEdgeEnd), YGValue{7.0f}); } - (void)testCorrectnesOfDefaultStyleValues { CKFlexboxComponentStyle style = {}; XCTAssertEqual(style.alignItems, CKFlexboxAlignItemsStretch); } - (void)testSameLayoutIsCalculatedWithAndWithoutDeepYogaTrees { RCLayout(^buildComponentTreeAndComputeLayout)(BOOL) = ^RCLayout(BOOL useDeepYogaTrees) { CKComponent *component = CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .spacing(5) .useDeepYogaTrees(useDeepYogaTrees) .child(CK::CompositeComponentBuilder() .component(CK::FlexboxComponentBuilder() .alignItems(CKFlexboxAlignItemsStart) .spacing(5) .useDeepYogaTrees(useDeepYogaTrees) .child(CK::CompositeComponentBuilder() .component(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .build()) .spacingBefore(15) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingBefore(-10) .spacingAfter(10) .build()) .build()) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingAfter(5) .child(CK::ComponentBuilder() .viewClass([UIView class]) .build()) .spacingBefore(-10) .spacingAfter(10) .build(); const CKSizeRange kSize = {{500, 500}, {500, 500}}; return [component layoutThatFits:kSize parentSize:kSize.max]; }; XCTAssertTrue(areLayoutsEqual(buildComponentTreeAndComputeLayout(NO), buildComponentTreeAndComputeLayout(YES))); } - (void)test_WhenUsingBothChildAndChildren_ChildrenAreAddedInSameOrder { auto const a = CK::ComponentBuilder().build(); auto const b = CK::ComponentBuilder().build(); auto const c = CK::ComponentBuilder().build(); auto const d = CK::ComponentBuilder().build(); auto const e = CK::ComponentBuilder().build(); auto const flexbox = CK::FlexboxComponentBuilder() .viewClass([UIView class]) .child(a) .children({{b}, {c}, {d}}) .child(e) .build(); const CKSizeRange kSize = {{500, 500}, {500, 500}}; auto const layout = [flexbox layoutThatFits:kSize parentSize:kSize.max]; auto components = std::vector<id<CKMountable>>{}; layout.enumerateLayouts([&](const RCLayout &l) { if (![l.component isKindOfClass:[CKFlexboxComponent class]]) { components.push_back(l.component); } }); auto const expected = std::vector<id<CKMountable>>{a, b, c, d, e}; XCTAssert(components == expected); } static BOOL areLayoutsEqual(const RCLayout &left, const RCLayout &right) { if (left.component.class != right.component.class) { return NO; } if (CGSizeEqualToSize(left.size, right.size) == NO || left.children->size() != right.children->size()) { return NO; } for(std::vector<RCLayoutChild>::size_type i = 0; i != left.children->size(); i++) { auto leftChild = left.children->at(i); auto rightChild = right.children->at(i); if (CGPointEqualToPoint(leftChild.position, rightChild.position) == NO) { return NO; } if (areLayoutsEqual(leftChild.layout, rightChild.layout) == NO) { return NO; } } return YES; } @end