You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

891 lines
35 KiB

/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <QuartzCore/QuartzCore.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <pop/POP.h>
#import <pop/POPAnimationPrivate.h>
#import <pop/POPAnimatorPrivate.h>
#import "POPAnimatable.h"
#import "POPAnimationRuntime.h"
#import "POPAnimationTestsExtras.h"
#import "POPBaseAnimationTests.h"
#import "POPCGUtils.h"
#import "POPAnimationInternal.h"
using namespace POP;
@interface POPAnimation (TestExtensions)
@property (strong, nonatomic) NSString *sampleKey;
@end
@implementation POPAnimation (TestExtensions)
- (NSString *)sampleKey { return [self valueForUndefinedKey:@"sampleKey"]; }
- (void)setSampleKey:(NSString *)aValue { [self setValue:aValue forUndefinedKey:@"sampleKey"];}
@end
@interface POPAnimationTests : POPBaseAnimationTests
@end
@implementation POPAnimationTests
- (void)testOrneryAbstractClasses
{
XCTAssertThrows([[POPAnimation alloc] init], @"should not be able to instiate abstract class");
XCTAssertThrows([[POPPropertyAnimation alloc] init], @"should not be able to instiate abstract class");
}
- (void)testWithPropertyNamedConstruction
{
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:kPOPLayerBounds];
XCTAssertTrue(anim.property == prop, @"expected:%@ actual:%@", prop, anim.property);
}
- (void)testAdditionRemoval
{
CALayer *layer1 = self.layer1;
CALayer *layer2 = self.layer2;
[layer1 removeAllAnimations];
[layer2 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
[layer1 pop_addAnimation:anim forKey:@"hello"];
NSArray *keys = [layer1 pop_animationKeys];
XCTAssertTrue(1 == keys.count);
XCTAssertTrue([@"hello" isEqualToString:keys.lastObject]);
[layer1 pop_removeAnimationForKey:@"hello"];
XCTAssertTrue(0 == [layer1 pop_animationKeys].count);
[layer1 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"hello"];
[layer1 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"world"];
[layer2 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"hello"];
XCTAssertTrue(2 == [layer1 pop_animationKeys].count);
XCTAssertTrue(1 == [layer2 pop_animationKeys].count);
[layer1 pop_removeAllAnimations];
XCTAssertTrue(0 == [layer1 pop_animationKeys].count);
XCTAssertTrue(1 == [layer2 pop_animationKeys].count);
}
- (void)testStartStopDelegation
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// expect start, stop finished
[[delegate expect] pop_animationDidStart:anim];
[[delegate expect] pop_animationDidStop:anim finished:YES];
anim.delegate = delegate;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
// verify expectations
[delegate verify];
}
- (void)testAnimationValues
{
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
// avoid fractional values; simplify verification
anim.roundingFactor = 1.0;
id layer = [OCMockObject niceMockForClass:[CALayer class]];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.25).cg_point()];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.5).cg_point()];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.75).cg_point()];
[[layer expect] setPosition:[anim.toValue CGPointValue]];
[layer pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.25);
[layer verify];
}
- (void)testNoAutoreverseRepeatCount0
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.repeatCount = 0;
anim.roundingFactor = 1.0;
anim.autoreverses = NO;
NSValue *originalToValue = anim.toValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 2.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKeyPath:@"position"], originalToValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalToValue);
}
- (void)testNoAutoreverseRepeatCount1
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.repeatCount = 1;
anim.roundingFactor = 1.0;
anim.autoreverses = NO;
NSValue *originalToValue = anim.toValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 3.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKeyPath:@"position"], originalToValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalToValue);
}
- (void)testNoAutoreverseRepeatCount4
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.repeatCount = 4;
anim.roundingFactor = 1.0;
anim.autoreverses = NO;
NSValue *originalToValue = anim.toValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 6.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKeyPath:@"position"], originalToValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalToValue);
}
- (void)testAutoreverseRepeatCount0
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.roundingFactor = 1.0;
anim.autoreverses = YES;
anim.repeatCount = 0;
[anim.tracer start];
NSValue *originalFromValue = anim.fromValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 3.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKey:@"position"], originalFromValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalFromValue);
NSArray *autoreversedEvents = [anim.tracer eventsWithType:kPOPAnimationEventAutoreversed];
XCTAssertTrue(1 == autoreversedEvents.count, @"unexpected autoreversed events %@", autoreversedEvents);
anim.autoreverses = NO;
}
- (void)testAutoreverseRepeatCount1
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.roundingFactor = 1.0;
anim.autoreverses = YES;
anim.repeatCount = 1;
[anim.tracer start];
NSValue *originalFromValue = anim.fromValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 3.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKey:@"position"], originalFromValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalFromValue);
NSArray *autoreversedEvents = [anim.tracer eventsWithType:kPOPAnimationEventAutoreversed];
XCTAssertTrue(1 == autoreversedEvents.count, @"unexpected autoreversed events %@", autoreversedEvents);
anim.autoreverses = NO;
}
- (void)testAutoreverseRepeatCount4
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.roundingFactor = 1.0;
anim.autoreverses = YES;
NSInteger repeatCount = 4;
anim.repeatCount = repeatCount;
[anim.tracer start];
NSValue *originalFromValue = anim.fromValue;
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 9.0, 0.25); // animate longer than needed to verify animation has stopped in the appropriate place
XCTAssertEqualObjects([layer1 valueForKey:@"position"], originalFromValue, @"expected equality; value1:%@ value2:%@", [layer1 valueForKey:@"position"], originalFromValue);
NSArray *autoreversedEvents = [anim.tracer eventsWithType:kPOPAnimationEventAutoreversed];
XCTAssertTrue((repeatCount * 2) - 1 == (int)autoreversedEvents.count, @"unexpected autoreversed events %@", autoreversedEvents);
anim.autoreverses = NO;
}
- (void)testReAddition
{
CALayer *layer1 = self.layer1;
CALayer *layer2 = self.layer2;
[layer1 removeAllAnimations];
[layer2 removeAllAnimations];
static NSString *key = @"key";
POPAnimation *anim1, *anim2;
id delegate1, delegate2;
anim1 = FBTestLinearPositionAnimation(self.beginTime);
delegate1 = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// expect start, stop not finished
[[delegate1 expect] pop_animationDidStart:anim1];
[[delegate1 expect] pop_animationDidStop:anim1 finished:NO];
anim1.delegate = delegate1;
[layer1 pop_addAnimation:anim1 forKey:key];
anim2 = FBTestLinearPositionAnimation(self.beginTime);
delegate2 = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// expect start, stop finished
[[delegate2 expect] pop_animationDidStart:anim2];
[[delegate2 expect] pop_animationDidStop:anim2 finished:YES];
anim2.delegate = delegate2;
// add with same key
[layer1 pop_addAnimation:anim2 forKey:key];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
// verify expectations
[delegate1 verify];
[delegate2 verify];
}
- (void)testAnimationDidStartBlock
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// set animation did start block
anim.animationDidStartBlock = ^(POPAnimation *a) {
[delegate pop_animationDidStart:a];
};
[[delegate expect] pop_animationDidStart:anim];
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
[delegate verify];
}
- (void)testAnimationDidReachToValueBlock
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// set animation did reach to value block
anim.animationDidReachToValueBlock = ^(POPAnimation *a) {
[delegate pop_animationDidReachToValue:a];
};
[[delegate expect] pop_animationDidReachToValue:anim];
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
[delegate verify];
}
- (void)testCompletionBlock
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
anim.completionBlock = ^(POPAnimation *a, BOOL finished) {
[delegate pop_animationDidStop:a finished:finished];
};
// test for unfinished completion
[[delegate expect] pop_animationDidStop:anim finished:NO];
[layer1 pop_addAnimation:anim forKey:@"key"];
[layer1 pop_removeAllAnimations];
[delegate verify];
anim = FBTestLinearPositionAnimation(self.beginTime);
delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// set completion block
anim.completionBlock = ^(POPAnimation *a, BOOL finished) {
[delegate pop_animationDidStop:a finished:finished];
};
// test for finished completion
[[delegate expect] pop_animationDidStop:anim finished:YES];
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
[delegate verify];
}
- (void)testAnimationDidApplyBlock
{
CALayer *layer1 = self.layer1;
[layer1 removeAllAnimations];
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
// set animation did apply block
anim.animationDidApplyBlock = ^(POPAnimation *a) {
[delegate pop_animationDidApply:a];
};
[[delegate expect] pop_animationDidApply:anim];
[layer1 pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]);
[delegate verify];
}
- (void)testReuse
{
NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(200, 200)];
CGFloat testProgress = 0.25;
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
anim.fromValue = fromValue;
anim.toValue = toValue;
anim.roundingFactor = 1.0;
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
[[delegate expect] pop_animationDidStart:anim];
[[delegate expect] pop_animationDidStop:anim finished:YES];
anim.delegate = delegate;
id layer = [OCMockObject niceMockForClass:[CALayer class]];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([fromValue CGPointValue]), Vector2r([toValue CGPointValue]), testProgress).cg_point()];
[[layer expect] setPosition:[toValue CGPointValue]];
[layer pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, [NSNumber numberWithFloat:testProgress * anim.duration], [NSNumber numberWithFloat:anim.duration]]);
[layer verify];
[delegate verify];
// new delegate & layer, same animation
delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
[[delegate expect] pop_animationDidStart:anim];
[[delegate expect] pop_animationDidStop:anim finished:YES];
anim.delegate = delegate;
layer = [OCMockObject niceMockForClass:[CALayer class]];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([fromValue CGPointValue]), Vector2r([toValue CGPointValue]), testProgress).cg_point()];
[[layer expect] setPosition:[toValue CGPointValue]];
[layer pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, [NSNumber numberWithFloat:testProgress * anim.duration], [NSNumber numberWithFloat:anim.duration]]);
[layer verify];
[delegate verify];
}
- (void)testCancelBeforeBegin
{
POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime + 100000);
[anim.tracer start];
CALayer *layer = [CALayer layer];
[layer pop_addAnimation:anim forKey:@"key"];
[layer pop_removeAllAnimations];
NSArray *didStartEvents = [anim.tracer eventsWithType:kPOPAnimationEventDidStart];
NSArray *didStopEvents = [anim.tracer eventsWithType:kPOPAnimationEventDidStop];
XCTAssertTrue(1 == didStartEvents.count, @"unexpected start events %@", didStartEvents);
XCTAssertTrue(1 == didStopEvents.count, @"unexpected stop events %@", didStopEvents);
}
- (void)testAddedKeys
{
POPAnimation *anim = FBTestLinearPositionAnimation();
anim.sampleKey = @"value";
XCTAssertEqualObjects(anim.sampleKey, @"value", @"property value read should equal write");
}
- (void)testValueTypeResolution
{
POPSpringAnimation *anim = [POPSpringAnimation animation];
XCTAssertNil(anim.fromValue);
XCTAssertNil(anim.toValue);
XCTAssertNil(anim.velocity);
id pointValue = [NSValue valueWithCGPoint:CGPointMake(1, 2)];
anim.fromValue = pointValue;
anim.toValue = pointValue;
anim.velocity = pointValue;
XCTAssertEqualObjects(anim.fromValue, pointValue, @"property value read should equal write");
XCTAssertEqualObjects(anim.toValue, pointValue, @"property value read should equal write");
XCTAssertEqualObjects(anim.velocity, pointValue, @"property value read should equal write");
POPSpringAnimation *anim2 = [POPSpringAnimation animation];
id rectValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 20, 20)];
anim2.fromValue = rectValue;
anim2.toValue = rectValue;
anim2.velocity = rectValue;
XCTAssertEqualObjects(anim2.fromValue, rectValue, @"property value read should equal write");
XCTAssertEqualObjects(anim2.toValue, rectValue, @"property value read should equal write");
XCTAssertEqualObjects(anim2.velocity, rectValue, @"property value read should equal write");
#if TARGET_OS_IPHONE
POPSpringAnimation *anim3 = [POPSpringAnimation animation];
id edgeInsetsValue = [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(20, 40, 20, 40)];
anim3.fromValue = edgeInsetsValue;
anim3.toValue = edgeInsetsValue;
anim3.velocity = edgeInsetsValue;
XCTAssertEqualObjects(anim3.fromValue, edgeInsetsValue, @"property value read should equal write");
XCTAssertEqualObjects(anim3.toValue, edgeInsetsValue, @"property value read should equal write");
XCTAssertEqualObjects(anim3.velocity, edgeInsetsValue, @"property value read should equal write");
#endif
POPSpringAnimation *anim4 = [POPSpringAnimation animation];
id transformValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
XCTAssertThrows(anim4.fromValue = transformValue, @"should not be able to set %@", transformValue);
}
- (void)testTracer
{
POPAnimatable *circle = [POPAnimatable new];
POPSpringAnimation *anim = [POPSpringAnimation animation];
POPAnimationTracer *tracer = anim.tracer;
XCTAssertNotNil(tracer, @"missing tracer");
[tracer start];
NSNumber *animFromValue = @0.0;
NSNumber *animToValue = @1.0;
NSNumber *animVelocity = @0.1;
float animBounciness = 4.1;
float animSpeed = 13.0;
float animFriction = 123.;
float animMass = 0.9;
float animTension = 401.;
anim.property = self.radiusProperty;
anim.fromValue = animFromValue;
anim.toValue = animToValue;
anim.velocity = animVelocity;
anim.dynamicsFriction = animFriction;
anim.dynamicsMass = animMass;
anim.dynamicsTension = animTension;
anim.springBounciness = animBounciness;
anim.springSpeed = animSpeed;
[circle pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 0.01);
[tracer stop];
NSArray *allEvents = tracer.allEvents;
NSArray *fromEvents = [tracer eventsWithType:kPOPAnimationEventFromValueUpdate];
NSArray *toEvents = [tracer eventsWithType:kPOPAnimationEventToValueUpdate];
NSArray *velocityEvents = [tracer eventsWithType:kPOPAnimationEventVelocityUpdate];
NSArray *bouncinessEvents = [tracer eventsWithType:kPOPAnimationEventBouncinessUpdate];
NSArray *speedEvents = [tracer eventsWithType:kPOPAnimationEventSpeedUpdate];
NSArray *frictionEvents = [tracer eventsWithType:kPOPAnimationEventFrictionUpdate];
NSArray *massEvents = [tracer eventsWithType:kPOPAnimationEventMassUpdate];
NSArray *tensionEvents = [tracer eventsWithType:kPOPAnimationEventTensionUpdate];
NSArray *startEvents = [tracer eventsWithType:kPOPAnimationEventDidStart];
NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop];
NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
// all events
XCTAssertTrue(0 != allEvents.count, @"unexpected allEvents count %@", allEvents);
// from events
XCTAssertTrue(1 == fromEvents.count, @"unexpected fromEvents count %@", fromEvents);
id eventFromValue = [(POPAnimationValueEvent *)fromEvents.lastObject value];
XCTAssertEqualObjects(animFromValue, eventFromValue, @"unexpected eventFromValue; expected:%@ actual:%@", animFromValue, eventFromValue);
// to events
XCTAssertTrue(1 == toEvents.count, @"unexpected toEvents count %@", toEvents);
id eventToValue = [(POPAnimationValueEvent *)toEvents.lastObject value];
XCTAssertEqualObjects(animToValue, eventToValue, @"unexpected eventToValue; expected:%@ actual:%@", animToValue, eventToValue);
// velocity events
XCTAssertTrue(1 == velocityEvents.count, @"unexpected velocityEvents count %@", velocityEvents);
id eventVelocity = [(POPAnimationValueEvent *)velocityEvents.lastObject value];
XCTAssertEqualObjects(animVelocity, eventVelocity, @"unexpected eventVelocity; expected:%@ actual:%@", animVelocity, eventVelocity);
// bounciness events
XCTAssertTrue(1 == bouncinessEvents.count, @"unexpected bouncinessEvents count %@", bouncinessEvents);
id eventBounciness = [(POPAnimationValueEvent *)bouncinessEvents.lastObject value];
XCTAssertEqualObjects(@(animBounciness), eventBounciness, @"unexpected bounciness; expected:%@ actual:%@", @(animBounciness), eventBounciness);
// speed events
XCTAssertTrue(1 == speedEvents.count, @"unexpected speedEvents count %@", speedEvents);
id eventSpeed = [(POPAnimationValueEvent *)speedEvents.lastObject value];
XCTAssertEqualObjects(@(animSpeed), eventSpeed, @"unexpected speed; expected:%@ actual:%@", @(animSpeed), eventSpeed);
// friction events
XCTAssertTrue(1 == frictionEvents.count, @"unexpected frictionEvents count %@", frictionEvents);
id eventFriction = [(POPAnimationValueEvent *)frictionEvents.lastObject value];
XCTAssertEqualObjects(@(animFriction), eventFriction, @"unexpected friction; expected:%@ actual:%@", @(animFriction), eventFriction);
// mass events
XCTAssertTrue(1 == massEvents.count, @"unexpected massEvents count %@", massEvents);
id eventMass = [(POPAnimationValueEvent *)massEvents.lastObject value];
XCTAssertEqualObjects(@(animMass), eventMass, @"unexpected mass; expected:%@ actual:%@", @(animMass), eventMass);
// tension events
XCTAssertTrue(1 == tensionEvents.count, @"unexpected tensionEvents count %@", tensionEvents);
id eventTension = [(POPAnimationValueEvent *)tensionEvents.lastObject value];
XCTAssertEqualObjects(@(animTension), eventTension, @"unexpected tension; expected:%@ actual:%@", @(animTension), eventTension);
// start & stop event
XCTAssertTrue(1 == startEvents.count, @"unexpected startEvents count %@", startEvents);
XCTAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents);
// start before stop
NSUInteger startIdx = [allEvents indexOfObjectIdenticalTo:startEvents.firstObject];
NSUInteger stopIdx = [allEvents indexOfObjectIdenticalTo:stopEvents.firstObject];
XCTAssertTrue(startIdx < stopIdx, @"unexpected start/stop ordering startIdx:%lu stopIdx:%lu", (unsigned long)startIdx, (unsigned long)stopIdx);
// did reach event
XCTAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents);
// did reach after start before stop
NSUInteger didReachIdx = [allEvents indexOfObjectIdenticalTo:didReachEvents.firstObject];
XCTAssertTrue(didReachIdx > startIdx, @"unexpected didReach/start ordering didReachIdx:%lu startIdx:%lu", (unsigned long)didReachIdx, (unsigned long)startIdx);
XCTAssertTrue(didReachIdx < stopIdx, @"unexpected didReach/stop ordering didReachIdx:%lu stopIdx:%lu", (unsigned long)didReachIdx, (unsigned long)stopIdx);
// write events
XCTAssertTrue(0 != writeEvents.count, @"unexpected writeEvents count %@", writeEvents);
id firstWriteValue = [(POPAnimationValueEvent *)writeEvents.firstObject value];
XCTAssertTrue(NSOrderedSame == [anim.fromValue compare:firstWriteValue], @"unexpected firstWriteValue; fromValue:%@ actual:%@", anim.fromValue, firstWriteValue);
id lastWriteValue = [(POPAnimationValueEvent *)writeEvents.lastObject value];
XCTAssertEqualObjects(lastWriteValue, anim.toValue, @"unexpected lastWriteValue; expected:%@ actual:%@", anim.toValue, lastWriteValue);
}
- (void)testAnimationContinuation
{
POPAnimatable *circle = [POPAnimatable new];
POPSpringAnimation *anim = [POPSpringAnimation animation];
anim.property = self.radiusProperty;
anim.fromValue = @0.0;
anim.toValue = @1.0;
anim.velocity = @10.0;
anim.springBounciness = 4;
POPAnimationTracer *tracer = anim.tracer;
[tracer start];
[circle pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05);
NSArray *didReachToEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop];
// assert did reach but not stop
XCTAssertTrue(1 == didReachToEvents.count, @"unexpected didReachToEvents count %@", didReachToEvents);
XCTAssertTrue(0 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents);
// update to value continuing animation
anim.toValue = @0.0;
POPAnimatorRenderDuration(self.animator, self.beginTime, 2.0, 0.1);
[tracer stop];
// two did reach to events
didReachToEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
XCTAssertTrue(2 == didReachToEvents.count, @"unexpected didReachToEvents count %@", didReachToEvents);
// first event value > animation to value
id firstDidReachValue = [(POPAnimationValueEvent *)didReachToEvents.firstObject value];
XCTAssertTrue(NSOrderedAscending == [anim.toValue compare:firstDidReachValue], @"unexpected firstDidReachValue; toValue:%@ actual:%@", anim.toValue, firstDidReachValue);
// second event value < animation to value
id lastDidReachValue = [(POPAnimationValueEvent *)didReachToEvents.lastObject value];
XCTAssertTrue(NSOrderedDescending == [anim.toValue compare:lastDidReachValue], @"unexpected lastDidReachValue; toValue:%@ actual:%@", anim.toValue, lastDidReachValue);
// did stop event
stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop];
XCTAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents);
XCTAssertEqualObjects([(POPAnimationValueEvent *)stopEvents.lastObject value], @YES, @"unexpected stop event: %@", stopEvents.lastObject);
}
- (void)testRoundingFactor
{
POPAnimatable *circle = [POPAnimatable new];
{
// non retina, additive & non-additive
BOOL additive = NO;
LStart:
POPBasicAnimation *anim = [POPBasicAnimation animation];
anim.property = self.radiusProperty;
anim.fromValue = @0.0;
anim.toValue = @1.0;
anim.roundingFactor = 1.0;
anim.additive = additive;
POPAnimationTracer *tracer = anim.tracer;
[tracer start];
[circle pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05);
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
BOOL containValue = POPAnimationEventsContainValue(writeEvents, @0.5);
XCTAssertFalse(containValue, @"unexpected write value %@", writeEvents);
if (!additive) {
additive = YES;
goto LStart;
}
}
{
// retina, additive & non-additive
BOOL additive = NO;
LStartRetina:
POPBasicAnimation *anim = [POPBasicAnimation animation];
anim.property = self.radiusProperty;
anim.fromValue = @0.0;
anim.toValue = @1.0;
anim.roundingFactor = 0.5;
POPAnimationTracer *tracer = anim.tracer;
[tracer start];
[circle pop_addAnimation:anim forKey:@"key"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05);
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
BOOL containValue = POPAnimationEventsContainValue(writeEvents, @0.5);
XCTAssertTrue(containValue, @"unexpected write value %@", writeEvents);
if (!additive) {
additive = YES;
goto LStartRetina;
}
}
}
- (void)testAdditiveAnimation
{
const CGFloat baseValue = 1.;
const CGFloat fromValue = 1.;
const CGFloat toValue = 2.;
POPAnimatable *circle = [POPAnimatable new];
circle.radius = baseValue;
POPBasicAnimation *anim;
anim = [POPBasicAnimation animation];
anim.property = self.radiusProperty;
anim.fromValue = @(fromValue);
anim.toValue = @(toValue);
anim.additive = YES;
[circle startRecording];
[circle pop_addAnimation:anim forKey:@"key1"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 0.5, 0.05);
NSArray *writeEvents = [circle recordedValuesForKey:@"radius"];
CGFloat firstValue = [[writeEvents firstObject] floatValue];
CGFloat lastValue = [[writeEvents lastObject] floatValue];
XCTAssertTrue(firstValue >= baseValue + fromValue, @"write value expected:%f actual:%f", baseValue + fromValue, firstValue);
XCTAssertTrue(lastValue == baseValue + toValue, @"write value expected:%f actual:%f", baseValue + toValue, lastValue);
}
- (void)testNilKey
{
POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime);
// avoid fractional values; simplify verification
anim.roundingFactor = 1.0;
id layer = [OCMockObject niceMockForClass:[CALayer class]];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.25).cg_point()];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.5).cg_point()];
[[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.75).cg_point()];
[[layer expect] setPosition:[anim.toValue CGPointValue]];
// verify nil key can be added, same as CA
[layer pop_addAnimation:anim forKey:nil];
// verify attempting to remove nil key is a noop, same as CA
XCTAssertNoThrow([layer pop_removeAnimationForKey:nil], @"unexpected exception");
POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.25);
[layer verify];
}
- (void)testIntegerAnimation
{
const int toValue = 1;
NSNumber *boxedToValue = @1.0;
POPBasicAnimation *anim;
// literal
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// integer
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// short
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// unsigned short
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// int
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// unsigned int
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// long
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
// unsigned long
anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
XCTAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception");
XCTAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue);
anim.fromValue = @0;
POPAnimationTracer *tracer = anim.tracer;
[tracer start];
CALayer *layer = [CALayer layer];
[layer pop_addAnimation:anim forKey:nil];
POPAnimatorRenderDuration(self.animator, self.beginTime + 0.1, 1, 0.1);
// verify writes happened
NSArray *writeEvents = tracer.writeEvents;
XCTAssertTrue(writeEvents.count == 5, @"unexpected events:%@", writeEvents);
// verify initial value
POPAnimationValueEvent *firstWriteEvent = writeEvents.firstObject;
XCTAssertTrue([firstWriteEvent.value isEqual:anim.fromValue], @"expected equality; value1:%@ value%@", firstWriteEvent.value, anim.fromValue);
// verify final value
XCTAssertEqualObjects([layer valueForKey:@"opacity"], anim.toValue, @"expected equality; value1:%@ value2:%@", [layer valueForKey:@"opacity"], anim.toValue);
}
- (void)testPlatformColorSupport
{
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBackgroundColor];
#if TARGET_OS_IPHONE
XCTAssertNoThrow(anim.fromValue = [UIColor whiteColor], @"unexpected exception");
XCTAssertNoThrow(anim.toValue = [UIColor redColor], @"unexpected exception");
#else
XCTAssertNoThrow(anim.fromValue = [NSColor whiteColor], @"unexpected exception");
XCTAssertNoThrow(anim.toValue = [NSColor redColor], @"unexpected exception");
#endif
POPAnimationTracer *tracer = anim.tracer;
[tracer start];
CALayer *layer = [CALayer layer];
[layer pop_addAnimation:anim forKey:@"color"];
POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.1);
// expect some interpolation
NSArray *writeEvents = tracer.writeEvents;
XCTAssertTrue(writeEvents.count > 1, @"unexpected write events %@", writeEvents);
// get layer color components
CGFloat layerValues[4];
POPCGColorGetRGBAComponents(layer.backgroundColor, layerValues);
// get to color components
CGFloat toValues[4];
POPCGColorGetRGBAComponents((__bridge CGColorRef)anim.toValue, toValues);
// assert equality
XCTAssertTrue(layerValues[0] == toValues[0] && layerValues[1] == toValues[1] && layerValues[2] == toValues[2] && layerValues[3] == toValues[3], @"unexpected last color: [r:%f g:%f b:%f a:%f]", layerValues[0], layerValues[1], layerValues[2], layerValues[3]);
}
- (void)testNSCopyingSupportPOPBasicAnimation
{
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:@"test_property_name"];
configureConcretePropertyAnimation(anim);
[self testCopyingSucceedsForConcretePropertyAnimation:anim];
anim.duration = 1.8;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
POPBasicAnimation *copy = [anim copy];
XCTAssertEqual(copy.duration, anim.duration, @"expected equality; value1:%@ value2:%@", @(copy.duration), @(anim.duration));
XCTAssertEqualObjects(copy.timingFunction, anim.timingFunction, @"expected equality; value1:%@ value2:%@", copy.timingFunction, anim.timingFunction);
}
@end