From 7fff5face45de216c25d5c3269c9fd94f3b7e6a4 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Sun, 13 Mar 2022 17:36:52 +0000 Subject: [PATCH] flattening directory use case syncing into the main logic, adding tests around directory view model --- .../dapk/st/domain/sync/RoomPersistence.kt | 2 +- features/directory/build.gradle | 10 ++++ .../app/dapk/st/directory/DirectoryUseCase.kt | 11 ++-- .../dapk/st/directory/DirectoryViewModel.kt | 10 ++-- .../app/dapk/st/directory/ShortcutHandler.kt | 1 - .../st/directory/DirectoryViewModelTest.kt | 52 +++++++++++++++++++ .../kotlin/app/dapk/st/matrix/sync/Store.kt | 2 +- .../app/dapk/st/matrix/sync/SyncService.kt | 3 +- .../sync/internal/DefaultSyncService.kt | 2 +- 9 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 features/directory/src/test/kotlin/app/dapk/st/directory/DirectoryViewModelTest.kt diff --git a/domains/store/src/main/kotlin/app/dapk/st/domain/sync/RoomPersistence.kt b/domains/store/src/main/kotlin/app/dapk/st/domain/sync/RoomPersistence.kt index 0ed472b..497fcb2 100644 --- a/domains/store/src/main/kotlin/app/dapk/st/domain/sync/RoomPersistence.kt +++ b/domains/store/src/main/kotlin/app/dapk/st/domain/sync/RoomPersistence.kt @@ -91,7 +91,7 @@ internal class RoomPersistence( } } - override suspend fun observeUnreadCountById(): Flow> { + override fun observeUnreadCountById(): Flow> { return database.roomEventQueries.selectAllUnread() .asFlow() .mapToList() diff --git a/features/directory/build.gradle b/features/directory/build.gradle index 1eb3d4b..2ff001a 100644 --- a/features/directory/build.gradle +++ b/features/directory/build.gradle @@ -10,4 +10,14 @@ dependencies { implementation project(":core") implementation project(":design-library") implementation("io.coil-kt:coil-compose:1.4.0") + + kotlinTest(it) + + androidImportFixturesWorkaround(project, project(":matrix:services:sync")) + androidImportFixturesWorkaround(project, project(":matrix:services:message")) + androidImportFixturesWorkaround(project, project(":matrix:common")) + androidImportFixturesWorkaround(project, project(":core")) + androidImportFixturesWorkaround(project, project(":domains:store")) + androidImportFixturesWorkaround(project, project(":domains:android:viewmodel")) + androidImportFixturesWorkaround(project, project(":domains:android:stub")) } \ No newline at end of file diff --git a/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryUseCase.kt b/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryUseCase.kt index 1d553ed..69b6538 100644 --- a/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryUseCase.kt +++ b/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryUseCase.kt @@ -10,6 +10,7 @@ import app.dapk.st.matrix.sync.* import app.dapk.st.matrix.sync.SyncService.SyncEvent.Typing import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map @JvmInline value class UnreadCount(val value: Int) @@ -26,18 +27,14 @@ class DirectoryUseCase( private val roomStore: RoomStore, ) { - suspend fun startSyncing(): Flow { - return syncService.startSyncing() - } - - suspend fun state(): Flow { - val userId = credentialsStore.credentials()!!.userId + fun state(): Flow { return combine( + syncService.startSyncing().map { credentialsStore.credentials()!!.userId }, syncService.overview(), messageService.localEchos(), roomStore.observeUnreadCountById(), syncService.events() - ) { overviewState, localEchos, unread, events -> + ) { userId, overviewState, localEchos, unread, events -> overviewState.mergeWithLocalEchos(localEchos, userId).map { roomOverview -> RoomFoo( overview = roomOverview, diff --git a/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryViewModel.kt b/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryViewModel.kt index a1b3117..44a68ad 100644 --- a/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryViewModel.kt +++ b/features/directory/src/main/kotlin/app/dapk/st/directory/DirectoryViewModel.kt @@ -4,6 +4,8 @@ import androidx.lifecycle.viewModelScope import app.dapk.st.directory.DirectoryScreenState.Content import app.dapk.st.directory.DirectoryScreenState.EmptyLoading import app.dapk.st.viewmodel.DapkViewModel +import app.dapk.st.viewmodel.MutableStateFactory +import app.dapk.st.viewmodel.defaultStateFactory import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach @@ -12,18 +14,16 @@ import kotlinx.coroutines.launch class DirectoryViewModel( private val shortcutHandler: ShortcutHandler, private val directoryUseCase: DirectoryUseCase, + factory: MutableStateFactory = defaultStateFactory(), ) : DapkViewModel( - initialState = EmptyLoading + initialState = EmptyLoading, + factory, ) { private var syncJob: Job? = null fun start() { syncJob = viewModelScope.launch { - directoryUseCase.startSyncing().collect() - } - - viewModelScope.launch { directoryUseCase.state().onEach { shortcutHandler.onDirectoryUpdate(it.map { it.overview }) if (it.isNotEmpty()) { diff --git a/features/directory/src/main/kotlin/app/dapk/st/directory/ShortcutHandler.kt b/features/directory/src/main/kotlin/app/dapk/st/directory/ShortcutHandler.kt index b3f835b..c5dd83d 100644 --- a/features/directory/src/main/kotlin/app/dapk/st/directory/ShortcutHandler.kt +++ b/features/directory/src/main/kotlin/app/dapk/st/directory/ShortcutHandler.kt @@ -13,7 +13,6 @@ class ShortcutHandler(private val context: Context) { private val cachedRoomIds = mutableListOf() fun onDirectoryUpdate(overviews: List) { - val update = overviews.map { it.roomId } if (cachedRoomIds != update) { diff --git a/features/directory/src/test/kotlin/app/dapk/st/directory/DirectoryViewModelTest.kt b/features/directory/src/test/kotlin/app/dapk/st/directory/DirectoryViewModelTest.kt new file mode 100644 index 0000000..752805a --- /dev/null +++ b/features/directory/src/test/kotlin/app/dapk/st/directory/DirectoryViewModelTest.kt @@ -0,0 +1,52 @@ +package app.dapk.st.directory + +import ViewModelTest +import fixture.aRoomOverview +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import org.junit.Test +import test.delegateReturn + +private val AN_OVERVIEW = aRoomOverview() +private val AN_OVERVIEW_STATE = RoomFoo(AN_OVERVIEW, UnreadCount(1), null) + +class DirectoryViewModelTest { + + private val runViewModelTest = ViewModelTest() + private val fakeDirectoryUseCase = FakeDirectoryUseCase() + private val fakeShortcutHandler = FakeShortcutHandler() + + private val viewModel = DirectoryViewModel( + fakeShortcutHandler.instance, + fakeDirectoryUseCase.instance, + runViewModelTest.testMutableStateFactory(), + ) + + @Test + fun `when creating view model, then initial state is empty loading`() = runViewModelTest { + viewModel.test() + + assertInitialState(DirectoryScreenState.EmptyLoading) + } + + @Test + fun `when starting, then updates shortcuts and emits room state`() = runViewModelTest { + fakeShortcutHandler.instance.expectUnit { it.onDirectoryUpdate(listOf(AN_OVERVIEW)) } + fakeDirectoryUseCase.given().returns(flowOf(listOf(AN_OVERVIEW_STATE))) + + viewModel.test().start() + + assertStates(DirectoryScreenState.Content(listOf(AN_OVERVIEW_STATE))) + verifyExpects() + } +} + +class FakeShortcutHandler { + val instance = mockk() +} + +class FakeDirectoryUseCase { + val instance = mockk() + fun given() = every { instance.state() }.delegateReturn() +} \ No newline at end of file diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/Store.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/Store.kt index e0f123e..e4368b6 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/Store.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/Store.kt @@ -13,7 +13,7 @@ interface RoomStore { suspend fun insertUnread(roomId: RoomId, eventIds: List) suspend fun markRead(roomId: RoomId) suspend fun observeUnread(): Flow>> - suspend fun observeUnreadCountById(): Flow> + fun observeUnreadCountById(): Flow> suspend fun observeEvent(eventId: EventId): Flow suspend fun findEvent(eventId: EventId): RoomEvent? diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/SyncService.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/SyncService.kt index d614d8f..725d13a 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/SyncService.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/SyncService.kt @@ -8,7 +8,6 @@ import app.dapk.st.matrix.MatrixServiceInstaller import app.dapk.st.matrix.ServiceDepFactory import app.dapk.st.matrix.common.* import app.dapk.st.matrix.sync.internal.DefaultSyncService -import app.dapk.st.matrix.sync.internal.SideEffectFlowIterator import app.dapk.st.matrix.sync.internal.request.* import app.dapk.st.matrix.sync.internal.room.MessageDecrypter import app.dapk.st.matrix.sync.internal.room.MissingMessageDecrypter @@ -20,7 +19,7 @@ private val SERVICE_KEY = SyncService::class interface SyncService : MatrixService { suspend fun invites(): Flow - suspend fun overview(): Flow + fun overview(): Flow fun room(roomId: RoomId): Flow fun startSyncing(): Flow fun events(): Flow> diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/DefaultSyncService.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/DefaultSyncService.kt index 0c0f38c..6ac54b7 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/DefaultSyncService.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/DefaultSyncService.kt @@ -100,7 +100,7 @@ internal class DefaultSyncService( override fun startSyncing() = syncFlow override suspend fun invites() = overviewStore.latestInvites() - override suspend fun overview() = overviewStore.latest() + override fun overview() = overviewStore.latest() override fun room(roomId: RoomId) = roomStore.latest(roomId) override fun events() = syncEventsFlow override suspend fun observeEvent(eventId: EventId) = roomStore.observeEvent(eventId)