blob: 08b0b8c18a37bb43b144e77d577b5fbc111993e9 [file] [log] [blame]
package com.airbnb.lottie;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.Rect;
import androidx.collection.LongSparseArray;
import androidx.collection.SparseArrayCompat;
import com.airbnb.lottie.model.Font;
import com.airbnb.lottie.model.FontCharacter;
import com.airbnb.lottie.model.Marker;
import com.airbnb.lottie.model.layer.Layer;
import com.airbnb.lottie.utils.LottieValueAnimator;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
public class LottieValueAnimatorUnitTest extends BaseTest {
private interface VerifyListener {
void verify(InOrder inOrder);
}
private LottieComposition composition;
private LottieValueAnimator animator;
private Animator.AnimatorListener spyListener;
private InOrder inOrder;
private AtomicBoolean isDone;
@Before
public void setup() {
animator = createAnimator();
composition = createComposition(0, 1000);
animator.setComposition(composition);
spyListener = Mockito.mock(Animator.AnimatorListener.class);
isDone = new AtomicBoolean(false);
}
private LottieValueAnimator createAnimator() {
// Choreographer#postFrameCallback hangs with robolectric.
return new LottieValueAnimator() {
@Override public void postFrameCallback() {
running = true;
}
@Override public void removeFrameCallback() {
running = false;
}
};
}
private LottieComposition createComposition(int startFrame, int endFrame) {
LottieComposition composition = new LottieComposition();
composition.init(new Rect(), startFrame, endFrame, 1000, new ArrayList<Layer>(),
new LongSparseArray<Layer>(0), new HashMap<String, List<Layer>>(0),
new HashMap<String, LottieImageAsset>(0), new SparseArrayCompat<FontCharacter>(0),
new HashMap<String, Font>(0), new ArrayList<Marker>());
return composition;
}
@Test
public void testInitialState() {
assertClose(0f, animator.getFrame());
}
@Test
public void testResumingMaintainsValue() {
animator.setFrame(500);
animator.resumeAnimation();
assertClose(500f, animator.getFrame());
}
@Test
public void testFrameConvertsToAnimatedFraction() {
animator.setFrame(500);
animator.resumeAnimation();
assertClose(0.5f, animator.getAnimatedFraction());
assertClose(0.5f, animator.getAnimatedValueAbsolute());
}
@Test
public void testPlayingResetsValue() {
animator.setFrame(500);
animator.playAnimation();
assertClose(0f, animator.getFrame());
assertClose(0f, animator.getAnimatedFraction());
}
@Test
public void testReversingMaintainsValue() {
animator.setFrame(250);
animator.reverseAnimationSpeed();
assertClose(250f, animator.getFrame());
assertClose(0.75f, animator.getAnimatedFraction());
assertClose(0.25f, animator.getAnimatedValueAbsolute());
}
@Test
public void testReversingWithMinValueMaintainsValue() {
animator.setMinFrame(100);
animator.setFrame(1000);
animator.reverseAnimationSpeed();
assertClose(1000f, animator.getFrame());
assertClose(0f, animator.getAnimatedFraction());
assertClose(1f, animator.getAnimatedValueAbsolute());
}
@Test
public void testReversingWithMaxValueMaintainsValue() {
animator.setMaxFrame(900);
animator.reverseAnimationSpeed();
assertClose(0f, animator.getFrame());
assertClose(1f, animator.getAnimatedFraction());
assertClose(0f, animator.getAnimatedValueAbsolute());
}
@Test
public void testResumeReversingWithMinValueMaintainsValue() {
animator.setMaxFrame(900);
animator.reverseAnimationSpeed();
animator.resumeAnimation();
assertClose(900f, animator.getFrame());
assertClose(0f, animator.getAnimatedFraction());
assertClose(0.9f, animator.getAnimatedValueAbsolute());
}
@Test
public void testPlayReversingWithMinValueMaintainsValue() {
animator.setMaxFrame(900);
animator.reverseAnimationSpeed();
animator.playAnimation();
assertClose(900f, animator.getFrame());
assertClose(0f, animator.getAnimatedFraction());
assertClose(0.9f, animator.getAnimatedValueAbsolute());
}
@Test
public void testMinAndMaxBothSet() {
animator.setMinFrame(200);
animator.setMaxFrame(800);
animator.setFrame(400);
assertClose(0.33333f, animator.getAnimatedFraction());
assertClose(0.4f, animator.getAnimatedValueAbsolute());
animator.reverseAnimationSpeed();
assertClose(400f, animator.getFrame());
assertClose(0.66666f, animator.getAnimatedFraction());
assertClose(0.4f, animator.getAnimatedValueAbsolute());
animator.resumeAnimation();
assertClose(400f, animator.getFrame());
assertClose(0.66666f, animator.getAnimatedFraction());
assertClose(0.4f, animator.getAnimatedValueAbsolute());
animator.playAnimation();
assertClose(800f, animator.getFrame());
assertClose(0f, animator.getAnimatedFraction());
assertClose(0.8f, animator.getAnimatedValueAbsolute());
}
@Test
public void testSetFrameIntegrity() {
animator.setMinAndMaxFrames(200, 800);
// setFrame < minFrame should clamp to minFrame
animator.setFrame(100);
assertEquals(200f, animator.getFrame());
animator.setFrame(900);
assertEquals(800f, animator.getFrame());
}
@Test(expected = IllegalArgumentException.class)
public void testMinAndMaxFrameIntegrity() {
animator.setMinAndMaxFrames(800, 200);
}
@Test
public void testDefaultAnimator() {
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
inOrder.verify(spyListener, times(1)).onAnimationStart(animator, false);
inOrder.verify(spyListener, times(1)).onAnimationEnd(animator, false);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
Mockito.verify(spyListener, times(0)).onAnimationRepeat(animator);
}
});
}
@Test
public void testReverseAnimator() {
animator.reverseAnimationSpeed();
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
inOrder.verify(spyListener, times(1)).onAnimationStart(animator, true);
inOrder.verify(spyListener, times(1)).onAnimationEnd(animator, true);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
Mockito.verify(spyListener, times(0)).onAnimationRepeat(animator);
}
});
}
@Test
public void testLoopingAnimatorOnce() {
animator.setRepeatCount(1);
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
Mockito.verify(spyListener, times(1)).onAnimationStart(animator, false);
Mockito.verify(spyListener, times(1)).onAnimationRepeat(animator);
Mockito.verify(spyListener, times(1)).onAnimationEnd(animator, false);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
}
});
}
@Test
public void testLoopingAnimatorZeroTimes() {
animator.setRepeatCount(0);
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
Mockito.verify(spyListener, times(1)).onAnimationStart(animator, false);
Mockito.verify(spyListener, times(0)).onAnimationRepeat(animator);
Mockito.verify(spyListener, times(1)).onAnimationEnd(animator, false);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
}
});
}
@Test
public void testLoopingAnimatorTwice() {
animator.setRepeatCount(2);
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
Mockito.verify(spyListener, times(1)).onAnimationStart(animator, false);
Mockito.verify(spyListener, times(2)).onAnimationRepeat(animator);
Mockito.verify(spyListener, times(1)).onAnimationEnd(animator, false);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
}
});
}
@Test
public void testLoopingAnimatorOnceReverse() {
animator.setFrame(1000);
animator.setRepeatCount(1);
animator.reverseAnimationSpeed();
testAnimator(new VerifyListener() {
@Override public void verify(InOrder inOrder) {
inOrder.verify(spyListener, times(1)).onAnimationStart(animator, true);
inOrder.verify(spyListener, times(1)).onAnimationRepeat(animator);
inOrder.verify(spyListener, times(1)).onAnimationEnd(animator, true);
Mockito.verify(spyListener, times(0)).onAnimationCancel(animator);
}
});
}
@Test
public void setMinFrameSmallerThanComposition() {
animator.setMinFrame(-9000);
assertClose(animator.getMinFrame(), composition.getStartFrame());
}
@Test
public void setMaxFrameLargerThanComposition() {
animator.setMaxFrame(9000);
assertClose(animator.getMaxFrame(), composition.getEndFrame());
}
@Test
public void setMinFrameBeforeComposition() {
LottieValueAnimator animator = createAnimator();
animator.setMinFrame(100);
animator.setComposition(composition);
assertClose(100.0f, animator.getMinFrame());
}
@Test
public void setMaxFrameBeforeComposition() {
LottieValueAnimator animator = createAnimator();
animator.setMaxFrame(100);
animator.setComposition(composition);
assertClose(100.0f, animator.getMaxFrame());
}
@Test
public void setMinAndMaxFrameBeforeComposition() {
LottieValueAnimator animator = createAnimator();
animator.setMinAndMaxFrames(100, 900);
animator.setComposition(composition);
assertClose(100.0f, animator.getMinFrame());
assertClose(900.0f, animator.getMaxFrame());
}
@Test
public void setMinFrameAfterComposition() {
LottieValueAnimator animator = createAnimator();
animator.setComposition(composition);
animator.setMinFrame(100);
assertClose(100.0f, animator.getMinFrame());
}
@Test
public void setMaxFrameAfterComposition() {
LottieValueAnimator animator = createAnimator();
animator.setComposition(composition);
animator.setMaxFrame(100);
assertEquals(100.0f, animator.getMaxFrame());
}
@Test
public void setMinAndMaxFrameAfterComposition() {
LottieValueAnimator animator = createAnimator();
animator.setComposition(composition);
animator.setMinAndMaxFrames(100, 900);
assertClose(100.0f, animator.getMinFrame());
assertClose(900.0f, animator.getMaxFrame());
}
@Test
public void maxFrameOfNewShorterComposition() {
LottieValueAnimator animator = createAnimator();
animator.setComposition(composition);
LottieComposition composition2 = createComposition(0, 500);
animator.setComposition(composition2);
assertClose(500.0f, animator.getMaxFrame());
}
@Test
public void maxFrameOfNewLongerComposition() {
LottieValueAnimator animator = createAnimator();
animator.setComposition(composition);
LottieComposition composition2 = createComposition(0, 1500);
animator.setComposition(composition2);
assertClose(1500.0f, animator.getMaxFrame());
}
@Test
public void clearComposition() {
animator.clearComposition();
assertClose(0.0f, animator.getMaxFrame());
assertClose(0.0f, animator.getMinFrame());
}
@Test
public void resetComposition() {
animator.clearComposition();
animator.setComposition(composition);
assertClose(0.0f, animator.getMinFrame());
assertClose(1000.0f, animator.getMaxFrame());
}
@Test
public void resetAndSetMinBeforeComposition() {
animator.clearComposition();
animator.setMinFrame(100);
animator.setComposition(composition);
assertClose(100.0f, animator.getMinFrame());
assertClose(1000.0f, animator.getMaxFrame());
}
@Test
public void resetAndSetMinAterComposition() {
animator.clearComposition();
animator.setComposition(composition);
animator.setMinFrame(100);
assertClose(100.0f, animator.getMinFrame());
assertClose(1000.0f, animator.getMaxFrame());
}
private void testAnimator(final VerifyListener verifyListener) {
spyListener = Mockito.spy(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
verifyListener.verify(inOrder);
isDone.set(true);
}
});
inOrder = inOrder(spyListener);
animator.addListener(spyListener);
animator.playAnimation();
while (!isDone.get()) {
animator.doFrame(System.nanoTime());
}
}
/**
* Animations don't render on the out frame so if an animation is 1000 frames, the actual end will be 999.99. This causes
* actual fractions to be something like .74999 when you might expect 75.
*/
private static void assertClose(float expected, float actual) {
assertEquals(expected, actual, expected * 0.01f);
}
}