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 d89f5d91f..9286da727 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,167 @@ 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, - onboardingUiState = OnboardingUiState.Loading, - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + onboardingState = OnboardingUiState.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, - onboardingUiState = OnboardingUiState.NotShown, - feedState = NewsFeedUiState.Success(emptyList()), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = true, + onboardingState = OnboardingUiState.NotShown, + 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, - onboardingUiState = - OnboardingUiState.Shown( - topics = testTopics, - authors = testAuthors - ), - feedState = NewsFeedUiState.Success( - feed = emptyList() - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + onboardingState = OnboardingUiState.Shown( + 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, - onboardingUiState = - OnboardingUiState.Shown( - // 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, + onboardingState = + OnboardingUiState.Shown( + // 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, - onboardingUiState = - OnboardingUiState.Shown( - // 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, + onboardingState = OnboardingUiState.Shown( + // 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, - onboardingUiState = - OnboardingUiState.Shown( - topics = testTopics, - authors = testAuthors - ), - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + onboardingState = + OnboardingUiState.Shown( + 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, - onboardingUiState = OnboardingUiState.NotShown, - feedState = NewsFeedUiState.Loading, - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) - } + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + onboardingState = OnboardingUiState.NotShown, + 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, - onboardingUiState = OnboardingUiState.NotShown, - feedState = NewsFeedUiState.Success( - feed = previewNewsResources.map { - SaveableNewsResource(it, false) - } - ), - onTopicCheckedChanged = { _, _ -> }, - onAuthorCheckedChanged = { _, _ -> }, - saveFollowedTopics = {}, - onNewsResourcesCheckedChanged = { _, _ -> } - ) + launchForYouRobot( + composeTestRule = composeTestRule, + isSyncing = false, + onboardingState = OnboardingUiState.NotShown, + 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, + onboardingState: OnboardingUiState, + feedState: NewsFeedUiState, + func: ForYouRobot.() -> Unit +) = ForYouRobot(composeTestRule).apply { + setContent(isSyncing, onboardingState, feedState) + func() +} + private val testTopic = Topic( id = "", name = "",