androidandroid-jetpack-composeandroid-espressoandroid-testingandroid-jetpack-compose-testing

How to simulate a swipe gesture on a Card in a Composable Test?


I have an app that has a composable MyCard().

I am testing the app in myTest() and would like to simulate a swipeRight gesture on the card.

When I use performTouchInput { swipeRight() } nothing happens. The UI does not update and card stays in the same place.

How can I simulate swipe right gesture on the card? What am I missing?

Desired Result

enter image description here

Code

@OptIn(ExperimentalCoroutinesApi::class)
class MyTest {
    @get:Rule
    val composeRule = createComposeRule()

    @Before
    fun setUp() {
        composeRule.setContent {
             MyCard()
        }
    }

    @Test
    fun myTest() = runTest {
        composeRule.onNodeWithTag("DraggableCard")
            .performTouchInput { swipeRight() }
    }
}
@SuppressLint("UnusedTransitionTargetStateParameter")
@Composable
fun MyCard() {
    var swipeState by remember { mutableStateOf(false) }

    val transitionState = remember {
        MutableTransitionState(swipeState).apply { targetState = !swipeState }
    }
    val transition = updateTransition(transitionState, "cardTransition")

    val offsetTransition by transition.animateFloat(
        label = "cardOffsetTransition",
        transitionSpec = { tween(durationMillis = 300) },
        targetValueByState = { if (swipeState) 75f else 0f },)

    Card(
        modifier = Modifier
            .testTag("DraggableCard")
            .fillMaxWidth()
            .height(35.dp)
            .padding(horizontal = 4.dp, vertical = 1.dp)
            .offset { IntOffset(offsetTransition.roundToInt(), 0) }
            .pointerInput(Unit) {
                detectHorizontalDragGestures { _, dragAmount ->
                    when {
                        dragAmount >= 6 -> { swipeState = true }
                        dragAmount < -6 -> { swipeState = false }
                    }
                }
            },
        backgroundColor = Color.Gray,
        content = { Text(text = "Hello") }
    )
}


Solution

  • I had to be careful with which node I was swiping right on and how I controlled the tests autoclock.

    If it is not working, try swiping right different nodes in the semantics hierarchy, not the card itself.

    Below is the pseudo code that eventually worked for me.

    private fun swipeRight(symbol: String) {
        composeRule.waitForIdle()
    
        composeRule.mainClock.autoAdvance = false
    
        composeRule.onNode(hasText("DraggableCard"))
            .performTouchInput { swipeRight() }
    
        composeRule.mainClock.advanceTimeBy(ANIMATION_DURATION.toLong() + 5L) /*add 5s buffer*/
    
        composeRule.mainClock.autoAdvance = true
    
        composeRule.onNode(hasText("DraggableCard")).assertExists()
            .performClick()
    
    }
    
    
    @SuppressLint("UnusedTransitionTargetStateParameter")
    @Composable
    fun DraggableCard(
        isSwiped: Boolean,
        cardHeight: Dp,
        cardOffset: Float,
    ) {
        val transitionState = remember {
            MutableTransitionState(isSwiped).apply {
                targetState = !isSwiped
            }
        }
        val transition = updateTransition(transitionState, "cardTransition")
    
        val offsetTransition by transition.animateFloat(
            label = "cardOffsetTransition",
            transitionSpec = { tween(durationMillis = 300) },
            targetValueByState = { if (isSwiped) cardOffset else 0f },
            )
    
        Card(
            modifier = Modifier
                .semantics(mergeDescendants = false) {
                    testTag = "DraggableCard"
                }
                .fillMaxWidth()
                .height(cardHeight)
                .offset { IntOffset(offsetTransition.roundToInt(), 0) }
                .pointerInput(Unit) {
                    detectHorizontalDragGestures { _, dragAmount ->
                        when {
                            dragAmount >= 6 -> {}
                            dragAmount < -6 -> {}
                        }
                    }
                },
            content =
            { Text(text = "Hello") }
        )
    }