mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-20 08:27:32 +08:00
Removes the need to copy-paste stanzas from other files anymore as we'll rely on #4478 to generate the correct stanza for us instead. This was an automated change generated by running a find-and-replace regular expression: ``` /\* Copyright ([0-9]+)-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\. \*/ ``` ``` /\* Copyright ([0-9]+)-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\. \*/ ``` ``` /\* Copyright ([0-9]+)-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\. \*/ ``` ``` // Copyright $1-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. ```
825 lines
34 KiB
Objective-C
825 lines
34 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));
|
|
}
|
|
|
|
// 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
|