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?
@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") }
)
}
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") }
)
}