From 355b0540aa034ae47785268af11666a56a6a1b5c Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 4 Jan 2023 19:19:54 +0000 Subject: [PATCH 1/3] Move construction of UserNewsResource into separate function Change-Id: I7c1f6427cd7d95c2016349fec301b88455b33cf2 --- .../domain/GetUserNewsResourcesUseCase.kt | 26 +---- .../core/domain/model/UserNewsResource.kt | 34 +++++- .../domain/GetUserNewsResourcesUseCaseTest.kt | 110 +++++------------- .../core/domain/UserNewsResourceTest.kt | 106 +++++++++++++++++ .../repository/TestUserDataRepository.kt | 7 ++ .../core/ui/NewsResourceCardTest.kt | 2 +- .../nowinandroid/core/ui/NewsResourceCard.kt | 2 +- .../feature/foryou/ForYouViewModelTest.kt | 10 +- .../feature/topic/TopicScreenTest.kt | 2 +- 9 files changed, 186 insertions(+), 113 deletions(-) create mode 100644 core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt index 19d65581f..39bd38e1e 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt @@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.core.domain import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository -import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserData @@ -48,30 +47,15 @@ class GetUserNewsResourcesUseCase @Inject constructor( newsRepository.getNewsResources() } else { newsRepository.getNewsResources(filterTopicIds = filterTopicIds) - }.mapToSaveableNewsResources(userDataRepository.userData) + }.mapToUserNewsResources(userDataRepository.userData) } -private fun Flow>.mapToSaveableNewsResources( - userData: Flow +private fun Flow>.mapToUserNewsResources( + userDataStream: Flow ): Flow> = filterNot { it.isEmpty() } - .combine(userData) { newsResources, userData -> + .combine(userDataStream) { newsResources, userData -> newsResources.map { newsResource -> - UserNewsResource( - id = newsResource.id, - title = newsResource.title, - content = newsResource.content, - url = newsResource.url, - headerImageUrl = newsResource.headerImageUrl, - publishDate = newsResource.publishDate, - type = newsResource.type, - topics = newsResource.topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = userData.followedTopics.contains(topic.id) - ) - }, - isSaved = userData.bookmarkedNewsResources.contains(newsResource.id) - ) + UserNewsResource.from(newsResource, userData) } } diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt index b8cef84a7..280f6b78a 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt @@ -21,6 +21,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Codelab import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Unknown import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video +import com.google.samples.apps.nowinandroid.core.model.data.UserData import kotlinx.datetime.Instant import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -39,9 +40,30 @@ data class UserNewsResource( val headerImageUrl: String?, val publishDate: Instant, val type: NewsResourceType, - val topics: List, + val followableTopics: List, val isSaved: Boolean -) +) { + companion object { + fun from(newsResource: NewsResource, userData: UserData): UserNewsResource { + return UserNewsResource( + id = newsResource.id, + title = newsResource.title, + content = newsResource.content, + url = newsResource.url, + headerImageUrl = newsResource.headerImageUrl, + publishDate = newsResource.publishDate, + type = newsResource.type, + followableTopics = newsResource.topics.map { topic -> + FollowableTopic( + topic = topic, + isFollowed = userData.followedTopics.contains(topic.id) + ) + }, + isSaved = userData.bookmarkedNewsResources.contains(newsResource.id) + ) + } + } +} val previewUserNewsResources = listOf( UserNewsResource( @@ -60,7 +82,7 @@ val previewUserNewsResources = listOf( nanosecond = 0 ).toInstant(TimeZone.UTC), type = Codelab, - topics = listOf(previewFollowableTopics[1]), + followableTopics = listOf(previewFollowableTopics[1]), isSaved = true ), UserNewsResource( @@ -74,7 +96,7 @@ val previewUserNewsResources = listOf( headerImageUrl = "https://i.ytimg.com/vi/-fJ6poHQrjM/maxresdefault.jpg", publishDate = Instant.parse("2021-11-09T00:00:00.000Z"), type = Video, - topics = listOf(previewFollowableTopics[0], previewFollowableTopics[1]), + followableTopics = listOf(previewFollowableTopics[0], previewFollowableTopics[1]), isSaved = false ), UserNewsResource( @@ -88,7 +110,7 @@ val previewUserNewsResources = listOf( headerImageUrl = "https://i.ytimg.com/vi/ZARz0pjm5YM/maxresdefault.jpg", publishDate = Instant.parse("2021-11-01T00:00:00.000Z"), type = Video, - topics = listOf(previewFollowableTopics[2]), + followableTopics = listOf(previewFollowableTopics[2]), isSaved = false ), UserNewsResource( @@ -100,7 +122,7 @@ val previewUserNewsResources = listOf( headerImageUrl = "", publishDate = Instant.parse("2022-10-01T00:00:00.000Z"), type = Unknown, - topics = listOf(previewFollowableTopics[2]), + followableTopics = listOf(previewFollowableTopics[2]), isSaved = true ) ) diff --git a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt index d8fd950de..80d6013c9 100644 --- a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt +++ b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt @@ -16,11 +16,13 @@ package com.google.samples.apps.nowinandroid.core.domain -import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource +import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.FOLLOW_SYSTEM 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.ThemeBrand.DEFAULT import com.google.samples.apps.nowinandroid.core.model.data.Topic +import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule @@ -44,107 +46,59 @@ class GetUserNewsResourcesUseCaseTest { @Test fun whenNoFilters_allNewsResourcesAreReturned() = runTest { - // Obtain the saveable news resources stream. - val saveableNewsResources = useCase() + // Obtain the user news resources stream. + val userNewsResources = useCase() - // Send some news resources and bookmarks. + // Send some news resources and user data into the data repositories. newsRepository.sendNewsResources(sampleNewsResources) - userDataRepository.setNewsResourceBookmarks( - setOf(sampleNewsResources[0].id, sampleNewsResources[2].id) + + // Construct the test user data with bookmarks and followed topics. + val userData = UserData( + bookmarkedNewsResources = setOf(sampleNewsResources[0].id, sampleNewsResources[2].id), + followedTopics = setOf(sampleTopic1.id), + themeBrand = DEFAULT, + darkThemeConfig = FOLLOW_SYSTEM, + shouldHideOnboarding = false ) - // Set a followed topic for the user. - userDataRepository.setFollowedTopicIds(setOf(sampleTopic1.id)) + userDataRepository.setUserData(userData) // Check that the correct news resources are returned with their bookmarked state. assertEquals( listOf( - UserNewsResource( - sampleNewsResources[0].id, - sampleNewsResources[0].title, - sampleNewsResources[0].content, - sampleNewsResources[0].url, - sampleNewsResources[0].headerImageUrl, - sampleNewsResources[0].publishDate, - sampleNewsResources[0].type, - sampleNewsResources[0].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id == sampleTopic1.id - ) - }, - true - ), - UserNewsResource( - sampleNewsResources[1].id, - sampleNewsResources[1].title, - sampleNewsResources[1].content, - sampleNewsResources[1].url, - sampleNewsResources[1].headerImageUrl, - sampleNewsResources[1].publishDate, - sampleNewsResources[1].type, - sampleNewsResources[1].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id == sampleTopic1.id - ) - }, - false - ), - UserNewsResource( - sampleNewsResources[2].id, - sampleNewsResources[2].title, - sampleNewsResources[2].content, - sampleNewsResources[2].url, - sampleNewsResources[2].headerImageUrl, - sampleNewsResources[2].publishDate, - sampleNewsResources[2].type, - sampleNewsResources[2].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id == sampleTopic1.id - ) - }, - true - ), + UserNewsResource.from(sampleNewsResources[0], userData), + UserNewsResource.from(sampleNewsResources[1], userData), + UserNewsResource.from(sampleNewsResources[2], userData), ), - saveableNewsResources.first() + userNewsResources.first() ) } @Test fun whenFilteredByTopicId_matchingNewsResourcesAreReturned() = runTest { - // Obtain a stream of saveable news resources for the given topic id. - val saveableNewsResources = useCase(filterTopicIds = setOf(sampleTopic1.id)) + // Obtain a stream of user news resources for the given topic id. + val userNewsResources = useCase(filterTopicIds = setOf(sampleTopic1.id)) - // Send some news resources and bookmarks. + // Send test data into the repositories. newsRepository.sendNewsResources(sampleNewsResources) - userDataRepository.setNewsResourceBookmarks(setOf()) + val userData = UserData( + bookmarkedNewsResources = emptySet(), + followedTopics = emptySet(), + themeBrand = DEFAULT, + darkThemeConfig = FOLLOW_SYSTEM, + shouldHideOnboarding = false + ) + userDataRepository.setUserData(userData) // Check that only news resources with the given topic id are returned. assertEquals( sampleNewsResources .filter { it.topics.contains(sampleTopic1) } .map { - UserNewsResource( - id = it.id, - title = it.title, - content = it.content, - url = it.url, - headerImageUrl = it.headerImageUrl, - publishDate = it.publishDate, - type = it.type, - topics = it.topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = false - ) - }, - isSaved = false - ) + UserNewsResource.from(it, userData) }, - saveableNewsResources.first() + userNewsResources.first() ) } } diff --git a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt new file mode 100644 index 000000000..6268b28f8 --- /dev/null +++ b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2023 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.core.domain + +import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic +import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource +import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.FOLLOW_SYSTEM +import com.google.samples.apps.nowinandroid.core.model.data.NewsResource +import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Article +import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.DEFAULT +import com.google.samples.apps.nowinandroid.core.model.data.Topic +import com.google.samples.apps.nowinandroid.core.model.data.UserData +import kotlinx.datetime.Clock +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class UserNewsResourceTest { + + /** + * Given: Some user data and news resources + * When: They are combined using `UserNewsResource.from` + * Then: The correct UserNewsResources are constructed + */ + @Test + fun userNewsResourcesAreConstructedFromNewsResourcesAndUserData() { + + val newsResource1 = NewsResource( + id = "N1", + title = "Test news title", + content = "Test news content", + url = "Test URL", + headerImageUrl = "Test image URL", + publishDate = Clock.System.now(), + type = Article, + topics = listOf( + Topic( + id = "T1", + name = "Topic 1", + shortDescription = "Topic 1 short description", + longDescription = "Topic 1 long description", + url = "Topic 1 URL", + imageUrl = "Topic 1 image URL" + ), + Topic( + id = "T2", + name = "Topic 2", + shortDescription = "Topic 2 short description", + longDescription = "Topic 2 long description", + url = "Topic 2 URL", + imageUrl = "Topic 2 image URL" + ), + ) + ) + + val userData = UserData( + bookmarkedNewsResources = setOf("N1"), + followedTopics = setOf("T1"), + themeBrand = DEFAULT, + darkThemeConfig = FOLLOW_SYSTEM, + shouldHideOnboarding = true + ) + + val userNewsResource = UserNewsResource.from(newsResource1, userData) + + // Check that the simple field mappings have been done correctly. + assertEquals(newsResource1.id, userNewsResource.id) + assertEquals(newsResource1.title, userNewsResource.title) + assertEquals(newsResource1.content, userNewsResource.content) + assertEquals(newsResource1.url, userNewsResource.url) + assertEquals(newsResource1.headerImageUrl, userNewsResource.headerImageUrl) + assertEquals(newsResource1.publishDate, userNewsResource.publishDate) + + // Check that each Topic has been converted to a FollowedTopic correctly. + assertEquals(newsResource1.topics.size, userNewsResource.followableTopics.size) + for (topic in newsResource1.topics) { + + // Construct the expected FollowableTopic. + val followableTopic = FollowableTopic( + topic = topic, + isFollowed = userData.followedTopics.contains(topic.id) + ) + assertTrue(userNewsResource.followableTopics.contains(followableTopic)) + } + + // Check that the saved flag is set correctly. + assertEquals( + userData.bookmarkedNewsResources.contains(newsResource1.id), + userNewsResource.isSaved + ) + } +} diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt index 30ccbd402..04d5b3b10 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt @@ -98,4 +98,11 @@ class TestUserDataRepository : UserDataRepository { */ fun getCurrentFollowedTopics(): Set? = _userData.replayCache.firstOrNull()?.followedTopics + + /** + * A test-only API to allow setting of user data directly. + */ + fun setUserData(userData: UserData) { + _userData.tryEmit(userData) + } } diff --git a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt index d1f5d7e87..69ea5fb14 100644 --- a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt +++ b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt @@ -79,7 +79,7 @@ class NewsResourceCardTest { @Test fun testTopicsChipColorBackground_matchesFollowedState() { - val followableTopics = previewUserNewsResources[1].topics + val followableTopics = previewUserNewsResources[1].followableTopics composeTestRule.setContent { NewsResourceTopics(topics = followableTopics) diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index d2024f694..8a0a04dbf 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -116,7 +116,7 @@ fun NewsResourceCardExpanded( Spacer(modifier = Modifier.height(12.dp)) NewsResourceShortDescription(userNewsResource.content) Spacer(modifier = Modifier.height(12.dp)) - NewsResourceTopics(userNewsResource.topics) + NewsResourceTopics(userNewsResource.followableTopics) } } } diff --git a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt index 5ab8d92db..7069c5ff5 100644 --- a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt +++ b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt @@ -292,7 +292,7 @@ class ForYouViewModelTest { headerImageUrl = it.headerImageUrl, publishDate = it.publishDate, type = it.type, - topics = it.topics.map { topic -> + followableTopics = it.topics.map { topic -> FollowableTopic( topic = topic, isFollowed = followedTopicIds.contains(topic.id) @@ -356,7 +356,7 @@ class ForYouViewModelTest { headerImageUrl = sampleNewsResources[1].headerImageUrl, publishDate = sampleNewsResources[1].publishDate, type = sampleNewsResources[1].type, - topics = sampleNewsResources[1].topics.map { topic -> + followableTopics = sampleNewsResources[1].topics.map { topic -> FollowableTopic( topic = topic, isFollowed = topic.id == followedTopicId @@ -372,7 +372,7 @@ class ForYouViewModelTest { headerImageUrl = sampleNewsResources[2].headerImageUrl, publishDate = sampleNewsResources[2].publishDate, type = sampleNewsResources[2].type, - topics = sampleNewsResources[2].topics.map { topic -> + followableTopics = sampleNewsResources[2].topics.map { topic -> FollowableTopic( topic = topic, isFollowed = topic.id == followedTopicId @@ -482,7 +482,7 @@ class ForYouViewModelTest { headerImageUrl = sampleNewsResources[1].headerImageUrl, publishDate = sampleNewsResources[1].publishDate, type = sampleNewsResources[1].type, - topics = sampleNewsResources[1].topics.map { topic -> + followableTopics = sampleNewsResources[1].topics.map { topic -> FollowableTopic( topic = topic, isFollowed = followedTopicIds.contains(topic.id) @@ -498,7 +498,7 @@ class ForYouViewModelTest { headerImageUrl = sampleNewsResources[2].headerImageUrl, publishDate = sampleNewsResources[2].publishDate, type = sampleNewsResources[2].type, - topics = sampleNewsResources[2].topics.map { topic -> + followableTopics = sampleNewsResources[2].topics.map { topic -> FollowableTopic( topic = topic, isFollowed = followedTopicIds.contains(topic.id) 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 c635b7a53..6e94aba9d 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 @@ -187,7 +187,7 @@ private val sampleUserNewsResources = listOf( headerImageUrl = "https://i.ytimg.com/vi/-fJ6poHQrjM/maxresdefault.jpg", publishDate = Instant.parse("2021-11-09T00:00:00.000Z"), type = Video, - topics = listOf( + followableTopics = listOf( FollowableTopic( topic = Topic( id = "0", From 24df51349d6341af62e2ebe63e28073cd24f6ab3 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 5 Jan 2023 12:58:13 +0000 Subject: [PATCH 2/3] Move mapping logic into secondary constructor Change-Id: I032326f502d27f2d3bd9fb8273ae44df878d172c --- .../domain/GetUserNewsResourcesUseCase.kt | 2 +- .../core/domain/model/UserNewsResource.kt | 43 +++---- .../domain/GetUserNewsResourcesUseCaseTest.kt | 8 +- .../core/domain/UserNewsResourceTest.kt | 2 +- .../repository/TestUserDataRepository.kt | 2 +- .../feature/foryou/ForYouViewModelTest.kt | 114 +++++------------- 6 files changed, 56 insertions(+), 115 deletions(-) diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt index 39bd38e1e..d77f8e9df 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt @@ -56,6 +56,6 @@ private fun Flow>.mapToUserNewsResources( filterNot { it.isEmpty() } .combine(userDataStream) { newsResources, userData -> newsResources.map { newsResource -> - UserNewsResource.from(newsResource, userData) + UserNewsResource(newsResource, userData) } } diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt index 280f6b78a..872f93f16 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt @@ -30,9 +30,10 @@ import kotlinx.datetime.toInstant /* ktlint-disable max-line-length */ /** - * A [NewsResource] with the additional user information. + * A [NewsResource] with additional user information such as whether the user is following the + * news resource's topics and whether they have saved (bookmarked) this news resource. */ -data class UserNewsResource( +data class UserNewsResource internal constructor( val id: String, val title: String, val content: String, @@ -43,26 +44,26 @@ data class UserNewsResource( val followableTopics: List, val isSaved: Boolean ) { - companion object { - fun from(newsResource: NewsResource, userData: UserData): UserNewsResource { - return UserNewsResource( - id = newsResource.id, - title = newsResource.title, - content = newsResource.content, - url = newsResource.url, - headerImageUrl = newsResource.headerImageUrl, - publishDate = newsResource.publishDate, - type = newsResource.type, - followableTopics = newsResource.topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = userData.followedTopics.contains(topic.id) - ) - }, - isSaved = userData.bookmarkedNewsResources.contains(newsResource.id) + constructor(newsResource: NewsResource, userData: UserData) : this( + id = newsResource.id, + title = newsResource.title, + content = newsResource.content, + url = newsResource.url, + headerImageUrl = newsResource.headerImageUrl, + publishDate = newsResource.publishDate, + type = newsResource.type, + followableTopics = newsResource.topics.map { topic -> + FollowableTopic( + topic = topic, + isFollowed = userData.followedTopics.contains(topic.id) ) - } - } + }, + isSaved = userData.bookmarkedNewsResources.contains(newsResource.id) + ) +} + +fun List.mapToUserNewsResources(userData: UserData): List { + return map { UserNewsResource(it, userData) } } val previewUserNewsResources = listOf( diff --git a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt index 80d6013c9..61eab3ba8 100644 --- a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt +++ b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt @@ -66,9 +66,9 @@ class GetUserNewsResourcesUseCaseTest { // Check that the correct news resources are returned with their bookmarked state. assertEquals( listOf( - UserNewsResource.from(sampleNewsResources[0], userData), - UserNewsResource.from(sampleNewsResources[1], userData), - UserNewsResource.from(sampleNewsResources[2], userData), + UserNewsResource(sampleNewsResources[0], userData), + UserNewsResource(sampleNewsResources[1], userData), + UserNewsResource(sampleNewsResources[2], userData), ), userNewsResources.first() ) @@ -96,7 +96,7 @@ class GetUserNewsResourcesUseCaseTest { sampleNewsResources .filter { it.topics.contains(sampleTopic1) } .map { - UserNewsResource.from(it, userData) + UserNewsResource(it, userData) }, userNewsResources.first() ) diff --git a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt index 6268b28f8..d1ea7b569 100644 --- a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt +++ b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt @@ -75,7 +75,7 @@ class UserNewsResourceTest { shouldHideOnboarding = true ) - val userNewsResource = UserNewsResource.from(newsResource1, userData) + val userNewsResource = UserNewsResource(newsResource1, userData) // Check that the simple field mappings have been done correctly. assertEquals(newsResource1.id, userNewsResource.id) diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt index 04d5b3b10..8dba1bfc7 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filterNotNull -private val emptyUserData = UserData( +val emptyUserData = UserData( bookmarkedNewsResources = emptySet(), followedTopics = emptySet(), themeBrand = ThemeBrand.DEFAULT, diff --git a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt index 7069c5ff5..4427b44fe 100644 --- a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt +++ b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt @@ -20,12 +20,14 @@ import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCa import com.google.samples.apps.nowinandroid.core.domain.GetUserNewsResourcesUseCase import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource +import com.google.samples.apps.nowinandroid.core.domain.model.mapToUserNewsResources 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 com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository +import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserData import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor import com.google.samples.apps.nowinandroid.core.testing.util.TestSyncStatusMonitor @@ -265,7 +267,8 @@ class ForYouViewModelTest { topicsRepository.sendTopics(sampleTopics) val followedTopicIds = setOf("0", "1") - userDataRepository.setFollowedTopicIds(followedTopicIds) + val userData = emptyUserData.copy(followedTopics = followedTopicIds) + userDataRepository.setUserData(userData) viewModel.dismissOnboarding() assertEquals( @@ -282,25 +285,7 @@ class ForYouViewModelTest { ) assertEquals( NewsFeedUiState.Success( - feed = - sampleNewsResources.map { - UserNewsResource( - id = it.id, - title = it.title, - content = it.content, - url = it.url, - headerImageUrl = it.headerImageUrl, - publishDate = it.publishDate, - type = it.type, - followableTopics = it.topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = followedTopicIds.contains(topic.id) - ) - }, - isSaved = false - ) - } + feed = sampleNewsResources.mapToUserNewsResources(userData) ), viewModel.feedState.value ) @@ -345,41 +330,14 @@ class ForYouViewModelTest { ), viewModel.onboardingUiState.value ) + + val userData = emptyUserData.copy(followedTopics = setOf(followedTopicId)) + assertEquals( NewsFeedUiState.Success( feed = listOf( - UserNewsResource( - id = sampleNewsResources[1].id, - title = sampleNewsResources[1].title, - content = sampleNewsResources[1].content, - url = sampleNewsResources[1].url, - headerImageUrl = sampleNewsResources[1].headerImageUrl, - publishDate = sampleNewsResources[1].publishDate, - type = sampleNewsResources[1].type, - followableTopics = sampleNewsResources[1].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id == followedTopicId - ) - }, - isSaved = false - ), - UserNewsResource( - id = sampleNewsResources[2].id, - title = sampleNewsResources[2].title, - content = sampleNewsResources[2].content, - url = sampleNewsResources[2].url, - headerImageUrl = sampleNewsResources[2].headerImageUrl, - publishDate = sampleNewsResources[2].publishDate, - type = sampleNewsResources[2].type, - followableTopics = sampleNewsResources[2].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id == followedTopicId - ) - }, - isSaved = false - ) + UserNewsResource(sampleNewsResources[1], userData), + UserNewsResource(sampleNewsResources[2], userData), ) ), viewModel.feedState.value @@ -460,12 +418,24 @@ class ForYouViewModelTest { val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() } val followedTopicIds = setOf("1") + val userData = emptyUserData.copy( + followedTopics = followedTopicIds, + shouldHideOnboarding = true + ) topicsRepository.sendTopics(sampleTopics) - userDataRepository.setFollowedTopicIds(followedTopicIds) - userDataRepository.setShouldHideOnboarding(true) + userDataRepository.setUserData(userData) newsRepository.sendNewsResources(sampleNewsResources) - viewModel.updateNewsResourceSaved("2", true) + + val bookmarkedNewsResourceId = "2" + viewModel.updateNewsResourceSaved( + newsResourceId = bookmarkedNewsResourceId, + isChecked = true + ) + + val userDataExpected = userData.copy( + bookmarkedNewsResources = setOf(bookmarkedNewsResourceId) + ) assertEquals( OnboardingUiState.NotShown, @@ -474,38 +444,8 @@ class ForYouViewModelTest { assertEquals( NewsFeedUiState.Success( feed = listOf( - UserNewsResource( - id = sampleNewsResources[1].id, - title = sampleNewsResources[1].title, - content = sampleNewsResources[1].content, - url = sampleNewsResources[1].url, - headerImageUrl = sampleNewsResources[1].headerImageUrl, - publishDate = sampleNewsResources[1].publishDate, - type = sampleNewsResources[1].type, - followableTopics = sampleNewsResources[1].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = followedTopicIds.contains(topic.id) - ) - }, - isSaved = true - ), - UserNewsResource( - id = sampleNewsResources[2].id, - title = sampleNewsResources[2].title, - content = sampleNewsResources[2].content, - url = sampleNewsResources[2].url, - headerImageUrl = sampleNewsResources[2].headerImageUrl, - publishDate = sampleNewsResources[2].publishDate, - type = sampleNewsResources[2].type, - followableTopics = sampleNewsResources[2].topics.map { topic -> - FollowableTopic( - topic = topic, - isFollowed = followedTopicIds.contains(topic.id) - ) - }, - isSaved = false - ), + UserNewsResource(newsResource = sampleNewsResources[1], userDataExpected), + UserNewsResource(newsResource = sampleNewsResources[2], userDataExpected) ) ), viewModel.feedState.value From 6207fe3bcf34d95fd653ca5e24ea5e4dc1cd3994 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 5 Jan 2023 13:09:23 +0000 Subject: [PATCH 3/3] mapToUserNewsResources converts List to List Change-Id: I072effef0f66caf51227ba8c64f0857fb837308d --- .../nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt index d77f8e9df..07cd6d856 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt @@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid.core.domain import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource +import com.google.samples.apps.nowinandroid.core.domain.model.mapToUserNewsResources import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserData import javax.inject.Inject @@ -55,7 +56,5 @@ private fun Flow>.mapToUserNewsResources( ): Flow> = filterNot { it.isEmpty() } .combine(userDataStream) { newsResources, userData -> - newsResources.map { newsResource -> - UserNewsResource(newsResource, userData) - } + newsResources.mapToUserNewsResources(userData) }