airbnb / lottie-android

Render After Effects animations natively on Android and iOS, Web, and React Native
http://airbnb.io/lottie/
Apache License 2.0
35.03k stars 5.41k forks source link

Jetpack Compose tests with Lottie & infinite animations #1907

Closed krzdabrowski closed 2 years ago

krzdabrowski commented 3 years ago

Is your feature request related to a problem? Please describe. I have a Composable that will iterate continuously by using LottieConstants.IterateForever. I would like to be able to test it but as Compose Testing codelab suggests, there might be a need for special animation API like InfiniteTransition:

In Compose, the animation APIs were designed with testability in mind, so the problem can be fixed by using the correct API. Instead of restarting the animateDpAsState animation, we can use infinite animations.

Infinite animations are a special case that Compose tests understand so they're not going to keep the test busy.

Describe the solution you'd like To find a way (new API method in Lottie?) to test infinite Compose Lottie animations.

Describe alternatives you've considered I haven't found a way to combine existing Lottie API along with InfiniteTransition API.

Additional context When I'm using Lottie's standard API, I'm currently receiving same error message as defined in codelab linked above: Idling resource timed out: possibly due to compose being busy.

Any input would be appreciated.

alorma commented 3 years ago

I've found a way for this:

Create a

val LocalLottieIterations = compositionLocalOf { IterateForever }

And on test set it to 1

timusus commented 2 years ago

Another possible solution is to just take control of the compose clock yourself:

@Test
fun myTest() {
    composeTestRule.mainClock.autoAdvance = false

    setContent { 
        ...
    }

    composeTestRule.mainClock.advanceTimeBy(1000)

    performAssertion()
}

This prevents compose from re-composing indefinitely, and instead just render 1000ms worth of frames before performing your testing assertions.

timusus commented 2 years ago

Actually, now that I understand CompositionLocal, I think it's a much better approach.

Define the CompositionLocal:

import androidx.compose.runtime.compositionLocalOf
import com.airbnb.lottie.compose.LottieConstants

@JvmInline
value class LottieAnimationIterations(val iterations: Int)

val LocalLottieAnimationIterations = compositionLocalOf { LottieAnimationIterations(LottieConstants.IterateForever) }

In your regular Composable:

val progress by animateLottieCompositionAsState(
    composition = compositionResult,
    iterations = LocalLottieAnimationIterations.current.iterations
)

In your test:

composeTestRule.setContent {
    CompositionLocalProvider(LocalLottieAnimationIterations provides LottieAnimationIterations(1)) {
        MyComposable()
    }
}