| // Copyright 2017-present the Material Components for iOS authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #import <XCTest/XCTest.h> |
| |
| #import "MaterialButtons.h" |
| #import "MaterialShadowElevations.h" |
| |
| static UIImage *fakeImage(void) { |
| CGSize imageSize = CGSizeMake(24, 24); |
| UIGraphicsBeginImageContext(imageSize); |
| [UIColor.whiteColor setFill]; |
| UIRectFill(CGRectMake(0, 0, imageSize.width, imageSize.height)); |
| UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); |
| UIGraphicsEndImageContext(); |
| return image; |
| } |
| |
| @interface FakeFloatingButton : MDCFloatingButton |
| @property(nonatomic, assign) BOOL intrinsicContentSizeCalled; |
| @property(nonatomic, assign) BOOL invalidateIntrinsicContentSizeCalled; |
| @property(nonatomic, assign) BOOL sizeThatFitsCalled; |
| @property(nonatomic, assign) BOOL layoutSubviewsCalled; |
| |
| - (void)reset; |
| @end |
| |
| @implementation FakeFloatingButton |
| |
| - (void)reset { |
| self.intrinsicContentSizeCalled = NO; |
| self.invalidateIntrinsicContentSizeCalled = NO; |
| self.layoutSubviewsCalled = NO; |
| self.sizeThatFitsCalled = NO; |
| } |
| |
| - (CGSize)intrinsicContentSize { |
| self.intrinsicContentSizeCalled = YES; |
| return [super intrinsicContentSize]; |
| } |
| |
| - (void)invalidateIntrinsicContentSize { |
| self.invalidateIntrinsicContentSizeCalled = YES; |
| [super invalidateIntrinsicContentSize]; |
| } |
| |
| - (CGSize)sizeThatFits:(CGSize)size { |
| self.sizeThatFitsCalled = YES; |
| return [super sizeThatFits:size]; |
| } |
| |
| - (void)layoutSubviews { |
| self.layoutSubviewsCalled = YES; |
| [super layoutSubviews]; |
| } |
| |
| @end |
| |
| @interface FloatingButtonsTests : XCTestCase |
| @end |
| |
| @implementation FloatingButtonsTests |
| |
| #pragma mark - setHitAreaInsets:forShape:inMode: |
| |
| - (void)testDefaultHitAreaInsetsValues { |
| // Given |
| MDCFloatingButton *defaultButtonNormal = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| MDCFloatingButton *defaultButtonExpanded = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| defaultButtonExpanded.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButtonNormal = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| MDCFloatingButton *miniButtonExpanded = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| miniButtonExpanded.mode = MDCFloatingButtonModeExpanded; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, defaultButtonNormal.hitAreaInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, |
| defaultButtonExpanded.hitAreaInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(-4, -4, -4, -4), |
| miniButtonNormal.hitAreaInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, miniButtonExpanded.hitAreaInsets)); |
| } |
| |
| - (void)testSetHitAreaInsetsForShapeInNormalMode { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton setHitAreaInsets:UIEdgeInsetsMake(1, 2, 3, 4) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| defaultButton.mode = MDCFloatingButtonModeNormal; |
| [miniButton setHitAreaInsets:UIEdgeInsetsMake(9, 8, 7, 6) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeNormal]; |
| miniButton.mode = MDCFloatingButtonModeNormal; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(1, 2, 3, 4), |
| defaultButton.hitAreaInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(9, 8, 7, 6), |
| miniButton.hitAreaInsets)); |
| } |
| |
| - (void)testSetHitAreaInsetsForShapeInExpandedMode { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| |
| // When |
| [defaultButton setHitAreaInsets:UIEdgeInsetsMake(1, 2, 3, 4) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| [miniButton setHitAreaInsets:UIEdgeInsetsMake(9, 8, 7, 6) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(1, 2, 3, 4), |
| defaultButton.hitAreaInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(9, 8, 7, 6), |
| miniButton.hitAreaInsets)); |
| } |
| |
| - (void)testChangingHitAreaInsetsWontTriggerResizing { |
| // Given |
| FakeFloatingButton *button = [[FakeFloatingButton alloc] init]; // Default shape |
| [button reset]; |
| |
| // When |
| [button setHitAreaInsets:UIEdgeInsetsMake(17, 21, 25, 29) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| |
| // Then |
| XCTAssertFalse(button.intrinsicContentSizeCalled); |
| XCTAssertFalse(button.invalidateIntrinsicContentSizeCalled); |
| XCTAssertFalse(button.sizeThatFitsCalled); |
| XCTAssertFalse(button.layoutSubviewsCalled); |
| } |
| |
| #pragma mark - setContentEdgeInsets:forShape:inMode: |
| |
| - (void)testDefaultContentEdgeInsetsValues { |
| // Given |
| MDCFloatingButton *defaultButtonNormal = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| MDCFloatingButton *defaultButtonExpanded = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| defaultButtonExpanded.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButtonNormal = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| MDCFloatingButton *miniButtonExpanded = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| miniButtonExpanded.mode = MDCFloatingButtonModeExpanded; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, |
| defaultButtonNormal.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, |
| defaultButtonExpanded.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(8, 8, 8, 8), |
| miniButtonNormal.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, |
| miniButtonExpanded.contentEdgeInsets)); |
| } |
| |
| - (void)testSetContentEdgeInsetsForShapeInNormalMode { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton setContentEdgeInsets:UIEdgeInsetsMake(1, 2, 3, 4) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| defaultButton.mode = MDCFloatingButtonModeNormal; |
| [miniButton setContentEdgeInsets:UIEdgeInsetsMake(9, 8, 7, 6) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeNormal]; |
| miniButton.mode = MDCFloatingButtonModeNormal; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(1, 2, 3, 4), |
| defaultButton.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(9, 8, 7, 6), |
| miniButton.contentEdgeInsets)); |
| } |
| |
| - (void)testSetContentEdgeInsetsForShapeInExpandedMode { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| |
| // When |
| [defaultButton setContentEdgeInsets:UIEdgeInsetsMake(1, 2, 3, 4) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| [miniButton setContentEdgeInsets:UIEdgeInsetsMake(9, 8, 7, 6) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(1, 2, 3, 4), |
| defaultButton.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(9, 8, 7, 6), |
| miniButton.contentEdgeInsets)); |
| } |
| |
| - (void)testSettingContentEdgeInsetsInvalidatesIntrinsicContentSize { |
| // Given |
| FakeFloatingButton *button = [[FakeFloatingButton alloc] init]; // Default shape |
| [button reset]; |
| |
| // When |
| [button setContentEdgeInsets:UIEdgeInsetsMake(9, 8, 7, 7) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| |
| // Then |
| XCTAssertFalse(button.intrinsicContentSizeCalled); |
| XCTAssertTrue(button.invalidateIntrinsicContentSizeCalled); |
| XCTAssertFalse(button.sizeThatFitsCalled); |
| XCTAssertFalse(button.layoutSubviewsCalled); |
| } |
| |
| #pragma mark - setMaximumSize:forShape:inMode: |
| |
| - (void)testDefaultMaximumSizeForShapeInNormalModeSizeToFit { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| [defaultButton setTitle:@"a very long title" forState:UIControlStateNormal]; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| [miniButton setTitle:@"a very long title" forState:UIControlStateNormal]; |
| |
| // When |
| [defaultButton sizeToFit]; |
| [miniButton sizeToFit]; |
| |
| // Then |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.width, [MDCFloatingButton defaultDimension]); |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.height, [MDCFloatingButton defaultDimension]); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.width, [MDCFloatingButton miniDimension]); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.height, [MDCFloatingButton miniDimension]); |
| } |
| |
| - (void)testDefaultMaximumSizeForShapeInExpandedModeSizeToFit { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; |
| [defaultButton setTitle:@"An even longer title that should require more than 328 points to render" |
| forState:UIControlStateNormal]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton sizeToFit]; |
| |
| // Then |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.width, 328); |
| } |
| |
| - (void)testSetMaximumSizeForShapeInModeNormal { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| [defaultButton setTitle:@"a very long title" forState:UIControlStateNormal]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| [miniButton setTitle:@"a very long title" forState:UIControlStateNormal]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton setMaximumSize:CGSizeMake(100, 50) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| defaultButton.mode = MDCFloatingButtonModeNormal; |
| [miniButton setMaximumSize:CGSizeMake(100, 50) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeNormal]; |
| miniButton.mode = MDCFloatingButtonModeNormal; |
| |
| // Then |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.width, 100); |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.height, 50); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.width, 100); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.height, 50); |
| } |
| |
| - (void)testSetMaximumSizeForShapeInModeExpanded { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| [defaultButton setTitle:@"An even longer title that should require more than 328 points to render" |
| forState:UIControlStateNormal]; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| [miniButton setTitle:@"An even longer title that should require more than 328 points to render" |
| forState:UIControlStateNormal]; |
| |
| // When |
| [defaultButton setMaximumSize:CGSizeMake(50, 100) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| [miniButton setMaximumSize:CGSizeMake(50, 100) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // Then |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.width, 50); |
| XCTAssertLessThanOrEqual(defaultButton.bounds.size.height, 100); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.width, 50); |
| XCTAssertLessThanOrEqual(miniButton.bounds.size.height, 100); |
| } |
| |
| - (void)testSettingMaximumSizeInvalidatesIntrinsicContentSize { |
| // Given |
| FakeFloatingButton *button = [[FakeFloatingButton alloc] init]; // Default shape |
| [button reset]; |
| |
| // When |
| [button setMaximumSize:CGSizeMake(7, 7) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| |
| // Then |
| XCTAssertFalse(button.intrinsicContentSizeCalled); |
| XCTAssertTrue(button.invalidateIntrinsicContentSizeCalled); |
| XCTAssertFalse(button.sizeThatFitsCalled); |
| XCTAssertFalse(button.layoutSubviewsCalled); |
| } |
| |
| #pragma mark - setMinimumSize:forShape:inMode: |
| |
| - (void)testDefaultMinimumSizeForShapeInNormalModeSizeToFit { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| |
| // When |
| [defaultButton sizeToFit]; |
| [miniButton sizeToFit]; |
| |
| // Then |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.width, |
| [MDCFloatingButton defaultDimension]); |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.height, |
| [MDCFloatingButton defaultDimension]); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.width, [MDCFloatingButton miniDimension]); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.height, [MDCFloatingButton miniDimension]); |
| } |
| |
| - (void)testDefaultMinimumSizeForShapeInExpandedModeSizeToFit { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton sizeToFit]; |
| |
| // Then |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.height, 48); |
| } |
| |
| - (void)testSetMinimumSizeForShapeInModeNormal { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| |
| // When |
| [defaultButton setMinimumSize:CGSizeMake(100, 50) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| [defaultButton setMaximumSize:CGSizeZero |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| defaultButton.mode = MDCFloatingButtonModeNormal; |
| [defaultButton sizeToFit]; |
| |
| [miniButton setMinimumSize:CGSizeMake(100, 50) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeNormal]; |
| [miniButton setMaximumSize:CGSizeZero |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeNormal]; |
| miniButton.mode = MDCFloatingButtonModeNormal; |
| [miniButton sizeToFit]; |
| |
| // Then |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.width, 100); |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.height, 50); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.width, 100); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.height, 50); |
| } |
| |
| - (void)testSetMinimumSizeForShapeInModeExpanded { |
| // Given |
| MDCFloatingButton *defaultButton = [[MDCFloatingButton alloc] init]; // Default shape |
| MDCFloatingButton *miniButton = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeMini]; |
| |
| // When |
| [defaultButton setMinimumSize:CGSizeMake(50, 100) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| defaultButton.mode = MDCFloatingButtonModeExpanded; |
| [defaultButton sizeToFit]; |
| |
| [miniButton setMinimumSize:CGSizeMake(50, 100) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| miniButton.mode = MDCFloatingButtonModeExpanded; |
| [miniButton sizeToFit]; |
| |
| // Then |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.width, 50); |
| XCTAssertGreaterThanOrEqual(defaultButton.bounds.size.height, 100); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.width, 50); |
| XCTAssertGreaterThanOrEqual(miniButton.bounds.size.height, 100); |
| } |
| |
| - (void)testSettingMinimumSizeInvalidatesIntrinsicContentSize { |
| // Given |
| FakeFloatingButton *button = [[FakeFloatingButton alloc] init]; // Default shape |
| [button reset]; |
| |
| // When |
| [button setMinimumSize:CGSizeMake(12, 12) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeNormal]; |
| |
| // Then |
| XCTAssertFalse(button.intrinsicContentSizeCalled); |
| XCTAssertTrue(button.invalidateIntrinsicContentSizeCalled); |
| XCTAssertFalse(button.layoutSubviewsCalled); |
| XCTAssertFalse(button.layoutSubviewsCalled); |
| } |
| |
| #pragma mark - layoutSubviews |
| |
| - (void)testExpandedLayout { |
| // Given |
| MDCFloatingButton *button = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| button.mode = MDCFloatingButtonModeExpanded; |
| [button setTitle:@"A very long title that can never fit" forState:UIControlStateNormal]; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button setMaximumSize:CGSizeMake(100, 48) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| button.imageTitleSpace = 12; |
| |
| // When |
| [button sizeToFit]; |
| |
| // Then |
| CGRect buttonBounds = CGRectStandardize(button.bounds); |
| CGRect imageFrame = CGRectStandardize(button.imageView.frame); |
| CGRect titleFrame = CGRectStandardize(button.titleLabel.frame); |
| XCTAssertEqualWithAccuracy(imageFrame.origin.x, 16, 1); |
| XCTAssertEqualWithAccuracy(CGRectGetMaxX(titleFrame), buttonBounds.size.width - 24, 1); |
| XCTAssertEqualWithAccuracy(titleFrame.origin.x, |
| CGRectGetMaxX(imageFrame) + button.imageTitleSpace, 1); |
| } |
| |
| - (void)testExpandedLayoutShortTitle { |
| // Given |
| MDCFloatingButton *button = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| button.mode = MDCFloatingButtonModeExpanded; |
| [button setTitle:@"A" forState:UIControlStateNormal]; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button setMaximumSize:CGSizeMake(100, 48) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| button.imageTitleSpace = 12; |
| |
| // When |
| [button sizeToFit]; |
| |
| // Then |
| CGRect buttonBounds = CGRectStandardize(button.bounds); |
| CGRect imageFrame = CGRectStandardize(button.imageView.frame); |
| CGRect titleFrame = CGRectStandardize(button.titleLabel.frame); |
| XCTAssertEqualWithAccuracy(imageFrame.origin.x, 16, 1); |
| XCTAssertEqualWithAccuracy(CGRectGetMaxX(titleFrame), buttonBounds.size.width - 24, 1); |
| XCTAssertEqualWithAccuracy(titleFrame.origin.x, |
| CGRectGetMaxX(imageFrame) + button.imageTitleSpace, 1); |
| } |
| |
| - (void)testExpandedLayoutWithImageLocationTrailing { |
| // Given |
| MDCFloatingButton *button = |
| [[MDCFloatingButton alloc] initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault]; |
| button.mode = MDCFloatingButtonModeExpanded; |
| button.imageLocation = MDCFloatingButtonImageLocationTrailing; |
| [button setTitle:@"A very long title that can never fit" forState:UIControlStateNormal]; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button setMaximumSize:CGSizeMake(100, 48) |
| forShape:MDCFloatingButtonShapeDefault |
| inMode:MDCFloatingButtonModeExpanded]; |
| button.imageTitleSpace = 12; |
| |
| // When |
| [button sizeToFit]; |
| |
| // Then |
| CGRect imageFrame = button.imageView.frame; |
| CGRect titleFrame = button.titleLabel.frame; |
| XCTAssertEqualWithAccuracy(CGRectGetMaxX(imageFrame), CGRectGetWidth(button.bounds)-16, 1); |
| XCTAssertEqualWithAccuracy(CGRectGetMinX(titleFrame), 24, 1); |
| XCTAssertEqualWithAccuracy(CGRectGetMinX(imageFrame), |
| CGRectGetMaxX(titleFrame) + button.imageTitleSpace, 1); |
| } |
| |
| - (void)testExpandedLayoutWithNonZeroContentEdgeInsets { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] initWithFrame:CGRectZero |
| shape:MDCFloatingButtonShapeMini]; |
| button.mode = MDCFloatingButtonModeExpanded; |
| [button setTitle:@"A very long title that can never fit" forState:UIControlStateNormal]; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button setMinimumSize:CGSizeMake(100, 48) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| [button setMaximumSize:CGSizeMake(100, 48) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| [button setContentEdgeInsets:UIEdgeInsetsMake(4, -4, 8, -8) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| |
| // When |
| [button sizeToFit]; |
| |
| // Then |
| CGRect buttonBounds = CGRectStandardize(button.bounds); |
| CGRect imageFrame = CGRectStandardize(button.imageView.frame); |
| CGRect titleFrame = CGRectStandardize(button.titleLabel.frame); |
| XCTAssertEqualWithAccuracy(imageFrame.origin.x, 12, 1); |
| XCTAssertEqualWithAccuracy(CGRectGetMaxX(titleFrame), buttonBounds.size.width - 16, 1); |
| XCTAssertEqualWithAccuracy(titleFrame.origin.x, |
| CGRectGetMaxX(imageFrame) + button.imageTitleSpace, 1); |
| } |
| |
| - (void)testExpandedLayoutBehaviorIsIdempotent { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] init]; // Default shape |
| button.mode = MDCFloatingButtonModeExpanded; |
| [button setTitle:@"Title" forState:UIControlStateNormal]; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button sizeToFit]; |
| CGRect originalImageFrame = button.imageView.frame; |
| CGRect originalTitleFrame = button.titleLabel.frame; |
| |
| // When |
| [button layoutIfNeeded]; |
| [button sizeToFit]; |
| [button layoutIfNeeded]; |
| [button layoutIfNeeded]; |
| |
| // Then |
| XCTAssertTrue(CGRectEqualToRect(originalImageFrame, button.imageView.frame), |
| @"Image frames are not equal.\nExpected: %@\nReceived: %@", |
| NSStringFromCGRect(originalImageFrame), NSStringFromCGRect(button.imageView.frame)); |
| XCTAssertTrue(CGRectEqualToRect(originalTitleFrame, button.titleLabel.frame), |
| @"Title frames are not equal.\nExpected: %@\nReceived: %@", |
| NSStringFromCGRect(originalTitleFrame), |
| NSStringFromCGRect(button.titleLabel.frame)); |
| |
| } |
| |
| #pragma mark - Encoding/decoding |
| |
| - (void)testEncoding { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] initWithFrame:CGRectMake(1, 2, 3, 4) |
| shape:MDCFloatingButtonShapeMini]; |
| button.mode = MDCFloatingButtonModeExpanded; |
| button.imageLocation = MDCFloatingButtonImageLocationTrailing; |
| [button setImage:fakeImage() forState:UIControlStateNormal]; |
| [button setTitle:@"Title" forState:UIControlStateNormal]; |
| [button setMinimumSize:CGSizeMake(75, 37) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| [button setMaximumSize:CGSizeMake(99, 65) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| [button setContentEdgeInsets:UIEdgeInsetsMake(5, 3, 1, 7) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| [button setHitAreaInsets:UIEdgeInsetsMake(8, 6, 4, 2) |
| forShape:MDCFloatingButtonShapeMini |
| inMode:MDCFloatingButtonModeExpanded]; |
| |
| // When |
| NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:button]; |
| MDCFloatingButton *unarchivedButton = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData]; |
| [button sizeToFit]; |
| [button layoutIfNeeded]; |
| [unarchivedButton sizeToFit]; |
| [unarchivedButton layoutIfNeeded]; |
| |
| // Then |
| XCTAssertTrue(CGRectEqualToRect(button.bounds, unarchivedButton.bounds), |
| @"Unarchived bounds did not match original button's bounds.\nExpected: %@\n" |
| "Received: %@",NSStringFromCGRect(button.bounds), |
| NSStringFromCGRect(unarchivedButton.bounds)); |
| XCTAssertEqual(button.mode, unarchivedButton.mode); |
| XCTAssertEqual(button.imageLocation, unarchivedButton.imageLocation); |
| XCTAssertEqualObjects(button.currentTitle, unarchivedButton.currentTitle); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(button.contentEdgeInsets, |
| unarchivedButton.contentEdgeInsets)); |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(button.hitAreaInsets, |
| unarchivedButton.hitAreaInsets)); |
| XCTAssertTrue(CGSizeEqualToSize(button.minimumSize, unarchivedButton.minimumSize)); |
| XCTAssertTrue(CGSizeEqualToSize(button.maximumSize, unarchivedButton.maximumSize)); |
| } |
| |
| - (void)testShapeMigrationFromLargeIcon { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] initWithFrame:CGRectZero |
| shape:MDCFloatingButtonShapeMini]; |
| [button setValue:@(2) forKey:@"shape"]; |
| [button sizeToFit]; |
| |
| // When |
| NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:button]; |
| MDCFloatingButton *unarchivedButton = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData]; |
| [unarchivedButton sizeToFit]; |
| |
| // Then |
| CGFloat dimension = [MDCFloatingButton defaultDimension]; |
| XCTAssertTrue(CGRectEqualToRect(unarchivedButton.bounds, CGRectMake(0, 0, dimension, dimension))); |
| XCTAssertEqualObjects([unarchivedButton valueForKey:@"shape"], @(0)); |
| } |
| |
| - (void)testDefaultElevationsForState { |
| // Given |
| MDCFloatingButton *button = [MDCFloatingButton appearance]; |
| |
| // Then |
| XCTAssertEqual([button elevationForState:UIControlStateNormal], MDCShadowElevationFABResting); |
| XCTAssertEqual([button elevationForState:UIControlStateHighlighted], |
| MDCShadowElevationFABPressed); |
| XCTAssertEqual([button elevationForState:UIControlStateDisabled], MDCShadowElevationNone); |
| } |
| |
| #pragma mark - Animations |
| |
| - (void)testCollapseExpandRestoresIdentityTransform { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] init]; |
| CGAffineTransform transform = CGAffineTransformIdentity; |
| button.transform = transform; |
| |
| // When |
| [button collapse:NO completion:nil]; |
| [button expand:NO completion:nil]; |
| |
| // Then |
| XCTAssertTrue( |
| CGAffineTransformEqualToTransform(button.transform, transform), |
| @"Collapse and expand did not restore the original transform.\nExpected: %@\nReceived: %@", |
| NSStringFromCGAffineTransform(transform), NSStringFromCGAffineTransform(button.transform)); |
| } |
| |
| - (void)testCollapseExpandRestoresTransform { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] init]; |
| CGAffineTransform transform = CGAffineTransformRotate(button.transform, (CGFloat)M_PI_2); |
| button.transform = transform; |
| |
| // When |
| [button collapse:NO completion:nil]; |
| [button expand:NO completion:nil]; |
| |
| // Then |
| XCTAssertTrue( |
| CGAffineTransformEqualToTransform(button.transform, transform), |
| @"Collapse and expand did not restore the original transform.\nExpected: %@\nReceived: %@", |
| NSStringFromCGAffineTransform(transform), NSStringFromCGAffineTransform(button.transform)); |
| } |
| |
| // This test is flaky. |
| // <unknown>:0: error: -[FloatingButtonsTests testCollapseExpandAnimatedRestoresTransform] : Asynchronous wait failed: Exceeded timeout of 1 seconds, with unfulfilled expectations: "Expand animation complete". |
| - (void)disabled_testCollapseExpandAnimatedRestoresTransform { |
| // Given |
| MDCFloatingButton *button = [[MDCFloatingButton alloc] init]; |
| CGAffineTransform transform = CGAffineTransformMakeTranslation(10, (CGFloat)-77.1); |
| button.transform = transform; |
| XCTestExpectation *expectation = [self expectationWithDescription:@"Collapse animation complete"]; |
| |
| // When |
| [button collapse:YES |
| completion:^{ |
| [expectation fulfill]; |
| }]; |
| |
| // Then |
| // Collapse should take around 200ms in total |
| [self waitForExpectationsWithTimeout:1 handler:nil]; |
| |
| XCTAssertFalse(CGAffineTransformEqualToTransform(button.transform, transform), |
| @"Collapse did not modify the original transform.\nOriginal: %@\nReceived: %@", |
| NSStringFromCGAffineTransform(transform), |
| NSStringFromCGAffineTransform(button.transform)); |
| |
| expectation = [self expectationWithDescription:@"Expand animation complete"]; |
| |
| [button expand:YES |
| completion:^{ |
| [expectation fulfill]; |
| }]; |
| |
| // Expand should take around 200ms in total |
| [self waitForExpectationsWithTimeout:1 handler:nil]; |
| |
| XCTAssertTrue( |
| CGAffineTransformEqualToTransform(button.transform, transform), |
| @"Collapse and expand did not restore the original transform.\nExpected: %@\nReceived: %@", |
| NSStringFromCGAffineTransform(transform), NSStringFromCGAffineTransform(button.transform)); |
| } |
| |
| #pragma mark - Sizing methods |
| |
| - (void)testSizeThatFits { |
| // Given |
| MDCFloatingButton *miniButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeMini]; |
| MDCFloatingButton *defaultButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeDefault]; |
| |
| // When |
| CGSize miniSize = [miniButton sizeThatFits:CGSizeZero]; |
| CGSize defaultSize = [defaultButton sizeThatFits:CGSizeZero]; |
| |
| // Then |
| CGFloat miniDimension = [MDCFloatingButton miniDimension]; |
| CGFloat defaultDimension = [MDCFloatingButton defaultDimension]; |
| XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(miniDimension, miniDimension), miniSize)); |
| XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(defaultDimension, defaultDimension), defaultSize)); |
| } |
| |
| - (void)testIntrinsicContentSize { |
| // Given |
| MDCFloatingButton *miniButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeMini]; |
| MDCFloatingButton *defaultButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeDefault]; |
| |
| // When |
| CGSize miniSize = [miniButton intrinsicContentSize]; |
| CGSize defaultSize = [defaultButton intrinsicContentSize]; |
| |
| // Then |
| CGFloat miniDimension = [MDCFloatingButton miniDimension]; |
| CGFloat defaultDimension = [MDCFloatingButton defaultDimension]; |
| XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(miniDimension, miniDimension), miniSize)); |
| XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(defaultDimension, defaultDimension), defaultSize)); |
| } |
| |
| - (void)testHitAreaInsetsSurviveSizeThatFits { |
| // Given |
| MDCFloatingButton *defaultButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeDefault]; |
| MDCFloatingButton *miniButton = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeMini]; |
| |
| // When |
| [defaultButton sizeThatFits:CGSizeZero]; |
| [miniButton sizeThatFits:CGSizeZero]; |
| |
| // Then |
| XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, defaultButton.hitAreaInsets)); |
| XCTAssertTrue( |
| UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(-4, -4, -4, -4), miniButton.hitAreaInsets)); |
| } |
| |
| - (void)testCornerRadius { |
| // Given |
| MDCFloatingButton *button = |
| [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeDefault]; |
| [button sizeToFit]; |
| [button layoutIfNeeded]; |
| |
| // Then |
| XCTAssertNotEqualWithAccuracy(CGRectGetHeight(button.bounds), 0, 0.0001); |
| XCTAssertEqualWithAccuracy(button.layer.cornerRadius, CGRectGetHeight(button.bounds) / 2, 0.0001); |
| } |
| |
| @end |