Skip to content

Commit

Permalink
Compose List Item Child (#60)
Browse files Browse the repository at this point in the history
* Compose List Item Child

* Simplify list child declaration

lazy { getChild() } -> child {}
  • Loading branch information
alex-tiurin authored Mar 7, 2024
1 parent dd15bc8 commit d3cc9cd
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ object ComposeListPage : Page<ComposeListPage>() {
fun getContactItemByTestTag(contact: Contact): ComposeFriendListItem = lazyList.getItem(hasTestTag(getContactItemTestTagById(contact)))
fun getContactItemByName(contact: Contact): ComposeFriendListItem = lazyList.getItem(hasAnyDescendant(hasText(contact.name) and hasTestTag(contactNameTestTag)))
class ComposeFriendListItem : UltronComposeListItem() {
val name by lazy { getChild(hasTestTag(contactNameTestTag)) }
val name by child { hasTestTag(contactNameTestTag) }
val status by lazy { getChild(hasTestTag(contactStatusTestTag)) }
val notExisted by child { hasTestTag("NotExistedChild") }
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object FriendsListPage : Page<FriendsListPage>() {
}

class FriendRecyclerItem : UltronRecyclerViewItem() {
val name by lazy { getChild(withId(R.id.tv_name)) }
val name by child { withId(R.id.tv_name) }
val status by lazy { getChild(withId(R.id.tv_status)) }
val avatar by lazy { getChild(withId(R.id.avatar)) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package com.atiurin.sampleapp.tests.compose

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.test.*
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.performScrollToIndex
import com.atiurin.sampleapp.activity.ComposeListActivity
import com.atiurin.sampleapp.compose.*
import com.atiurin.sampleapp.compose.contactStatusTestTag
import com.atiurin.sampleapp.compose.contactsListContentDesc
import com.atiurin.sampleapp.compose.contactsListHeaderTag
import com.atiurin.sampleapp.compose.contactsListTestTag
import com.atiurin.sampleapp.compose.getContactItemTestTagById
import com.atiurin.sampleapp.data.repositories.CONTACTS
import com.atiurin.sampleapp.data.repositories.ContactRepositoty
import com.atiurin.sampleapp.framework.utils.AssertUtils
Expand All @@ -18,14 +24,15 @@ import com.atiurin.ultron.core.common.options.ContentDescriptionContainsOption
import com.atiurin.ultron.core.common.options.TextContainsOption
import com.atiurin.ultron.core.compose.createUltronComposeRule
import com.atiurin.ultron.core.compose.list.composeList
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.assertTextEquals
import com.atiurin.ultron.extensions.click
import com.atiurin.ultron.extensions.assertIsDisplayed
import com.atiurin.ultron.extensions.findNodeInTree
import org.junit.Assert
import org.junit.Rule
import org.junit.Test

class ComposeListTest: BaseTest() {
class ComposeListTest : BaseTest() {
@get:Rule
val composeRule = createUltronComposeRule<ComposeListActivity>()

Expand All @@ -38,22 +45,25 @@ class ComposeListTest: BaseTest() {
fun item_existItem() {
val index = 20
val contact = CONTACTS[index]
listWithMergedTree.item(hasText(contact.name))
listWithMergedTree.item(hasAnyDescendant(hasText(contact.name)))
.assertIsDisplayed()
.assertTextContains(contact.name)
.assertMatches(hasAnyDescendant(hasText(contact.name)))
}

@Test
fun item_notExistItem() {
AssertUtils.assertException { listWithMergedTree.item(hasText("123gakshgdasl kgas")).assertIsDisplayed() }
AssertUtils.assertException {
listWithMergedTree.item(hasText("123gakshgdasl kgas")).assertIsDisplayed()
}
}

@Test
fun visibleItem_indexInScope() {
val index = 2
val contact = CONTACTS[index]
listWithMergedTree.visibleItem(index)
.assertTextContains(contact.name)
listWithMergedTree.visibleItem(index).printToLog("ULTRON")
.assertMatches(hasAnyDescendant(hasText(contact.name)))

}

@Test
Expand All @@ -68,8 +78,8 @@ class ComposeListTest: BaseTest() {
val contact = CONTACTS[0]
listWithMergedTree.firstVisibleItem()
.assertIsDisplayed()
.assertTextContains(contact.name)
.assertTextContains(contact.status)
.assertMatches(hasAnyDescendant(hasText(contact.name)))
.assertMatches(hasAnyDescendant(hasText(contact.status)))
}

@Test
Expand Down Expand Up @@ -117,7 +127,7 @@ class ComposeListTest: BaseTest() {
}
hasText(contact.name).assertIsDisplayed()
Assert.assertTrue(children.size > 10)
val child = children.find { child -> child.config[SemanticsProperties.Text].any { it.text == contact.name } }
val child = children.findNodeInTree(hasText(contact.name))
Assert.assertNotNull(child)
}

Expand Down Expand Up @@ -256,26 +266,26 @@ class ComposeListTest: BaseTest() {
}

@Test
fun assertVisibleItemsCount_properCountProvided(){
fun assertVisibleItemsCount_properCountProvided() {
val count = listWithMergedTree.getVisibleItemsCount()
listWithMergedTree.assertVisibleItemsCount(count)
}

@Test
fun assertVisibleItemsCount_invalidCountProvided(){
fun assertVisibleItemsCount_invalidCountProvided() {
AssertUtils.assertException { listWithMergedTree.withTimeout(1000).assertVisibleItemsCount(100) }
}

@Test
fun itemByPosition_propertyConfiguredTest(){
fun itemByPosition_propertyConfiguredTest() {
val index = 20
val contact = CONTACTS[index]
val item = listPage.lazyList.item(20).assertIsDisplayed()
item.assertMatches(hasTestTag(getContactItemTestTagById(contact)))
}

@Test
fun getItemByPosition_propertyConfiguredTest(){
fun getItemByPosition_propertyConfiguredTest() {
val index = 20
val contact = CONTACTS[index]
listPage.getItemByPosition(index).apply {
Expand All @@ -286,18 +296,27 @@ class ComposeListTest: BaseTest() {
}

@Test
fun assertItemDoesNotExistWithSearch_NotExistedItem(){
fun assertItemDoesNotExistWithSearch_NotExistedItem() {
listWithMergedTree.assertItemDoesNotExist(hasText("NOT EXISTED TeXT"))
}

@Test
fun assertItemDoesNotExistWithSearch_ExistedItem(){
fun assertItemDoesNotExistWithSearch_ExistedItem() {
val contact = ContactRepositoty.getLast()
AssertUtils.assertException {
listWithMergedTree.withTimeout(2000).assertItemDoesNotExist(hasText(contact.name))
}
}

@Test
fun getItem_NotExistedItemChild() {
val index = 20
val contact = CONTACTS[index]
listPage.getContactItemByName(contact).apply {
AssertUtils.assertException { notExisted.withTimeout(1000).assertIsDisplayed() }
}
}

private fun setEmptyListContent() {
composeRule.setContent {
LazyColumn(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.atiurin.sampleapp.tests.compose

import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import com.atiurin.sampleapp.activity.ComposeListWithPositionTestTagActivity
import com.atiurin.sampleapp.compose.ListItemPositionPropertyKey
import com.atiurin.sampleapp.compose.contactsListContentDesc
Expand All @@ -26,7 +28,7 @@ class ComposeListWithPositionTestTagTest {
val contact = CONTACTS[index]
list.item(hasTestTag(getContactItemTestTagByPosition(index)))
.assertIsDisplayed()
.assertTextContains(contact.name)
.assertMatches(hasAnyDescendant(hasText(contact.name)))
}

@Test
Expand All @@ -35,30 +37,31 @@ class ComposeListWithPositionTestTagTest {
val contact = CONTACTS[count]
list.lastVisibleItem()
.assertIsDisplayed()
.assertTextContains(contact.name)
.assertTextContains(contact.status)
.assertMatches(hasAnyDescendant(hasText(contact.name)))
.assertMatches(hasAnyDescendant(hasText(contact.status)))
}

@Test
fun itemByPosition_propertyNOTConfiguredInTest(){
fun itemByPosition_propertyNOTConfiguredInTest() {
AssertUtils.assertException {
list.item(20).assertIsDisplayed()
}
}

@Test
fun itemByPosition_propertyNOTConfiguredInApplication(){
fun itemByPosition_propertyNOTConfiguredInApplication() {
AssertUtils.assertException {
composeListWithProperty.withTimeout(1000).item(20).assertIsDisplayed()
}
}

@Test
fun getItemByPosition_propertyNOTConfiguredInTest(){
fun getItemByPosition_propertyNOTConfiguredInTest() {
AssertUtils.assertException { list.getItem<ComposeListPage.ComposeFriendListItem>(20).assertIsDisplayed() }
}

@Test
fun getItemByPosition_propertyNOTConfiguredInApplication(){
fun getItemByPosition_propertyNOTConfiguredInApplication() {
AssertUtils.assertException {
composeListWithProperty.withTimeout(1000).getItem<ComposeListPage.ComposeFriendListItem>(20).assertIsDisplayed()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -58,9 +59,7 @@ fun ContactsList(
testTagProvider: (Contact, Int) -> String,
modifierProvider: (Int) -> Modifier,
) {
var selectedItem = remember {
mutableStateOf("")
}
val selectedItem = remember { mutableStateOf("") }
Text(text = "Selected item = ${selectedItem.value}")
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
Expand All @@ -76,39 +75,43 @@ fun ContactsList(
}
}
itemsIndexed(contacts, key = { _, c -> c.name }) { index, contact ->
Column(
modifier = modifierProvider.invoke(index)
.then(Modifier.clickable {
selectedItem.value = contact.name
val intent = Intent(context, ComposeSecondActivity::class.java)
intent.putExtra(ComposeSecondActivity.INTENT_CONTACT_ID, contact.id)
ContextCompat.startActivity(context, intent, null)
})
.then(Modifier.semantics {
testTag = testTagProvider.invoke(contact, index)
})
Box(modifier = modifierProvider
.invoke(index)
.semantics {
testTag = testTagProvider.invoke(contact, index)
}
) {
Row {
Image(
painter = painterResource(contact.avatar),
contentDescription = "avatar",
contentScale = ContentScale.Crop, // crop the image if it's not a square
modifier = Modifier
.size(80.dp)
.clip(CircleShape) // clip to the circle shape
.border(2.dp, Color.Transparent, CircleShape) // add a border (optional)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(contact.name, Modifier.semantics { testTag = contactNameTestTag }, fontSize = TextUnit(20f, TextUnitType.Sp))
Spacer(modifier = Modifier.height(8.dp))
Text(text = contact.status, Modifier.semantics { testTag = contactStatusTestTag }, fontSize = TextUnit(16f, TextUnitType.Sp))
Spacer(modifier = Modifier.height(8.dp))
}
Column(
modifier = Modifier
.then(Modifier.clickable {
selectedItem.value = contact.name
val intent = Intent(context, ComposeSecondActivity::class.java)
intent.putExtra(ComposeSecondActivity.INTENT_CONTACT_ID, contact.id)
ContextCompat.startActivity(context, intent, null)
})
) {
Row {
Image(
painter = painterResource(contact.avatar),
contentDescription = "avatar",
contentScale = ContentScale.Crop, // crop the image if it's not a square
modifier = Modifier
.size(80.dp)
.clip(CircleShape) // clip to the circle shape
.border(2.dp, Color.Transparent, CircleShape) // add a border (optional)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(contact.name, Modifier.semantics { testTag = contactNameTestTag }, fontSize = TextUnit(20f, TextUnitType.Sp))
Spacer(modifier = Modifier.height(8.dp))
Text(text = contact.status, Modifier.semantics { testTag = contactStatusTestTag }, fontSize = TextUnit(16f, TextUnitType.Sp))
Spacer(modifier = Modifier.height(8.dp))
}

}
Spacer(modifier = Modifier.height(8.dp))
Divider(color = Color.Black)
}
Spacer(modifier = Modifier.height(8.dp))
Divider(color = Color.Black)
}
}
}
Expand All @@ -122,4 +125,4 @@ val ListItemPositionPropertyKey = SemanticsPropertyKey<Int>("ListItemPosition")
var SemanticsPropertyReceiver.listItemPosition by ListItemPositionPropertyKey
fun Modifier.listItemPosition(position: Int): Modifier {
return semantics { listItemPosition = position }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.atiurin.ultron.core.compose

import androidx.activity.ComponentActivity
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.SemanticsNodeInteractionCollection
import androidx.compose.ui.test.SemanticsSelector
import androidx.compose.ui.test.TestContext
import androidx.compose.ui.test.junit4.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
Expand Down Expand Up @@ -80,4 +83,3 @@ fun createEmptyUltronComposeRule(): ComposeTestRule {

fun ComposeTestRule.getTestContext() = this.getProperty<TestContext>("testContext")


Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.atiurin.ultron.core.compose.operation.ComposeOperationType
import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams
import com.atiurin.ultron.exceptions.UltronAssertionException
import com.atiurin.ultron.exceptions.UltronException
import com.atiurin.ultron.extensions.findNodeInTree
import com.atiurin.ultron.utils.AssertUtils
import org.junit.Assert

Expand Down Expand Up @@ -51,6 +52,7 @@ class UltronComposeList(
}
return UltronComposeListItem(this, position, true)
}

fun firstItem(): UltronComposeListItem = item(0)

/**
Expand Down Expand Up @@ -147,7 +149,7 @@ class UltronComposeList(
) { listInteraction ->
listInteraction.performScrollToNode(itemMatcher)
.onChildren().filterToOne(itemMatcher)
.onChildren().filterToOne(childMatcher)
.findNodeInTree(childMatcher, useUnmergedTree)
}
)

Expand All @@ -171,7 +173,7 @@ class UltronComposeList(
operationType = ComposeOperationType.GET_LIST_ITEM_CHILD
)
) { listInteraction ->
listInteraction.onChildAt(index).onChildren().filterToOne(childMatcher)
listInteraction.onChildAt(index).findNodeInTree(childMatcher, useUnmergedTree)
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ open class UltronComposeListItem {
*/
protected constructor()

fun child(block: () -> SemanticsMatcher): Lazy<UltronComposeSemanticsNodeInteraction> = lazy {
getChild(block())
}

fun setExecutor(ultronComposeList: UltronComposeList, itemMatcher: SemanticsMatcher) {
this.executor = MatcherComposeItemExecutor(ultronComposeList, itemMatcher)
}
Expand Down Expand Up @@ -129,6 +133,7 @@ open class UltronComposeListItem {
fun inputTextSelection(selection: TextRange) = apply { getItemUltronComposeInteraction().inputTextSelection(selection) }
fun clearText() = apply { getItemUltronComposeInteraction().clearText() }
fun replaceText(text: String) = apply { getItemUltronComposeInteraction().replaceText(text) }
fun printToLog(tag: String, maxDepth: Int = Int.MAX_VALUE) = apply { getItemUltronComposeInteraction().printToLog(tag, maxDepth) }

@OptIn(ExperimentalTestApi::class)
fun performMouseInput(block: MouseInjectionScope.() -> Unit) = apply { getItemUltronComposeInteraction().performMouseInput(block) }
Expand Down
Loading

0 comments on commit d3cc9cd

Please sign in to comment.