ganfra
1 year ago
15 changed files with 354 additions and 53 deletions
@ -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<Unit>()) |
||||||
|
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<Unit>()) |
||||||
|
testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) |
||||||
|
val failureState = awaitItem() |
||||||
|
assertThat(failureState.sendActionState).isEqualTo(Async.Failure<Unit>(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) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
) |
||||||
|
|
@ -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) |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue