adding tests around the messaging view model

This commit is contained in:
Adam Brown 2022-03-07 23:18:25 +00:00
parent ccecfb08e0
commit fcfabcda27
17 changed files with 372 additions and 68 deletions

View File

@ -71,7 +71,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
private val driver = AndroidSqliteDriver(DapkDb.Schema, context, "dapk.db")
private val database = DapkDb(driver)
private val clock = Clock.systemUTC()
private val coroutineDispatchers = CoroutineDispatchers(Dispatchers.IO)
val storeModule = unsafeLazy {
@ -116,6 +116,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
context,
buildMeta,
coroutineDispatchers,
clock,
)
}
@ -128,6 +129,7 @@ internal class FeatureModules internal constructor(
context: Context,
buildMeta: BuildMeta,
coroutineDispatchers: CoroutineDispatchers,
clock: Clock,
) {
val directoryModule by unsafeLazy {
@ -155,6 +157,7 @@ internal class FeatureModules internal constructor(
matrixModules.room,
storeModule.value.credentialsStore(),
storeModule.value.roomStore(),
clock
)
}
val homeModule by unsafeLazy { HomeModule(storeModule.value, matrixModules.profile) }

View File

@ -3,7 +3,7 @@ package test
import io.mockk.MockKMatcherScope
import io.mockk.MockKVerificationScope
import io.mockk.coJustRun
import io.mockk.coVerifyAll
import io.mockk.coVerify
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext
@ -15,13 +15,15 @@ fun runExpectTest(testBody: suspend ExpectTestScope.() -> Unit) {
class ExpectTest(override val coroutineContext: CoroutineContext) : ExpectTestScope {
private val expects = mutableListOf<suspend MockKVerificationScope.() -> Unit>()
private val expects = mutableListOf<Pair<Int, suspend MockKVerificationScope.() -> Unit>>()
override fun verifyExpects() = coVerifyAll { expects.forEach { it.invoke(this@coVerifyAll) } }
override fun verifyExpects() = expects.forEach { (times, block) ->
coVerify(exactly = times) { block.invoke(this) }
}
override fun <T> T.expectUnit(block: suspend MockKMatcherScope.(T) -> Unit) {
override fun <T> T.expectUnit(times: Int, block: suspend MockKMatcherScope.(T) -> Unit) {
coJustRun { block(this@expectUnit) }.ignore()
expects.add { block(this@expectUnit) }
expects.add(times to { block(this@expectUnit) })
}
}
@ -30,5 +32,5 @@ private fun Any.ignore() = Unit
interface ExpectTestScope : CoroutineScope {
fun verifyExpects()
fun <T> T.expectUnit(block: suspend MockKMatcherScope.(T) -> Unit)
fun <T> T.expectUnit(times: Int = 1, block: suspend MockKMatcherScope.(T) -> Unit)
}

View File

@ -11,4 +11,13 @@ dependencies {
implementation project(":features:navigator")
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:common"))
androidImportFixturesWorkaround(project, project(":core"))
androidImportFixturesWorkaround(project, project(":domains:store"))
androidImportFixturesWorkaround(project, project(":domains:android:viewmodel"))
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
}

View File

@ -0,0 +1,37 @@
package app.dapk.st.messenger
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomMember
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.sync.MessageMeta
import app.dapk.st.matrix.sync.RoomEvent
class LocalEchoMapper {
fun MessageService.LocalEcho.toMessage(message: MessageService.Message.TextMessage, member: RoomMember): RoomEvent.Message {
return RoomEvent.Message(
eventId = this.eventId ?: EventId(this.localId),
content = message.content.body,
author = member,
utcTimestamp = message.timestampUtc,
meta = this.toMeta()
)
}
fun RoomEvent.mergeWith(echo: MessageService.LocalEcho) = when (this) {
is RoomEvent.Message -> this.copy(meta = echo.toMeta())
is RoomEvent.Reply -> this.copy(message = this.message.copy(meta = echo.toMeta()))
}
private fun MessageService.LocalEcho.toMeta() = MessageMeta.LocalEcho(
echoId = this.localId,
state = when (val localEchoState = this.state) {
MessageService.LocalEcho.State.Sending -> MessageMeta.LocalEcho.State.Sending
MessageService.LocalEcho.State.Sent -> MessageMeta.LocalEcho.State.Sent
is MessageService.LocalEcho.State.Error -> MessageMeta.LocalEcho.State.Error(
localEchoState.message,
type = MessageMeta.LocalEcho.State.Error.Type.UNKNOWN,
)
}
)
}

View File

@ -0,0 +1,7 @@
package app.dapk.st.messenger
import java.util.*
internal class LocalIdFactory {
fun create() = "local.${UUID.randomUUID()}"
}

View File

@ -3,63 +3,50 @@ package app.dapk.st.messenger
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomMember
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.sync.MessageMeta
import app.dapk.st.matrix.sync.RoomEvent
import app.dapk.st.matrix.sync.RoomState
internal typealias MergeWithLocalEchosUseCase = (RoomState, RoomMember, List<MessageService.LocalEcho>) -> RoomState
internal class MergeWithLocalEchosUseCaseImpl : MergeWithLocalEchosUseCase {
internal class MergeWithLocalEchosUseCaseImpl(
private val localEventMapper: LocalEchoMapper,
) : MergeWithLocalEchosUseCase {
override fun invoke(roomState: RoomState, member: RoomMember, echos: List<MessageService.LocalEcho>): RoomState {
val echosByEventId = echos.associateBy { it.eventId }
val stateByEventId = roomState.events.associateBy { it.eventId }
val uniqueEchos = echos.filter { echo ->
echo.eventId == null || stateByEventId[echo.eventId] == null
}.map { localEcho ->
when (val message = localEcho.message) {
is MessageService.Message.TextMessage -> {
createMessage(localEcho, message, member)
}
}
}
val uniqueEchos = uniqueEchos(echos, stateByEventId, member)
val existingWithEcho = updateExistingEventsWithLocalEchoMeta(roomState, echosByEventId)
val existingWithEcho = roomState.events.map {
when (val echo = echosByEventId[it.eventId]) {
null -> it
else -> when (it) {
is RoomEvent.Message -> it.copy(
meta = echo.toMeta()
)
is RoomEvent.Reply -> it.copy(message = it.message.copy(meta = echo.toMeta()))
}
}
}
val sortedEvents = (existingWithEcho + uniqueEchos)
.sortedByDescending { if (it is RoomEvent.Message) it.utcTimestamp else null }
.distinctBy { it.eventId }
return roomState.copy(events = sortedEvents)
}
}
private fun createMessage(localEcho: MessageService.LocalEcho, message: MessageService.Message.TextMessage, member: RoomMember) = RoomEvent.Message(
eventId = localEcho.eventId ?: EventId(localEcho.localId),
content = message.content.body,
author = member,
utcTimestamp = message.timestampUtc,
meta = localEcho.toMeta()
)
private fun MessageService.LocalEcho.toMeta() = MessageMeta.LocalEcho(
echoId = this.localId,
state = when (val localEchoState = this.state) {
MessageService.LocalEcho.State.Sending -> MessageMeta.LocalEcho.State.Sending
MessageService.LocalEcho.State.Sent -> MessageMeta.LocalEcho.State.Sent
is MessageService.LocalEcho.State.Error -> MessageMeta.LocalEcho.State.Error(
localEchoState.message,
type = MessageMeta.LocalEcho.State.Error.Type.UNKNOWN,
)
private fun uniqueEchos(echos: List<MessageService.LocalEcho>, stateByEventId: Map<EventId, RoomEvent>, member: RoomMember): List<RoomEvent.Message> {
return with(localEventMapper) {
echos
.filter { echo -> echo.eventId == null || stateByEventId[echo.eventId] == null }
.map { localEcho ->
when (val message = localEcho.message) {
is MessageService.Message.TextMessage -> {
localEcho.toMessage(message, member)
}
}
}
}
}
)
private fun updateExistingEventsWithLocalEchoMeta(roomState: RoomState, echosByEventId: Map<EventId?, MessageService.LocalEcho>): List<RoomEvent> {
return with(localEventMapper) {
roomState.events.map { roomEvent ->
when (val echo = echosByEventId[roomEvent.eventId]) {
null -> roomEvent
else -> roomEvent.mergeWith(echo)
}
}
}
}
}

View File

@ -6,6 +6,7 @@ import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.room.RoomService
import app.dapk.st.matrix.sync.RoomStore
import app.dapk.st.matrix.sync.SyncService
import java.time.Clock
class MessengerModule(
private val syncService: SyncService,
@ -13,11 +14,12 @@ class MessengerModule(
private val roomService: RoomService,
private val credentialsStore: CredentialsStore,
private val roomStore: RoomStore,
private val clock: Clock,
) : ProvidableModule {
internal fun messengerViewModel(): MessengerViewModel {
return MessengerViewModel(messageService, roomService, roomStore, credentialsStore, timelineUseCase())
return MessengerViewModel(messageService, roomService, roomStore, credentialsStore, timelineUseCase(), LocalIdFactory(), clock)
}
private fun timelineUseCase() = TimelineUseCase(syncService, messageService, roomService, MergeWithLocalEchosUseCaseImpl())
private fun timelineUseCase() = TimelineUseCaseImpl(syncService, messageService, roomService, MergeWithLocalEchosUseCaseImpl(LocalEchoMapper()))
}

View File

@ -12,23 +12,30 @@ import app.dapk.st.matrix.room.RoomService
import app.dapk.st.matrix.sync.RoomEvent
import app.dapk.st.matrix.sync.RoomStore
import app.dapk.st.viewmodel.DapkViewModel
import app.dapk.st.viewmodel.MutableStateFactory
import app.dapk.st.viewmodel.defaultStateFactory
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import java.time.Clock
internal class MessengerViewModel(
private val messageService: MessageService,
private val roomService: RoomService,
private val roomStore: RoomStore,
private val credentialsStore: CredentialsStore,
private val useCase: TimelineUseCase,
private val observeTimeline: ObserveTimelineUseCase,
private val localIdFactory: LocalIdFactory,
private val clock: Clock,
factory: MutableStateFactory<MessengerScreenState> = defaultStateFactory(),
) : DapkViewModel<MessengerScreenState, MessengerEvent>(
initialState = MessengerScreenState(
roomId = null,
roomState = Lce.Loading(),
composerState = ComposerState.Text(value = "")
)
),
factory = factory,
) {
private var syncJob: Job? = null
@ -49,7 +56,7 @@ internal class MessengerViewModel(
val credentials = credentialsStore.credentials()!!
var lastKnownReadEvent: EventId? = null
useCase.state(action.roomId, credentials.userId).distinctUntilChanged().onEach { state ->
observeTimeline.invoke(action.roomId, credentials.userId).distinctUntilChanged().onEach { state ->
state.lastestMessageEventFromOthers(self = credentials.userId)?.let {
if (lastKnownReadEvent != it) {
updateRoomReadStateAsync(latestReadEvent = it, state)
@ -82,6 +89,8 @@ internal class MessengerViewModel(
MessageService.Message.Content.TextContent(body = copy.value),
roomId = roomState.roomOverview.roomId,
sendEncrypted = roomState.roomOverview.isEncrypted,
localId = localIdFactory.create(),
timestampUtc = clock.millis(),
)
)
}

View File

@ -10,20 +10,16 @@ import app.dapk.st.matrix.sync.SyncService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
data class MessengerState(
val self: UserId,
val roomState: RoomState,
val typing: SyncService.SyncEvent.Typing?
)
internal typealias ObserveTimelineUseCase = (RoomId, UserId) -> Flow<MessengerState>
internal class TimelineUseCase(
internal class TimelineUseCaseImpl(
private val syncService: SyncService,
private val messageService: MessageService,
private val roomService: RoomService,
private val mergeWithLocalEchosUseCase: MergeWithLocalEchosUseCase
) {
) : ObserveTimelineUseCase {
suspend fun state(roomId: RoomId, userId: UserId): Flow<MessengerState> {
override fun invoke(roomId: RoomId, userId: UserId): Flow<MessengerState> {
return combine(
syncService.startSyncing(),
syncService.room(roomId),
@ -50,3 +46,9 @@ internal class TimelineUseCase(
}
}
data class MessengerState(
val self: UserId,
val roomState: RoomState,
val typing: SyncService.SyncEvent.Typing?
)

View File

@ -0,0 +1,10 @@
package app.dapk.st.messenger
import io.mockk.every
import io.mockk.mockk
import test.delegateReturn
internal class FakeLocalIdFactory {
val instance = mockk<LocalIdFactory>()
fun givenCreate() = every { instance.create() }.delegateReturn()
}

View File

@ -0,0 +1,82 @@
package app.dapk.st.messenger
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.MessageType
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.RoomMember
import app.dapk.st.matrix.message.MessageService
import fixture.*
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
private val A_ROOM_MESSAGE_EVENT = aRoomMessageEvent(eventId = anEventId("1"))
private val A_LOCAL_ECHO_EVENT_ID = anEventId("2")
private const val A_LOCAL_ECHO_BODY = "body"
private val A_ROOM_MEMBER = aRoomMember()
private val ANOTHER_ROOM_MESSAGE_EVENT = A_ROOM_MESSAGE_EVENT.copy(eventId = anEventId("a second room event"))
class MergeWithLocalEchosUseCaseTest {
private val fakeLocalEchoMapper = FakeLocalEventMapper()
private val mergeWithLocalEchosUseCase = MergeWithLocalEchosUseCaseImpl(fakeLocalEchoMapper.instance)
@Test
fun `given no local echos, when merging, then returns original state`() {
val roomState = aRoomState(events = listOf(A_ROOM_MESSAGE_EVENT))
val result = mergeWithLocalEchosUseCase.invoke(roomState, A_ROOM_MEMBER, emptyList())
result shouldBeEqualTo roomState
}
@Test
fun `given local echo with sending state, when merging then maps to room event with local echo state`() {
val second = createLocalEcho(A_LOCAL_ECHO_EVENT_ID, A_LOCAL_ECHO_BODY, state = MessageService.LocalEcho.State.Sending)
fakeLocalEchoMapper.givenMapping(second, aTextMessage(aTextContent(A_LOCAL_ECHO_BODY)), A_ROOM_MEMBER).returns(ANOTHER_ROOM_MESSAGE_EVENT)
val roomState = aRoomState(events = listOf(A_ROOM_MESSAGE_EVENT))
val result = mergeWithLocalEchosUseCase.invoke(roomState, A_ROOM_MEMBER, listOf(second))
result shouldBeEqualTo roomState.copy(
events = listOf(
A_ROOM_MESSAGE_EVENT,
ANOTHER_ROOM_MESSAGE_EVENT,
)
)
}
private fun createLocalEcho(eventId: EventId, body: String, state: MessageService.LocalEcho.State) = aLocalEcho(
eventId,
aTextMessage(aTextContent(body)),
state,
)
}
fun aLocalEcho(
eventId: EventId? = anEventId(),
message: MessageService.Message = aTextMessage(),
state: MessageService.LocalEcho.State = MessageService.LocalEcho.State.Sending,
) = MessageService.LocalEcho(eventId, message, state)
fun aTextMessage(
content: MessageService.Message.Content.TextContent = aTextContent(),
sendEncrypted: Boolean = false,
roomId: RoomId = aRoomId(),
localId: String = "a-local-id",
timestampUtc: Long = 0,
) = MessageService.Message.TextMessage(content, sendEncrypted, roomId, localId, timestampUtc)
fun aTextContent(
body: String = "text content body",
type: String = MessageType.TEXT.value,
) = MessageService.Message.Content.TextContent(body, type)
class FakeLocalEventMapper {
val instance = mockk<LocalEchoMapper>()
fun givenMapping(echo: MessageService.LocalEcho, event: MessageService.Message.TextMessage, roomMember: RoomMember) = every {
with(instance) { echo.toMessage(event, roomMember) }
}
}

View File

@ -0,0 +1,141 @@
package app.dapk.st.messenger
import ViewModelTest
import app.dapk.st.core.Lce
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.UserId
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.room.RoomService
import app.dapk.st.matrix.sync.RoomState
import fake.FakeCredentialsStore
import fake.FakeRoomStore
import fixture.*
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import org.junit.Test
import test.delegateReturn
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
private const val A_CURRENT_TIMESTAMP = 10000L
private val A_ROOM_ID = aRoomId("messenger state room id")
private const val A_MESSAGE_CONTENT = "message content"
private const val A_LOCAL_ID = "local.1111-2222-3333"
private val AN_EVENT_ID = anEventId("state event")
private val A_SELF_ID = aUserId("self")
class MessengerViewModelTest {
private val runViewModelTest = ViewModelTest()
private val fakeMessageService = FakeMessageService()
private val fakeRoomService = FakeRoomService()
private val fakeRoomStore = FakeRoomStore()
private val fakeCredentialsStore = FakeCredentialsStore().also { it.givenCredentials().returns(aUserCredentials(userId = A_SELF_ID)) }
private val fakeObserveTimelineUseCase = FakeObserveTimelineUseCase()
private val viewModel = MessengerViewModel(
fakeMessageService,
fakeRoomService,
fakeRoomStore,
fakeCredentialsStore,
fakeObserveTimelineUseCase,
localIdFactory = FakeLocalIdFactory().also { it.givenCreate().returns(A_LOCAL_ID) }.instance,
clock = fixedClock(A_CURRENT_TIMESTAMP),
factory = runViewModelTest.testMutableStateFactory(),
)
@Test
fun `when creating view model, then initial state is loading room state`() = runViewModelTest {
viewModel.test()
assertInitialState(
MessengerScreenState(
roomId = null,
roomState = Lce.Loading(),
composerState = ComposerState.Text(value = "")
)
)
}
@Test
fun `given timeline emits state, when starting, then updates state and marks room and events as read`() = runViewModelTest {
fakeRoomStore.expectUnit(times = 2) { it.markRead(A_ROOM_ID) }
fakeRoomService.expectUnit { it.markFullyRead(A_ROOM_ID, AN_EVENT_ID) }
val state = aMessengerStateWithEvent(AN_EVENT_ID, A_SELF_ID)
fakeObserveTimelineUseCase.given(A_ROOM_ID, A_SELF_ID).returns(flowOf(state))
viewModel.test().post(MessengerAction.OnMessengerVisible(A_ROOM_ID))
assertStates<MessengerScreenState>(
{ copy(roomId = A_ROOM_ID) },
{ copy(roomState = Lce.Content(state)) },
)
verifyExpects()
}
@Test
fun `when posting composer update, then updates state`() = runViewModelTest {
viewModel.test().post(MessengerAction.ComposerTextUpdate(A_MESSAGE_CONTENT))
assertStates<MessengerScreenState>({
copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT))
})
}
@Test
fun `given composer message state when posting send text, then resets composer state and sends message`() = runViewModelTest {
fakeMessageService.expectUnit { it.scheduleMessage(expectEncryptedMessage(A_ROOM_ID, A_LOCAL_ID, A_CURRENT_TIMESTAMP, A_MESSAGE_CONTENT)) }
viewModel.test(initialState = initialStateWithComposerMessage(A_ROOM_ID, A_MESSAGE_CONTENT)).post(MessengerAction.ComposerSendText)
assertStates<MessengerScreenState>({ copy(composerState = ComposerState.Text("")) })
verifyExpects()
}
private fun initialStateWithComposerMessage(roomId: RoomId, messageContent: String): MessengerScreenState {
val roomState = RoomState(
aRoomOverview(roomId = roomId, isEncrypted = true),
listOf(anEncryptedRoomMessageEvent(utcTimestamp = 1))
)
return aMessageScreenState(roomId, aMessengerState(roomState = roomState), messageContent)
}
private fun expectEncryptedMessage(roomId: RoomId, localId: String, timestamp: Long, messageContent: String): MessageService.Message.TextMessage {
val content = MessageService.Message.Content.TextContent(body = messageContent)
return MessageService.Message.TextMessage(content, sendEncrypted = true, roomId, localId, timestamp)
}
private fun aMessengerStateWithEvent(eventId: EventId, selfId: UserId) = aRoomStateWithEventId(eventId).toMessengerState(selfId)
private fun RoomState.toMessengerState(selfId: UserId) = aMessengerState(self = selfId, roomState = this)
private fun aRoomStateWithEventId(eventId: EventId): RoomState {
val element = anEncryptedRoomMessageEvent(eventId = eventId, utcTimestamp = 1)
return RoomState(aRoomOverview(roomId = A_ROOM_ID, isEncrypted = true), listOf(element))
}
}
fun aMessageScreenState(roomId: RoomId = aRoomId(), roomState: MessengerState, messageContent: String?) = MessengerScreenState(
roomId = roomId,
roomState = Lce.Content(roomState),
composerState = ComposerState.Text(value = messageContent ?: "")
)
fun aMessengerState(
self: UserId = aUserId(),
roomState: RoomState,
) = MessengerState(self, roomState, typing = null)
class FakeObserveTimelineUseCase : ObserveTimelineUseCase by mockk() {
fun given(roomId: RoomId, selfId: UserId) = coEvery { this@FakeObserveTimelineUseCase.invoke(roomId, selfId) }.delegateReturn()
}
class FakeMessageService : MessageService by mockk()
class FakeRoomService : RoomService by mockk()
fun fixedClock(timestamp: Long = 0) = Clock.fixed(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC)

View File

@ -42,8 +42,8 @@ interface MessageService : MatrixService {
@SerialName("content") val content: Content.TextContent,
@SerialName("send_encrypted") val sendEncrypted: Boolean,
@SerialName("room_id") val roomId: RoomId,
@SerialName("local_id") val localId: String = "local.${UUID.randomUUID()}",
@SerialName("timestamp") val timestampUtc: Long = System.currentTimeMillis(),
@SerialName("local_id") val localId: String,
@SerialName("timestamp") val timestampUtc: Long,
) : Message()
@Serializable

View File

@ -21,9 +21,9 @@ interface SyncService : MatrixService {
suspend fun invites(): Flow<InviteState>
suspend fun overview(): Flow<OverviewState>
suspend fun room(roomId: RoomId): Flow<RoomState>
fun room(roomId: RoomId): Flow<RoomState>
fun startSyncing(): Flow<Unit>
suspend fun events(): Flow<List<SyncEvent>>
fun events(): Flow<List<SyncEvent>>
suspend fun observeEvent(eventId: EventId): Flow<EventId>
suspend fun forceManualRefresh(roomIds: List<RoomId>)

View File

@ -101,8 +101,8 @@ internal class DefaultSyncService(
override fun startSyncing() = syncFlow
override suspend fun invites() = overviewStore.latestInvites()
override suspend fun overview() = overviewStore.latest()
override suspend fun room(roomId: RoomId) = roomStore.latest(roomId)
override suspend fun events() = syncEventsFlow
override fun room(roomId: RoomId) = roomStore.latest(roomId)
override fun events() = syncEventsFlow
override suspend fun observeEvent(eventId: EventId) = roomStore.observeEvent(eventId)
override suspend fun forceManualRefresh(roomIds: List<RoomId>) {
withContext(Dispatchers.IO) {

View File

@ -0,0 +1,10 @@
package fixture
import app.dapk.st.matrix.sync.RoomEvent
import app.dapk.st.matrix.sync.RoomOverview
import app.dapk.st.matrix.sync.RoomState
fun aRoomState(
roomOverview: RoomOverview = aRoomOverview(),
events: List<RoomEvent> = listOf(aRoomMessageEvent()),
) = RoomState(roomOverview, events)

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.fail
import org.amshove.kluent.shouldBeEqualTo
import java.util.*
fun flowTest(block: suspend MatrixTestScope.() -> Unit) {
runTest {
@ -129,6 +130,8 @@ class MatrixTestScope(private val testScope: TestScope) {
content = MessageService.Message.Content.TextContent(body = content),
roomId = roomId,
sendEncrypted = true,
localId = "local.${UUID.randomUUID()}",
timestampUtc = System.currentTimeMillis(),
)
)
}