From ced60c672e6bfa3d8dfd611fe9e395dad769fd81 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 May 2023 17:06:27 +0200 Subject: [PATCH] Media: add more tests --- .../media/local/AndroidLocalMediaFactory.kt | 11 +- .../impl/media/local/LocalMediaFactory.kt | 10 +- .../impl/media/viewer/MediaViewerPresenter.kt | 11 +- .../MessageComposerPresenter.kt | 28 ++--- .../messages/MessagesPresenterTest.kt | 2 +- .../AttachmentsPreviewPresenterTest.kt | 95 ++++++++++++++++ .../features/messages/fixtures/media.kt | 36 +++++++ .../messages/media/FakeLocalMediaFactory.kt | 37 +++++++ .../media/viewer/MediaViewerPresenterTest.kt | 102 ++++++++++++++++++ .../MessageComposerPresenterTest.kt | 9 +- .../matrix/impl/di/SessionMatrixModule.kt | 12 ++- .../android/libraries/matrix/test/TestData.kt | 5 + .../matrix/test/media/FakeMediaLoader.kt | 7 +- .../matrix/test/media/MediaSource.kt | 18 ++-- .../matrix/test/room/FakeMatrixRoom.kt | 24 +++-- 15 files changed, 354 insertions(+), 53 deletions(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/FakeLocalMediaFactory.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt rename features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/FakeLocalMediaFactory.kt => libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt (58%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt index 8c7bef7cbe..ebc9bb9490 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt @@ -18,10 +18,12 @@ package io.element.android.features.messages.impl.media.local import android.content.Context import android.net.Uri +import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.media.MediaFile import javax.inject.Inject @ContributesBinding(AppScope::class) @@ -29,8 +31,13 @@ class AndroidLocalMediaFactory @Inject constructor( @ApplicationContext private val context: Context ) : LocalMediaFactory { - override fun createFromUri(uri: Uri?, mimeType: String?): LocalMedia? { - if (uri == null) return null + override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia { + val resolvedMimeType = mimeType ?: MimeTypes.OctetStream + val uri = mediaFile.path().toUri() + return LocalMedia(uri, resolvedMimeType) + } + + override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { val resolvedMimeType = mimeType ?: context.contentResolver.getType(uri) ?: MimeTypes.OctetStream return LocalMedia(uri, resolvedMimeType) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaFactory.kt index e7d2ea1ba6..04b2a54757 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaFactory.kt @@ -17,12 +17,20 @@ package io.element.android.features.messages.impl.media.local import android.net.Uri +import io.element.android.libraries.matrix.api.media.MediaFile interface LocalMediaFactory { + + /** + * This method will create a [LocalMedia] with the given [MediaFile] and [mimeType] + * + */ + fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia + /** * This method will create a [LocalMedia] with the given [uri] and [mimeType] * If the [mimeType] is null, it'll try to read it from the content. * */ - fun createFromUri(uri: Uri?, mimeType: String?): LocalMedia? + fun createFromUri(uri: Uri, mimeType: String?): LocalMedia } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt index 7f672fcd3e..542436ed9f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.core.net.toUri import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -32,7 +31,7 @@ import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMediaFactory import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -40,7 +39,7 @@ import kotlinx.coroutines.launch class MediaViewerPresenter @AssistedInject constructor( @Assisted private val inputs: MediaViewerNode.Inputs, private val localMediaFactory: LocalMediaFactory, - private val client: MatrixClient, + private val mediaLoader: MatrixMediaLoader, ) : Presenter { @AssistedFactory @@ -79,14 +78,12 @@ class MediaViewerPresenter @AssistedInject constructor( } private fun CoroutineScope.loadMedia(mediaFile: MutableState, localMedia: MutableState>) = launch { - mediaFile.value = null localMedia.value = Async.Loading() - client.mediaLoader.loadMediaFile(inputs.mediaSource, inputs.mimeType) + mediaLoader.loadMediaFile(inputs.mediaSource, inputs.mimeType) .onSuccess { mediaFile.value = it }.mapCatching { - val uri = it.path().toUri() - localMediaFactory.createFromUri(uri, inputs.mimeType)!! + localMediaFactory.createFromMediaFile(it, inputs.mimeType) }.onSuccess { localMedia.value = Async.Success(it) }.onFailure { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index a1c1e3e6d1..ae975f4ddb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -206,22 +206,22 @@ class MessageComposerPresenter @Inject constructor( mimeType: String? = null, compressIfPossible: Boolean = true, ) { + if (uri == null) { + attachmentsState.value = AttachmentsState.None + return + } val localMedia = localMediaFactory.createFromUri(uri, mimeType) - attachmentsState.value = if (localMedia == null) { - AttachmentsState.None + val mediaAttachment = Attachment.Media(localMedia, compressIfPossible) + val isPreviewable = when { + MimeTypes.isImage(localMedia.mimeType) -> true + MimeTypes.isVideo(localMedia.mimeType) -> true + MimeTypes.isAudio(localMedia.mimeType) -> true + else -> false + } + attachmentsState.value = if (isPreviewable) { + AttachmentsState.Previewing(persistentListOf(mediaAttachment)) } else { - val mediaAttachment = Attachment.Media(localMedia, compressIfPossible) - val isPreviewable = when { - MimeTypes.isImage(localMedia.mimeType) -> true - MimeTypes.isVideo(localMedia.mimeType) -> true - MimeTypes.isAudio(localMedia.mimeType) -> true - else -> false - } - if (isPreviewable) { - AttachmentsState.Previewing(persistentListOf(mediaAttachment)) - } else { - AttachmentsState.Sending(persistentListOf(mediaAttachment)) - } + AttachmentsState.Sending(persistentListOf(mediaAttachment)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 93db073f78..f2691ca1a9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -26,7 +26,7 @@ import io.element.android.features.messages.impl.MessagesEvents import io.element.android.features.messages.impl.MessagesPresenter import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.media.local.FakeLocalMediaFactory +import io.element.android.features.messages.media.FakeLocalMediaFactory import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.networkmonitor.test.FakeNetworkMonitor diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt new file mode 100644 index 0000000000..0b16a254a1 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package io.element.android.features.messages.attachments + +import androidx.media3.common.MimeTypes +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.fixtures.aLocalMedia +import io.element.android.features.messages.impl.attachments.Attachment +import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents +import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter +import io.element.android.features.messages.impl.media.local.LocalMedia +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.mediaupload.api.MediaPreProcessor +import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AttachmentsPreviewPresenterTest { + + private val mediaPreProcessor = FakeMediaPreProcessor() + + @Test + fun `present - send media success scenario`() = runTest { + val room = FakeMatrixRoom() + val presenter = anAttachmentsPreviewPresenter(room = room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) + initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + val loadingState = awaitItem() + assertThat(loadingState.sendActionState).isEqualTo(Async.Loading()) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + val successState = awaitItem() + assertThat(successState.sendActionState).isEqualTo(Async.Success(Unit)) + assertThat(room.sendMediaCount).isEqualTo(1) + } + } + + @Test + fun `present - send media failure scenario`() = runTest { + val room = FakeMatrixRoom() + val failure = MediaPreProcessor.Failure(null) + room.givenSendMediaResult(Result.failure(failure)) + val presenter = anAttachmentsPreviewPresenter(room = room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) + initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + val loadingState = awaitItem() + assertThat(loadingState.sendActionState).isEqualTo(Async.Loading()) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + val failureState = awaitItem() + assertThat(failureState.sendActionState).isEqualTo(Async.Failure(failure)) + assertThat(room.sendMediaCount).isEqualTo(0) + failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) + val clearedState = awaitItem() + assertThat(clearedState.sendActionState).isEqualTo(Async.Uninitialized) + } + } + + private fun anAttachmentsPreviewPresenter( + localMedia: LocalMedia = aLocalMedia(mimeType = MimeTypes.IMAGE_JPEG), + room: MatrixRoom = FakeMatrixRoom() + ): AttachmentsPreviewPresenter { + return AttachmentsPreviewPresenter( + attachment = Attachment.Media(localMedia, compressIfPossible = false), + mediaSender = MediaSender(mediaPreProcessor, room) + ) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt new file mode 100644 index 0000000000..5b63bc1e68 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package io.element.android.features.messages.fixtures + +import android.net.Uri +import io.element.android.features.messages.impl.attachments.Attachment +import io.element.android.features.messages.impl.media.local.LocalMedia +import io.mockk.mockk + +fun aLocalMedia( + uri: Uri = mockk("localMediaUri"), + mimeType: String +) = LocalMedia( + uri = uri, + mimeType = mimeType +) + +fun aMediaAttachment(localMedia: LocalMedia, compressIfPossible: Boolean = true) = Attachment.Media( + localMedia = localMedia, + compressIfPossible = compressIfPossible, +) + diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/FakeLocalMediaFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/FakeLocalMediaFactory.kt new file mode 100644 index 0000000000..b75eec284a --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/FakeLocalMediaFactory.kt @@ -0,0 +1,37 @@ +/* + * 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. + */ + +package io.element.android.features.messages.media + +import android.net.Uri +import io.element.android.features.messages.fixtures.aLocalMedia +import io.element.android.features.messages.impl.media.local.LocalMedia +import io.element.android.features.messages.impl.media.local.LocalMediaFactory +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.media.MediaFile + +class FakeLocalMediaFactory() : LocalMediaFactory { + + var fallbackMimeType: String = MimeTypes.OctetStream + + override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia { + return aLocalMedia(mimeType = mimeType ?: fallbackMimeType) + } + + override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { + return aLocalMedia(uri, mimeType ?: fallbackMimeType) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt new file mode 100644 index 0000000000..a828356586 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/media/viewer/MediaViewerPresenterTest.kt @@ -0,0 +1,102 @@ +/* + * 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. + */ + +package io.element.android.features.messages.media.viewer + +import androidx.media3.common.MimeTypes +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.media.viewer.MediaViewerEvents +import io.element.android.features.messages.impl.media.viewer.MediaViewerNode +import io.element.android.features.messages.impl.media.viewer.MediaViewerPresenter +import io.element.android.features.messages.media.FakeLocalMediaFactory +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS +import io.element.android.libraries.matrix.test.media.FakeMediaLoader +import io.element.android.libraries.matrix.test.media.aMediaSource +import kotlinx.coroutines.test.runTest +import org.junit.Test + +private const val TESTED_MIME_TYPE = MimeTypes.IMAGE_JPEG +private const val TESTED_MEDIA_NAME = "MediaName" + +class MediaViewerPresenterTest { + + private val localMediaFactory = FakeLocalMediaFactory() + private val mediaLoader = FakeMediaLoader() + + @Test + fun `present - download media success scenario`() = runTest { + val presenter = aMediaViewerPresenter() + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isEqualTo(Async.Uninitialized) + assertThat(initialState.name).isEqualTo(TESTED_MEDIA_NAME) + val loadingState = awaitItem() + assertThat(loadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + val successState = awaitItem() + val successData = successState.downloadedMedia.dataOrNull() + assertThat(successState.downloadedMedia).isInstanceOf(Async.Success::class.java) + assertThat(successData).isNotNull() + } + } + + @Test + fun `present - download media failure then retry with success scenario`() = runTest { + val presenter = aMediaViewerPresenter() + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + mediaLoader.shouldFail = true + val initialState = awaitItem() + assertThat(initialState.downloadedMedia).isEqualTo(Async.Uninitialized) + assertThat(initialState.name).isEqualTo(TESTED_MEDIA_NAME) + val loadingState = awaitItem() + assertThat(loadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + val failureState = awaitItem() + assertThat(failureState.downloadedMedia).isInstanceOf(Async.Failure::class.java) + mediaLoader.shouldFail = false + failureState.eventSink(MediaViewerEvents.RetryLoading) + //There is one recomposition because of the retry mechanism + skipItems(1) + val retryLoadingState = awaitItem() + assertThat(retryLoadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + val successState = awaitItem() + val successData = successState.downloadedMedia.dataOrNull() + assertThat(successState.downloadedMedia).isInstanceOf(Async.Success::class.java) + assertThat(successData).isNotNull() + } + } + + private fun aMediaViewerPresenter(mimeType: String = TESTED_MIME_TYPE): MediaViewerPresenter { + return MediaViewerPresenter( + inputs = MediaViewerNode.Inputs( + name = TESTED_MEDIA_NAME, + mediaSource = aMediaSource(), + mimeType = mimeType + ), + localMediaFactory = localMediaFactory, + mediaLoader = mediaLoader + ) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index 1736672c98..40ca7def0a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -21,7 +21,7 @@ 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.features.messages.impl.media.local.FakeLocalMediaFactory +import io.element.android.features.messages.media.FakeLocalMediaFactory import io.element.android.features.messages.impl.messagecomposer.AttachmentsState import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter @@ -52,7 +52,6 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode import io.mockk.mockk import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import java.io.File @@ -392,7 +391,9 @@ class MessageComposerPresenterTest { val sendingState = awaitItem() assertThat(sendingState.showAttachmentSourcePicker).isFalse() assertThat(sendingState.attachmentsState).isInstanceOf(AttachmentsState.Sending::class.java) - cancelAndIgnoreRemainingEvents() + val sentState = awaitItem() + assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None) + assertThat(room.sendMediaCount).isEqualTo(1) } } @@ -440,7 +441,7 @@ class MessageComposerPresenterTest { initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) val sendingState = awaitItem() assertThat(sendingState.attachmentsState).isInstanceOf(AttachmentsState.Sending::class.java) - val finalState= awaitItem() + val finalState = awaitItem() assertThat(finalState.attachmentsState).isInstanceOf(AttachmentsState.None::class.java) snackbarDispatcher.snackbarMessage.test { // Assert error message received diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index b49050cdc2..104a204164 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -22,16 +22,16 @@ import dagger.Provides import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.verification.SessionVerificationService @Module @ContributesTo(SessionScope::class) object SessionMatrixModule { @Provides @SingleIn(SessionScope::class) - fun providesRustSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService { + fun providesSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService { return matrixClient.sessionVerificationService() } @@ -40,4 +40,10 @@ object SessionMatrixModule { fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver { return matrixClient.roomMembershipObserver() } + + @Provides + @SingleIn(SessionScope::class) + fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader { + return matrixClient.mediaLoader + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 60e8c0caa0..56a2e3cd85 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId +import kotlin.time.DurationUnit +import kotlin.time.toDuration const val A_USER_NAME = "alice" const val A_PASSWORD = "password" @@ -52,6 +54,9 @@ val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, true, null) const val AN_AVATAR_URL = "mxc://data" const val A_FAILURE_REASON = "There has been a failure" + +const val FAKE_DELAY_IN_MS = 100L + val A_THROWABLE = Throwable(A_FAILURE_REASON) val AN_EXCEPTION = Exception(A_FAILURE_REASON) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt index ebc823f6d1..266c81c605 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt @@ -17,14 +17,17 @@ package io.element.android.libraries.matrix.test.media import io.element.android.libraries.matrix.api.media.MatrixMediaLoader -import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaFile +import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS +import kotlinx.coroutines.delay class FakeMediaLoader : MatrixMediaLoader { var shouldFail = false override suspend fun loadMediaContent(source: MediaSource): Result { + delay(FAKE_DELAY_IN_MS) return if (shouldFail) { Result.failure(RuntimeException()) } else { @@ -33,6 +36,7 @@ class FakeMediaLoader : MatrixMediaLoader { } override suspend fun loadMediaThumbnail(source: MediaSource, width: Long, height: Long): Result { + delay(FAKE_DELAY_IN_MS) return if (shouldFail) { Result.failure(RuntimeException()) } else { @@ -41,6 +45,7 @@ class FakeMediaLoader : MatrixMediaLoader { } override suspend fun loadMediaFile(source: MediaSource, mimeType: String?): Result { + delay(FAKE_DELAY_IN_MS) return if (shouldFail) { Result.failure(RuntimeException()) } else { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/FakeLocalMediaFactory.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt similarity index 58% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/FakeLocalMediaFactory.kt rename to libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt index 83915dd89d..4a0e9005d2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/FakeLocalMediaFactory.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt @@ -14,17 +14,11 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.media.local +package io.element.android.libraries.matrix.test.media -import android.net.Uri -import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.media.MediaSource -class FakeLocalMediaFactory() : LocalMediaFactory { - - var fallbackMimeType: String = MimeTypes.OctetStream - - override fun createFromUri(uri: Uri?, mimeType: String?): LocalMedia? { - if (uri == null) return null - return LocalMedia(uri, mimeType ?: fallbackMimeType) - } -} +fun aMediaSource(url: String = "") = MediaSource( + url = url, + json = null +) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index a790812137..88d23d5b91 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -94,7 +95,7 @@ class FakeMatrixRoom( } override suspend fun sendMessage(message: String): Result { - delay(100) + delay(FAKE_DELAY_IN_MS) return Result.success(Unit) } @@ -103,7 +104,7 @@ class FakeMatrixRoom( override suspend fun editMessage(originalEventId: EventId, message: String): Result { editMessageParameter = message - delay(100) + delay(FAKE_DELAY_IN_MS) return Result.success(Unit) } @@ -112,7 +113,7 @@ class FakeMatrixRoom( override suspend fun replyMessage(eventId: EventId, message: String): Result { replyMessageParameter = message - delay(100) + delay(FAKE_DELAY_IN_MS) return Result.success(Unit) } @@ -121,7 +122,7 @@ class FakeMatrixRoom( override suspend fun redactEvent(eventId: EventId, reason: String?): Result { redactEventEventIdParam = eventId - delay(100) + delay(FAKE_DELAY_IN_MS) return Result.success(Unit) } @@ -136,13 +137,20 @@ class FakeMatrixRoom( return rejectInviteResult } - override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result = sendMediaResult.also { sendMediaCount++ } + override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result = fakeSendMedia() - override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result = sendMediaResult.also { sendMediaCount++ } + override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result = fakeSendMedia() - override suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result = sendMediaResult.also { sendMediaCount++ } + override suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result = fakeSendMedia() - override suspend fun sendFile(file: File, fileInfo: FileInfo): Result = sendMediaResult.also { sendMediaCount++ } + override suspend fun sendFile(file: File, fileInfo: FileInfo): Result = fakeSendMedia() + + private suspend fun fakeSendMedia(): Result { + delay(FAKE_DELAY_IN_MS) + return sendMediaResult.onSuccess { + sendMediaCount++ + } + } override fun close() = Unit