port messageviewmodel tests to the reducer domain and add more
This commit is contained in:
parent
a5b7ede2d8
commit
b9dda51112
|
@ -10,8 +10,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import test.ExpectTest
|
||||
import test.ExpectTestScope
|
||||
|
||||
interface ReducerTest<S, E> {
|
||||
operator fun invoke(block: suspend ReducerTestScope<S, E>.() -> Unit)
|
||||
|
@ -63,16 +61,31 @@ class ReducerTestScope<S, E>(
|
|||
manualState = state
|
||||
}
|
||||
|
||||
fun setState(block: (S) -> S) {
|
||||
manualState = block(reducerScope.getState())
|
||||
}
|
||||
|
||||
fun assertInitialState(expected: S) {
|
||||
reducerFactory.initialState() shouldBeEqualTo expected
|
||||
}
|
||||
|
||||
fun assertEvents(events: List<E>) {
|
||||
fakeEventSource.assertEvents(events)
|
||||
}
|
||||
|
||||
fun assertOnlyStateChange(expected: S) {
|
||||
assertStateChange(expected)
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertOnlyStateChange(block: (S) -> S) {
|
||||
val expected = block(reducerScope.getState())
|
||||
assertStateChange(expected)
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertStateChange(expected: S) {
|
||||
capturedResult shouldBeEqualTo expected
|
||||
}
|
||||
|
@ -86,7 +99,11 @@ class ReducerTestScope<S, E>(
|
|||
}
|
||||
|
||||
fun assertNoStateChange() {
|
||||
assertEquals(reducerFactory.initialState(), capturedResult)
|
||||
assertEquals(reducerScope.getState(), capturedResult)
|
||||
}
|
||||
|
||||
fun assertNoEvents() {
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertOnlyDispatches(expected: List<Action>) {
|
||||
|
@ -103,7 +120,7 @@ class ReducerTestScope<S, E>(
|
|||
|
||||
fun assertNoChanges() {
|
||||
assertNoStateChange()
|
||||
fakeEventSource.assertNoEvents()
|
||||
assertNoEvents()
|
||||
assertNoDispatches()
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import app.dapk.st.core.extensions.takeIfContent
|
|||
import app.dapk.st.design.components.BubbleModel
|
||||
import app.dapk.st.domain.application.message.MessageOptionsStore
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.engine.MessengerPageState
|
||||
import app.dapk.st.engine.RoomEvent
|
||||
import app.dapk.st.engine.SendMessage
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
|
@ -33,7 +34,7 @@ internal fun messengerReducer(
|
|||
initialState = MessengerScreenState(
|
||||
roomId = roomId,
|
||||
roomState = Lce.Loading(),
|
||||
composerState = initialAttachments?.let { ComposerState.Attachments(it, null) } ?: ComposerState.Text(value = "", reply = null),
|
||||
composerState = initialComposerState(initialAttachments),
|
||||
viewerState = null,
|
||||
),
|
||||
|
||||
|
@ -120,32 +121,12 @@ internal fun messengerReducer(
|
|||
is ComposerState.Text -> {
|
||||
dispatch(ComposerStateChange.Clear)
|
||||
state.roomState.takeIfContent()?.let { content ->
|
||||
val roomState = content.roomState
|
||||
chatEngine.send(
|
||||
message = SendMessage.TextMessage(
|
||||
content = composerState.value,
|
||||
reply = composerState.reply?.let {
|
||||
SendMessage.TextMessage.Reply(
|
||||
author = it.author,
|
||||
originalMessage = when (it) {
|
||||
is RoomEvent.Image -> TODO()
|
||||
is RoomEvent.Reply -> TODO()
|
||||
is RoomEvent.Message -> it.content.asString()
|
||||
is RoomEvent.Encrypted -> error("Should never happen")
|
||||
},
|
||||
eventId = it.eventId,
|
||||
timestampUtc = it.utcTimestamp,
|
||||
)
|
||||
}
|
||||
),
|
||||
room = roomState.roomOverview,
|
||||
)
|
||||
chatEngine.sendTextMessage(content, composerState)
|
||||
}
|
||||
}
|
||||
|
||||
is ComposerState.Attachments -> {
|
||||
dispatch(ComposerStateChange.Clear)
|
||||
|
||||
state.roomState.takeIfContent()?.let { content ->
|
||||
val roomState = content.roomState
|
||||
chatEngine.send(SendMessage.ImageMessage(uri = composerState.values.first().uri.value), roomState.roomOverview)
|
||||
|
@ -156,6 +137,33 @@ internal fun messengerReducer(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun ChatEngine.sendTextMessage(content: MessengerPageState, composerState: ComposerState.Text) {
|
||||
val roomState = content.roomState
|
||||
val message = SendMessage.TextMessage(
|
||||
content = composerState.value,
|
||||
reply = composerState.reply?.toSendMessageReply(),
|
||||
)
|
||||
this.send(message = message, room = roomState.roomOverview)
|
||||
}
|
||||
|
||||
private fun RoomEvent.toSendMessageReply() = SendMessage.TextMessage.Reply(
|
||||
author = this.author,
|
||||
originalMessage = when (this) {
|
||||
is RoomEvent.Image -> TODO()
|
||||
is RoomEvent.Reply -> TODO()
|
||||
is RoomEvent.Message -> this.content.asString()
|
||||
is RoomEvent.Encrypted -> error("Should never happen")
|
||||
},
|
||||
eventId = this.eventId,
|
||||
timestampUtc = this.utcTimestamp,
|
||||
)
|
||||
|
||||
|
||||
private fun initialComposerState(initialAttachments: List<MessageAttachment>?) = initialAttachments
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { ComposerState.Attachments(it, null) }
|
||||
?: ComposerState.Text(value = "", reply = null)
|
||||
|
||||
private fun BubbleModel.findCopyableContent(): CopyableResult = when (this) {
|
||||
is BubbleModel.Encrypted -> CopyableResult.NothingToCopy
|
||||
is BubbleModel.Image -> CopyableResult.NothingToCopy
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
package app.dapk.st.messenger
|
||||
|
||||
import android.os.Build
|
||||
import app.dapk.st.core.*
|
||||
import app.dapk.st.design.components.BubbleModel
|
||||
import app.dapk.st.engine.RoomEvent
|
||||
import app.dapk.st.engine.RoomState
|
||||
import app.dapk.st.engine.SendMessage
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.UserId
|
||||
import app.dapk.st.matrix.common.asString
|
||||
import app.dapk.st.messenger.state.*
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import fake.FakeChatEngine
|
||||
import fake.FakeMessageOptionsStore
|
||||
import fixture.*
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Test
|
||||
import test.ReducerTestScope
|
||||
import test.delegateReturn
|
||||
import test.expect
|
||||
import test.testReducer
|
||||
|
||||
private const val READ_RECEIPTS_ARE_DISABLED = true
|
||||
private val A_ROOM_ID = aRoomId("messenger state room id")
|
||||
private const val A_MESSAGE_CONTENT = "message content"
|
||||
private val AN_EVENT_ID = anEventId("state event")
|
||||
private val A_SELF_ID = aUserId("self")
|
||||
private val A_MESSENGER_PAGE_STATE = aMessengerStateWithEvent(AN_EVENT_ID, A_SELF_ID)
|
||||
private val A_MESSAGE_ATTACHMENT = MessageAttachment(AndroidUri("a-uri"), MimeType.Image)
|
||||
private val A_REPLY = aRoomReplyMessageEvent()
|
||||
private val AN_IMAGE_BUBBLE = BubbleModel.Image(
|
||||
BubbleModel.Image.ImageContent(100, 200, "a-url"),
|
||||
mockk(),
|
||||
BubbleModel.Event("author-id", "author-name", edited = false, time = "10:27")
|
||||
)
|
||||
|
||||
private val A_TEXT_BUBBLE = BubbleModel.Text(
|
||||
content = RichText(listOf(RichText.Part.Normal(A_MESSAGE_CONTENT))),
|
||||
BubbleModel.Event("author-id", "author-name", edited = false, time = "10:27")
|
||||
)
|
||||
|
||||
class MessengerReducerTest {
|
||||
|
||||
private val fakeMessageOptionsStore = FakeMessageOptionsStore()
|
||||
private val fakeChatEngine = FakeChatEngine()
|
||||
private val fakeCopyToClipboard = FakeCopyToClipboard()
|
||||
private val fakeDeviceMeta = FakeDeviceMeta()
|
||||
private val fakeJobBag = FakeJobBag()
|
||||
|
||||
private val runReducerTest = testReducer { fakeEventSource ->
|
||||
messengerReducer(
|
||||
fakeJobBag.instance,
|
||||
fakeChatEngine,
|
||||
fakeCopyToClipboard.instance,
|
||||
fakeDeviceMeta.instance,
|
||||
fakeMessageOptionsStore.instance,
|
||||
A_ROOM_ID,
|
||||
emptyList(),
|
||||
fakeEventSource,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given empty initial attachments, then initial state is loading with text composer`() = reducerWithInitialState(initialAttachments = emptyList()) {
|
||||
assertInitialState(
|
||||
MessengerScreenState(
|
||||
roomId = A_ROOM_ID,
|
||||
roomState = Lce.Loading(),
|
||||
composerState = ComposerState.Text(value = "", reply = null),
|
||||
viewerState = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given null initial attachments, then initial state is loading with text composer`() = reducerWithInitialState(initialAttachments = null) {
|
||||
assertInitialState(
|
||||
MessengerScreenState(
|
||||
roomId = A_ROOM_ID,
|
||||
roomState = Lce.Loading(),
|
||||
composerState = ComposerState.Text(value = "", reply = null),
|
||||
viewerState = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given initial attachments, then initial state is loading attachment composer`() = reducerWithInitialState(listOf(A_MESSAGE_ATTACHMENT)) {
|
||||
assertInitialState(
|
||||
MessengerScreenState(
|
||||
roomId = A_ROOM_ID,
|
||||
roomState = Lce.Loading(),
|
||||
composerState = ComposerState.Attachments(listOf(A_MESSAGE_ATTACHMENT), reply = null),
|
||||
viewerState = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given messages emits state, when Visible, then dispatches content`() = runReducerTest {
|
||||
fakeJobBag.instance.expect { it.add("messages", any()) }
|
||||
fakeMessageOptionsStore.givenReadReceiptsDisabled().returns(READ_RECEIPTS_ARE_DISABLED)
|
||||
val state = aMessengerStateWithEvent(AN_EVENT_ID, A_SELF_ID)
|
||||
fakeChatEngine.givenMessages(A_ROOM_ID, READ_RECEIPTS_ARE_DISABLED).returns(flowOf(state))
|
||||
|
||||
reduce(ComponentLifecycle.Visible)
|
||||
|
||||
assertOnlyDispatches(listOf(MessagesStateChange.Content(state)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Gone, then cancels sync job`() = runReducerTest {
|
||||
fakeJobBag.instance.expect { it.cancel("messages") }
|
||||
|
||||
reduce(ComponentLifecycle.Gone)
|
||||
|
||||
assertNoChanges()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Content StateChange, then updates room state`() = runReducerTest {
|
||||
reduce(MessagesStateChange.Content(A_MESSENGER_PAGE_STATE))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(roomState = Lce.Content(A_MESSENGER_PAGE_STATE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when SelectAttachmentToSend, then updates composer state`() = runReducerTest {
|
||||
reduce(ComposerStateChange.SelectAttachmentToSend(A_MESSAGE_ATTACHMENT))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = ComposerState.Attachments(listOf(A_MESSAGE_ATTACHMENT), reply = null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Show ImagePreview, then updates viewer state`() = runReducerTest {
|
||||
reduce(ComposerStateChange.ImagePreview.Show(AN_IMAGE_BUBBLE))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(viewerState = ViewerState(AN_IMAGE_BUBBLE))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Hide ImagePreview, then updates viewer state`() = runReducerTest {
|
||||
reduce(ComposerStateChange.ImagePreview.Hide)
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(viewerState = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when TextUpdate StateChange, then updates composer state`() = runReducerTest {
|
||||
reduce(ComposerStateChange.TextUpdate(A_MESSAGE_CONTENT))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Clear ComposerStateChange, then clear composer state`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = null)) }
|
||||
|
||||
reduce(ComposerStateChange.Clear)
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = ComposerState.Text(value = "", reply = null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given text composer, when Enter ReplyMode, then updates composer state with reply`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = null)) }
|
||||
|
||||
reduce(ComposerStateChange.ReplyMode.Enter(A_REPLY))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = (previous.composerState as ComposerState.Text).copy(reply = A_REPLY))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given text composer, when Exit ReplyMode, then updates composer state`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = A_REPLY)) }
|
||||
|
||||
reduce(ComposerStateChange.ReplyMode.Exit)
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = (previous.composerState as ComposerState.Text).copy(reply = null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given attachment composer, when Enter ReplyMode, then updates composer state with reply`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Attachments(listOf(A_MESSAGE_ATTACHMENT), reply = null)) }
|
||||
|
||||
reduce(ComposerStateChange.ReplyMode.Enter(A_REPLY))
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = (previous.composerState as ComposerState.Attachments).copy(reply = A_REPLY))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given attachment composer, when Exit ReplyMode, then updates composer state`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Attachments(listOf(A_MESSAGE_ATTACHMENT), reply = A_REPLY)) }
|
||||
|
||||
reduce(ComposerStateChange.ReplyMode.Exit)
|
||||
|
||||
assertOnlyStateChange { previous ->
|
||||
previous.copy(composerState = (previous.composerState as ComposerState.Attachments).copy(reply = null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when OpenGalleryPicker, then emits event`() = runReducerTest {
|
||||
reduce(ScreenAction.OpenGalleryPicker)
|
||||
|
||||
assertOnlyEvents(listOf(MessengerEvent.SelectImageAttachment))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given android api is lower than S_v2 and has text content, when CopyToClipboard, then copies to system and toasts`() = runReducerTest {
|
||||
fakeDeviceMeta.givenApiVersion().returns(Build.VERSION_CODES.S)
|
||||
fakeCopyToClipboard.instance.expect { it.copy(CopyToClipboard.Copyable.Text(A_MESSAGE_CONTENT)) }
|
||||
|
||||
reduce(ScreenAction.CopyToClipboard(A_TEXT_BUBBLE))
|
||||
|
||||
assertEvents(listOf(MessengerEvent.Toast("Copied to clipboard")))
|
||||
assertNoDispatches()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given android api is higher than S_v2 and has text content, when CopyToClipboard, then copies to system and does not toast`() = runReducerTest {
|
||||
fakeDeviceMeta.givenApiVersion().returns(Build.VERSION_CODES.TIRAMISU)
|
||||
fakeCopyToClipboard.instance.expect { it.copy(CopyToClipboard.Copyable.Text(A_MESSAGE_CONTENT)) }
|
||||
|
||||
reduce(ScreenAction.CopyToClipboard(A_TEXT_BUBBLE))
|
||||
|
||||
assertNoChanges()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given image content, when CopyToClipboard, then toasts nothing to copy`() = runReducerTest {
|
||||
reduce(ScreenAction.CopyToClipboard(AN_IMAGE_BUBBLE))
|
||||
|
||||
assertEvents(listOf(MessengerEvent.Toast("Nothing to copy")))
|
||||
assertNoDispatches()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given text composer, when SendMessage, then clear composer and sends text message`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = null), roomState = Lce.Content(A_MESSENGER_PAGE_STATE)) }
|
||||
fakeChatEngine.expectUnit { it.send(expectTextMessage(A_MESSAGE_CONTENT), A_MESSENGER_PAGE_STATE.roomState.roomOverview) }
|
||||
|
||||
reduce(ScreenAction.SendMessage)
|
||||
|
||||
assertDispatches(listOf(ComposerStateChange.Clear))
|
||||
assertNoEvents()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `given text composer with reply, when SendMessage, then clear composer and sends text message`() = runReducerTest {
|
||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = A_REPLY.message), roomState = Lce.Content(A_MESSENGER_PAGE_STATE)) }
|
||||
fakeChatEngine.expectUnit { it.send(expectTextMessage(A_MESSAGE_CONTENT, reply = A_REPLY.message), A_MESSENGER_PAGE_STATE.roomState.roomOverview) }
|
||||
|
||||
reduce(ScreenAction.SendMessage)
|
||||
|
||||
assertDispatches(listOf(ComposerStateChange.Clear))
|
||||
assertNoEvents()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given attachment composer, when SendMessage, then clear composer and sends image message`() = runReducerTest {
|
||||
setState {
|
||||
it.copy(
|
||||
composerState = ComposerState.Attachments(listOf(A_MESSAGE_ATTACHMENT), reply = null),
|
||||
roomState = Lce.Content(A_MESSENGER_PAGE_STATE)
|
||||
)
|
||||
}
|
||||
fakeChatEngine.expectUnit { it.send(expectImageMessage(A_MESSAGE_ATTACHMENT.uri), A_MESSENGER_PAGE_STATE.roomState.roomOverview) }
|
||||
|
||||
reduce(ScreenAction.SendMessage)
|
||||
|
||||
assertDispatches(listOf(ComposerStateChange.Clear))
|
||||
assertNoEvents()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
private fun expectTextMessage(messageContent: String, reply: RoomEvent? = null): SendMessage.TextMessage {
|
||||
return SendMessage.TextMessage(messageContent, reply = reply?.toSendMessageReply())
|
||||
}
|
||||
|
||||
private fun expectImageMessage(uri: AndroidUri): SendMessage.ImageMessage {
|
||||
return SendMessage.ImageMessage(uri.value)
|
||||
}
|
||||
|
||||
private fun RoomEvent.toSendMessageReply() = SendMessage.TextMessage.Reply(
|
||||
author = this.author,
|
||||
originalMessage = when (this) {
|
||||
is RoomEvent.Image -> TODO()
|
||||
is RoomEvent.Reply -> TODO()
|
||||
is RoomEvent.Message -> this.content.asString()
|
||||
is RoomEvent.Encrypted -> error("Should never happen")
|
||||
},
|
||||
eventId = this.eventId,
|
||||
timestampUtc = this.utcTimestamp,
|
||||
)
|
||||
|
||||
private fun reducerWithInitialState(
|
||||
initialAttachments: List<MessageAttachment>?,
|
||||
block: suspend ReducerTestScope<MessengerScreenState, MessengerEvent>.() -> Unit
|
||||
) = testReducer { fakeEventSource ->
|
||||
messengerReducer(
|
||||
fakeJobBag.instance,
|
||||
fakeChatEngine,
|
||||
fakeCopyToClipboard.instance,
|
||||
fakeDeviceMeta.instance,
|
||||
fakeMessageOptionsStore.instance,
|
||||
A_ROOM_ID,
|
||||
initialAttachments = initialAttachments,
|
||||
fakeEventSource,
|
||||
)
|
||||
}(block)
|
||||
|
||||
}
|
||||
|
||||
private fun aMessengerStateWithEvent(eventId: EventId, selfId: UserId) = aRoomStateWithEventId(eventId).toMessengerState(selfId)
|
||||
|
||||
private fun aRoomStateWithEventId(eventId: EventId): RoomState {
|
||||
val element = anEncryptedRoomMessageEvent(eventId = eventId, utcTimestamp = 1)
|
||||
return RoomState(aRoomOverview(roomId = A_ROOM_ID, isEncrypted = true), listOf(element))
|
||||
}
|
||||
|
||||
private fun RoomState.toMessengerState(selfId: UserId) = aMessengerState(self = selfId, roomState = this)
|
||||
|
||||
class FakeCopyToClipboard {
|
||||
val instance = mockk<CopyToClipboard>()
|
||||
}
|
||||
|
||||
class FakeJobBag {
|
||||
val instance = mockk<JobBag>()
|
||||
}
|
||||
|
||||
class FakeDeviceMeta {
|
||||
val instance = mockk<DeviceMeta>()
|
||||
|
||||
fun givenApiVersion() = every { instance.apiVersion }.delegateReturn()
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package app.dapk.st.messenger
|
||||
|
||||
import ViewModelTest
|
||||
import app.dapk.st.core.DeviceMeta
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.extensions.takeIfContent
|
||||
import app.dapk.st.engine.MessengerPageState
|
||||
import app.dapk.st.engine.RoomState
|
||||
import app.dapk.st.engine.SendMessage
|
||||
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.messenger.state.ComposerState
|
||||
import app.dapk.st.messenger.state.MessengerScreenState
|
||||
import fake.FakeChatEngine
|
||||
import fake.FakeMessageOptionsStore
|
||||
import fixture.*
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Test
|
||||
|
||||
private const val READ_RECEIPTS_ARE_DISABLED = true
|
||||
private val A_ROOM_ID = aRoomId("messenger state room id")
|
||||
private const val A_MESSAGE_CONTENT = "message content"
|
||||
private val AN_EVENT_ID = anEventId("state event")
|
||||
private val A_SELF_ID = aUserId("self")
|
||||
|
||||
class MessengerViewModelTest {
|
||||
|
||||
private val runViewModelTest = ViewModelTest()
|
||||
|
||||
private val fakeMessageOptionsStore = FakeMessageOptionsStore()
|
||||
private val fakeChatEngine = FakeChatEngine()
|
||||
private val fakeCopyToClipboard = FakeCopyToClipboard()
|
||||
private val deviceMeta = DeviceMeta(26)
|
||||
|
||||
private val viewModel = MessengerViewModel(
|
||||
fakeChatEngine,
|
||||
fakeMessageOptionsStore.instance,
|
||||
fakeCopyToClipboard.instance,
|
||||
deviceMeta,
|
||||
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 = "", reply = null),
|
||||
viewerState = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given timeline emits state, when starting, then updates state and marks room and events as read`() = runViewModelTest {
|
||||
fakeMessageOptionsStore.givenReadReceiptsDisabled().returns(READ_RECEIPTS_ARE_DISABLED)
|
||||
val state = aMessengerStateWithEvent(AN_EVENT_ID, A_SELF_ID)
|
||||
fakeChatEngine.givenMessages(A_ROOM_ID, READ_RECEIPTS_ARE_DISABLED).returns(flowOf(state))
|
||||
|
||||
viewModel.test().post(MessengerAction.OnMessengerVisible(A_ROOM_ID, attachments = null))
|
||||
|
||||
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, reply = null))
|
||||
})
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given composer message state when posting send text, then resets composer state and sends message`() = runViewModelTest {
|
||||
val initialState = initialStateWithComposerMessage(A_ROOM_ID, A_MESSAGE_CONTENT)
|
||||
fakeChatEngine.expectUnit { it.send(expectTextMessage(A_MESSAGE_CONTENT), initialState.roomState.takeIfContent()!!.roomState.roomOverview) }
|
||||
|
||||
viewModel.test(initialState = initialState).post(MessengerAction.ComposerSendText)
|
||||
|
||||
assertStates<MessengerScreenState>({ copy(composerState = ComposerState.Text("", reply = null)) })
|
||||
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 expectTextMessage(messageContent: String): SendMessage.TextMessage {
|
||||
return SendMessage.TextMessage(messageContent, reply = null)
|
||||
}
|
||||
|
||||
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: MessengerPageState, messageContent: String?) = MessengerScreenState(
|
||||
roomId = roomId,
|
||||
roomState = Lce.Content(roomState),
|
||||
composerState = ComposerState.Text(value = messageContent ?: "", reply = null),
|
||||
viewerState = null,
|
||||
)
|
||||
|
||||
class FakeCopyToClipboard {
|
||||
val instance = mockk<CopyToClipboard>()
|
||||
}
|
Loading…
Reference in New Issue