blob: cdfc85e270782af15451c4bf387769b57abd3eae [file] [log] [blame] [edit]
// Copyright 2019-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 <CoreGraphics/CoreGraphics.h>
#import <XCTest/XCTest.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprivate-header"
#import "MDCRippleLayer.h"
#import "MDCRippleLayerDelegate.h"
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Fake classes
@interface FakeMDCRippleLayer : MDCRippleLayer
@property(nonatomic, strong) NSMutableArray *addedAnimations;
@end
@implementation FakeMDCRippleLayer
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key {
if (!self.addedAnimations) {
self.addedAnimations = [NSMutableArray array];
}
[self.addedAnimations addObject:anim];
[super addAnimation:anim forKey:key];
}
@end
@interface FakeMDCRippleLayerAnimationDelegate : NSObject <MDCRippleLayerDelegate>
@property(nonatomic, strong) MDCRippleLayer *rippleLayer;
@property(nonatomic, assign) BOOL rippleTouchDownDidBegin;
@property(nonatomic, assign) BOOL rippleTouchDownDidEnd;
@property(nonatomic, assign) BOOL rippleTouchUpDidBegin;
@property(nonatomic, assign) BOOL rippleTouchUpDidEnd;
@end
@implementation FakeMDCRippleLayerAnimationDelegate
- (void)rippleLayerTouchDownAnimationDidBegin:(nonnull MDCRippleLayer *)rippleLayer {
_rippleTouchDownDidBegin = YES;
}
- (void)rippleLayerTouchDownAnimationDidEnd:(nonnull MDCRippleLayer *)rippleLayer {
_rippleTouchDownDidEnd = YES;
}
- (void)rippleLayerTouchUpAnimationDidBegin:(nonnull MDCRippleLayer *)rippleLayer {
_rippleTouchUpDidBegin = YES;
}
- (void)rippleLayerTouchUpAnimationDidEnd:(nonnull MDCRippleLayer *)rippleLayer {
_rippleTouchUpDidEnd = YES;
}
@end
#pragma mark - Tests
@interface MDCRippleLayerTests : XCTestCase
@end
@implementation MDCRippleLayerTests
- (void)testInit {
// Given
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
// Then
XCTAssertNil(rippleLayer.delegate);
XCTAssertEqual(rippleLayer.maximumRadius, 0);
XCTAssertFalse(rippleLayer.isStartAnimationActive);
XCTAssertEqualWithAccuracy(rippleLayer.rippleTouchDownStartTime, 0, 0.0001);
}
- (void)testLayerTouchDownDidBeginDelegate {
// Given
FakeMDCRippleLayerAnimationDelegate *delegate =
[[FakeMDCRippleLayerAnimationDelegate alloc] init];
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
rippleLayer.rippleLayerDelegate = delegate;
delegate.rippleLayer = rippleLayer;
// When
[rippleLayer startRippleAtPoint:CGPointMake(0, 0) animated:YES completion:nil];
// Then
XCTAssertTrue(delegate.rippleTouchDownDidBegin);
}
- (void)testLayerTouchDownDidEndDelegate {
// Given
FakeMDCRippleLayerAnimationDelegate *delegate =
[[FakeMDCRippleLayerAnimationDelegate alloc] init];
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
rippleLayer.rippleLayerDelegate = delegate;
delegate.rippleLayer = rippleLayer;
XCTestExpectation *expectation = [self expectationWithDescription:@"completed"];
// When
[rippleLayer startRippleAtPoint:CGPointMake(0, 0)
animated:YES
completion:^{
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:3 handler:nil];
// Then
XCTAssertTrue(delegate.rippleTouchDownDidEnd);
}
- (void)testLayerTouchUpDidBeginDelegate {
// Given
FakeMDCRippleLayerAnimationDelegate *delegate =
[[FakeMDCRippleLayerAnimationDelegate alloc] init];
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
rippleLayer.rippleLayerDelegate = delegate;
delegate.rippleLayer = rippleLayer;
// When
[rippleLayer endRippleAnimated:YES completion:nil];
// Then
XCTAssertTrue(delegate.rippleTouchUpDidBegin);
}
- (void)testLayerTouchUpDidEndDelegate {
// Given
FakeMDCRippleLayerAnimationDelegate *delegate =
[[FakeMDCRippleLayerAnimationDelegate alloc] init];
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
rippleLayer.rippleLayerDelegate = delegate;
delegate.rippleLayer = rippleLayer;
XCTestExpectation *expectation = [self expectationWithDescription:@"completed"];
// When
[rippleLayer endRippleAnimated:YES
completion:^{
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:3 handler:nil];
// Then
XCTAssertTrue(delegate.rippleTouchUpDidEnd);
}
- (void)testAnimationTimingInSpeedScaledLayer {
// Given
FakeMDCRippleLayer *rippleLayer = [[FakeMDCRippleLayer alloc] init];
rippleLayer.bounds = CGRectMake(0, 0, 10, 10);
rippleLayer.speed = (float)0.1;
CGFloat expectedRippleFadeoutDelay = (CGFloat)0.150;
// When
[rippleLayer startRippleAtPoint:CGPointMake(0, 0) animated:YES completion:nil];
NSTimeInterval startTime = rippleLayer.rippleTouchDownStartTime;
[rippleLayer endRippleAnimated:YES completion:nil];
// Then
XCTAssertEqual(rippleLayer.addedAnimations.count, 2U);
CAAnimation *animation = rippleLayer.addedAnimations.lastObject;
if (animation) {
startTime = [rippleLayer convertTime:startTime + expectedRippleFadeoutDelay fromLayer:nil];
XCTAssertEqualWithAccuracy(animation.beginTime, startTime, 0.010);
}
NSLog(@"\nA: %.12f\nE: %.12f\n", animation.beginTime, startTime);
}
- (void)testStartRippleAnimationCorrectness {
// Given
FakeMDCRippleLayer *rippleLayer = [[FakeMDCRippleLayer alloc] init];
rippleLayer.bounds = CGRectMake(0, 0, 100, 100);
CGPoint point = CGPointMake(10, 10);
// When
[rippleLayer startRippleAtPoint:point animated:YES completion:nil];
// Then
CAAnimationGroup *group = (CAAnimationGroup *)rippleLayer.addedAnimations.firstObject;
XCTAssertEqual(group.animations.count, 3U);
NSInteger animationsCount = 0;
for (CAAnimation *animation in group.animations) {
XCTAssertTrue(animation.removedOnCompletion);
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
if ([basicAnimation.keyPath isEqualToString:@"opacity"]) {
animationsCount += 1;
XCTAssertEqualObjects(@1, basicAnimation.toValue);
XCTAssertEqualObjects(@0, basicAnimation.fromValue);
XCTAssertEqualObjects([CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
basicAnimation.timingFunction);
} else if ([basicAnimation.keyPath isEqualToString:@"transform.scale"]) {
animationsCount += 1;
XCTAssertEqualObjects(@1, basicAnimation.toValue);
XCTAssertEqualWithAccuracy(
(CGFloat)0.37169, (CGFloat)((NSNumber *)basicAnimation.fromValue).doubleValue, 0.01);
}
} else if ([animation isKindOfClass:[CAKeyframeAnimation class]]) {
animationsCount += 1;
CAKeyframeAnimation *keyFrameAnimation = (CAKeyframeAnimation *)animation;
XCTAssertTrue(CGPointEqualToPoint(point, CGPathGetCurrentPoint(keyFrameAnimation.path)));
}
}
XCTAssertEqual(animationsCount, 3);
}
- (void)testEndRippleAnimationCorrectness {
// Given
FakeMDCRippleLayer *rippleLayer = [[FakeMDCRippleLayer alloc] init];
// When
[rippleLayer endRippleAnimated:YES completion:nil];
// Then
XCTAssertTrue([rippleLayer.addedAnimations.firstObject isKindOfClass:[CABasicAnimation class]]);
CABasicAnimation *basicAnimation = (CABasicAnimation *)rippleLayer.addedAnimations.firstObject;
XCTAssertEqual(rippleLayer.addedAnimations.count, 1U);
XCTAssertEqualObjects(@"opacity", basicAnimation.keyPath);
XCTAssertEqualObjects(@0, basicAnimation.toValue);
XCTAssertEqualObjects(@1, basicAnimation.fromValue);
XCTAssertEqualObjects([CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
basicAnimation.timingFunction);
XCTAssertEqualWithAccuracy(basicAnimation.duration, (CGFloat)0.15, 0.0001);
}
- (void)testFadeInRippleAnimationCorrectness {
// Given
FakeMDCRippleLayer *rippleLayer = [[FakeMDCRippleLayer alloc] init];
// When
[rippleLayer fadeInRippleAnimated:YES completion:nil];
// Then
XCTAssertTrue([rippleLayer.addedAnimations.firstObject isKindOfClass:[CABasicAnimation class]]);
CABasicAnimation *basicAnimation = (CABasicAnimation *)rippleLayer.addedAnimations.firstObject;
XCTAssertEqual(rippleLayer.addedAnimations.count, 1U);
XCTAssertEqualObjects(@"opacity", basicAnimation.keyPath);
XCTAssertEqualObjects(@1, basicAnimation.toValue);
XCTAssertEqualObjects(@0, basicAnimation.fromValue);
XCTAssertEqualObjects([CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
basicAnimation.timingFunction);
XCTAssertEqualWithAccuracy(basicAnimation.duration, (CGFloat)0.075, 0.0001);
}
- (void)testFadeOutRippleAnimationCorrectness {
// Given
FakeMDCRippleLayer *rippleLayer = [[FakeMDCRippleLayer alloc] init];
// When
[rippleLayer fadeOutRippleAnimated:YES completion:nil];
// Then
XCTAssertTrue([rippleLayer.addedAnimations.firstObject isKindOfClass:[CABasicAnimation class]]);
CABasicAnimation *basicAnimation = (CABasicAnimation *)rippleLayer.addedAnimations.firstObject;
XCTAssertEqual(rippleLayer.addedAnimations.count, 1U);
XCTAssertEqualObjects(@"opacity", basicAnimation.keyPath);
XCTAssertEqualObjects(@0, basicAnimation.toValue);
XCTAssertEqualObjects(@1, basicAnimation.fromValue);
XCTAssertEqualObjects([CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
basicAnimation.timingFunction);
XCTAssertEqualWithAccuracy(basicAnimation.duration, (CGFloat)0.075, 0.0001);
}
/** Test that setting @c maximumRadius correctly sets the property on @c MDCRippleLayer. */
- (void)testRippleRadiusSetToCustomValue {
// Given
MDCRippleLayer *rippleLayer = [[MDCRippleLayer alloc] init];
CGFloat fakeRadius = 25;
// When
rippleLayer.maximumRadius = fakeRadius;
// Then
XCTAssertEqual(rippleLayer.maximumRadius, fakeRadius);
}
@end
NS_ASSUME_NONNULL_END