content: 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.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.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<>(),
          new LongSparseArray<>(0), new HashMap<>(0),
          new HashMap<>(0), 1f, new SparseArrayCompat<>(0),
          new HashMap<>(0), new ArrayList<>(), 0, 0);
      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);
    }
  }
  