diff --git a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt index ca1daaa..e33d6b0 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt @@ -83,7 +83,7 @@ open class Blend { */ open fun stopPulsing(vararg views: View) { views - .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).runningAnimations } + .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).queuedAnimations } .mapNotNull { singlePropertyAnimation -> singlePropertyAnimation.animator } .filter { animator -> animator.repeatCount != 0 } .toSet() diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt index 85c2835..e4ee593 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt @@ -21,7 +21,7 @@ import java.util.ArrayList * It wraps a [ValueAnimator] ([innerAnimator]) and delegates all [Animator] methods to it, with some redirection to * ensure proper adherence to e.g. listener contracts. * - * For proper functioning in sets, you must call [commitFutureValuesIfNotCommitted] when the set of animations is + * For proper functioning in sets, you must call [queueAnimationsIfNotAlreadyQueued] when the set of animations is * started, even if this animation is delayed. If not, the end state of the set of animations becomes dependent on * when the individual animators start, which can be unpredictable when they're user-initiated. */ @@ -43,7 +43,7 @@ open class BlendableAnimator : Animator() { * The actions to run before this animator starts. */ @VisibleForTesting val beforeStartActions: MutableList<() -> Unit> = mutableListOf() - private var valuesCommitted = false + private var animationsQueued = false val allAnimations: List> get() = animations.toList() @@ -96,17 +96,27 @@ open class BlendableAnimator : Animator() { } /** - * Commit the future values of all [animations] to the [com.wealthfront.blend.properties.AnimationData] objects - * described by their properties. + * Queue [animations], adding their future values to the [com.wealthfront.blend.properties.AnimationData] associated + * with each property/subject pair. * - * These future values are necessary for any animations started after this animator to blend properly. + * This should be called when the animation set that contains this animator is started, so animations started after + * this one can properly blend with this one. */ - open fun commitFutureValuesIfNotCommitted() { - if (!valuesCommitted) { + open fun queueAnimationsIfNotAlreadyQueued() { + if (!animationsQueued) { beforeStartActions.forEach { it() } - animations.forEach { it.setUpOnAnimationStart(this) } + animations.forEach { it.setUpOnAnimationQueued(this) } } - valuesCommitted = true + animationsQueued = true + } + + /** + * Mark all [animations] as part of a fully-queued set of animations. + * + * This step is necessary for individual animations in a set to not remove each other when being queued. + */ + open fun markAnimationsAsFullyQueued() { + animations.forEach { it.isPartOfAFullyQueuedSet = true } } override fun start() { @@ -120,16 +130,16 @@ open class BlendableAnimator : Animator() { innerAnimator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { animations.forEach { it.runEndActions() } - valuesCommitted = false + animationsQueued = false } }) - commitFutureValuesIfNotCommitted() if (repeatCount > 0) { cancelAnimatorOnViewsDetached() } + queueAnimationsIfNotAlreadyQueued() innerAnimator.start() - super.start() + markAnimationsAsFullyQueued() } private fun cancelAnimatorOnViewsDetached() { diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt index ac58616..f001299 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt @@ -1,5 +1,6 @@ package com.wealthfront.blend.animator +import androidx.annotation.VisibleForTesting import com.wealthfront.blend.properties.AdditiveProperty /** @@ -29,17 +30,26 @@ class SinglePropertyAnimation( * The animator running this animation */ var animator: BlendableAnimator? = null - private set + @VisibleForTesting internal set + /** + * Whether this animation has started running. Note that it can be queued and not started. + */ + val isStarted: Boolean get() = animator?.isStarted ?: false + /** + * Whether the set that this animation belongs to is done queuing all of its animations. Useful for figuring out + * when to cancel unstarted (but queued) animations. + */ + var isPartOfAFullyQueuedSet: Boolean = false /** - * Set up the starting values for this animation and commit the [targetValue] as [property]'s future value. + * Set up the starting values for this animation and queue the [targetValue] as [property]'s future value. */ - fun setUpOnAnimationStart(animator: BlendableAnimator) { + fun setUpOnAnimationQueued(animator: BlendableAnimator) { this.animator = animator - property.setUpOnAnimationStart(subject) + property.setUpOnAnimationQueued(subject) startValue = property.getFutureValue(subject) previousValue = startValue - property.addRunningAnimation(subject, this) + property.addQueuedAnimation(subject, this) property.addInterruptableEndActions(subject, *interruptibleEndActions.toTypedArray()) } @@ -52,12 +62,17 @@ class SinglePropertyAnimation( previousValue = newValue } + fun markCancelled() { + startValue = targetValue + previousValue = targetValue + } + /** * Run the end actions added by [BlendableAnimator.doOnFinishedUnlessLastAnimationInterrupted], if we haven't been * interrupted by another animation on [subject].[property]. */ fun runEndActions() { property.runEndActions(subject, this) - property.removeRunningAnimator(subject, this) + property.removeQueuedAnimation(subject, this) } } \ No newline at end of file diff --git a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt index 2e855b5..7203da0 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt @@ -35,9 +35,9 @@ interface AnimationStarter { return animatorSet.asAnimator().apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { - animatorSet.childAnimators - .mapNotNull { it as? BlendableAnimator } - .forEach { it.commitFutureValuesIfNotCommitted() } + val animators = animatorSet.childAnimators.mapNotNull { it as? BlendableAnimator } + animators.forEach { it.queueAnimationsIfNotAlreadyQueued() } + animators.forEach { it.markAnimationsAsFullyQueued() } } }) } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt index 8e5b655..ce6c0ef 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt @@ -10,8 +10,8 @@ import kotlin.math.roundToInt * To properly blend animations, it includes methods like [getFutureValue], [addInterruptableEndActions], and * [getAnimationData]. * - * We store data on running animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks of - * references to [Subject]s. The mechanism for storing is described by the implementation of [getAnimationData] + * We store data on queued animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks + * of references to [Subject]s. The mechanism for storing is described by the implementation of [getAnimationData] */ interface AdditiveProperty { @@ -52,19 +52,19 @@ interface AdditiveProperty { /** * Perform any setup tasks when an animation on this property starts. */ - fun setUpOnAnimationStart(subject: Subject) { } + fun setUpOnAnimationQueued(subject: Subject) { } /** - * Add a running animation to the [subject]'s [AnimationData] for this property. + * Add a queued animation to the [subject]'s [AnimationData] for this property. */ - fun addRunningAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).addRunningAnimation(animation) + fun addQueuedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).addQueuedAnimation(animation) /** - * Remove a running animation to the [subject]'s [AnimationData] for this property. + * Remove a queued animation from the [subject]'s [AnimationData] for this property. */ - fun removeRunningAnimator(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).removeRunningAnimation(animation) + fun removeQueuedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).removeQueuedAnimation(animation) /** * Add [endActions] to the [subject]'s [AnimationData] for this property so they can be cancelled if another @@ -79,7 +79,7 @@ interface AdditiveProperty { */ fun runEndActions(subject: Subject, animation: SinglePropertyAnimation<*>) { val animationData = getAnimationData(subject) - if (animation == animationData.runningAnimations.lastOrNull()) { + if (animation == animationData.queuedAnimations.lastOrNull()) { animationData.interruptableEndActions.forEach { it() } animationData.interruptableEndActions.clear() } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt index 4745dee..bbc6f16 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt @@ -4,16 +4,17 @@ import com.wealthfront.blend.animator.SinglePropertyAnimation import kotlin.math.roundToInt /** - * A container for storing data about running animations to facilitate blending. + * A container for storing data about queued animations to facilitate blending. * * Associated with a specific subject and a property. */ class AnimationData { /** - * All animations currently running on the associated subject for the associated property. + * All animations currently queued to run on the associated subject for the associated property. These animations + * may or may not be currently running, however. */ - val runningAnimations: MutableList> = mutableListOf() + val queuedAnimations: MutableList> = mutableListOf() /** * End actions that will be cancelled if another animation is started for the associated subject.property. */ @@ -21,35 +22,44 @@ class AnimationData { var isLatestAnimationDone = false /** - * Get the value of this property as if all running animations have finished. + * Get the value of this property as if all queued animations have finished. */ val futureValue: Float? - get() = runningAnimations.lastOrNull()?.targetValue + get() = queuedAnimations.lastOrNull { it.isStarted }?.targetValue /** - * Register a new animation that has started. + * Register a new animation that has started. This cancels any [interruptableEndActions] that we have, since a new + * animation has started. * - * This also cancels any [interruptableEndActions] that we have, since a new animation has started. + * This also cancels all non-started [queuedAnimations], in order to avoid animating to any unexpected values. + * See [this github issue](https://github.com/wealthfront/blend/issues/4) for more information. */ - fun addRunningAnimation(animation: SinglePropertyAnimation<*>) { + fun addQueuedAnimation(animation: SinglePropertyAnimation<*>) { if (isLatestAnimationDone) { - runningAnimations -= runningAnimations.last() + queuedAnimations -= queuedAnimations.last() } isLatestAnimationDone = false - runningAnimations += animation + queuedAnimations.filter { + !it.isStarted && it.isPartOfAFullyQueuedSet + }.forEach { + it.markCancelled() + queuedAnimations.remove(it) + } + queuedAnimations += animation interruptableEndActions.clear() } /** - * Remove a running animation because it has either finished or been cancelled. + * Remove a queued animation because it has either finished or been cancelled. */ - fun removeRunningAnimation(animation: SinglePropertyAnimation<*>) { - if (runningAnimations.lastOrNull() == animation && runningAnimations.size > 1) { + fun removeQueuedAnimation(animation: SinglePropertyAnimation<*>) { + animation.markCancelled() + if (queuedAnimations.lastOrNull() == animation && queuedAnimations.size > 1) { isLatestAnimationDone = true } else { - runningAnimations -= animation - if (isLatestAnimationDone && runningAnimations.size == 1) { - runningAnimations.clear() + queuedAnimations -= animation + if (isLatestAnimationDone && queuedAnimations.size == 1) { + queuedAnimations.clear() isLatestAnimationDone = false } } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt index 0be6d69..a7b3041 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt @@ -35,7 +35,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE @@ -67,7 +67,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt index f882940..3e7effe 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt @@ -44,7 +44,7 @@ fun makeExternalAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationStart(subject: Subject) { + override fun setUpOnAnimationQueued(subject: Subject) { setUpAction?.invoke(subject) } } @@ -101,7 +101,7 @@ internal fun makeAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationStart(subject: Subject) { + override fun setUpOnAnimationQueued(subject: Subject) { setUpAction?.invoke(subject) } } @@ -159,7 +159,7 @@ internal fun wrapViewProperty( override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { setUpAction?.invoke(subject) } } diff --git a/blend-library/src/test/java/com/wealthfront/TestExtensions.kt b/blend-library/src/test/java/com/wealthfront/TestExtensions.kt new file mode 100644 index 0000000..b0f9088 --- /dev/null +++ b/blend-library/src/test/java/com/wealthfront/TestExtensions.kt @@ -0,0 +1,39 @@ +@file:Suppress("UNCHECKED_CAST") + +package com.wealthfront + +import org.mockito.ArgumentCaptor +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.stubbing.OngoingStubbing +import org.mockito.stubbing.Stubber + +internal fun whenever(x: T): OngoingStubbing = `when`(x) + +internal fun Stubber.whenever(x: T): T = `when`(x) + +// Casting to avoid a kotlin-NPE ( See : https://medium.com/@elye.project/befriending-kotlin-and-mockito-1c2e7b0ef791 ) +internal fun any(): T { + Mockito.any() + return null as T +} + +internal fun eq(value: T): T { + Mockito.eq(value) + return value +} + +internal fun isA(value: Class): T { + Mockito.isA(value) + return null as T +} + +internal fun argThat(argumentMatcher: (R) -> Boolean): T { + Mockito.argThat { arg: R -> argumentMatcher(arg) } + return null as T +} + +internal fun ArgumentCaptor.captureNotNull(): T { + this.capture() + return null as T +} diff --git a/blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt b/blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt new file mode 100644 index 0000000..52683e0 --- /dev/null +++ b/blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt @@ -0,0 +1,116 @@ +package com.wealthfront.blend.properties + +import android.view.View +import com.google.common.truth.Truth.assertThat +import com.wealthfront.blend.animator.BlendableAnimator +import com.wealthfront.blend.animator.SinglePropertyAnimation +import com.wealthfront.whenever +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations.initMocks + +class AnimationDataTest { + + lateinit var animationData: AnimationData + @Mock lateinit var view: View + @Mock lateinit var property: AdditiveProperty + @Mock lateinit var startedAnimator: BlendableAnimator + @Mock lateinit var unstartedAnimator: BlendableAnimator + + @Before + fun setUp() { + initMocks(this) + animationData = AnimationData() + + whenever(startedAnimator.isStarted).thenReturn(true) + } + + @Test + fun getFutureValue_currentValue() { + assertThat(animationData.futureValue).isNull() + } + + @Test + fun getFutureValue_futureValue() { + val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) + assertThat(animationData.futureValue).isWithin(0.01f).of(20f) + } + + @Test + fun getFutureValue_futureValue_animationEndedEarly() { + val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(latestAnimation) + assertThat(animationData.futureValue).isWithin(0.01f).of(20f) + } + + @Test + fun getFutureValue_futureValue_animationEndedEarly_clear() { + val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(firstAnimation) + assertThat(animationData.futureValue).isNull() + } + + @Test + fun removeChainedAnimation_newAnimationAdded() { + val delayedAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = unstartedAnimator + isPartOfAFullyQueuedSet = true + } + val newAnimation = SinglePropertyAnimation(view, property, 20f) + animationData.addQueuedAnimation(delayedAnimation) + animationData.addQueuedAnimation(newAnimation) + animationData.removeQueuedAnimation(newAnimation) + assertThat(animationData.futureValue).isNull() + } + + @Test + fun doNotRemoveChainedAnimation_newAnimationAdded_chainedAnimationIsPartOfUnfinishedSet() { + val delayedAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = unstartedAnimator + isPartOfAFullyQueuedSet = false + } + val newAnimation = SinglePropertyAnimation(view, property, 20f) + animationData.addQueuedAnimation(delayedAnimation) + animationData.addQueuedAnimation(newAnimation) + animationData.removeQueuedAnimation(newAnimation) + delayedAnimation.animator = startedAnimator + assertThat(animationData.futureValue).isNotNull() + } +} \ No newline at end of file diff --git a/blend-test/build.gradle.kts b/blend-test/build.gradle.kts index 3232cc5..5b7b7ef 100644 --- a/blend-test/build.gradle.kts +++ b/blend-test/build.gradle.kts @@ -19,6 +19,8 @@ dependencies { exclude(group = "commons-logging", module = "commons-logging") exclude(group = "org.apache.httpcomponents", module = "httpclient") } + + testImplementation(Libs.testCore) } android { diff --git a/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt b/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt index 41ef1bd..e5c6a8f 100644 --- a/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt +++ b/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt @@ -5,7 +5,7 @@ import com.wealthfront.blend.animator.BlendableAnimator class ImmediateBlendableAnimator : BlendableAnimator() { override fun start() { - commitFutureValuesIfNotCommitted() + queueAnimationsIfNotAlreadyQueued() innerAnimator.listeners?.forEach { it.onAnimationStart(innerAnimator) } animations.forEach { it.applyChanges(1f) } everyFrameActions.forEach { it() } diff --git a/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt b/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt new file mode 100644 index 0000000..f911575 --- /dev/null +++ b/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt @@ -0,0 +1,9 @@ +package com.wealthfront + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider.getApplicationContext + +val application: Context + @JvmName("application") + get() = getApplicationContext() diff --git a/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt b/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt index b06b008..3bc72cc 100644 --- a/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt +++ b/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt @@ -17,17 +17,13 @@ import com.google.common.truth.Truth.assertThat import com.wealthfront.ViewSubject.Companion.VIEW_SUBJECT_FACTORY import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ViewAssertionExtensionsTest { val view = View(application) diff --git a/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt b/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt index 537aa4f..4663f64 100644 --- a/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt +++ b/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt @@ -17,17 +17,13 @@ import com.wealthfront.ViewAssertions.assertThatView import com.wealthfront.ViewSubject.Companion.VIEW_SUBJECT_FACTORY import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ViewSubjectTest { val view = View(application) diff --git a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt index 164a2b2..54ac3e2 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt @@ -13,25 +13,23 @@ import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.content.ContextCompat.getColor import com.google.common.truth.Truth.assertThat -import com.wealthfront.blend.test.R import com.wealthfront.ViewAssertions.assertThatView +import com.wealthfront.application import com.wealthfront.blend.animator.BlendableAnimator import com.wealthfront.blend.mock.ImmediateBlend import com.wealthfront.blend.properties.AdditiveProperty import com.wealthfront.blend.properties.AnimationData +import com.wealthfront.blend.test.R import com.wealthfront.ktx.matchParentHeight import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config // Tests requiring animation listeners use an `Instant` subclass of Blend to avoid the robolectric issue // described here: https://github.com/robolectric/robolectric/issues/2930 @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class BlendDslTest { val blend: Blend = Blend() @@ -332,7 +330,7 @@ class BlendDslTest { return animationDatas[subject] ?: AnimationData().also { animationDatas[subject] = it } } - override fun setUpOnAnimationStart(subject: TestObject) { + override fun setUpOnAnimationQueued(subject: TestObject) { addInterruptableEndActions(subject, { animationDatas.remove(subject) }) diff --git a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt index 7320ad7..bba98d1 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt @@ -8,9 +8,7 @@ import android.view.ViewGroup import android.view.animation.LinearInterpolator import com.google.common.truth.Truth.assertThat import com.wealthfront.any -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.properties.AdditiveProperty -import com.wealthfront.blend.properties.AnimationData import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA import com.wealthfront.blend.properties.AdditiveViewProperties.ELEVATION import com.wealthfront.blend.properties.AdditiveViewProperties.HEIGHT @@ -32,6 +30,7 @@ import com.wealthfront.blend.properties.AdditiveViewProperties.WIDTH import com.wealthfront.blend.properties.AdditiveViewProperties.X import com.wealthfront.blend.properties.AdditiveViewProperties.Y import com.wealthfront.blend.properties.AdditiveViewProperties.Z +import com.wealthfront.blend.properties.AnimationData import com.wealthfront.eq import com.wealthfront.whenever import org.junit.Before @@ -44,10 +43,8 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class BlendableAnimatorTest { lateinit var blendableAnimator: BlendableAnimator @@ -129,7 +126,7 @@ class BlendableAnimatorTest { } @Test - fun animateValue_multipleAnimators_respectFutureAnimations() { + fun animateValue_multipleAnimators_respectStartedAnimations() { setUpTag(subject, X) blendableAnimator.addAnimation(SinglePropertyAnimation( subject = subject, @@ -144,8 +141,9 @@ class BlendableAnimatorTest { )) secondBlendableAnimator.interpolator = LinearInterpolator() whenever(X.getCurrentValue(subject)).thenReturn(0f) + whenever(valueAnimator.isStarted).thenReturn(true) - blendableAnimator.commitFutureValuesIfNotCommitted() + blendableAnimator.start() secondBlendableAnimator.start() verify(secondValueAnimator).addUpdateListener(animatorUpdateListenerCaptor.capture()) diff --git a/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt b/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt index 8e06304..2c99587 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt @@ -2,9 +2,9 @@ package com.wealthfront.blend.mock import android.view.View import com.google.common.truth.Truth.assertThat +import com.wealthfront.application import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.animator.BlendableAnimator import com.wealthfront.blend.animator.SinglePropertyAnimation import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA @@ -13,11 +13,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ImmediateBlendableAnimatorTest { lateinit var animator: BlendableAnimator diff --git a/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt b/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt index 691aaf5..ee9dc1f 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt @@ -3,7 +3,6 @@ package com.wealthfront.blend.mock import android.view.View import android.view.ViewGroup import com.google.common.truth.Truth.assertThat -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.builder.SetPropertyValueAction import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA import com.wealthfront.blend.properties.AdditiveViewProperties.TRANSLATION_X @@ -14,10 +13,8 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class MockBlendDslTest { lateinit var blend: MockBlend diff --git a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt b/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt deleted file mode 100644 index 4a8f764..0000000 --- a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.wealthfront.blend.properties - -import android.view.View -import com.google.common.truth.Truth.assertThat -import com.wealthfront.blend.animator.SinglePropertyAnimation -import org.junit.Before -import org.junit.Test -import org.mockito.Mock -import org.mockito.MockitoAnnotations.initMocks - -class AnimationDataTest { - - lateinit var animationData: AnimationData - @Mock lateinit var view: View - @Mock lateinit var property: AdditiveProperty - - @Before - fun setUp() { - initMocks(this) - animationData = AnimationData() - } - - @Test - fun getFutureValue_currentValue() { - assertThat(animationData.futureValue).isNull() - } - - @Test - fun getFutureValue_futureValue() { - val firstAnimation = SinglePropertyAnimation(view, property, 10f) - val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) - assertThat(animationData.futureValue).isWithin(0.01f).of(20f) - } - - @Test - fun getFutureValue_futureValue_animationEndedEarly() { - val firstAnimation = SinglePropertyAnimation(view, property, 10f) - val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(latestAnimation) - assertThat(animationData.futureValue).isWithin(0.01f).of(20f) - } - - @Test - fun getFutureValue_futureValue_animationEndedEarly_clear() { - val firstAnimation = SinglePropertyAnimation(view, property, 10f) - val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(firstAnimation) - assertThat(animationData.futureValue).isNull() - } -} \ No newline at end of file