mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-20 08:27:32 +08:00
For shorter text, the extended FAB looks better without a minimum and with a leading alignment of text. Closes #2731
825 lines
33 KiB
Objective-C
825 lines
33 KiB
Objective-C
/*
|
|
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));
|
|
}
|
|
|
|
- (void)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
|