blob: 776948aa2676cde57b31389b25bb174a00da6a47 [file] [log] [blame]
// 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