Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.samples.apps.nowinandroid.core.navigation.NavigationState
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
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
import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey
Expand Down Expand Up @@ -51,16 +52,19 @@ import kotlin.test.assertEquals
@HiltAndroidTest
class NiaAppStateTest {

@get:Rule
@get:Rule(0)
val composeTestRule = createComposeRule()

@get:Rule(1)
val mainDispatcherRule = MainDispatcherRule()

// Create the test dependencies.
private val networkMonitor = TestNetworkMonitor()

private val timeZoneMonitor = TestTimeZoneMonitor()

private val userNewsResourceRepository =
CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository())
CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository(), mainDispatcherRule.testDispatcher)

// Subject under test.
private lateinit var state: NiaAppState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -33,8 +37,8 @@ import javax.inject.Inject
class CompositeUserNewsResourceRepository @Inject constructor(
val newsRepository: NewsRepository,
val userDataRepository: UserDataRepository,
@Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher,
) : UserNewsResourceRepository {

/**
* Returns available news resources (joined with user data) matching the given query.
*/
Expand All @@ -44,7 +48,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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,13 +26,15 @@ 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
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
Expand All @@ -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() {
Expand Down Expand Up @@ -75,18 +79,22 @@ internal class DefaultSearchContentsRepository @Inject constructor(
.distinctUntilChanged()
.flatMapLatest(topicDao::getTopicEntities)
return combine(newsResourcesFlow, topicsFlow) { newsResources, topics ->
SearchResult(
topics = topics.map { it.asExternalModel() },
newsResources = newsResources.map { it.asExternalModel() },
)
}
trace("DefaultSearchContentsRepository.searchContents") {
SearchResult(
topics = topics.map { it.asExternalModel() },
newsResources = newsResources.map { it.asExternalModel() },
)
}
}.flowOn(defaultDispatcher)
}

override fun getSearchContentsCount(): Flow<Int> =
combine(
newsResourceFtsDao.getCount(),
topicFtsDao.getCount(),
) { newsResourceCount, topicsCount ->
newsResourceCount + topicsCount
}
trace("DefaultSearchContentsRepository.getSearchContentsCount") {
newsResourceCount + topicsCount
}
}.flowOn(defaultDispatcher)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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.
Expand All @@ -41,18 +47,20 @@ class GetFollowableTopicsUseCase @Inject constructor(
userDataRepository.userData,
topicsRepository.getTopics(),
) { userData, topics ->
val followedTopics = topics
.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = topic.id in userData.followedTopics,
)
trace("GetFollowableTopicsUseCase.invoke") {
val followedTopics = topics
.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = topic.id in userData.followedTopics,
)
}
when (sortBy) {
NAME -> followedTopics.sortedBy { it.topic.name }
else -> followedTopics
}
when (sortBy) {
NAME -> followedTopics.sortedBy { it.topic.name }
else -> followedTopics
}
}
}.flowOn(defaultDispatcher)
}

enum class TopicSortField {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,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

/**
Expand All @@ -33,16 +37,20 @@ 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 ->
UserSearchResult(
topics = searchResult.topics.map { topic ->
Expand All @@ -58,4 +66,4 @@ private fun Flow<SearchResult>.mapToUserSearchResult(userDataStream: Flow<UserDa
)
},
)
}
}.flowOn(defaultDispatcher)
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class GetFollowableTopicsUseCaseTest {
val useCase = GetFollowableTopicsUseCase(
topicsRepository,
userDataRepository,
mainDispatcherRule.testDispatcher,
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class BookmarksViewModelTest {
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
dispatcherRule.testDispatcher,
)
private lateinit var viewModel: BookmarksViewModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class InterestsViewModelTest {
private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase(
topicsRepository = topicsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
)
private lateinit var viewModel: InterestsViewModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class SearchViewModelTest {
private val getSearchContentsUseCase = GetSearchContentsUseCase(
searchContentsRepository = searchContentsRepository,
userDataRepository = userDataRepository,
dispatcherRule.testDispatcher,
)
private val recentSearchRepository = TestRecentSearchRepository()
private val getRecentQueryUseCase = GetRecentSearchQueriesUseCase(recentSearchRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class TopicViewModelTest {
private val userNewsResourceRepository = CompositeUserNewsResourceRepository(
newsRepository = newsRepository,
userDataRepository = userDataRepository,
defaultDispatcher = dispatcherRule.testDispatcher,
)
private lateinit var viewModel: TopicViewModel

Expand Down
Loading