flattening directory use case syncing into the main logic, adding tests around directory view model

This commit is contained in:
Adam Brown 2022-03-13 17:36:52 +00:00
parent e0a7830b5d
commit 7fff5face4
9 changed files with 75 additions and 18 deletions

View File

@ -91,7 +91,7 @@ internal class RoomPersistence(
}
}
override suspend fun observeUnreadCountById(): Flow<Map<RoomId, Int>> {
override fun observeUnreadCountById(): Flow<Map<RoomId, Int>> {
return database.roomEventQueries.selectAllUnread()
.asFlow()
.mapToList()

View File

@ -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"))
}

View File

@ -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<Unit> {
return syncService.startSyncing()
}
suspend fun state(): Flow<DirectoryState> {
val userId = credentialsStore.credentials()!!.userId
fun state(): Flow<DirectoryState> {
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,

View File

@ -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<DirectoryScreenState> = defaultStateFactory(),
) : DapkViewModel<DirectoryScreenState, DirectoryEvent>(
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()) {

View File

@ -13,7 +13,6 @@ class ShortcutHandler(private val context: Context) {
private val cachedRoomIds = mutableListOf<RoomId>()
fun onDirectoryUpdate(overviews: List<RoomOverview>) {
val update = overviews.map { it.roomId }
if (cachedRoomIds != update) {

View File

@ -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<ShortcutHandler>()
}
class FakeDirectoryUseCase {
val instance = mockk<DirectoryUseCase>()
fun given() = every { instance.state() }.delegateReturn()
}

View File

@ -13,7 +13,7 @@ interface RoomStore {
suspend fun insertUnread(roomId: RoomId, eventIds: List<EventId>)
suspend fun markRead(roomId: RoomId)
suspend fun observeUnread(): Flow<Map<RoomOverview, List<RoomEvent>>>
suspend fun observeUnreadCountById(): Flow<Map<RoomId, Int>>
fun observeUnreadCountById(): Flow<Map<RoomId, Int>>
suspend fun observeEvent(eventId: EventId): Flow<EventId>
suspend fun findEvent(eventId: EventId): RoomEvent?

View File

@ -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<InviteState>
suspend fun overview(): Flow<OverviewState>
fun overview(): Flow<OverviewState>
fun room(roomId: RoomId): Flow<RoomState>
fun startSyncing(): Flow<Unit>
fun events(): Flow<List<SyncEvent>>

View File

@ -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)