pull/1238/merge
Jaehwa Noh 9 months ago committed by GitHub
commit 35b26bdce7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -31,6 +31,7 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepo
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@ -58,7 +59,11 @@ class NiaAppStateTest {
private val timeZoneMonitor = TestTimeZoneMonitor()
private val userNewsResourceRepository =
CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository())
CompositeUserNewsResourceRepository(
newsRepository = TestNewsRepository(),
userDataRepository = TestUserDataRepository(),
defaultDispatcher = Dispatchers.Default,
)
// Subject under test.
private lateinit var state: NiaAppState

@ -18,11 +18,15 @@ package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@ -33,6 +37,7 @@ import javax.inject.Inject
class CompositeUserNewsResourceRepository @Inject constructor(
val newsRepository: NewsRepository,
val userDataRepository: UserDataRepository,
@Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher,
) : UserNewsResourceRepository {
/**
@ -44,7 +49,7 @@ class CompositeUserNewsResourceRepository @Inject constructor(
newsRepository.getNewsResources(query)
.combine(userDataRepository.userData) { newsResources, userData ->
newsResources.mapToUserNewsResources(userData)
}
}.flowOn(defaultDispatcher)
/**
* Returns available news resources (joined with user data) for the followed topics.

@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.core.data.repository
import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
@ -25,6 +26,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.database.model.asFtsEntity
import com.google.samples.apps.nowinandroid.core.model.data.SearchResult
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -42,6 +45,7 @@ internal class DefaultSearchContentsRepository @Inject constructor(
private val topicDao: TopicDao,
private val topicFtsDao: TopicFtsDao,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
@Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher,
) : SearchContentsRepository {
override suspend fun populateFtsData() {
@ -75,11 +79,13 @@ internal class DefaultSearchContentsRepository @Inject constructor(
.distinctUntilChanged()
.flatMapLatest(topicDao::getTopicEntities)
return combine(newsResourcesFlow, topicsFlow) { newsResources, topics ->
trace("DefaultSearchContentsRepository.searchContents") {
SearchResult(
topics = topics.map { it.asExternalModel() },
newsResources = newsResources.map { it.asExternalModel() },
)
}
}.flowOn(defaultDispatcher)
}
override fun getSearchContentsCount(): Flow<Int> =
@ -87,6 +93,8 @@ internal class DefaultSearchContentsRepository @Inject constructor(
newsResourceFtsDao.getCount(),
topicFtsDao.getCount(),
) { newsResourceCount, topicsCount ->
trace("DefaultSearchContentsRepository.getSearchContentsCount") {
newsResourceCount + topicsCount
}
}.flowOn(defaultDispatcher)
}

@ -24,20 +24,26 @@ import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResourc
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.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
class CompositeUserNewsResourceRepositoryTest {
@get:Rule
val dispatcherRule = MainDispatcherRule()
private val newsRepository = TestNewsRepository()
private val userDataRepository = TestUserDataRepository()
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = dispatcherRule.testDispatcher,
)
@Test

@ -16,13 +16,18 @@
package com.google.samples.apps.nowinandroid.core.domain
import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NAME
import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NONE
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
/**
@ -31,6 +36,7 @@ import javax.inject.Inject
class GetFollowableTopicsUseCase @Inject constructor(
private val topicsRepository: TopicsRepository,
private val userDataRepository: UserDataRepository,
@Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher,
) {
/**
* Returns a list of topics with their associated followed state.
@ -41,6 +47,7 @@ class GetFollowableTopicsUseCase @Inject constructor(
userDataRepository.userData,
topicsRepository.getTopics(),
) { userData, topics ->
trace("GetFollowableTopicsUseCase.invoke") {
val followedTopics = topics
.map { topic ->
FollowableTopic(
@ -53,6 +60,7 @@ class GetFollowableTopicsUseCase @Inject constructor(
else -> followedTopics
}
}
}.flowOn(defaultDispatcher)
}
enum class TopicSortField {

@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.core.domain
import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
@ -23,8 +24,12 @@ import com.google.samples.apps.nowinandroid.core.model.data.SearchResult
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.UserSearchResult
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
/**
@ -33,17 +38,22 @@ import javax.inject.Inject
class GetSearchContentsUseCase @Inject constructor(
private val searchContentsRepository: SearchContentsRepository,
private val userDataRepository: UserDataRepository,
@Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher,
) {
operator fun invoke(
searchQuery: String,
): Flow<UserSearchResult> =
searchContentsRepository.searchContents(searchQuery)
.mapToUserSearchResult(userDataRepository.userData)
.mapToUserSearchResult(userDataRepository.userData, defaultDispatcher)
}
private fun Flow<SearchResult>.mapToUserSearchResult(userDataStream: Flow<UserData>): Flow<UserSearchResult> =
private fun Flow<SearchResult>.mapToUserSearchResult(
userDataStream: Flow<UserData>,
defaultDispatcher: CoroutineDispatcher,
): Flow<UserSearchResult> =
combine(userDataStream) { searchResult, userData ->
trace("Flow<SearchResult>.mapToUserSearchResult") {
UserSearchResult(
topics = searchResult.topics.map { topic ->
FollowableTopic(
@ -59,3 +69,4 @@ private fun Flow<SearchResult>.mapToUserSearchResult(userDataStream: Flow<UserDa
},
)
}
}.flowOn(defaultDispatcher)

@ -37,8 +37,9 @@ class GetFollowableTopicsUseCaseTest {
private val userDataRepository = TestUserDataRepository()
val useCase = GetFollowableTopicsUseCase(
topicsRepository,
userDataRepository,
topicsRepository = topicsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
)
@Test

@ -30,7 +30,11 @@ import org.junit.runner.Description
* for the duration of the test.
*/
class MainDispatcherRule(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
/**
* Expose testDispatcher to share the scheduler to the test.
* See more in [Documentation](https://developer.android.com/kotlin/coroutines/test#injecting-test-dispatchers)
*/
val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
override fun starting(description: Description) = Dispatchers.setMain(testDispatcher)

@ -48,6 +48,7 @@ class BookmarksViewModelTest {
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = dispatcherRule.testDispatcher,
)
private lateinit var viewModel: BookmarksViewModel

@ -65,11 +65,13 @@ class ForYouViewModelTest {
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
)
private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase(
topicsRepository = topicsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
)
private val savedStateHandle = SavedStateHandle()

@ -59,6 +59,7 @@ class InterestsViewModelTest {
private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase(
topicsRepository = topicsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
)
private lateinit var viewModel: InterestsViewModel

@ -57,6 +57,7 @@ class SearchViewModelTest {
private val getSearchContentsUseCase = GetSearchContentsUseCase(
searchContentsRepository = searchContentsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = dispatcherRule.testDispatcher,
)
private val recentSearchRepository = TestRecentSearchRepository()
private val getRecentQueryUseCase = GetRecentSearchQueriesUseCase(recentSearchRepository)

@ -64,6 +64,7 @@ class TopicViewModelTest {
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = dispatcherRule.testDispatcher,
)
private lateinit var viewModel: TopicViewModel

Loading…
Cancel
Save