blob: e60630896207f322840d1e9fc095d4ce48f786c6 [file] [log] [blame]
// Copyright 2020-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 "MaterialButtons.h"
#import "MDCFloatingButtonModeAnimator.h"
#import "MDCFloatingButtonModeAnimatorDelegate.h"
#import <XCTest/XCTest.h>
@interface MDCFloatingButtonModeAnimatorTests : XCTestCase <MDCFloatingButtonModeAnimatorDelegate>
@property(nonatomic, strong) MDCFloatingButtonModeAnimator *animator;
@property(nonatomic, strong) UILabel *titleLabel;
@property(nonatomic, strong) UIView *containerView;
@property(nonatomic, strong) UIView *buttonView;
@property(nonatomic) BOOL delegateDidAskToCommitLayoutChanges;
@end
static const CGRect kNormalFrame = (CGRect){{0, 0}, {100, 50}};
static const CGRect kExpandedFrame = (CGRect){{0, 0}, {200, 40}};
@implementation MDCFloatingButtonModeAnimatorTests
- (void)setUp {
[super setUp];
self.containerView = [[UIView alloc] init];
self.buttonView = [[UIView alloc] init];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Some text";
self.buttonView.frame = kNormalFrame;
self.containerView.frame = self.buttonView.bounds;
self.containerView.autoresizingMask =
(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
[self.buttonView addSubview:self.containerView];
[self.titleLabel sizeToFit];
self.titleLabel.center = CGPointMake(CGRectGetMidX(self.containerView.bounds),
CGRectGetMidY(self.containerView.bounds));
self.titleLabel.autoresizingMask =
(UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin);
[self.containerView addSubview:self.titleLabel];
self.animator = [[MDCFloatingButtonModeAnimator alloc] initWithTitleLabel:self.titleLabel
titleLabelContainerView:self.containerView];
self.delegateDidAskToCommitLayoutChanges = NO;
}
- (void)tearDown {
self.titleLabel = nil;
self.containerView = nil;
self.buttonView = nil;
self.animator = nil;
self.delegateDidAskToCommitLayoutChanges = NO;
[super tearDown];
}
#pragma mark - MDCFloatingButtonModeAnimatorDelegate
- (void)floatingButtonModeAnimatorCommitLayoutChanges:(MDCFloatingButtonModeAnimator *)modeAnimator
mode:(MDCFloatingButtonMode)mode {
self.delegateDidAskToCommitLayoutChanges = YES;
[self commitMode:mode];
}
#pragma mark - Defaults
- (void)testDefaults {
XCTAssertNil(self.animator.delegate);
XCTAssertFalse(self.containerView.clipsToBounds);
XCTAssertEqual([[self.titleLabel.layer animationKeys] count], 0);
XCTAssertEqual([self.containerView.subviews count], 1);
}
#pragma mark - Container
// The only effect the animator has on the container is to toggle clipsToBounds while animating.
#pragma mark Non-animated
- (void)testImmediateToNormalContainerDoesNotClip {
// When
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:NO
animateAlongside:nil
completion:nil];
// Then
XCTAssertFalse(self.containerView.clipsToBounds);
}
- (void)testImmediateToExpandedContainerDoesNotClip {
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:NO
animateAlongside:nil
completion:nil];
// Then
XCTAssertFalse(self.containerView.clipsToBounds);
}
#pragma mark Animated
- (void)testAnimateToNormalContainerDoesClip {
// When
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertTrue(self.containerView.clipsToBounds);
}
- (void)testAnimateToExpandedContainerDoesClip {
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertTrue(self.containerView.clipsToBounds);
}
#pragma mark - Title label
#pragma mark Non-animated
- (void)testImmediateToExpandedDoesNotAskToCommitLayoutChanges {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:NO
animateAlongside:nil
completion:nil];
// Then
XCTAssertFalse(self.delegateDidAskToCommitLayoutChanges);
}
- (void)testImmediateToNormalDoesNotAskToCommitLayoutChanges {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:NO
animateAlongside:nil
completion:nil];
// Then
XCTAssertFalse(self.delegateDidAskToCommitLayoutChanges);
}
#pragma mark Animated
- (void)testAnimateToExpandedAsksToCommitLayoutChanges {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertTrue(self.delegateDidAskToCommitLayoutChanges);
}
- (void)testAnimateToNormalAsksToCommitLayoutChanges {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertTrue(self.delegateDidAskToCommitLayoutChanges);
}
- (void)testAnimateFromNormalToExpandedGeneratesLabelAnimations {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:YES
animateAlongside:nil
completion:nil];
// Then
NSSet *animatedKeyPaths = [self animatedKeyPathsForLayer:self.titleLabel.layer];
NSSet *expectedKeyPaths = [NSSet setWithObjects:@"position.y", @"opacity", nil];
XCTAssertEqualObjects(animatedKeyPaths, expectedKeyPaths);
}
- (void)testAnimateFromNormalToNormalDoesNotGenerateLabelAnimations {
// Given
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertEqual([[self.titleLabel.layer animationKeys] count], 0);
}
// Does not generate animations because it animates a replica view instead.
- (void)testAnimateFromExpandedToNormalDoesNotGenerateLabelAnimations {
// Given
[self commitMode:MDCFloatingButtonModeExpanded];
self.animator.delegate = self;
// When
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:YES
animateAlongside:nil
completion:nil];
// Then
XCTAssertEqual([[self.titleLabel.layer animationKeys] count], 0);
}
#pragma mark - Blocks
#pragma mark Non-animated
- (void)testImmediateToExpandedInvokesAnimateAlongsideAndCompletion {
// When
__block BOOL didInvokeAnimateAlongside = NO;
__block BOOL didInvokeCompletion = NO;
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:NO
animateAlongside:^{
didInvokeAnimateAlongside = YES;
}
completion:^(BOOL finished) {
didInvokeCompletion = YES;
}];
// Then
XCTAssertTrue(didInvokeAnimateAlongside);
XCTAssertTrue(didInvokeCompletion);
}
- (void)testImmediateToNormalInvokesAnimateAlongsideAndCompletion {
// When
__block BOOL didInvokeAnimateAlongside = NO;
__block BOOL didInvokeCompletion = NO;
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:NO
animateAlongside:^{
didInvokeAnimateAlongside = YES;
}
completion:^(BOOL finished) {
didInvokeCompletion = YES;
}];
// Then
XCTAssertTrue(didInvokeAnimateAlongside);
XCTAssertTrue(didInvokeCompletion);
}
#pragma mark Animated
- (void)testAnimateToExpandedInvokesAnimateAlongsideButNotCompletion {
// When
__block BOOL didInvokeAnimateAlongside = NO;
__block BOOL didInvokeCompletion = NO;
[self.animator modeDidChange:MDCFloatingButtonModeExpanded
animated:YES
animateAlongside:^{
didInvokeAnimateAlongside = YES;
}
completion:^(BOOL finished) {
didInvokeCompletion = YES;
}];
// Then
XCTAssertTrue(didInvokeAnimateAlongside);
XCTAssertFalse(didInvokeCompletion);
}
- (void)testAnimateToNormalInvokesAnimateAlongsideButNotCompletion {
// When
__block BOOL didInvokeAnimateAlongside = NO;
__block BOOL didInvokeCompletion = NO;
[self.animator modeDidChange:MDCFloatingButtonModeNormal
animated:YES
animateAlongside:^{
didInvokeAnimateAlongside = YES;
}
completion:^(BOOL finished) {
didInvokeCompletion = YES;
}];
// Then
XCTAssertTrue(didInvokeAnimateAlongside);
XCTAssertFalse(didInvokeCompletion);
}
#pragma mark - Helper methods
- (NSSet *)animatedKeyPathsForLayer:(CALayer *)layer {
NSMutableSet *animatedKeyPaths = [NSMutableSet set];
NSArray *animationKeys = [layer animationKeys];
for (NSString *key in animationKeys) {
CAAnimation *animation = [layer animationForKey:key];
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
[animatedKeyPaths addObject:basicAnimation.keyPath];
}
}
return animatedKeyPaths;
}
- (void)commitMode:(MDCFloatingButtonMode)mode {
if (mode == MDCFloatingButtonModeNormal) {
self.buttonView.frame = kNormalFrame;
} else if (mode == MDCFloatingButtonModeExpanded) {
self.buttonView.frame = kExpandedFrame;
}
[self.buttonView layoutIfNeeded];
}
@end