From 97196db5c18ef97bd6e4baff29b96534f2ca77cd Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 16 Aug 2024 05:46:45 +0900 Subject: [PATCH 1/9] :memo: Separated ContributorsScreenRobot from ContributorsScreenTest. --- core/testing/build.gradle.kts | 1 + .../testing/robot/ContributorsScreenRobot.kt | 37 +++++++++++++++++++ .../contributors/ContributorsScreenTest.kt | 36 +----------------- 3 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index f2fbc1cfe..f50133408 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(projects.feature.sponsors) implementation(projects.feature.favorites) implementation(projects.feature.eventmap) + implementation(projects.feature.contributors) implementation(libs.daggerHiltAndroidTesting) implementation(libs.roborazzi) implementation(libs.kermit) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt new file mode 100644 index 000000000..ab96f2eba --- /dev/null +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt @@ -0,0 +1,37 @@ +package io.github.droidkaigi.confsched.testing.robot + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasTestTag +import androidx.compose.ui.test.hasText +import io.github.droidkaigi.confsched.contributors.ContributorsScreen +import io.github.droidkaigi.confsched.contributors.ContributorsScreenTestTag +import io.github.droidkaigi.confsched.ui.Inject + +class ContributorsScreenRobot @Inject constructor( + screenRobot: DefaultScreenRobot, + contributorsServerRobot: DefaultContributorsServerRobot, +) : ScreenRobot by screenRobot, + ContributorsServerRobot by contributorsServerRobot { + fun setupScreenContent() { + robotTestRule.setContent { + ContributorsScreen( + onNavigationIconClick = { }, + onContributorsItemClick = { }, + ) + } + } + + fun checkContributorsDisplayed() { + composeTestRule + .onNode(hasTestTag(ContributorsScreenTestTag)) + .assertIsDisplayed() + } + + fun checkErrorSnackbarDisplayed() { + composeTestRule + .onNode( + hasText("Fake IO Exception"), + useUnmergedTree = true, + ).assertIsDisplayed() + } +} diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 9074a36ac..6b0d872b1 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -1,17 +1,12 @@ package io.github.droidkaigi.confsched.contributors -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.hasText import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidTest import io.github.droidkaigi.confsched.testing.DescribedBehavior import io.github.droidkaigi.confsched.testing.describeBehaviors import io.github.droidkaigi.confsched.testing.execute +import io.github.droidkaigi.confsched.testing.robot.ContributorsScreenRobot import io.github.droidkaigi.confsched.testing.robot.ContributorsServerRobot -import io.github.droidkaigi.confsched.testing.robot.DefaultContributorsServerRobot -import io.github.droidkaigi.confsched.testing.robot.DefaultScreenRobot -import io.github.droidkaigi.confsched.testing.robot.ScreenRobot import io.github.droidkaigi.confsched.testing.robot.runRobot import io.github.droidkaigi.confsched.testing.rules.RobotTestRule import org.junit.Rule @@ -68,32 +63,3 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior Date: Fri, 16 Aug 2024 06:09:22 +0900 Subject: [PATCH 2/9] :wrench: Match the contents of the Lists provided by the fake methods of the Staff data class and Contributor data class. --- .../io/github/droidkaigi/confsched/model/Contributor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Contributor.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Contributor.kt index 8dadebb50..672080082 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Contributor.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Contributor.kt @@ -12,11 +12,11 @@ public data class Contributor( public companion object } -public fun Contributor.Companion.fakes(): PersistentList = (0..20) +public fun Contributor.Companion.fakes(): PersistentList = (1..20) .map { Contributor( id = it, - username = it.toString(), + username = "username $it", profileUrl = "https://developer.android.com/", iconUrl = "https://placehold.jp/150x150.png", ) From 15e3c3afb728ae296b1f277a135a1bce8292ebc5 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 16 Aug 2024 06:10:35 +0900 Subject: [PATCH 3/9] :recycle: Enhanced ContributorsScreen testing. modified to check if the elements of individual items are visible. --- .../testing/robot/ContributorsScreenRobot.kt | 70 +++++++++++++++- .../contributors/ContributorsScreenTest.kt | 82 +++++++++++++++---- .../contributors/ContributorsScreen.kt | 8 +- .../component/ContributorsItem.kt | 10 ++- 4 files changed, 146 insertions(+), 24 deletions(-) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt index ab96f2eba..14d676cad 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt @@ -1,10 +1,18 @@ package io.github.droidkaigi.confsched.testing.robot +import androidx.compose.ui.test.assertContentDescriptionEquals import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.performScrollToNode +import io.github.droidkaigi.confsched.contributors.ContributorsItemTestTag +import io.github.droidkaigi.confsched.contributors.ContributorsLazyColumnTestTag import io.github.droidkaigi.confsched.contributors.ContributorsScreen -import io.github.droidkaigi.confsched.contributors.ContributorsScreenTestTag +import io.github.droidkaigi.confsched.contributors.component.ContributorsItemImageTestTag +import io.github.droidkaigi.confsched.contributors.component.ContributorsUserNameTextTestTag +import io.github.droidkaigi.confsched.model.Contributor +import io.github.droidkaigi.confsched.model.fakes import io.github.droidkaigi.confsched.ui.Inject class ContributorsScreenRobot @Inject constructor( @@ -21,10 +29,64 @@ class ContributorsScreenRobot @Inject constructor( } } - fun checkContributorsDisplayed() { + fun scrollToTestTag( + testTag: String, + ) { composeTestRule - .onNode(hasTestTag(ContributorsScreenTestTag)) - .assertIsDisplayed() + .onNode(hasTestTag(ContributorsLazyColumnTestTag)) + .performScrollToNode(hasTestTag(testTag)) + waitUntilIdle() + } + + fun checkExistsContributorItem( + fromTo: Pair, + ) { + val contributorsList = Contributor.fakes().subList(fromTo.first, fromTo.second) + contributorsList.forEach { contributor -> + composeTestRule + .onNode(hasTestTag(ContributorsItemTestTag.plus(contributor.id))) + .assertExists() + .assertIsDisplayed() + + composeTestRule + .onNode( + matcher = hasTestTag(ContributorsItemImageTestTag.plus(contributor.username)), + useUnmergedTree = true, + ) + .assertExists() + .assertIsDisplayed() + .assertContentDescriptionEquals(contributor.username) + + composeTestRule + .onNode( + matcher = hasTestTag(ContributorsUserNameTextTestTag.plus(contributor.username)), + useUnmergedTree = true, + ) + .assertExists() + .assertIsDisplayed() + .assertTextEquals(contributor.username) + } + } + + fun checkDoesNotExistsContributorItem() { + val contributor = Contributor.fakes().first() + composeTestRule + .onNode(hasTestTag(ContributorsItemTestTag.plus(contributor.id))) + .assertDoesNotExist() + + composeTestRule + .onNode( + matcher = hasTestTag(ContributorsItemImageTestTag.plus(contributor.username)), + useUnmergedTree = true, + ) + .assertDoesNotExist() + + composeTestRule + .onNode( + matcher = hasTestTag(ContributorsUserNameTextTestTag.plus(contributor.username)), + useUnmergedTree = true, + ) + .assertDoesNotExist() } fun checkErrorSnackbarDisplayed() { diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 6b0d872b1..6cfe0af05 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -37,26 +37,76 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior> { return describeBehaviors(name = "ContributorsScreen") { - describe("when launch") { + describe("when server is operational") { run { setupContributorServer(ContributorsServerRobot.ServerStatus.Operational) - setupScreenContent() } - itShould("show contributors list") { - captureScreenWithChecks( - checks = { checkContributorsDisplayed() }, - ) + describe("when launch") { + run { + setupScreenContent() + } + itShould("show contributor 1 to 5") { + captureScreenWithChecks { + checkExistsContributorItem( + fromTo = 0 to 5, + ) + } + } + describe("when scroll to contributor 9") { + run { + scrollToTestTag(ContributorsItemTestTag.plus(9)) + } + itShould("show contributor 6 to 10") { + captureScreenWithChecks { + checkExistsContributorItem( + fromTo = 5 to 10, + ) + } + } + } + describe("when scroll to contributor 12") { + run { + scrollToTestTag(ContributorsItemTestTag.plus(12)) + } + itShould("show contributor 11 to 15") { + captureScreenWithChecks { + checkExistsContributorItem( + fromTo = 10 to 15, + ) + } + } + } + describe("when scroll to contributor 20") { + run { + scrollToTestTag(ContributorsItemTestTag.plus(20)) + } + itShould("show contributor 16 to 20") { + captureScreenWithChecks { + checkExistsContributorItem( + fromTo = 15 to 20, + ) + } + } + } } - } - describe("when launch with error") { - run { - setupContributorServer(ContributorsServerRobot.ServerStatus.Error) - setupScreenContent() - } - itShould("show error message") { - captureScreenWithChecks( - checks = { checkErrorSnackbarDisplayed() }, - ) + + describe("when server is down") { + run { + setupContributorServer(ContributorsServerRobot.ServerStatus.Error) + } + describe("when launch") { + run { + setupScreenContent() + } + itShould("does not show contributor, and show snackbar") { + captureScreenWithChecks( + checks = { + checkDoesNotExistsContributorItem() + checkErrorSnackbarDisplayed() + }, + ) + } + } } } } diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt index 545874374..fedfa6d65 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt @@ -34,6 +34,8 @@ import kotlinx.collections.immutable.PersistentList const val contributorsScreenRoute = "contributors" const val ContributorsScreenTestTag = "ContributorsScreenTestTag" +const val ContributorsLazyColumnTestTag = "ContributorsLazyColumnTestTag" +const val ContributorsItemTestTag = "ContributorsItemTestTag:" fun NavGraphBuilder.contributorsScreens( onNavigationIconClick: () -> Unit, @@ -150,13 +152,15 @@ private fun Contributors( modifier: Modifier = Modifier, ) { LazyColumn( - modifier = modifier, + modifier = modifier.testTag(ContributorsLazyColumnTestTag), ) { items(contributors) { ContributorsItem( contributor = it, onClick = onContributorsItemClick, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .testTag(ContributorsItemTestTag.plus(it.id)), ) } } diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt index d03398201..9686c005e 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt @@ -17,12 +17,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import io.github.droidkaigi.confsched.model.Contributor import io.github.droidkaigi.confsched.ui.previewOverride import io.github.droidkaigi.confsched.ui.rememberAsyncImagePainter +const val ContributorsItemImageTestTag = "ContributorsItemImageTestTag:" +const val ContributorsUserNameTextTestTag = "ContributorsUserNameTextTestTag:" + private val contributorIconShape = CircleShape @Composable @@ -44,7 +48,7 @@ fun ContributorsItem( painter = previewOverride(previewPainter = { rememberVectorPainter(image = Icons.Default.Person) }) { rememberAsyncImagePainter(contributor.iconUrl) }, - contentDescription = null, + contentDescription = contributor.username, modifier = Modifier .size(52.dp) .clip(contributorIconShape) @@ -52,13 +56,15 @@ fun ContributorsItem( width = 1.dp, color = MaterialTheme.colorScheme.outline, shape = contributorIconShape, - ), + ) + .testTag(ContributorsItemImageTestTag.plus(contributor.username)), ) Text( text = contributor.username, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis, + modifier = Modifier.testTag(ContributorsUserNameTextTestTag.plus(contributor.username)), ) } } From ae10a1dcd94fffe5aa3e3884302c9c226a0805be Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:55:57 +0900 Subject: [PATCH 4/9] :wrench: The test was simplified and the test was modified to check only whether two or more contributors are displayed. --- .../testing/robot/ContributorsScreenRobot.kt | 11 ----- .../contributors/ContributorsScreenTest.kt | 40 +------------------ .../contributors/ContributorsScreen.kt | 3 +- 3 files changed, 3 insertions(+), 51 deletions(-) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt index 14d676cad..5b1601483 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt @@ -5,9 +5,7 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.performScrollToNode import io.github.droidkaigi.confsched.contributors.ContributorsItemTestTag -import io.github.droidkaigi.confsched.contributors.ContributorsLazyColumnTestTag import io.github.droidkaigi.confsched.contributors.ContributorsScreen import io.github.droidkaigi.confsched.contributors.component.ContributorsItemImageTestTag import io.github.droidkaigi.confsched.contributors.component.ContributorsUserNameTextTestTag @@ -29,15 +27,6 @@ class ContributorsScreenRobot @Inject constructor( } } - fun scrollToTestTag( - testTag: String, - ) { - composeTestRule - .onNode(hasTestTag(ContributorsLazyColumnTestTag)) - .performScrollToNode(hasTestTag(testTag)) - waitUntilIdle() - } - fun checkExistsContributorItem( fromTo: Pair, ) { diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 6cfe0af05..199ab4ebf 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -45,49 +45,13 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior Date: Fri, 16 Aug 2024 13:06:32 +0900 Subject: [PATCH 5/9] :wrench: Remove commas from description as there is a problem with VRT screenshots not displaying if there is a comma in them. --- .../droidkaigi/confsched/contributors/ContributorsScreenTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 199ab4ebf..145765bc0 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -62,7 +62,7 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior Date: Fri, 16 Aug 2024 21:36:12 +0900 Subject: [PATCH 6/9] Refactor ContributorsScreenTest --- .../testing/robot/ContributorsScreenRobot.kt | 31 +++++++--- .../confsched/testing/utils/Matchers.kt | 60 +++++++++++++++++++ .../contributors/ContributorsScreenTest.kt | 19 ++++-- .../contributors/ContributorsScreen.kt | 8 ++- 4 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt index 5b1601483..38e60390a 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt @@ -5,12 +5,16 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText -import io.github.droidkaigi.confsched.contributors.ContributorsItemTestTag +import androidx.compose.ui.test.performScrollToIndex +import io.github.droidkaigi.confsched.contributors.ContributorsItemTestTagPrefix import io.github.droidkaigi.confsched.contributors.ContributorsScreen +import io.github.droidkaigi.confsched.contributors.ContributorsTestTag import io.github.droidkaigi.confsched.contributors.component.ContributorsItemImageTestTag import io.github.droidkaigi.confsched.contributors.component.ContributorsUserNameTextTestTag import io.github.droidkaigi.confsched.model.Contributor import io.github.droidkaigi.confsched.model.fakes +import io.github.droidkaigi.confsched.testing.utils.assertCountAtLeast +import io.github.droidkaigi.confsched.testing.utils.hasTestTag import io.github.droidkaigi.confsched.ui.Inject class ContributorsScreenRobot @Inject constructor( @@ -27,13 +31,19 @@ class ContributorsScreenRobot @Inject constructor( } } - fun checkExistsContributorItem( - fromTo: Pair, + fun scrollToIndex10() { + composeTestRule + .onNode(hasTestTag(ContributorsTestTag)) + .performScrollToIndex(10) + } + + fun checkRangeContributorItemsDisplayed( + fromTo: IntRange, ) { - val contributorsList = Contributor.fakes().subList(fromTo.first, fromTo.second) + val contributorsList = Contributor.fakes().subList(fromTo.first, fromTo.last) contributorsList.forEach { contributor -> composeTestRule - .onNode(hasTestTag(ContributorsItemTestTag.plus(contributor.id))) + .onNode(hasTestTag(ContributorsItemTestTagPrefix.plus(contributor.id))) .assertExists() .assertIsDisplayed() @@ -57,10 +67,17 @@ class ContributorsScreenRobot @Inject constructor( } } - fun checkDoesNotExistsContributorItem() { + fun checkContributorItemsDisplayed() { + // Check there are two contributors + composeTestRule + .onAllNodes(hasTestTag(ContributorsItemTestTagPrefix, substring = true)) + .assertCountAtLeast(2) + } + + fun checkDoesNotFirstContributorItemDisplayed() { val contributor = Contributor.fakes().first() composeTestRule - .onNode(hasTestTag(ContributorsItemTestTag.plus(contributor.id))) + .onNode(hasTestTag(ContributorsItemTestTagPrefix.plus(contributor.id))) .assertDoesNotExist() composeTestRule diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt new file mode 100644 index 000000000..65e4fc9ec --- /dev/null +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt @@ -0,0 +1,60 @@ +package io.github.droidkaigi.confsched.testing.utils + +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionCollection + +fun hasTestTag( + testTag: String, + substring: Boolean = false, + ignoreCase: Boolean = false, +): SemanticsMatcher { + return SemanticsMatcher( + "TestTag ${if (substring) "contains" else "is"} '$testTag' (ignoreCase: $ignoreCase)" + ) { node -> + val nodeTestTag: String? = node.config.getOrNull(SemanticsProperties.TestTag) + when { + nodeTestTag == null -> false + substring -> nodeTestTag.contains(testTag, ignoreCase) + else -> nodeTestTag.equals(testTag, ignoreCase) + } + } +} + +fun SemanticsNodeInteractionCollection.assertCountAtLeast( + minimumExpectedSize: Int, +): SemanticsNodeInteractionCollection { + val errorOnFail = "Failed to assert minimum count of nodes." + val matchedNodes = fetchSemanticsNodes( + atLeastOneRootRequired = minimumExpectedSize > 0, + errorOnFail + ) + if (matchedNodes.size < minimumExpectedSize) { + throw AssertionError( + buildErrorMessageForMinimumCountMismatch( + errorMessage = errorOnFail, + foundNodes = matchedNodes, + minimumExpectedCount = minimumExpectedSize + ) + ) + } + return this +} + +private fun buildErrorMessageForMinimumCountMismatch( + errorMessage: String, + foundNodes: List, + minimumExpectedCount: Int, +): String { + return buildString { + appendLine(errorMessage) + appendLine("Expected at least: $minimumExpectedCount") + appendLine("Found: ${foundNodes.size}") + appendLine("Matched nodes:") + foundNodes.forEachIndexed { index, node -> + appendLine("$index: $node") + } + } +} diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 145765bc0..2cca81f29 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -45,13 +45,24 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior Unit, @@ -151,7 +152,8 @@ private fun Contributors( modifier: Modifier = Modifier, ) { LazyColumn( - modifier = modifier, + modifier = modifier + .testTag(ContributorsTestTag), ) { items(contributors) { ContributorsItem( @@ -159,7 +161,7 @@ private fun Contributors( onClick = onContributorsItemClick, modifier = Modifier .fillMaxWidth() - .testTag(ContributorsItemTestTag.plus(it.id)), + .testTag(ContributorsItemTestTagPrefix.plus(it.id)), ) } } From d4f51f9ab88b4d2ce92f29c3edc711fd30d75ce8 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 16 Aug 2024 23:39:51 +0900 Subject: [PATCH 7/9] Fix format --- .../github/droidkaigi/confsched/testing/utils/Matchers.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt index 65e4fc9ec..7f2077b57 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/utils/Matchers.kt @@ -12,7 +12,7 @@ fun hasTestTag( ignoreCase: Boolean = false, ): SemanticsMatcher { return SemanticsMatcher( - "TestTag ${if (substring) "contains" else "is"} '$testTag' (ignoreCase: $ignoreCase)" + "TestTag ${if (substring) "contains" else "is"} '$testTag' (ignoreCase: $ignoreCase)", ) { node -> val nodeTestTag: String? = node.config.getOrNull(SemanticsProperties.TestTag) when { @@ -29,15 +29,15 @@ fun SemanticsNodeInteractionCollection.assertCountAtLeast( val errorOnFail = "Failed to assert minimum count of nodes." val matchedNodes = fetchSemanticsNodes( atLeastOneRootRequired = minimumExpectedSize > 0, - errorOnFail + errorOnFail, ) if (matchedNodes.size < minimumExpectedSize) { throw AssertionError( buildErrorMessageForMinimumCountMismatch( errorMessage = errorOnFail, foundNodes = matchedNodes, - minimumExpectedCount = minimumExpectedSize - ) + minimumExpectedCount = minimumExpectedSize, + ), ) } return this From 9a834761eb224428328ad333df1cde1b64a94e93 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Sat, 17 Aug 2024 03:06:17 +0900 Subject: [PATCH 8/9] :wrench: Modify the name to something that identifies it as a Prefix. --- .../testing/robot/ContributorsScreenRobot.kt | 12 ++++++------ .../contributors/component/ContributorsItem.kt | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt index 38e60390a..1eb8ae1f6 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ContributorsScreenRobot.kt @@ -9,8 +9,8 @@ import androidx.compose.ui.test.performScrollToIndex import io.github.droidkaigi.confsched.contributors.ContributorsItemTestTagPrefix import io.github.droidkaigi.confsched.contributors.ContributorsScreen import io.github.droidkaigi.confsched.contributors.ContributorsTestTag -import io.github.droidkaigi.confsched.contributors.component.ContributorsItemImageTestTag -import io.github.droidkaigi.confsched.contributors.component.ContributorsUserNameTextTestTag +import io.github.droidkaigi.confsched.contributors.component.ContributorsItemImageTestTagPrefix +import io.github.droidkaigi.confsched.contributors.component.ContributorsUserNameTextTestTagPrefix import io.github.droidkaigi.confsched.model.Contributor import io.github.droidkaigi.confsched.model.fakes import io.github.droidkaigi.confsched.testing.utils.assertCountAtLeast @@ -49,7 +49,7 @@ class ContributorsScreenRobot @Inject constructor( composeTestRule .onNode( - matcher = hasTestTag(ContributorsItemImageTestTag.plus(contributor.username)), + matcher = hasTestTag(ContributorsItemImageTestTagPrefix.plus(contributor.username)), useUnmergedTree = true, ) .assertExists() @@ -58,7 +58,7 @@ class ContributorsScreenRobot @Inject constructor( composeTestRule .onNode( - matcher = hasTestTag(ContributorsUserNameTextTestTag.plus(contributor.username)), + matcher = hasTestTag(ContributorsUserNameTextTestTagPrefix.plus(contributor.username)), useUnmergedTree = true, ) .assertExists() @@ -82,14 +82,14 @@ class ContributorsScreenRobot @Inject constructor( composeTestRule .onNode( - matcher = hasTestTag(ContributorsItemImageTestTag.plus(contributor.username)), + matcher = hasTestTag(ContributorsItemImageTestTagPrefix.plus(contributor.username)), useUnmergedTree = true, ) .assertDoesNotExist() composeTestRule .onNode( - matcher = hasTestTag(ContributorsUserNameTextTestTag.plus(contributor.username)), + matcher = hasTestTag(ContributorsUserNameTextTestTagPrefix.plus(contributor.username)), useUnmergedTree = true, ) .assertDoesNotExist() diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt index 9686c005e..d203636be 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/component/ContributorsItem.kt @@ -24,8 +24,8 @@ import io.github.droidkaigi.confsched.model.Contributor import io.github.droidkaigi.confsched.ui.previewOverride import io.github.droidkaigi.confsched.ui.rememberAsyncImagePainter -const val ContributorsItemImageTestTag = "ContributorsItemImageTestTag:" -const val ContributorsUserNameTextTestTag = "ContributorsUserNameTextTestTag:" +const val ContributorsItemImageTestTagPrefix = "ContributorsItemImageTestTag:" +const val ContributorsUserNameTextTestTagPrefix = "ContributorsUserNameTextTestTag:" private val contributorIconShape = CircleShape @@ -57,14 +57,14 @@ fun ContributorsItem( color = MaterialTheme.colorScheme.outline, shape = contributorIconShape, ) - .testTag(ContributorsItemImageTestTag.plus(contributor.username)), + .testTag(ContributorsItemImageTestTagPrefix.plus(contributor.username)), ) Text( text = contributor.username, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis, - modifier = Modifier.testTag(ContributorsUserNameTextTestTag.plus(contributor.username)), + modifier = Modifier.testTag(ContributorsUserNameTextTestTagPrefix.plus(contributor.username)), ) } } From 2178993fbf76b292119a2519dcb19322a14b6833 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sat, 17 Aug 2024 12:10:27 +0900 Subject: [PATCH 9/9] Fix naming of test --- .../confsched/contributors/ContributorsScreenTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt index 2cca81f29..fedbc8a35 100644 --- a/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt +++ b/feature/contributors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreenTest.kt @@ -45,7 +45,7 @@ class ContributorsScreenTest(private val testCase: DescribedBehavior