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.
688 lines
25 KiB
688 lines
25 KiB
9 years ago
|
/**
|
||
|
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 <OCMock/OCMock.h>
|
||
|
|
||
|
#import <QuartzCore/QuartzCore.h>
|
||
|
|
||
|
#import <XCTest/XCTest.h>
|
||
|
|
||
|
#import <pop/POPAnimation.h>
|
||
|
#import <pop/POPAnimationPrivate.h>
|
||
|
#import <pop/POPAnimator.h>
|
||
|
#import <pop/POPAnimatorPrivate.h>
|
||
|
#import <pop/POPAnimationExtras.h>
|
||
|
|
||
|
#import "POPAnimatable.h"
|
||
|
#import "POPAnimationInternal.h"
|
||
|
#import "POPAnimationTestsExtras.h"
|
||
|
#import "POPBaseAnimationTests.h"
|
||
|
#import "POPCGUtils.h"
|
||
|
|
||
|
@interface POPSpringAnimationTests : POPBaseAnimationTests
|
||
|
@end
|
||
|
|
||
|
@implementation POPSpringAnimationTests
|
||
|
|
||
|
static NSString *animationKey = @"key";
|
||
|
|
||
|
- (POPSpringAnimation *)_positionAnimation
|
||
|
{
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.fromValue = @0.0;
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];
|
||
|
anim.springBounciness = 4.0;
|
||
|
return anim;
|
||
|
}
|
||
|
|
||
|
- (void)testCompletion
|
||
|
{
|
||
|
// animation
|
||
|
// the default from, to and bounciness values are used
|
||
|
NSArray *markers = @[@0.5, @0.75, @1.0];
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition];
|
||
|
anim.progressMarkers = markers;
|
||
|
XCTAssertEqualObjects(markers, anim.progressMarkers, @"%@ shoudl equal %@", markers, anim.progressMarkers);
|
||
|
|
||
|
// delegate
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
|
||
|
// expect start, progress & stop to all be called
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// layer
|
||
|
id layer = [OCMockObject niceMockForClass:[CALayer class]];
|
||
|
|
||
|
// expect position to be called
|
||
|
CGPoint position = CGPointMake(100, 100);
|
||
|
position = [(CALayer *)[[layer stub] andReturnValue:OCMOCK_VALUE(position)] position];
|
||
|
[layer pop_addAnimation:anim forKey:@"key"];
|
||
|
|
||
|
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @0.1, @0.2]);
|
||
|
[layer verify];
|
||
|
[delegate verify];
|
||
|
}
|
||
|
|
||
|
- (void)testConvergence
|
||
|
{
|
||
|
POPAnimatable *circle = [POPAnimatable new];
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];
|
||
|
anim.fromValue = @0.0;
|
||
|
anim.toValue = @100.0;
|
||
|
anim.velocity = @100.0;
|
||
|
anim.springBounciness = 0.5;
|
||
|
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
[circle pop_addAnimation:anim forKey:@"key"];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0);
|
||
|
[tracer stop];
|
||
|
|
||
|
// finished
|
||
|
POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject];
|
||
|
XCTAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent);
|
||
|
|
||
|
// convergence threshold
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue, anim.property.threshold);
|
||
|
XCTAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %lu", (unsigned long)toValueFrameCount);
|
||
|
}
|
||
|
|
||
|
- (void)testConvergenceRounded
|
||
|
{
|
||
|
POPAnimatable *circle = [POPAnimatable new];
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];
|
||
|
anim.fromValue = @0.0;
|
||
|
anim.toValue = @100.0;
|
||
|
anim.velocity = @100.0;
|
||
|
anim.springBounciness = 0.5;
|
||
|
anim.roundingFactor = 1.0;
|
||
|
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
[circle pop_addAnimation:anim forKey:@"key"];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0);
|
||
|
[tracer stop];
|
||
|
|
||
|
// finished
|
||
|
POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject];
|
||
|
XCTAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent);
|
||
|
|
||
|
// convergence threshold
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue);
|
||
|
XCTAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %lu", (unsigned long)toValueFrameCount);
|
||
|
}
|
||
|
|
||
|
- (void)testConvergenceClampedRounded
|
||
|
{
|
||
|
POPAnimatable *circle = [POPAnimatable new];
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];
|
||
|
anim.fromValue = @0.0;
|
||
|
anim.toValue = @100.0;
|
||
|
anim.velocity = @100.0;
|
||
|
anim.springBounciness = 0.5;
|
||
|
anim.roundingFactor = 1.0;
|
||
|
anim.clampMode = kPOPAnimationClampEnd;
|
||
|
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
[circle pop_addAnimation:anim forKey:@"key"];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0);
|
||
|
[tracer stop];
|
||
|
|
||
|
// finished
|
||
|
POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject];
|
||
|
XCTAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent);
|
||
|
|
||
|
// convergence threshold
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue);
|
||
|
XCTAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %lu", (unsigned long)toValueFrameCount);
|
||
|
}
|
||
|
|
||
|
- (void)testRemovedOnCompletionNoStartStopBasics
|
||
|
{
|
||
|
CALayer *layer = self.layer1;
|
||
|
POPSpringAnimation *anim = self._positionAnimation;
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
|
||
|
// cleanup
|
||
|
[layer pop_removeAllAnimations];
|
||
|
|
||
|
// configure animation
|
||
|
anim.removedOnCompletion = NO;
|
||
|
anim.toValue = @5.0;
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
__block BOOL completionBlock = NO;
|
||
|
__block BOOL completionBlockFinished = NO;
|
||
|
anim.completionBlock = ^(POPAnimation *a, BOOL finished) {
|
||
|
completionBlock = YES;
|
||
|
completionBlockFinished = finished;
|
||
|
};
|
||
|
|
||
|
// start tracer
|
||
|
[tracer start];
|
||
|
|
||
|
// expect start and stopped
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
[layer pop_addAnimation:anim forKey:animationKey];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 20.0, 1.0/60.0);
|
||
|
NSArray *allEvents = tracer.allEvents;
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
XCTAssertTrue(completionBlock, @"completion block did not execute %@", allEvents);
|
||
|
XCTAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents);
|
||
|
|
||
|
// assert animation has not been removed
|
||
|
XCTAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]);
|
||
|
}
|
||
|
|
||
|
- (void)testRemovedOnCompletionNoContinuations
|
||
|
{
|
||
|
static NSString *animationKey = @"key";
|
||
|
static NSArray *toValues = @[@50.0, @100.0, @20.0, @80.0];
|
||
|
static NSArray *durations = @[@2.0, @0.3, @0.4, @2.0];
|
||
|
|
||
|
CALayer *layer = self.layer1;
|
||
|
POPSpringAnimation *anim = self._positionAnimation;
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
|
||
|
// cleanup
|
||
|
[layer pop_removeAllAnimations];
|
||
|
|
||
|
// configure animation
|
||
|
anim.removedOnCompletion = NO;
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// start tracer
|
||
|
[tracer start];
|
||
|
|
||
|
__block CFTimeInterval beginTime;
|
||
|
__block BOOL completionBlock = NO;
|
||
|
__block BOOL completionBlockFinished = NO;
|
||
|
|
||
|
[toValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *ptrStop) {
|
||
|
anim.toValue = obj;
|
||
|
|
||
|
if (0 == idx) {
|
||
|
[tracer reset];
|
||
|
|
||
|
// starts and stops
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
anim.completionBlock = ^(POPAnimation *a, BOOL finished) {
|
||
|
completionBlock = YES;
|
||
|
completionBlockFinished = finished;
|
||
|
};
|
||
|
|
||
|
[layer pop_addAnimation:anim forKey:animationKey];
|
||
|
|
||
|
beginTime = self.beginTime;
|
||
|
CFTimeInterval dt = [durations[idx] doubleValue];
|
||
|
POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0);
|
||
|
beginTime += dt;
|
||
|
|
||
|
NSArray *allEvents = tracer.allEvents;
|
||
|
NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
XCTAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents);
|
||
|
XCTAssertTrue(completionBlock, @"completion block did not execute %@", allEvents);
|
||
|
XCTAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents);
|
||
|
} else if (toValues.count - 1 == idx) {
|
||
|
// continue stoped animation
|
||
|
[tracer reset];
|
||
|
completionBlock = NO;
|
||
|
completionBlockFinished = NO;
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
CFTimeInterval dt = [durations[idx] doubleValue];
|
||
|
POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0);
|
||
|
beginTime += dt;
|
||
|
|
||
|
NSArray *allEvents = tracer.allEvents;
|
||
|
NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
XCTAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents);
|
||
|
XCTAssertTrue(completionBlock, @"completion block did not execute %@", allEvents);
|
||
|
XCTAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents);
|
||
|
} else {
|
||
|
// continue stoped (idx = 1) or started animation
|
||
|
if (1 == idx) {
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
}
|
||
|
|
||
|
// reset state
|
||
|
[tracer reset];
|
||
|
completionBlock = NO;
|
||
|
completionBlockFinished = NO;
|
||
|
|
||
|
CFTimeInterval dt = [durations[idx] doubleValue];
|
||
|
POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0);
|
||
|
beginTime += dt;
|
||
|
|
||
|
NSArray *allEvents = tracer.allEvents;
|
||
|
NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
XCTAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents);
|
||
|
XCTAssertFalse(completionBlock, @"completion block did not execute %@ %@", anim, allEvents);
|
||
|
XCTAssertFalse(completionBlockFinished, @"completion block did not finish %@ %@", anim, allEvents);
|
||
|
}
|
||
|
|
||
|
// assert animation has not been removed
|
||
|
XCTAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]);
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)testNoOperationAnimation
|
||
|
{
|
||
|
const CGPoint initialValue = CGPointMake(100, 100);
|
||
|
|
||
|
CALayer *layer = self.layer1;
|
||
|
layer.position = initialValue;
|
||
|
[layer pop_removeAllAnimations];
|
||
|
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition];
|
||
|
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// starts and stops
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
[layer pop_addAnimation:anim forKey:animationKey];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 1.0/60.0);
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
|
||
|
// verify number values
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
for (POPAnimationValueEvent *writeEvent in writeEvents) {
|
||
|
XCTAssertEqualObjects(writeEvent.value, [NSValue valueWithCGPoint:initialValue], @"unexpected write event:%@ anim:%@", writeEvent, anim);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)testLazyValueInitialization
|
||
|
{
|
||
|
CALayer *layer = self.layer1;
|
||
|
layer.position = CGPointZero;
|
||
|
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];
|
||
|
anim.fromValue = @100.0;
|
||
|
anim.beginTime = self.beginTime + 0.3;
|
||
|
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
// add animation, but do not start
|
||
|
[layer pop_addAnimation:anim forKey:animationKey];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 0.2, 1.0/60.0);
|
||
|
XCTAssertNotNil(anim.fromValue, @"unexpected from value %@", anim);
|
||
|
XCTAssertNil(anim.toValue, @"unexpected to value %@", anim);
|
||
|
|
||
|
// start animation
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime + 0.2, 0.2, 1.0/60.0);
|
||
|
XCTAssertNotNil(anim.fromValue, @"unexpected from value %@", anim);
|
||
|
XCTAssertNotNil(anim.toValue, @"unexpected to value %@", anim);
|
||
|
|
||
|
// continue running animation
|
||
|
anim.fromValue = nil;
|
||
|
anim.toValue = @200.0;
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime + 0.4, 0.2, 1.0/60.0);
|
||
|
XCTAssertNotNil(anim.fromValue, @"unexpected from value %@", anim);
|
||
|
XCTAssertNotNil(anim.toValue, @"unexpected to value %@", anim);
|
||
|
}
|
||
|
|
||
|
- (void)testLatentSpring
|
||
|
{
|
||
|
POPSpringAnimation *translationAnimation = [POPSpringAnimation animation];
|
||
|
translationAnimation.dynamicsTension = 990;
|
||
|
translationAnimation.dynamicsFriction = 230;
|
||
|
translationAnimation.dynamicsMass = 1.0;
|
||
|
translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerOpacity];
|
||
|
translationAnimation.removedOnCompletion = NO;
|
||
|
[self pop_addAnimation:translationAnimation forKey:@"test"];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime + 0.4, 0.2, 1.0/60.0);
|
||
|
}
|
||
|
|
||
|
- (void)testRectSupport
|
||
|
{
|
||
|
const CGRect fromRect = CGRectMake(0, 0, 0, 0);
|
||
|
const CGRect toRect = CGRectMake(100, 200, 200, 400);
|
||
|
const CGRect velocityRect = CGRectMake(1000, 1000, 1000, 1000);
|
||
|
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds];
|
||
|
anim.fromValue = [NSValue valueWithCGRect:fromRect];
|
||
|
anim.toValue = [NSValue valueWithCGRect:toRect];
|
||
|
anim.velocity = [NSValue valueWithCGRect:velocityRect];
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// expect start and stop to be called
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:@""];
|
||
|
|
||
|
// run animation
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
|
||
|
POPAnimationValueEvent *lastEvent = [writeEvents lastObject];
|
||
|
CGRect lastRect = [lastEvent.value CGRectValue];
|
||
|
|
||
|
// verify last rect is to rect
|
||
|
XCTAssertTrue(CGRectEqualToRect(lastRect, toRect), @"unexpected last rect value: %@", lastEvent);
|
||
|
}
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
- (void)testEdgeInsetsSupport
|
||
|
{
|
||
|
const UIEdgeInsets fromEdgeInsets = UIEdgeInsetsZero;
|
||
|
const UIEdgeInsets toEdgeInsets = UIEdgeInsetsMake(100, 200, 200, 400);
|
||
|
const UIEdgeInsets velocityEdgeInsets = UIEdgeInsetsMake(1000, 1000, 1000, 1000);
|
||
|
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPScrollViewContentInset];
|
||
|
anim.fromValue = [NSValue valueWithUIEdgeInsets:fromEdgeInsets];
|
||
|
anim.toValue = [NSValue valueWithUIEdgeInsets:toEdgeInsets];
|
||
|
anim.velocity = [NSValue valueWithUIEdgeInsets:velocityEdgeInsets];
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// expect start and stop to be called
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
id scrollView = [OCMockObject niceMockForClass:[UIScrollView class]];
|
||
|
[scrollView pop_addAnimation:anim forKey:nil];
|
||
|
|
||
|
// expect final value to be set
|
||
|
[[scrollView expect] setContentInset:toEdgeInsets];
|
||
|
|
||
|
// run animation
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
|
||
|
// verify scroll view
|
||
|
[scrollView verify];
|
||
|
|
||
|
POPAnimationValueEvent *lastEvent = [writeEvents lastObject];
|
||
|
UIEdgeInsets lastEdgeInsets = [lastEvent.value UIEdgeInsetsValue];
|
||
|
|
||
|
// verify last insets are to insets
|
||
|
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(lastEdgeInsets, toEdgeInsets), @"unexpected last edge insets value: %@", lastEvent);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
- (void)testColorSupport
|
||
|
{
|
||
|
CGFloat fromValues[4] = {1, 1, 1, 1};
|
||
|
CGFloat toValues[4] = {0, 0, 0, 1};
|
||
|
CGColorRef fromColor = POPCGColorRGBACreate(fromValues);
|
||
|
CGColorRef toColor = POPCGColorRGBACreate(toValues);
|
||
|
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds];
|
||
|
anim.fromValue = (__bridge_transfer id)fromColor;
|
||
|
anim.toValue = (__bridge_transfer id)toColor;
|
||
|
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:@""];
|
||
|
|
||
|
// run animation
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
|
||
|
// verify delegate
|
||
|
[delegate verify];
|
||
|
|
||
|
// expect some interpolation
|
||
|
XCTAssertTrue(writeEvents.count > 1, @"unexpected write events %@", writeEvents);
|
||
|
POPAnimationValueEvent *lastEvent = [writeEvents lastObject];
|
||
|
|
||
|
// verify last written color is to color
|
||
|
POPAssertColorEqual((__bridge CGColorRef)lastEvent.value, (__bridge CGColorRef)anim.toValue);
|
||
|
}
|
||
|
|
||
|
static BOOL _floatingPointEqual(CGFloat a, CGFloat b)
|
||
|
{
|
||
|
CGFloat epsilon = 0.0001;
|
||
|
return std::abs(a - b) < epsilon;
|
||
|
}
|
||
|
|
||
|
- (void)testBouncinessSpeedToTensionFrictionConversion
|
||
|
{
|
||
|
CGFloat sampleBounciness = 12.0;
|
||
|
CGFloat sampleSpeed = 5.0;
|
||
|
|
||
|
CGFloat tension, friction, mass;
|
||
|
[POPSpringAnimation convertBounciness:sampleBounciness speed:sampleSpeed toTension:&tension friction:&friction mass:&mass];
|
||
|
|
||
|
CGFloat outBounciness, outSpeed;
|
||
|
[POPSpringAnimation convertTension:tension friction:friction toBounciness:&outBounciness speed:&outSpeed];
|
||
|
|
||
|
XCTAssertTrue(_floatingPointEqual(sampleBounciness, outBounciness) && _floatingPointEqual(sampleSpeed, outSpeed), @"(bounciness, speed) conversion failed. Mapped (%f, %f) back to (%f, %f)", sampleBounciness, sampleSpeed, outBounciness, outSpeed);
|
||
|
}
|
||
|
|
||
|
- (void)testTensionFrictionToBouncinessSpeedConversion
|
||
|
{
|
||
|
CGFloat sampleTension = 240.0;
|
||
|
CGFloat sampleFriction = 25.0;
|
||
|
|
||
|
CGFloat bounciness, speed;
|
||
|
[POPSpringAnimation convertTension:sampleTension friction:sampleFriction toBounciness:&bounciness speed:&speed];
|
||
|
|
||
|
CGFloat outTension, outFriction, outMass;
|
||
|
[POPSpringAnimation convertBounciness:bounciness speed:speed toTension:&outTension friction:&outFriction mass:&outMass];
|
||
|
|
||
|
XCTAssertTrue(_floatingPointEqual(sampleTension, outTension) && _floatingPointEqual(sampleFriction, outFriction), @"(tension, friction) conversion failed. Mapped (%f, %f) back to (%f, %f)", sampleTension, sampleFriction, outTension, outFriction);
|
||
|
}
|
||
|
|
||
|
- (void)testRemovedOnCompletionNoContinuationValues
|
||
|
{
|
||
|
static CGFloat fromValue = 400.0;
|
||
|
static NSArray *toValues = @[@200.0, @400.0];
|
||
|
|
||
|
// configure animation
|
||
|
POPSpringAnimation *anim = self._positionAnimation;
|
||
|
anim.fromValue = [NSNumber numberWithFloat:fromValue];
|
||
|
anim.toValue = toValues[0];
|
||
|
anim.removedOnCompletion = NO;
|
||
|
|
||
|
// run animation, from 400 to 200
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:@""];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
// assert reached to value
|
||
|
XCTAssertTrue(layer.position.x == [anim.toValue floatValue], @"unexpected value:%@ %@", layer, anim);
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
// update to value, animate from 200 to 400
|
||
|
anim.toValue = toValues[1];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
// verify from 200 to 400
|
||
|
NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite];
|
||
|
XCTAssertTrue(writeEvents.count > 5, @"unexpected frame count %@", writeEvents);
|
||
|
|
||
|
CGFloat firstValue = [[(POPAnimationValueEvent *)[writeEvents firstObject] value] floatValue];
|
||
|
CGFloat lastValue = [[(POPAnimationValueEvent *)[writeEvents lastObject] value] floatValue];
|
||
|
XCTAssertEqualWithAccuracy(((CGFloat)[toValues[0] floatValue]), firstValue, 10, @"unexpected first value %@", writeEvents);
|
||
|
XCTAssertEqualWithAccuracy(((CGFloat)[toValues[1] floatValue]), lastValue, 10, @"unexpected last value %@", writeEvents);
|
||
|
}
|
||
|
|
||
|
- (void)testNilColor
|
||
|
{
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animation];
|
||
|
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBackgroundColor];
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
anim.toValue = (__bridge id)[UIColor redColor].CGColor;
|
||
|
#else
|
||
|
anim.toValue = (__bridge id)[NSColor redColor].CGColor;
|
||
|
#endif
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
// run animation
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:@""];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
// verify valid from color exists
|
||
|
CGColorRef fromColor = (__bridge CGColorRef)anim.fromValue;
|
||
|
XCTAssertTrue(fromColor, @"unexpected value %p", fromColor);
|
||
|
|
||
|
// verify from color clear
|
||
|
#if TARGET_OS_IPHONE
|
||
|
POPAssertColorEqual(fromColor, [UIColor clearColor].CGColor);
|
||
|
#else
|
||
|
POPAssertColorEqual(fromColor, [NSColor clearColor].CGColor);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
- (void)testExcessiveJumpInTime
|
||
|
{
|
||
|
POPSpringAnimation *anim = self._positionAnimation;
|
||
|
anim.toValue = @(1000.0);
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)];
|
||
|
anim.delegate = delegate;
|
||
|
|
||
|
// expect start and stop to be called
|
||
|
[[delegate expect] pop_animationDidStart:anim];
|
||
|
[[delegate expect] pop_animationDidStop:anim finished:YES];
|
||
|
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:animationKey];
|
||
|
|
||
|
// render with large time jump
|
||
|
POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @0.01, @300]);
|
||
|
|
||
|
// verify start stop
|
||
|
[delegate verify];
|
||
|
|
||
|
// verify last write event value
|
||
|
POPAnimationValueEvent *writeEvent = [[tracer eventsWithType:kPOPAnimationEventPropertyWrite] lastObject];
|
||
|
XCTAssertEqualObjects(writeEvent.value, anim.toValue, @"unexpected last write event %@", writeEvent);
|
||
|
}
|
||
|
|
||
|
- (void)testEquivalentFromToValues
|
||
|
{
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition];
|
||
|
anim.fromValue = [NSValue valueWithCGPoint:CGPointZero];
|
||
|
anim.toValue = [NSValue valueWithCGPoint:CGPointZero];
|
||
|
anim.velocity = [NSValue valueWithCGPoint:CGPointMake(1000.0, 1000.0)];
|
||
|
|
||
|
// start tracer
|
||
|
POPAnimationTracer *tracer = anim.tracer;
|
||
|
[tracer start];
|
||
|
|
||
|
// run animation
|
||
|
CALayer *layer = [CALayer layer];
|
||
|
[layer pop_addAnimation:anim forKey:@""];
|
||
|
POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0);
|
||
|
|
||
|
// verify last write event value
|
||
|
POPAnimationValueEvent *writeEvent = [[tracer eventsWithType:kPOPAnimationEventPropertyWrite] lastObject];
|
||
|
XCTAssertEqualObjects(writeEvent.value, anim.toValue, @"unexpected last write event %@", writeEvent);
|
||
|
}
|
||
|
|
||
|
- (void)testNSCopyingSupportPOPSpringAnimation
|
||
|
{
|
||
|
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:@"asdf_asdf_asdf"];
|
||
|
|
||
|
configureConcretePropertyAnimation(anim);
|
||
|
|
||
|
anim.velocity = @(4321);
|
||
|
anim.springBounciness = 11.1;
|
||
|
anim.springSpeed = 12;
|
||
|
anim.dynamicsTension = 0.83;
|
||
|
anim.dynamicsFriction = 0.97;
|
||
|
anim.dynamicsMass = 100;
|
||
|
|
||
|
POPSpringAnimation *copy = [anim copy];
|
||
|
|
||
|
XCTAssertEqualObjects(copy.velocity, anim.velocity, @"expected equality; value1:%@ value2:%@", copy.velocity, anim.velocity);
|
||
|
XCTAssertEqual(copy.springBounciness, anim.springBounciness, @"expected equality; value1:%@ value2:%@", @(copy.springBounciness), @(anim.springBounciness));
|
||
|
XCTAssertEqual(copy.springSpeed, anim.springSpeed, @"expected equality; value1:%@ value2:%@", @(copy.springSpeed), @(anim.springSpeed));
|
||
|
XCTAssertEqual(copy.dynamicsTension, anim.dynamicsTension, @"expected equality; value1:%@ value2:%@", @(copy.dynamicsTension), @(anim.dynamicsTension));
|
||
|
XCTAssertEqual(copy.dynamicsFriction, anim.dynamicsFriction, @"expected equality; value1:%@ value2:%@", @(copy.dynamicsFriction), @(anim.dynamicsFriction));
|
||
|
XCTAssertEqual(copy.dynamicsMass, anim.dynamicsMass, @"expected equality; value1:%@ value2:%@", @(copy.dynamicsMass), @(anim.dynamicsMass));
|
||
|
}
|
||
|
|
||
|
@end
|