diff --git a/features/messages/src/main/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenter.kt b/features/messages/src/main/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenter.kt index 29d5030b4e..4f7cbc287f 100644 --- a/features/messages/src/main/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenter.kt +++ b/features/messages/src/main/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenter.kt @@ -59,7 +59,10 @@ class MessageComposerPresenter @Inject constructor( when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value is MessageComposerEvents.UpdateText -> text.value = event.text.toStableCharSequence() - MessageComposerEvents.CloseSpecialMode -> composerMode.setToNormal() + MessageComposerEvents.CloseSpecialMode -> { + text.value = "".toStableCharSequence() + composerMode.setToNormal() + } is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage(event.message, composerMode, text) is MessageComposerEvents.SetMode -> composerMode.value = event.composerMode } diff --git a/features/messages/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt new file mode 100644 index 0000000000..b822caff9e --- /dev/null +++ b/features/messages/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.features.messages.textcomposer + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.ReceiveTurbine +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.data.StableCharSequence +import io.element.android.libraries.matrixtest.core.A_ROOM_ID +import io.element.android.libraries.matrixtest.room.A_MESSAGE +import io.element.android.libraries.matrixtest.room.FakeMatrixRoom +import io.element.android.libraries.matrixtest.timeline.AN_EVENT_ID +import io.element.android.libraries.matrixtest.timeline.A_SENDER_NAME +import io.element.android.libraries.textcomposer.MessageComposerMode +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MessageComposerPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isFullScreen).isFalse() + assertThat(initialState.text).isEqualTo(StableCharSequence("")) + assertThat(initialState.mode).isEqualTo(MessageComposerMode.Normal("")) + assertThat(initialState.isSendButtonVisible).isFalse() + } + } + + @Test + fun `present - toggle fullscreen`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + val fullscreenState = awaitItem() + assertThat(fullscreenState.isFullScreen).isTrue() + fullscreenState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + val notFullscreenState = awaitItem() + assertThat(notFullscreenState.isFullScreen).isFalse() + } + } + + @Test + fun `present - change message`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) + val withMessageState = awaitItem() + assertThat(withMessageState.text).isEqualTo(StableCharSequence(A_MESSAGE)) + assertThat(withMessageState.isSendButtonVisible).isTrue() + withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText("")) + val withEmptyMessageState = awaitItem() + assertThat(withEmptyMessageState.text).isEqualTo(StableCharSequence("")) + assertThat(withEmptyMessageState.isSendButtonVisible).isFalse() + } + } + + @Test + fun `present - change mode to edit`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + var state = awaitItem() + val mode = anEditMode() + state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state = awaitItem() + assertThat(state.mode).isEqualTo(mode) + state = awaitItem() + assertThat(state.text).isEqualTo(StableCharSequence(A_MESSAGE)) + assertThat(state.isSendButtonVisible).isTrue() + backToNormalMode(state, skipCount = 1) + } + } + + private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0) { + state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode) + skipItems(skipCount) + val normalState = awaitItem() + assertThat(normalState.mode).isEqualTo(MessageComposerMode.Normal("")) + assertThat(normalState.text).isEqualTo(StableCharSequence("")) + assertThat(normalState.isSendButtonVisible).isFalse() + } + + @Test + fun `present - change mode to reply`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + var state = awaitItem() + val mode = aReplyMode() + state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state = awaitItem() + assertThat(state.mode).isEqualTo(mode) + assertThat(state.text).isEqualTo(StableCharSequence("")) + assertThat(state.isSendButtonVisible).isFalse() + backToNormalMode(state) + } + } + + @Test + fun `present - change mode to quote`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + var state = awaitItem() + val mode = aQuoteMode() + state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state = awaitItem() + assertThat(state.mode).isEqualTo(mode) + assertThat(state.text).isEqualTo(StableCharSequence("")) + assertThat(state.isSendButtonVisible).isFalse() + backToNormalMode(state) + } + } + + @Test + fun `present - send message`() = runTest { + val presenter = MessageComposerPresenter( + this, + FakeMatrixRoom(A_ROOM_ID) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) + val withMessageState = awaitItem() + assertThat(withMessageState.text).isEqualTo(StableCharSequence(A_MESSAGE)) + assertThat(withMessageState.isSendButtonVisible).isTrue() + withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(A_MESSAGE)) + val messageSentState = awaitItem() + assertThat(messageSentState.text).isEqualTo(StableCharSequence("")) + assertThat(messageSentState.isSendButtonVisible).isFalse() + } + } + +} + +fun anEditMode() = MessageComposerMode.Edit(AN_EVENT_ID, A_MESSAGE) +fun aReplyMode() = MessageComposerMode.Reply(A_SENDER_NAME, AN_EVENT_ID, A_MESSAGE) +fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE) diff --git a/features/roomlist/src/test/kotlin/io/element/android/features/roomlist/RoomListPresenterTests.kt b/features/roomlist/src/test/kotlin/io/element/android/features/roomlist/RoomListPresenterTests.kt index baa547b4bb..46c1597475 100644 --- a/features/roomlist/src/test/kotlin/io/element/android/features/roomlist/RoomListPresenterTests.kt +++ b/features/roomlist/src/test/kotlin/io/element/android/features/roomlist/RoomListPresenterTests.kt @@ -30,7 +30,7 @@ import io.element.android.libraries.matrix.core.SessionId import io.element.android.libraries.matrixtest.FakeMatrixClient import io.element.android.libraries.matrixtest.core.A_ROOM_ID import io.element.android.libraries.matrixtest.core.A_ROOM_ID_VALUE -import io.element.android.libraries.matrixtest.room.A_LAST_MESSAGE +import io.element.android.libraries.matrixtest.room.A_MESSAGE import io.element.android.libraries.matrixtest.room.A_ROOM_NAME import io.element.android.libraries.matrixtest.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrixtest.room.aRoomSummaryFilled @@ -187,7 +187,7 @@ private val aRoomListRoomSummary = RoomListRoomSummary( name = A_ROOM_NAME, hasUnread = true, timestamp = A_FORMATTED_DATE, - lastMessage = A_LAST_MESSAGE, + lastMessage = A_MESSAGE, avatarData = AvatarData(name = A_ROOM_NAME), isPlaceholder = false, ) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/StableCharSequence.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/StableCharSequence.kt index 25f68f2fea..e4ffe2dcaa 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/StableCharSequence.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/StableCharSequence.kt @@ -24,6 +24,8 @@ class StableCharSequence(val charSequence: CharSequence) { override fun hashCode() = hash override fun equals(other: Any?) = other is StableCharSequence && other.hash == hash + + override fun toString(): String = "StableCharSequence(\"$charSequence\")" } fun CharSequence.toStableCharSequence() = StableCharSequence(this) diff --git a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/FakeMatrixRoom.kt b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/FakeMatrixRoom.kt index 7e46219126..7aac3b95a0 100644 --- a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/FakeMatrixRoom.kt +++ b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/FakeMatrixRoom.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.core.RoomId import io.element.android.libraries.matrix.room.MatrixRoom import io.element.android.libraries.matrix.timeline.MatrixTimeline import io.element.android.libraries.matrixtest.timeline.FakeMatrixTimeline +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -51,7 +52,8 @@ class FakeMatrixRoom( } override suspend fun sendMessage(message: String): Result { - TODO("Not yet implemented") + delay(100) + return Result.success(Unit) } override suspend fun editMessage(originalEventId: EventId, message: String): Result { diff --git a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/RoomSummaryFixture.kt b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/RoomSummaryFixture.kt index 0bed866300..79d65536bd 100644 --- a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/RoomSummaryFixture.kt +++ b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/room/RoomSummaryFixture.kt @@ -22,14 +22,14 @@ import io.element.android.libraries.matrix.room.RoomSummaryDetails import io.element.android.libraries.matrixtest.core.A_ROOM_ID const val A_ROOM_NAME = "aRoomName" -const val A_LAST_MESSAGE = "Last message" +const val A_MESSAGE = "Hello world!" fun aRoomSummaryFilled( roomId: RoomId = A_ROOM_ID, name: String = A_ROOM_NAME, isDirect: Boolean = false, avatarURLString: String? = null, - lastMessage: CharSequence? = A_LAST_MESSAGE, + lastMessage: CharSequence? = A_MESSAGE, lastMessageTimestamp: Long? = null, unreadNotificationCount: Int = 2, ) = RoomSummary.Filled( @@ -49,7 +49,7 @@ fun aRoomSummaryDetail( name: String = A_ROOM_NAME, isDirect: Boolean = false, avatarURLString: String? = null, - lastMessage: CharSequence? = A_LAST_MESSAGE, + lastMessage: CharSequence? = A_MESSAGE, lastMessageTimestamp: Long? = null, unreadNotificationCount: Int = 2, ) = RoomSummaryDetails( diff --git a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt index 665a2eccfb..17eb98cb50 100644 --- a/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import org.matrix.rustcomponents.sdk.TimelineListener +const val A_SENDER_NAME = "Alice" const val AN_EVENT_ID_VALUE = "!anEventId" val AN_EVENT_ID = EventId(AN_EVENT_ID_VALUE)