~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-android

1 collaborator
0
1
main
60 lines · 2.4 KB Raw History
package social.grain.support

import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.performScrollToNode

/**
 * Assert that [text] reaches the screen, allowing for the fetch behind it.
 *
 * A plain `assertExists` is a snapshot of one instant, and every one of these
 * screens is drawing something a request has to answer first. Waiting is not a
 * weaker assertion — it still fails if the text never arrives — but it does not
 * fail because a loaded machine took an extra frame.
 *
 * `waitUntil` rather than a sleep: under Robolectric the recomposer runs on the
 * test thread, so blocking it stops the very work being waited for.
 */
fun ComposeTestRule.awaitText(
    text: String,
    substring: Boolean = false,
    timeoutMs: Long = 10_000,
) {
    waitUntil(timeoutMs) {
        onAllNodes(hasText(text, substring = substring)).fetchSemanticsNodes().isNotEmpty()
    }
}

/** As [awaitText], for something named only by its content description. */
fun ComposeTestRule.awaitContentDescription(value: String, timeoutMs: Long = 10_000) {
    waitUntil(timeoutMs) {
        onAllNodes(hasContentDescription(value)).fetchSemanticsNodes().isNotEmpty()
    }
}

/**
 * Scroll whatever list is on screen until [text] is composed, then assert it.
 *
 * A lazy list never composes what is below the fold, so `assertExists` on a
 * chip four screens down fails for the layout rather than for the thing being
 * tested — and `performScrollTo` can't help, because it needs the node to exist
 * before it can scroll to it.
 */
fun ComposeTestRule.scrollTo(text: String) {
    val target = hasText(text)
    if (onAllNodes(target).fetchSemanticsNodes().isNotEmpty()) return

    // A screen can have several scrollable regions — a photo strip beside the
    // form, a chip row inside it — so try each rather than assuming which one
    // holds the row being looked for.
    val scrollers = onAllNodes(androidx.compose.ui.test.hasScrollAction()).fetchSemanticsNodes()
    for (index in scrollers.indices) {
        runCatching {
            onAllNodes(androidx.compose.ui.test.hasScrollAction())[index]
                .performScrollToNode(target)
        }
        if (onAllNodes(target).fetchSemanticsNodes().isNotEmpty()) return
    }
    onNode(target).assertExists()
}