Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.onChildren
import io.github.kakaocup.compose.node.assertion.LazyListNodeAssertions
import io.github.kakaocup.compose.node.builder.NodeMatcher
Expand Down Expand Up @@ -84,6 +85,38 @@ class KLazyListNode(
) as T)
}

/**
* Performs given actions/assertion on descendant at given position
*
* @param T Type of item at given position. Must be registered via constructor.
* @param position Position of item in lazy list
* @param function Tail lambda which receiver will be matched item with given type T
*/
@ExperimentalTestApi
inline fun <reified T : KLazyListItemNode<*>> descendantAt(
position: Int,
function: T.() -> Unit
) {
val provideItem = itemTypes.getOrElse(T::class) {
throw LazyListItemProvisionException(T::class)
}.provideItem

performScrollToIndex(position)

val semanticsNode = semanticsProvider
.orGlobal()
.checkNotNull()
.onNode(positionMatcher(position) and
hasAnyAncestor(semanticsMatcher)
)
.fetchSemanticsNode()

function(provideItem(
semanticsNode,
semanticsProvider.orGlobal().checkNotNull()
) as T)
}

/**
* Performs given actions/assertion on child that matches given matcher
*
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ activityComposeVersion = "1.11.0"
composeMaterialVersion = "1.3.1"
composeCompilerVersion = "1.5.15"

androidxComposeBom = "2025.10.00"
androidxComposeBom = "2025.11.01"

[libraries]
dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokkaGradlePluginVersion" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class LazyListTest {
swipeDown(startY = 200f)
}
list {
assertLengthEquals(33)
assertLengthEquals(34)
firstChild<LazyListHeaderNode> {
title.assertTextEquals("Items from 1 to 10")
}
Expand All @@ -55,12 +55,15 @@ class LazyListTest {
childAt<LazyListItemNode>(32) {
assertTextEquals("Item 30")
}
descendantAt<LazyListItemNode>(33) {
assertTextEquals("Nested Item 1")
}
}
list.performScrollToIndex(0)
pullToRefresh.performTouchInput {
swipeDown(startY = 200f)
}
list.assertLengthEquals(34)
list.assertLengthEquals(35)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.github.kakaocup.compose.sample

import android.annotation.SuppressLint
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand Down Expand Up @@ -68,6 +70,7 @@ fun LazyListScreen() {
when (item) {
is LazyListItem.Header -> ListItemHeader(item, positionModifier)
is LazyListItem.Item -> ListItemCell(item, positionModifier)
is LazyListItem.NestedItem -> NestedListItemCell(item, positionModifier)
}
}
}
Expand Down Expand Up @@ -110,6 +113,23 @@ private fun ListItemCell(item: LazyListItem.Item, modifier: Modifier = Modifier)
)
}

@Composable
private fun NestedListItemCell(item: LazyListItem.NestedItem, modifier: Modifier = Modifier) {
Box(
modifier = Modifier.border(BorderStroke(2.dp, Color.Blue))
) {
Text(
item.title,
Modifier
.fillMaxWidth()
.padding(16.dp)
.testTag("LazyListItemTitle")
.then(modifier)
)
}
}


private fun getItems(): List<LazyListItem> {
val result = mutableListOf<LazyListItem>()

Expand All @@ -120,12 +140,15 @@ private fun getItems(): List<LazyListItem> {
result += LazyListItem.Item("Item ${index + 1}")
}

result += LazyListItem.NestedItem("Nested Item 1")

return result
}

private sealed class LazyListItem {
data class Header(val title: String) : LazyListItem()
data class Item(val title: String) : LazyListItem()
data class NestedItem(val title: String) : LazyListItem()
}

val LazyListItemPositionSemantics = SemanticsPropertyKey<Int>("LazyListItemPosition")
Expand Down