Trigger swipe to dismiss behaviour in tests with jetpack compose

44 Views Asked by At

My use case is the following

I am trying to show a list of emails in a lazy column which in turn supports swipe to dismiss behaviour. I am trying to trigger the on swipe action in compose in my tests but it is not getting triggered. I would appreciate any assistance in this regard

Lazy column comprising of dummy email content

My source is as follows

  1. LazyColumn to show a list of data.

const val TAG_CONTENT = "content"

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmailContentList(
    modifier: Modifier = Modifier,
    emails: List<Email>,
    onInboxEvent: (InboxEvent) -> Unit
) {
    LazyColumn(modifier = modifier) {
        items(emails, key = {item -> item.id }) { email ->
            var isEmailDeleted by remember {
                mutableStateOf(false)
            }
            val dismissBoxState = rememberSwipeToDismissBoxState(
                positionalThreshold = { distance ->
                    distance * .25f
                }
            )
            // See https://slack-chats.kotlinlang.org/t/16382816/hi-since-the-compose-bom-composebom-2024-02-00-version-the-i#865df5e5-39c6-4dd3-9bc2-8f087300dd8d
            LaunchedEffect(key1 = dismissBoxState) {
                snapshotFlow {
                    dismissBoxState.currentValue
                }.collect {
                    if (it == SwipeToDismissBoxValue.StartToEnd) {
                        isEmailDeleted = true
                        onInboxEvent(InboxEvent.DeleteEvent(email.id))
                    } else {
                        isEmailDeleted = false
                    }
                }
            }
            val emailHeight by animateDpAsState(targetValue =
                if (isEmailDeleted) {
                    0.dp
                } else {
                    120.dp
                },
                label = "email_height",
                animationSpec = tween(delayMillis = 300)
            )

            EmailContent(
                modifier = Modifier
                    .padding(dimensionResource(id = R.dimen.email_padding_half))
                    .defaultMinSize(minHeight = emailHeight)
                    .fillMaxWidth(),
                email = email,
                height = emailHeight,
                onAccessibilityDelete = {
                    onInboxEvent(InboxEvent.DeleteEvent(email.id))
                },
                dismissState = dismissBoxState
            )
        }
    }
}

InboxEvent is a sealed interface encapsulating various states

sealed interface InboxEvent {

    data object RefreshEvent: InboxEvent

    data class DeleteEvent(val id: String): InboxEvent
}

data class

@Immutable
data class Email(
    val id: String,
    val title: String,
    val description: String
)
  1. Composable to display a single email
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmailContent(
    modifier: Modifier = Modifier,
    height: Dp,
    email: Email,
    onAccessibilityDelete: () -> Unit,
    dismissState: SwipeToDismissBoxState
) {
    SwipeToDismissBox(
        modifier = modifier,
        state = dismissState,
        enableDismissFromStartToEnd = true,
        enableDismissFromEndToStart = false,
        backgroundContent = {
            val deleteAsString = stringResource(id = R.string.inbox_delete)
            SwipeDismissBox(
                modifier = Modifier
                    .defaultMinSize(minHeight = height)
                    .fillMaxWidth()
                    .semantics {
                        customActions = listOf(
                            CustomAccessibilityAction(label = deleteAsString) {
                                onAccessibilityDelete.invoke()
                                true
                            }
                        )
                    },
                targetValue = dismissState.targetValue
            )
        }
    ) {
        val cardElevation by animateDpAsState(targetValue =
            if (dismissState.targetValue == SwipeToDismissBoxValue.StartToEnd) {
                dimensionResource(id = R.dimen.email_padding_half)
            } else {
                   0.dp
            },
            label = "card_elevation"
        )
        Card(modifier = modifier.then(Modifier.fillMaxWidth()), elevation = CardDefaults.cardElevation(cardElevation)) {
            ListItem(
                modifier = Modifier.fillMaxWidth(),
                headlineContent = {
                    Text(text = email.title, style = MaterialTheme.typography.headlineSmall)
                },
                supportingContent = {
                    Text(text = email.description, style = MaterialTheme.typography.bodyMedium,
                        maxLines = 2, overflow = TextOverflow.Ellipsis
                    )
                }
            )
        }
    }
}

The test to trigger a swipe to dismiss action

class EmailContentListTest {

    private lateinit var emails: List<Email>

    @get:Rule
    val composeRule = createComposeRule()

    @Before
    fun setUp() {
        emails = listOf(
            Email(id = "1", title = "title", description = "description")
        )
    }

    @Test
    fun validateThatEmailListIsSeen() {
        composeRule.setContent {
            JetpackComposeAuthenticationTheme {
                EmailContentList(modifier = Modifier.fillMaxSize(), emails = emails) {
                }
            }
        }
        composeRule.run {
            emails.forEach { email ->
                onNodeWithText(email.title).assertIsDisplayed()
                onNodeWithText(email.description).assertIsDisplayed()
            }
        }
    }

    @Test
    fun validateThatSwipeActionIsLessThanThresholdThenNoActionIsTaken() {
        composeRule.setContent {
            JetpackComposeAuthenticationTheme {
                EmailContentList(modifier = Modifier.fillMaxSize(), emails = emails) {
                }
            }
        }
        composeRule.run {
            emails.first().also { email ->
                onNodeWithText(email.title).assertIsDisplayed()
                onNodeWithText(email.description).assertIsDisplayed()
                onNodeWithText(email.description).performTouchInput {
                    // Swipe partially under the threshold to 20% of the horizontal distance
                    // See https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicSwipeToDismissBoxTest.kt;l=136-146?q=SwipeToDismissBoxTest
                    swipeWithVelocity(
                        start = Offset(0f, centerY),
                        end = Offset(centerX / 5f, centerY),
                        endVelocity = 1.0f
                    )
                }
                onNodeWithText(email.title).assertIsDisplayed()
                onNodeWithText(email.description).assertIsDisplayed()
            }
        }
    }

    // This is a failing test
    @Test
    fun validateThatSwipeActionIsGreaterThanThresholdThenDeleteActionIsTaken() {
        var isDeleted = false
        composeRule.setContent {
            JetpackComposeAuthenticationTheme {
                EmailContentList(modifier = Modifier.fillMaxSize(), emails = emails) {
                    isDeleted = true
                }
            }
        }
        composeRule.run {
            emails.forEachIndexed { index, email ->
                onNodeWithTag(TAG_CONTENT).onChildAt(index).performScrollTo().performTouchInput { 
                    swipeRight()
                }
               // This line fails as the the callback lambda function is not called.
                MatcherAssert.assertThat(isDeleted, Matchers.equalTo(true))
            }
        }
    }
}

I have tried to follow the pattern as shown in See https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicSwipeToDismissBoxTest.kt;l=136-146?q=SwipeToDismissBoxTest but it is not triggering the swipe callback. How can I trigger a callback on swipe action in tests?

0

There are 0 best solutions below