We are slowly migrating our big app to Compose and...
# compose
z
We are slowly migrating our big app to Compose and something that we stumbled upon is testing
LazyColumn
. We have many tests where we show a list of items, scroll to a particular index and perform actions on the item at that index - e.g. click it. But I can't find a way to do that using only an item index. There's a method performScrollToIndex(index) that does the scrolling, but after that it's not possible to get the semantics node at that index. If you do something like:
Copy code
val index = 1000
onNodeWithTag(COUNTRY_LIST).performScrollToIndex(index)
onNodeWithTag(COUNTRY_LIST).onChildAt(index).performClick()
it doesn't work, because the LazyColumn (it has a
Modifier.testTag(COUNTRY_LIST)
) only has "_<number_of_visible_items> + 2"_ number of children (nodes) at a given time. Ideally I'd like to do something like
onNodeWithTag(COUNTRY_LIST).performScrollToIndex(index).performClick()
, but
performScrollToIndex
returns the semantics node on which the method was called 😞 If you have a test tag, text or anything else that can be used to match the item at
index
, then you can perform actions/assertions on it, but do you know of a way to do that having only an index/position?
m
You might want to try something like this after you scroll:
Copy code
rule.onAllNodesWithTag("foobar")[0].assert(hasText("bizzbuzz"))
The key is that you need to assign the same test tag to every row in your LazyColumn. Your other option is to assign a test tag with some sort of prefix along with a suffix that indicates the row, and then you can select each row directly.
Copy code
LazyColumn {
    itemsIndexed(listOf(1,2,3,4)) { index, item ->
        Box(modifier = 
               Modifier.testTag("row:${index}")) {
        }
    }
}

rule.onNodeWithTag("row:0").assert(...)
z
The first proposal will not work in all cases - e.g. if you want to perform actions on the last element in the list 🙂 In that case the element in question wouldn't be the first node with a tag. The second option will work, but it feels a bit like a hack. I hope that there would be some hidden API to do that without having to explicitly set Tags with position in it or similar. There's a way how to scroll to a particular position, so there must be a way to match a node on that position as well 😄
m
@Zhelyazko Atanasov The point is that onAllNodesWithTag returns you a collection. YOu can index it however you want. Either with first indexes, or with the first() or last() function.
z
cc @Jelle Fresen [G]
j
Apologies, I haven't opened my Slack in weeks (months?). Yes, this is a known problem that has been reported before in https://issuetracker.google.com/212279112. If you have any way of identifying the row element (e.g.
hasText("United States")
), you could use
onNodeWithTag(COUNTRY_LIST).performScrollToNode(<matcher>)
and continue with
onNode(<matcher>)
. And instead of including the row index in the test tag, you could also assign a custom key to the item (
LazyColumn { items(data, key = { it.toKey() }) { .. } }
), use
performScrollToKey(aKey)
to scroll to the key, and search for the key and... hmm... I don't think we have a matcher like
hasKey(aKey)
. I'll file a bug so I can look into that.
🙏 1
z
@Jelle Fresen [G] yeah these are the options I came up with as well. As you pointed out there are some missing bits (APIs) that would make things a lot easier and simpler. I'll keep an eye on the issue tracker 🙂