From c35fca3a1b8bdd661cf0ac7d15501b0c12a73159 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Feb 2024 15:31:50 +0100 Subject: [PATCH] Add test on MediaViewerView --- libraries/mediaviewer/api/build.gradle.kts | 7 + .../api/viewer/MediaViewerStateProvider.kt | 4 +- .../api/viewer/MediaViewerViewTest.kt | 177 ++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerViewTest.kt diff --git a/libraries/mediaviewer/api/build.gradle.kts b/libraries/mediaviewer/api/build.gradle.kts index 2745302623..28590bfcfc 100644 --- a/libraries/mediaviewer/api/build.gradle.kts +++ b/libraries/mediaviewer/api/build.gradle.kts @@ -22,6 +22,11 @@ plugins { android { namespace = "io.element.android.libraries.mediaviewer.api" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -60,6 +65,8 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(libs.coroutines.core) testImplementation(libs.coroutines.test) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) ksp(libs.showkase.processor) } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt index a9d6e4fc98..d46fd64cc7 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerStateProvider.kt @@ -87,6 +87,7 @@ fun aMediaViewerState( mediaInfo: MediaInfo = anImageInfo(), canDownload: Boolean = true, canShare: Boolean = true, + eventSink: (MediaViewerEvents) -> Unit = {}, ) = MediaViewerState( mediaInfo = mediaInfo, thumbnailSource = null, @@ -94,4 +95,5 @@ fun aMediaViewerState( snackbarMessage = null, canDownload = canDownload, canShare = canShare, -) {} + eventSink = eventSink, +) diff --git a/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerViewTest.kt b/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerViewTest.kt new file mode 100644 index 0000000000..1faeb0772e --- /dev/null +++ b/libraries/mediaviewer/api/src/test/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerViewTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2024 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.mediaviewer.api.viewer + +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeDown +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.api.local.anImageInfo +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MediaViewerViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setMediaViewerView( + aMediaViewerState( + eventSink = eventsRecorder + ), + onBackPressed = callback, + ) + rule.pressBack() + } + } + + @Test + fun `clicking on open emit expected Event`() { + testMenuAction(CommonStrings.action_open_with, MediaViewerEvents.OpenWith) + } + + @Test + fun `clicking on save emit expected Event`() { + testMenuAction(CommonStrings.action_save, MediaViewerEvents.SaveOnDisk) + } + + @Test + fun `clicking on share emit expected Event`() { + testMenuAction(CommonStrings.action_share, MediaViewerEvents.Share) + } + + private fun testMenuAction(contentDescriptionRes: Int, expectedEvent: MediaViewerEvents) { + val eventsRecorder = EventsRecorder() + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Success( + LocalMedia(Uri.EMPTY, anImageInfo()) + ), + mediaInfo = anImageInfo(), + eventSink = eventsRecorder + ), + ) + val contentDescription = rule.activity.getString(contentDescriptionRes) + rule.onNodeWithContentDescription(contentDescription).performClick() + eventsRecorder.assertSingle(expectedEvent) + } + + @Ignore("This test is not passing yet, maybe due to interaction with ZoomableAsyncImage?") + @Test + fun `clicking on image hides the overlay`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Success( + LocalMedia(Uri.EMPTY, anImageInfo()) + ), + mediaInfo = anImageInfo(), + eventSink = eventsRecorder + ), + ) + // Ensure that the action are visible + val contentDescription = rule.activity.getString(CommonStrings.action_open_with) + rule.onNodeWithContentDescription(contentDescription).assertHasClickAction() + val imageContentDescription = rule.activity.getString(CommonStrings.common_image) + rule.onNodeWithContentDescription(imageContentDescription).performClick() + // assertHasNoClickAction does not work as expected (?) + // rule.onNodeWithContentDescription(contentDescription).assertHasNoClickAction() + rule.onNodeWithContentDescription(contentDescription).performClick() + // No emitted event + } + + @Ignore("This test is not passing yet, maybe due to interaction with ZoomableAsyncImage?") + @Test + fun `clicking swipe on the image invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Success( + LocalMedia(Uri.EMPTY, anImageInfo()) + ), + mediaInfo = anImageInfo(), + eventSink = eventsRecorder + ), + onBackPressed = callback, + ) + val imageContentDescription = rule.activity.getString(CommonStrings.common_image) + rule.onNodeWithContentDescription(imageContentDescription).performTouchInput { swipeDown() } + rule.mainClock.advanceTimeBy(1_000) + } + } + + @Test + fun `error case, click on retry emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Failure(IllegalStateException("error")), + mediaInfo = anImageInfo(), + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_retry) + eventsRecorder.assertSingle(MediaViewerEvents.RetryLoading) + } + + @Test + fun `error case, click on cancel emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setMediaViewerView( + aMediaViewerState( + downloadedMedia = AsyncData.Failure(IllegalStateException("error")), + mediaInfo = anImageInfo(), + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(MediaViewerEvents.ClearLoadingError) + } +} + +private fun AndroidComposeTestRule.setMediaViewerView( + state: MediaViewerState, + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + MediaViewerView( + state = state, + onBackPressed = onBackPressed, + ) + } +}