mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-04 19:49:34 +08:00
The new API allows the floating button to animate changes between normal and expanded states. PiperOrigin-RevId: 305703859
351 lines
10 KiB
Objective-C
351 lines
10 KiB
Objective-C
// 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
|