From a0096e755e1a490f8e25fd2ef6ff910f6e13d3e4 Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:38:54 +0100 Subject: [PATCH 1/6] add robot for author feature ui tests --- .../feature/author/AuthorRobot.kt | 65 +++++++++ .../feature/author/AuthorScreenTest.kt | 132 +++++++----------- 2 files changed, 115 insertions(+), 82 deletions(-) create mode 100644 feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorRobot.kt diff --git a/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorRobot.kt b/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorRobot.kt new file mode 100644 index 000000000..ae35b0118 --- /dev/null +++ b/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorRobot.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.feature.author + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource + +internal class AuthorRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + fun setContent(authorUiState: AuthorUiState, newsUiState: NewsUiState) { + composeTestRule.setContent { + AuthorScreen( + authorUiState = authorUiState, + newsUiState = newsUiState, + onBackClick = { }, + onFollowClick = { }, + onBookmarkChanged = { _, _ -> }, + ) + } + } + + fun loadingIndicatorExists() { + val authorLoading = composeTestRule.activity.getString(R.string.author_loading) + + composeTestRule + .onNodeWithContentDescription(authorLoading) + .assertExists() + } + + fun authorExists(author: FollowableAuthor) { + composeTestRule + .onNodeWithText(author.author.name) + .assertExists() + + composeTestRule + .onNodeWithText(author.author.bio) + .assertExists() + } + + fun newsResourceExists(newsResource: NewsResource) { + composeTestRule + .onNodeWithText(newsResource.title) + .assertExists() + } +} \ No newline at end of file diff --git a/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt b/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt index b955912f2..23654f2b2 100644 --- a/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt +++ b/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt @@ -17,16 +17,15 @@ package com.google.samples.apps.nowinandroid.feature.author import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video import kotlinx.datetime.Instant -import org.junit.Before import org.junit.Rule import org.junit.Test @@ -40,108 +39,77 @@ class AuthorScreenTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private lateinit var authorLoading: String - - @Before - fun setup() { - composeTestRule.activity.apply { - authorLoading = getString(R.string.author_loading) - } - } - @Test fun niaLoadingWheel_whenScreenIsLoading_showLoading() { - composeTestRule.setContent { - AuthorScreen( - authorUiState = AuthorUiState.Loading, - newsUiState = NewsUiState.Loading, - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) + launchAuthorRobot( + composeTestRule, AuthorUiState.Loading, NewsUiState.Loading, + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription(authorLoading) - .assertExists() } @Test fun authorTitle_whenAuthorIsSuccess_isShown() { val testAuthor = testAuthors.first() - composeTestRule.setContent { - AuthorScreen( - authorUiState = AuthorUiState.Success(testAuthor), - newsUiState = NewsUiState.Loading, - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) + launchAuthorRobot( + composeTestRule, + AuthorUiState.Success(testAuthor), + NewsUiState.Loading + ) { + authorExists(testAuthor) } - - // Name is shown - composeTestRule - .onNodeWithText(testAuthor.author.name) - .assertExists() - - // Bio is shown - composeTestRule - .onNodeWithText(testAuthor.author.bio) - .assertExists() } @Test fun news_whenAuthorIsLoading_isNotShown() { - composeTestRule.setContent { - AuthorScreen( - authorUiState = AuthorUiState.Loading, - newsUiState = NewsUiState.Success( - sampleNewsResources.mapIndexed { index, newsResource -> - SaveableNewsResource( - newsResource = newsResource, - isSaved = index % 2 == 0, - ) - } - ), - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) + val newsUiState = NewsUiState.Success( + sampleNewsResources.mapIndexed { index, newsResource -> + SaveableNewsResource( + newsResource = newsResource, + isSaved = index % 2 == 0, + ) + } + ) + launchAuthorRobot( + composeTestRule, + AuthorUiState.Loading, + newsUiState + ) { + loadingIndicatorExists() } - - // Loading indicator shown - composeTestRule - .onNodeWithContentDescription(authorLoading) - .assertExists() } @Test fun news_whenSuccessAndAuthorIsSuccess_isShown() { val testAuthor = testAuthors.first() - composeTestRule.setContent { - AuthorScreen( - authorUiState = AuthorUiState.Success(testAuthor), - newsUiState = NewsUiState.Success( - sampleNewsResources.mapIndexed { index, newsResource -> - SaveableNewsResource( - newsResource = newsResource, - isSaved = index % 2 == 0, - ) - } - ), - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) + val newsUiState = NewsUiState.Success( + sampleNewsResources.mapIndexed { index, newsResource -> + SaveableNewsResource( + newsResource = newsResource, + isSaved = index % 2 == 0, + ) + } + ) + launchAuthorRobot( + composeTestRule, + AuthorUiState.Success(testAuthor), + newsUiState + ) { + newsResourceExists(sampleNewsResources.first()) } - - // First news title shown - composeTestRule - .onNodeWithText(sampleNewsResources.first().title) - .assertExists() } } +private fun launchAuthorRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + authorUiState: AuthorUiState, + newsUiState: NewsUiState, + func: AuthorRobot.() -> Unit +) = AuthorRobot(composeTestRule).apply { + setContent(authorUiState, newsUiState) + func() +} + private const val AUTHOR_1_NAME = "Author 1" private const val AUTHOR_2_NAME = "Author 2" private const val AUTHOR_3_NAME = "Author 3" From 7fbaae1342a4a1bf5f0cdc0d8e919189d64a95d4 Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:39:22 +0100 Subject: [PATCH 2/6] add robot for bookmarks feature ui tests --- .../feature/bookmarks/BookmarksRobot.kt | 102 +++++++++++++++ .../feature/bookmarks/BookmarksScreenTest.kt | 123 +++++------------- 2 files changed, 135 insertions(+), 90 deletions(-) create mode 100644 feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksRobot.kt diff --git a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksRobot.kt b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksRobot.kt new file mode 100644 index 000000000..ce1a0c952 --- /dev/null +++ b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksRobot.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.feature.bookmarks + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.filter +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.hasScrollToNodeAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onAllNodesWithContentDescription +import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource +import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState + +internal class BookmarksRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + private val removedBookmarks = mutableSetOf() + + fun setContent(newsFeedUiState: NewsFeedUiState) { + composeTestRule.setContent { + BookmarksScreen( + feedState = newsFeedUiState, + removeFromBookmarks = { + removedBookmarks.add(it) + } + ) + } + } + + fun loadingIndicatorShown() { + composeTestRule + .onNodeWithContentDescription( + composeTestRule.activity.resources.getString(R.string.saved_loading) + ) + .assertExists() + } + + fun clickableNewsResourceExists(newsResource: NewsResource) { + composeTestRule + .onNodeWithText( + newsResource.title, + substring = true + ) + .assertExists() + .assertHasClickAction() + } + + fun scrollToNewsResource(newsResource: NewsResource) { + composeTestRule.onNode(hasScrollToNodeAction()) + .performScrollToNode( + hasText( + newsResource.title, + substring = true + ) + ) + } + + fun clickNewsResourceBookmark(newsResource: NewsResource) { + composeTestRule + .onAllNodesWithContentDescription( + composeTestRule.activity.getString( + com.google.samples.apps.nowinandroid.core.ui.R.string.unbookmark + ) + ).filter( + hasAnyAncestor( + hasText( + newsResource.title, + substring = true + ) + ) + ) + .assertCountEquals(1) + .onFirst() + .performClick() + } + + fun removedNewsResourceBookmark(newsResource: NewsResource) = + removedBookmarks.contains(newsResource.id) +} \ No newline at end of file diff --git a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt index f018be8c0..81332f98d 100644 --- a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt +++ b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt @@ -17,25 +17,12 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks import androidx.activity.ComponentActivity -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.ui.test.assertCountEquals -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.filter -import androidx.compose.ui.test.hasAnyAncestor -import androidx.compose.ui.test.hasScrollToNodeAction -import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onAllNodesWithContentDescription -import androidx.compose.ui.test.onFirst -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test @@ -49,93 +36,49 @@ class BookmarksScreenTest { @Test fun loading_showsLoadingSpinner() { - composeTestRule.setContent { - BookmarksScreen( - feedState = NewsFeedUiState.Loading, - removeFromBookmarks = { } - ) + launchBookmarksRobot( + composeTestRule, + NewsFeedUiState.Loading + ) { + loadingIndicatorShown() } - - composeTestRule - .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.saved_loading) - ) - .assertExists() } @Test fun feed_whenHasBookmarks_showsBookmarks() { - lateinit var windowSizeClass: WindowSizeClass - - composeTestRule.setContent { - BookmarksScreen( - feedState = NewsFeedUiState.Success( - previewNewsResources.take(2) - .map { SaveableNewsResource(it, true) } - ), - removeFromBookmarks = { } + launchBookmarksRobot( + composeTestRule, + NewsFeedUiState.Success( + previewNewsResources.take(2) + .map { SaveableNewsResource(it, true) } ) + ) { + clickNewsResourceBookmark(previewNewsResources[0]) + scrollToNewsResource(previewNewsResources[1]) + clickableNewsResourceExists(previewNewsResources[1]) } - - composeTestRule - .onNodeWithText( - previewNewsResources[0].title, - substring = true - ) - .assertExists() - .assertHasClickAction() - - composeTestRule.onNode(hasScrollToNodeAction()) - .performScrollToNode( - hasText( - previewNewsResources[1].title, - substring = true - ) - ) - - composeTestRule - .onNodeWithText( - previewNewsResources[1].title, - substring = true - ) - .assertExists() - .assertHasClickAction() } @Test fun feed_whenRemovingBookmark_removesBookmark() { - var removeFromBookmarksCalled = false - - composeTestRule.setContent { - BookmarksScreen( - feedState = NewsFeedUiState.Success( - previewNewsResources.take(2) - .map { SaveableNewsResource(it, true) } - ), - removeFromBookmarks = { newsResourceId -> - assertEquals(previewNewsResources[0].id, newsResourceId) - removeFromBookmarksCalled = true - } + launchBookmarksRobot( + composeTestRule, + NewsFeedUiState.Success( + previewNewsResources.take(2) + .map { SaveableNewsResource(it, true) } ) + ) { + clickNewsResourceBookmark(previewNewsResources[0]) + removedNewsResourceBookmark(previewNewsResources[0]) } - - composeTestRule - .onAllNodesWithContentDescription( - composeTestRule.activity.getString( - com.google.samples.apps.nowinandroid.core.ui.R.string.unbookmark - ) - ).filter( - hasAnyAncestor( - hasText( - previewNewsResources[0].title, - substring = true - ) - ) - ) - .assertCountEquals(1) - .onFirst() - .performClick() - - assertTrue(removeFromBookmarksCalled) } } + +private fun launchBookmarksRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + newsFeedUiState: NewsFeedUiState, + func: BookmarksRobot.() -> Unit +) = BookmarksRobot(composeTestRule).apply { + setContent(newsFeedUiState) + func() +} From 7f7725af966ad7d3a34f03b2b091082d7dff6f95 Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:40:53 +0100 Subject: [PATCH 3/6] add robot for ForYou feature ui tests --- .../feature/foryou/ForYouRobot.kt | 123 ++++++ .../feature/foryou/ForYouScreenTest.kt | 370 ++++++------------ 2 files changed, 234 insertions(+), 259 deletions(-) create mode 100644 feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouRobot.kt diff --git a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouRobot.kt b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouRobot.kt new file mode 100644 index 000000000..0c81da3d0 --- /dev/null +++ b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouRobot.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.feature.foryou + +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.hasScrollToNodeAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor +import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource +import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState +import com.google.samples.apps.nowinandroid.feature.foryou.R.string + +internal class ForYouRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + private val doneButtonMatcher by lazy { + hasText(composeTestRule.activity.resources.getString(string.done)) + } + + fun setContent( + isSyncing: Boolean, + interestsSelectionState: ForYouInterestsSelectionUiState, + feedState: NewsFeedUiState, + ) { + composeTestRule.setContent { + BoxWithConstraints { + ForYouScreen( + isSyncing = isSyncing, + interestsSelectionState = interestsSelectionState, + feedState = feedState, + onTopicCheckedChanged = { _, _ -> }, + onAuthorCheckedChanged = { _, _ -> }, + saveFollowedTopics = {}, + onNewsResourcesCheckedChanged = { _, _ -> } + ) + } + } + } + + fun loadingIndicatorExists() { + composeTestRule + .onNodeWithContentDescription( + composeTestRule.activity.resources.getString(string.for_you_loading) + ) + .assertExists() + } + + fun clickableAuthorExists(author: FollowableAuthor) { + composeTestRule + .onNodeWithText(author.author.name) + .assertExists() + .assertHasClickAction() + } + + fun clickableTopicExists(topic: FollowableTopic) { + composeTestRule + .onNodeWithText(topic.topic.name) + .assertExists() + .assertHasClickAction() + } + + fun scrollToDoneButton() { + composeTestRule + .onAllNodes(hasScrollToNodeAction()) + .onFirst() + .performScrollToNode(doneButtonMatcher) + } + + fun clickableDoneButtonExists(isEnabled: Boolean) { + composeTestRule + .onNode(doneButtonMatcher) + .assertExists() + .assertHasClickAction() + .apply { + if (isEnabled) assertIsEnabled() else assertIsNotEnabled() + } + } + + fun clickableNewsResourceExists(newsResource: NewsResource) { + composeTestRule + .onNodeWithText( + newsResource.title, + substring = true + ) + .assertExists() + .assertHasClickAction() + } + + fun scrollToNewsResource(newsResource: NewsResource) { + composeTestRule.onNode(hasScrollToNodeAction()) + .performScrollToNode( + hasText( + newsResource.title, + substring = true + ) + ) + } +} \ No newline at end of file diff --git a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt index af5f07372..4468db237 100644 --- a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt +++ b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt @@ -17,17 +17,9 @@ package com.google.samples.apps.nowinandroid.feature.foryou import androidx.activity.ComponentActivity -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertIsEnabled -import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.hasScrollToNodeAction -import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onFirst -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource @@ -42,305 +34,165 @@ class ForYouScreenTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private val doneButtonMatcher by lazy { - hasText( - composeTestRule.activity.resources.getString(R.string.done) - ) - } - @Test fun circularProgressIndicator_whenScreenIsLoading_exists() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = ForYouInterestsSelectionUiState.Loading, - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.Loading, + feedState = NewsFeedUiState.Loading, + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading) - ) - .assertExists() } @Test fun circularProgressIndicator_whenScreenIsSyncing_exists() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = true, - interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = NewsFeedUiState.Success(emptyList()), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = true, + interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, + feedState = NewsFeedUiState.Success(emptyList()), + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading) - ) - .assertExists() } @Test fun topicSelector_whenNoTopicsSelected_showsAuthorAndTopicChipsAndDisabledDoneButton() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = testTopics, - authors = testAuthors - ), - feedState = NewsFeedUiState.Success( - feed = emptyList() - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = testTopics, + authors = testAuthors + ), + feedState = NewsFeedUiState.Success(emptyList()), + ) { + testAuthors.forEach { author -> + clickableAuthorExists(author) } - } - testAuthors.forEach { testAuthor -> - composeTestRule - .onNodeWithText(testAuthor.author.name) - .assertExists() - .assertHasClickAction() - } + testTopics.forEach { topic -> + clickableTopicExists(topic) + } - testTopics.forEach { testTopic -> - composeTestRule - .onNodeWithText(testTopic.topic.name) - .assertExists() - .assertHasClickAction() + scrollToDoneButton() + clickableDoneButtonExists(false) } - - // Scroll until the Done button is visible - composeTestRule - .onAllNodes(hasScrollToNodeAction()) - .onFirst() - .performScrollToNode(doneButtonMatcher) - - composeTestRule - .onNode(doneButtonMatcher) - .assertExists() - .assertIsNotEnabled() - .assertHasClickAction() } @Test fun topicSelector_whenSomeTopicsSelected_showsAuthorAndTopicChipsAndEnabledDoneButton() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - // Follow one topic - topics = testTopics.mapIndexed { index, testTopic -> - testTopic.copy(isFollowed = index == 1) - }, - authors = testAuthors - ), - feedState = NewsFeedUiState.Success( - feed = emptyList() - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection( + // Follow one topic + topics = testTopics.mapIndexed { index, testTopic -> + testTopic.copy(isFollowed = index == 1) + }, + authors = testAuthors + ), + feedState = NewsFeedUiState.Success(emptyList()), + ) { + testAuthors.forEach { author -> + clickableAuthorExists(author) } - } - testAuthors.forEach { testAuthor -> - composeTestRule - .onNodeWithText(testAuthor.author.name) - .assertExists() - .assertHasClickAction() - } + testTopics.forEach { topic -> + clickableTopicExists(topic) + } - testTopics.forEach { testTopic -> - composeTestRule - .onNodeWithText(testTopic.topic.name) - .assertExists() - .assertHasClickAction() + scrollToDoneButton() + clickableDoneButtonExists(true) } - - // Scroll until the Done button is visible - composeTestRule - .onAllNodes(hasScrollToNodeAction()) - .onFirst() - .performScrollToNode(doneButtonMatcher) - - composeTestRule - .onNode(doneButtonMatcher) - .assertExists() - .assertIsEnabled() - .assertHasClickAction() } @Test fun topicSelector_whenSomeAuthorsSelected_showsAuthorAndTopicChipsAndEnabledDoneButton() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - // Follow one topic - topics = testTopics, - authors = testAuthors.mapIndexed { index, testAuthor -> - testAuthor.copy(isFollowed = index == 1) - } - ), - feedState = NewsFeedUiState.Success( - feed = emptyList() - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection( + // Follow one topic + topics = testTopics, + authors = testAuthors.mapIndexed { index, testAuthor -> + testAuthor.copy(isFollowed = index == 1) + } + ), + feedState = NewsFeedUiState.Success(emptyList()), + ) { + testAuthors.forEach { author -> + clickableAuthorExists(author) } - } - testAuthors.forEach { testAuthor -> - composeTestRule - .onNodeWithText(testAuthor.author.name) - .assertExists() - .assertHasClickAction() - } + testTopics.forEach { topic -> + clickableTopicExists(topic) + } - testTopics.forEach { testTopic -> - composeTestRule - .onNodeWithText(testTopic.topic.name) - .assertExists() - .assertHasClickAction() + scrollToDoneButton() + clickableDoneButtonExists(true) } - - // Scroll until the Done button is visible - composeTestRule - .onAllNodes(hasScrollToNodeAction()) - .onFirst() - .performScrollToNode(doneButtonMatcher) - - composeTestRule - .onNode(doneButtonMatcher) - .assertExists() - .assertIsEnabled() - .assertHasClickAction() } @Test fun feed_whenInterestsSelectedAndLoading_showsLoadingIndicator() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = - ForYouInterestsSelectionUiState.WithInterestsSelection( - topics = testTopics, - authors = testAuthors - ), - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection( + topics = testTopics, + authors = testAuthors + ), + feedState = NewsFeedUiState.Loading, + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading) - ) - .assertExists() } @Test fun feed_whenNoInterestsSelectionAndLoading_showsLoadingIndicator() { - composeTestRule.setContent { - BoxWithConstraints { - ForYouScreen( - isSyncing = false, - interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, + feedState = NewsFeedUiState.Loading, + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading) - ) - .assertExists() } @Test fun feed_whenNoInterestsSelectionAndLoaded_showsFeed() { - composeTestRule.setContent { - ForYouScreen( - isSyncing = false, - interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, - feedState = NewsFeedUiState.Success( - feed = previewNewsResources.map { - SaveableNewsResource(it, false) - } - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, + feedState = NewsFeedUiState.Success( + feed = previewNewsResources.map { + SaveableNewsResource(it, false) + } + ), + ) { + clickableNewsResourceExists(previewNewsResources[0]) + scrollToNewsResource(previewNewsResources[1]) + clickableNewsResourceExists(previewNewsResources[1]) } - - composeTestRule - .onNodeWithText( - previewNewsResources[0].title, - substring = true - ) - .assertExists() - .assertHasClickAction() - - composeTestRule.onNode(hasScrollToNodeAction()) - .performScrollToNode( - hasText( - previewNewsResources[1].title, - substring = true - ) - ) - - composeTestRule - .onNodeWithText( - previewNewsResources[1].title, - substring = true - ) - .assertExists() - .assertHasClickAction() } } +private fun launchForYouRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + isSyncing: Boolean, + interestsSelectionState: ForYouInterestsSelectionUiState, + feedState: NewsFeedUiState, + func: ForYouRobot.() -> Unit +) = ForYouRobot(composeTestRule).apply { + setContent(isSyncing, interestsSelectionState, feedState) + func() +} + private val testTopic = Topic( id = "", name = "", From 899e8055f86861191bc8032479f78837250662c1 Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:41:11 +0100 Subject: [PATCH 4/6] add robot for interests feature ui tests --- .../nowinandroid/interests/InterestsRobot.kt | 101 ++++++++++ .../interests/InterestsScreenTest.kt | 176 ++++++------------ 2 files changed, 162 insertions(+), 115 deletions(-) create mode 100644 feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsRobot.kt diff --git a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsRobot.kt b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsRobot.kt new file mode 100644 index 000000000..dea004876 --- /dev/null +++ b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsRobot.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.interests + +import androidx.activity.ComponentActivity +import androidx.annotation.StringRes +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onAllNodesWithContentDescription +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen +import com.google.samples.apps.nowinandroid.feature.interests.InterestsTabState +import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState +import com.google.samples.apps.nowinandroid.feature.interests.R.string + +internal class InterestsRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + private val interestsLoading = getString(string.interests_loading) + private val interestsEmptyHeader = getString(string.interests_empty_header) + private val interestsTopicCardFollowButton = + getString(string.interests_card_follow_button_content_desc) + private val interestsTopicCardUnfollowButton = + getString(string.interests_card_unfollow_button_content_desc) + + fun setContent(uiState: InterestsUiState, tabIndex: Int = 0) { + composeTestRule.setContent { + InterestsScreen( + uiState = uiState, + tabState = InterestsTabState( + titles = listOf(string.interests_topics, string.interests_people), + currentIndex = tabIndex + ), + followAuthor = { _, _ -> }, + followTopic = { _, _ -> }, + navigateToAuthor = {}, + navigateToTopic = {}, + switchTab = {}, + ) + } + } + + fun interestsLoadingExists() { + composeTestRule + .onNodeWithContentDescription(interestsLoading) + .assertExists() + } + + fun nodeWithTextDisplayed(text: String) { + composeTestRule + .onNodeWithText(text) + .assertIsDisplayed() + } + + fun nodesWithTextCountEquals(text: String, count: Int) { + composeTestRule + .onAllNodesWithText(text) + .assertCountEquals(count) + } + + fun interestsTopicCardFollowButtonCountEquals(count: Int) { + nodesWithContentDescriptionCountEquals(interestsTopicCardFollowButton, count) + } + + fun interestsTopicCardUnfollowButtonCountEquals(count: Int) { + nodesWithContentDescriptionCountEquals(interestsTopicCardUnfollowButton, count) + } + + private fun nodesWithContentDescriptionCountEquals(text: String, count: Int) { + composeTestRule + .onAllNodesWithContentDescription(text) + .assertCountEquals(count) + } + + fun interestsEmptyHeaderDisplayed() { + composeTestRule + .onNodeWithText(interestsEmptyHeader) + .assertIsDisplayed() + } + + private fun getString(@StringRes stringId: Int) = + composeTestRule.activity.resources.getString(stringId) +} \ No newline at end of file diff --git a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt index 4b067734f..6f3913f4a 100644 --- a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt +++ b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt @@ -17,23 +17,14 @@ package com.google.samples.apps.nowinandroid.interests import androidx.activity.ComponentActivity -import androidx.compose.runtime.Composable -import androidx.compose.ui.test.assertCountEquals -import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onAllNodesWithContentDescription -import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.Topic -import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen -import com.google.samples.apps.nowinandroid.feature.interests.InterestsTabState import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState -import com.google.samples.apps.nowinandroid.feature.interests.R -import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,142 +38,94 @@ class InterestsScreenTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private lateinit var interestsLoading: String - private lateinit var interestsEmptyHeader: String - private lateinit var interestsTopicCardFollowButton: String - private lateinit var interestsTopicCardUnfollowButton: String - - @Before - fun setup() { - composeTestRule.activity.apply { - interestsLoading = getString(R.string.interests_loading) - interestsEmptyHeader = getString(R.string.interests_empty_header) - interestsTopicCardFollowButton = - getString(R.string.interests_card_follow_button_content_desc) - interestsTopicCardUnfollowButton = - getString(R.string.interests_card_unfollow_button_content_desc) - } - } - @Test fun niaLoadingWheel_inTopics_whenScreenIsLoading_showLoading() { - composeTestRule.setContent { - InterestsScreen(uiState = InterestsUiState.Loading, tabIndex = 0) + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Loading, + 0 + ) { + interestsLoadingExists() } - - composeTestRule - .onNodeWithContentDescription(interestsLoading) - .assertExists() } @Test fun niaLoadingWheel_inAuthors_whenScreenIsLoading_showLoading() { - composeTestRule.setContent { - InterestsScreen(uiState = InterestsUiState.Loading, tabIndex = 1) + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Loading, + 1 + ) { + interestsLoadingExists() } - - composeTestRule - .onNodeWithContentDescription(interestsLoading) - .assertExists() } @Test fun interestsWithTopics_whenTopicsFollowed_showFollowedAndUnfollowedTopicsWithInfo() { - composeTestRule.setContent { - InterestsScreen( - uiState = InterestsUiState.Interests(topics = testTopics, authors = listOf()), - tabIndex = 0 - ) - } + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Interests(topics = testTopics, authors = listOf()), + 0 + ) { + nodeWithTextDisplayed(TOPIC_1_NAME) + nodeWithTextDisplayed(TOPIC_2_NAME) + nodeWithTextDisplayed(TOPIC_3_NAME) - composeTestRule - .onNodeWithText(TOPIC_1_NAME) - .assertIsDisplayed() - composeTestRule - .onNodeWithText(TOPIC_2_NAME) - .assertIsDisplayed() - composeTestRule - .onNodeWithText(TOPIC_3_NAME) - .assertIsDisplayed() + nodesWithTextCountEquals(TOPIC_SHORT_DESC, testTopics.count()) - composeTestRule - .onAllNodesWithText(TOPIC_SHORT_DESC) - .assertCountEquals(testTopics.count()) + interestsTopicCardFollowButtonCountEquals(numberOfUnfollowedTopics) + interestsTopicCardUnfollowButtonCountEquals(numberOfFollowedTopics) - composeTestRule - .onAllNodesWithContentDescription(interestsTopicCardFollowButton) - .assertCountEquals(numberOfUnfollowedTopics) - - composeTestRule - .onAllNodesWithContentDescription(interestsTopicCardUnfollowButton) - .assertCountEquals(testAuthors.filter { it.isFollowed }.size) + } } @Test fun interestsWithTopics_whenAuthorsFollowed_showFollowedAndUnfollowedTopicsWithInfo() { - composeTestRule.setContent { - InterestsScreen( - uiState = InterestsUiState.Interests(topics = listOf(), authors = testAuthors), - tabIndex = 1 - ) + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Interests(topics = listOf(), authors = testAuthors), + 1 + ) { + nodeWithTextDisplayed("Android Dev") + nodeWithTextDisplayed("Android Dev 2") + nodeWithTextDisplayed("Android Dev 3") + + interestsTopicCardFollowButtonCountEquals(numberOfUnfollowedAuthors) + interestsTopicCardUnfollowButtonCountEquals(numberOfFollowedAuthors) } - - composeTestRule - .onNodeWithText("Android Dev") - .assertIsDisplayed() - composeTestRule - .onNodeWithText("Android Dev 2") - .assertIsDisplayed() - composeTestRule - .onNodeWithText("Android Dev 3") - .assertIsDisplayed() - - composeTestRule - .onAllNodesWithContentDescription(interestsTopicCardFollowButton) - .assertCountEquals(numberOfUnfollowedAuthors) - - composeTestRule - .onAllNodesWithContentDescription(interestsTopicCardUnfollowButton) - .assertCountEquals(testTopics.filter { it.isFollowed }.size) } @Test fun topicsEmpty_whenDataIsEmptyOccurs_thenShowEmptyScreen() { - composeTestRule.setContent { - InterestsScreen(uiState = InterestsUiState.Empty, tabIndex = 0) + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Empty, + 0 + ) { + interestsEmptyHeaderDisplayed() } - - composeTestRule - .onNodeWithText(interestsEmptyHeader) - .assertIsDisplayed() } @Test fun authorsEmpty_whenDataIsEmptyOccurs_thenShowEmptyScreen() { - composeTestRule.setContent { - InterestsScreen(uiState = InterestsUiState.Empty, tabIndex = 1) + launchBookmarksRobot( + composeTestRule, + InterestsUiState.Empty, + 1 + ) { + interestsEmptyHeaderDisplayed() } - - composeTestRule - .onNodeWithText(interestsEmptyHeader) - .assertIsDisplayed() } +} - @Composable - private fun InterestsScreen(uiState: InterestsUiState, tabIndex: Int = 0) { - InterestsScreen( - uiState = uiState, - tabState = InterestsTabState( - titles = listOf(R.string.interests_topics, R.string.interests_people), - currentIndex = tabIndex - ), - followAuthor = { _, _ -> }, - followTopic = { _, _ -> }, - navigateToAuthor = {}, - navigateToTopic = {}, - switchTab = {}, - ) - } +private fun launchBookmarksRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + uiState: InterestsUiState, + tabIndex: Int, + func: InterestsRobot.() -> Unit +) = InterestsRobot(composeTestRule).apply { + setContent(uiState, tabIndex) + func() } private const val TOPIC_1_NAME = "Headlines" @@ -266,4 +209,7 @@ private val testAuthors = listOf( ) private val numberOfUnfollowedTopics = testTopics.filter { !it.isFollowed }.size +private val numberOfFollowedTopics = testTopics.filter { it.isFollowed }.size + private val numberOfUnfollowedAuthors = testAuthors.filter { !it.isFollowed }.size +private val numberOfFollowedAuthors = testAuthors.filter { it.isFollowed }.size From 54155fcf3734319e977dc2fbef375dd900ba572a Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:41:44 +0100 Subject: [PATCH 5/6] add robot for settings feature ui tests --- .../feature/settings/SettingsDialogRobot.kt | 60 ++++++++++++ .../feature/settings/SettingsDialogTest.kt | 96 ++++++++----------- 2 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogRobot.kt diff --git a/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogRobot.kt b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogRobot.kt new file mode 100644 index 000000000..c94f7b29b --- /dev/null +++ b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogRobot.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.feature.settings + +import androidx.activity.ComponentActivity +import androidx.annotation.StringRes +import androidx.compose.ui.test.assertIsSelected +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule + +internal class SettingsDialogRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + fun setContent(settingsUiState: SettingsUiState) { + composeTestRule.setContent { + SettingsDialog( + settingsUiState = settingsUiState, + onDismiss = { }, + onChangeThemeBrand = {}, + onChangeDarkThemeConfig = {} + ) + } + } + + fun loadingIndicatorExists() { + composeTestRule + .onNodeWithText(getString(R.string.loading)) + .assertExists() + } + + fun settingExists(name: String) { + composeTestRule + .onNodeWithText(name) + .assertExists() + } + + fun settingIsSelected(name: String) { + composeTestRule + .onNodeWithText(name) + .assertIsSelected() + } + + fun getString(@StringRes stringId: Int) = + composeTestRule.activity.resources.getString(stringId) +} \ No newline at end of file diff --git a/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt index 07b6b272c..bf1af77dc 100644 --- a/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt +++ b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt @@ -17,9 +17,9 @@ package com.google.samples.apps.nowinandroid.feature.settings import androidx.activity.ComponentActivity -import androidx.compose.ui.test.assertIsSelected +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading @@ -32,74 +32,60 @@ class SettingsDialogTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private fun getString(id: Int) = composeTestRule.activity.resources.getString(id) - @Test fun whenLoading_showsLoadingText() { - - composeTestRule.setContent { - SettingsDialog( - settingsUiState = Loading, - onDismiss = { }, - onChangeThemeBrand = {}, - onChangeDarkThemeConfig = {} - ) + launchSettingsDialogRobot(composeTestRule, Loading) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithText(getString(R.string.loading)) - .assertExists() } @Test fun whenStateIsSuccess_allSettingsAreDisplayed() { - composeTestRule.setContent { - SettingsDialog( - settingsUiState = Success( - UserEditableSettings( - brand = ANDROID, - darkThemeConfig = DARK - ) - ), - onDismiss = { }, - onChangeThemeBrand = {}, - onChangeDarkThemeConfig = {} + launchSettingsDialogRobot( + composeTestRule, + Success( + UserEditableSettings( + brand = ANDROID, + darkThemeConfig = DARK + ) ) - } + ) { + settingExists(getString(R.string.brand_default)) + settingExists(getString(R.string.brand_android)) + settingExists(getString(R.string.dark_mode_config_system_default)) - // Check that all the possible settings are displayed. - composeTestRule.onNodeWithText(getString(R.string.brand_default)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertExists() - composeTestRule.onNodeWithText( - getString(R.string.dark_mode_config_system_default) - ).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_light)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertExists() + settingExists(getString(R.string.dark_mode_config_light)) + settingExists(getString(R.string.dark_mode_config_dark)) - // Check that the correct settings are selected. - composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertIsSelected() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertIsSelected() + settingIsSelected(getString(R.string.brand_android)) + settingIsSelected(getString(R.string.dark_mode_config_dark)) + } } @Test fun whenStateIsSuccess_allLinksAreDisplayed() { - composeTestRule.setContent { - SettingsDialog( - settingsUiState = Success( - UserEditableSettings( - brand = ANDROID, - darkThemeConfig = DARK - ) - ), - onDismiss = { }, - onChangeThemeBrand = {}, - onChangeDarkThemeConfig = {} + launchSettingsDialogRobot( + composeTestRule, + Success( + UserEditableSettings( + brand = ANDROID, + darkThemeConfig = DARK + ) ) + ) { + settingExists(getString(R.string.privacy_policy)) + settingExists(getString(R.string.licenses)) + settingExists(getString(R.string.brand_guidelines)) + settingExists(getString(R.string.feedback)) } - - composeTestRule.onNodeWithText(getString(R.string.privacy_policy)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.licenses)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.brand_guidelines)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.feedback)).assertExists() } } + +private fun launchSettingsDialogRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + settingsUiState: SettingsUiState, + func: SettingsDialogRobot.() -> Unit +) = SettingsDialogRobot(composeTestRule).apply { + setContent(settingsUiState) + func() +} From 9045b06c027f86a2ac6a043e09eecdecddac51ed Mon Sep 17 00:00:00 2001 From: JanFidor Date: Sun, 6 Nov 2022 18:42:04 +0100 Subject: [PATCH 6/6] add robot for topic feature ui tests --- .../nowinandroid/feature/topic/TopicRobot.kt | 70 +++++++++ .../feature/topic/TopicScreenTest.kt | 135 +++++++----------- 2 files changed, 120 insertions(+), 85 deletions(-) create mode 100644 feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicRobot.kt diff --git a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicRobot.kt b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicRobot.kt new file mode 100644 index 000000000..06fc4d43b --- /dev/null +++ b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicRobot.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.feature.topic + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.hasScrollToNodeAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource + +internal class TopicRobot( + private val composeTestRule: AndroidComposeTestRule, ComponentActivity> +) { + private val topicLoading = composeTestRule.activity.resources.getString(R.string.topic_loading) + + fun setContent(topicUiState: TopicUiState, newsUiState: NewsUiState) { + composeTestRule.setContent { + TopicScreen( + topicUiState = topicUiState, + newsUiState = newsUiState, + onBackClick = { }, + onFollowClick = { }, + onBookmarkChanged = { _, _ -> }, + ) + } + } + + fun loadingIndicatorExists() { + composeTestRule + .onNodeWithContentDescription(topicLoading) + .assertExists() + } + + fun topicExists(topic: FollowableTopic) { + composeTestRule + .onNodeWithText(topic.topic.name) + .assertExists() + + composeTestRule + .onNodeWithText(topic.topic.longDescription) + .assertExists() + } + + fun scrollToNewsResource(newsResource: NewsResource) { + composeTestRule + .onAllNodes(hasScrollToNodeAction()) + .onFirst() + .performScrollToNode(hasText(newsResource.title)) + } +} \ No newline at end of file diff --git a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt index 0f20a5e5e..1df807635 100644 --- a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt +++ b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt @@ -17,20 +17,15 @@ package com.google.samples.apps.nowinandroid.feature.topic import androidx.activity.ComponentActivity -import androidx.compose.ui.test.hasScrollToNodeAction -import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onFirst -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performScrollToNode +import androidx.test.ext.junit.rules.ActivityScenarioRule import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video import com.google.samples.apps.nowinandroid.core.model.data.Topic import kotlinx.datetime.Instant -import org.junit.Before import org.junit.Rule import org.junit.Test @@ -44,109 +39,79 @@ class TopicScreenTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private lateinit var topicLoading: String - - @Before - fun setup() { - composeTestRule.activity.apply { - topicLoading = getString(R.string.topic_loading) - } - } - @Test fun niaLoadingWheel_whenScreenIsLoading_showLoading() { - composeTestRule.setContent { - TopicScreen( - topicUiState = TopicUiState.Loading, - newsUiState = NewsUiState.Loading, - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) + launchTopicRobot( + composeTestRule, + TopicUiState.Loading, + NewsUiState.Loading + ) { + loadingIndicatorExists() } - - composeTestRule - .onNodeWithContentDescription(topicLoading) - .assertExists() } @Test fun topicTitle_whenTopicIsSuccess_isShown() { val testTopic = testTopics.first() - composeTestRule.setContent { - TopicScreen( - topicUiState = TopicUiState.Success(testTopic), - newsUiState = NewsUiState.Loading, - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, - ) - } - - // Name is shown - composeTestRule - .onNodeWithText(testTopic.topic.name) - .assertExists() - // Description is shown - composeTestRule - .onNodeWithText(testTopic.topic.longDescription) - .assertExists() + launchTopicRobot( + composeTestRule, + TopicUiState.Success(testTopic), + NewsUiState.Loading + ) { + topicExists(testTopic) + } } @Test fun news_whenTopicIsLoading_isNotShown() { - composeTestRule.setContent { - TopicScreen( - topicUiState = TopicUiState.Loading, - newsUiState = NewsUiState.Success( - sampleNewsResources.mapIndexed { index, newsResource -> - SaveableNewsResource( - newsResource = newsResource, - isSaved = index % 2 == 0, - ) - } - ), - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, + launchTopicRobot( + composeTestRule, + TopicUiState.Loading, + NewsUiState.Success( + sampleNewsResources.mapIndexed { index, newsResource -> + SaveableNewsResource( + newsResource = newsResource, + isSaved = index % 2 == 0, + ) + } ) + ) { + loadingIndicatorExists() } - - // Loading indicator shown - composeTestRule - .onNodeWithContentDescription(topicLoading) - .assertExists() } @Test fun news_whenSuccessAndTopicIsSuccess_isShown() { val testTopic = testTopics.first() - composeTestRule.setContent { - TopicScreen( - topicUiState = TopicUiState.Success(testTopic), - newsUiState = NewsUiState.Success( - sampleNewsResources.mapIndexed { index, newsResource -> - SaveableNewsResource( - newsResource = newsResource, - isSaved = index % 2 == 0, - ) - } - ), - onBackClick = { }, - onFollowClick = { }, - onBookmarkChanged = { _, _ -> }, + launchTopicRobot( + composeTestRule, + TopicUiState.Success(testTopic), + NewsUiState.Success( + sampleNewsResources.mapIndexed { index, newsResource -> + SaveableNewsResource( + newsResource = newsResource, + isSaved = index % 2 == 0, + ) + } ) + ) { + // Scroll to first news title if available + scrollToNewsResource(sampleNewsResources.first()) } - - // Scroll to first news title if available - composeTestRule - .onAllNodes(hasScrollToNodeAction()) - .onFirst() - .performScrollToNode(hasText(sampleNewsResources.first().title)) } } +private fun launchTopicRobot( + composeTestRule: AndroidComposeTestRule, ComponentActivity>, + topicUiState: TopicUiState, + newsUiState: NewsUiState, + func: TopicRobot.() -> Unit +) = TopicRobot(composeTestRule).apply { + setContent(topicUiState, newsUiState) + func() +} + private const val TOPIC_1_NAME = "Headlines" private const val TOPIC_2_NAME = "UI" private const val TOPIC_3_NAME = "Tools"