From 87990c425cd16f90423d40fdc014ae2ba19ce7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 May 2023 13:58:29 +0200 Subject: [PATCH] Fix small issues, improve `Result.flatMap` --- .../features/messages/impl/MessagesView.kt | 8 ++- .../impl/media/local/LocalMediaFactory.kt | 2 - .../messages/MessagesPresenterTest.kt | 3 +- .../media/viewer/MediaViewerPresenterTest.kt | 2 +- .../libraries/core/extensions/Result.kt | 27 +++++--- .../libraries/core/extensions/ResultTests.kt | 69 +++++++++++++++++++ .../libraries/mediaupload/api/MediaSender.kt | 2 +- 7 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTests.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index cdaa27cc52..e3c6160b20 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -357,5 +357,11 @@ internal fun MessagesViewDarkPreview(@PreviewParameter(MessagesStateProvider::cl @Composable private fun ContentToPreview(state: MessagesState) { - MessagesView(state, {}, {}, {}, {}) + MessagesView( + state = state, + onBackPressed = {}, + onRoomDetailsClicked = {}, + onEventClicked = {}, + onPreviewAttachments = {} + ) } 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 581461de7f..09c44f4fba 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 @@ -23,14 +23,12 @@ 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 } 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 14807e3c7c..1d8ee54505 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,10 +26,10 @@ 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.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.messages.impl.timeline.groups.TimelineItemGrouper +import io.element.android.features.messages.media.FakeLocalMediaFactory import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService @@ -156,4 +156,3 @@ class MessagesPresenterTest { ) } } - 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 index 17c9a012ad..16f2894303 100644 --- 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 @@ -51,7 +51,7 @@ class MediaViewerPresenterTest { assertThat(initialState.name).isEqualTo(TESTED_MEDIA_NAME) val loadingState = awaitItem() assertThat(loadingState.downloadedMedia).isInstanceOf(Async.Loading::class.java) - testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS) + testScheduler.advanceTimeBy(FAKE_DELAY_IN_MS + 1) val successState = awaitItem() val successData = successState.downloadedMedia.dataOrNull() assertThat(successState.downloadedMedia).isInstanceOf(Async.Success::class.java) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt index 3ddd4f9105..f7d96aebf5 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt @@ -27,14 +27,23 @@ inline fun Result.mapFailure(transform: (exception: Throwable) -> } /** - * Can be used to transform some Throwable into some other. + * Can be used to apply a [transform] that returns a [Result] to a base [Result] and get another [Result]. + * @return The result of the transform as a [Result]. */ -inline fun Result.flatMap(transform: (R) -> Result): Result { - return when (val exception = exceptionOrNull()) { - null -> mapCatching(transform).fold( - onSuccess = { it }, - onFailure = { Result.failure(it) } - ) - else -> Result.failure(exception) - } +inline fun Result.flatMap(transform: (T) -> Result): Result { + return map(transform).fold( + onSuccess = { it }, + onFailure = { Result.failure(it) } + ) +} + +/** + * Can be used to apply a [transform] that returns a [Result] to a base [Result] and get another [Result], catching any exception. + * @return The result of the transform or a caught exception wrapped in a [Result]. + */ +inline fun Result.flatMapCatching(transform: (T) -> Result): Result { + return mapCatching(transform).fold( + onSuccess = { it }, + onFailure = { Result.failure(it) } + ) } diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTests.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTests.kt new file mode 100644 index 0000000000..70a45c9011 --- /dev/null +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTests.kt @@ -0,0 +1,69 @@ +/* + * 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.libraries.core.extensions + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ResultTests { + + @Test + fun testFlatMap() { + val initial = Result.success("initial") + val otherResult = initial.flatMap { Result.success("other") } + val errorResult = initial.flatMap { Result.failure(IllegalStateException("error")) } + + assertThat(otherResult.getOrNull()).isEqualTo("other") + assertThat(errorResult.exceptionOrNull()?.message).isEqualTo("error") + try { + initial.flatMap { error("caught error") } + } catch (e: IllegalStateException) { + assertThat(e.message).isEqualTo("caught error") + } + + val initialError = Result.failure(IllegalStateException("initial error")) + val mapErrorToSuccess = initialError.flatMap { Result.success("other") } + val mapErrorToError = initialError.flatMap { Result.failure(IllegalStateException("error")) } + val mapErrorAndCatch: Result = initialError.flatMap { error("error") } + + assertThat(mapErrorToSuccess.exceptionOrNull()?.message).isEqualTo("initial error") + assertThat(mapErrorToError.exceptionOrNull()?.message).isEqualTo("initial error") + assertThat(mapErrorAndCatch.exceptionOrNull()?.message).isEqualTo("initial error") + } + + @Test + fun testFlatMapCatching() { + val initial = Result.success("initial") + val otherResult = initial.flatMapCatching { Result.success("other") } + val errorResult = initial.flatMapCatching { Result.failure(IllegalStateException("error")) } + val caughtExceptionResult: Result = initial.flatMapCatching { error("caught error") } + + assertThat(otherResult.getOrNull()).isEqualTo("other") + assertThat(errorResult.exceptionOrNull()?.message).isEqualTo("error") + assertThat(caughtExceptionResult.exceptionOrNull()?.message).isEqualTo("caught error") + + val initialError = Result.failure(IllegalStateException("initial error")) + val mapErrorToSuccess = initialError.flatMapCatching { Result.success("other") } + val mapErrorToError = initialError.flatMapCatching { Result.failure(IllegalStateException("error")) } + val mapErrorAndCatch: Result = initialError.flatMapCatching { error("error") } + + assertThat(mapErrorToSuccess.exceptionOrNull()?.message).isEqualTo("initial error") + assertThat(mapErrorToError.exceptionOrNull()?.message).isEqualTo("initial error") + assertThat(mapErrorAndCatch.exceptionOrNull()?.message).isEqualTo("initial error") + } + +} diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index d08860d4ca..585670d939 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -54,7 +54,7 @@ class MediaSender @Inject constructor( is MediaUploadInfo.AnyFile -> { sendFile(info.file, info.info) } - else -> error("Unexpected MediaUploadInfo format: $info") + else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info")) } } }