Add test on MediaViewerDataSource

This commit is contained in:
Benoit Marty
2025-01-22 10:25:13 +01:00
committed by Benoit Marty
parent 36924070df
commit b0bfea9cc4
4 changed files with 284 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery.ui
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.core.preview.loremIpsum
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo
@@ -30,12 +31,13 @@ class MediaItemFileProvider : PreviewParameterProvider<MediaItem.File> {
fun aMediaItemFile(
id: UniqueId = UniqueId("fileId"),
eventId: EventId? = null,
filename: String = "filename",
caption: String? = null,
): MediaItem.File {
return MediaItem.File(
id = id,
eventId = null,
eventId = eventId,
mediaInfo = aPdfMediaInfo(
filename = filename,
caption = caption,

View File

@@ -13,10 +13,11 @@ import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem
fun aMediaItemLoadingIndicator(
id: UniqueId = UniqueId("loadingId"),
direction: Timeline.PaginationDirection = Timeline.PaginationDirection.BACKWARDS,
): MediaItem.LoadingIndicator {
return MediaItem.LoadingIndicator(
id = id,
direction = Timeline.PaginationDirection.BACKWARDS,
direction = direction,
timestamp = 123,
)
}

View File

@@ -7,6 +7,7 @@
package io.element.android.libraries.mediaviewer.impl.viewer
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
@@ -63,7 +64,8 @@ class MediaViewerDataSource(
return remember { dataFlow() }.collectAsState(initialData())
}
private fun dataFlow(): Flow<PersistentList<MediaViewerPageData>> {
@VisibleForTesting
fun dataFlow(): Flow<PersistentList<MediaViewerPageData>> {
return galleryDataSource.groupedMediaItemsFlow()
.map { groupedItems ->
val mediaItems = groupedItems.dataOrNull()?.getItems(galleryMode).orEmpty()
@@ -104,7 +106,7 @@ class MediaViewerDataSource(
}
}
if (isEmpty()) {
MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS)
add(MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS))
}
}.toPersistentList()

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.mediaviewer.impl.viewer
import android.net.Uri
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
import io.element.android.libraries.mediaviewer.impl.gallery.FakeMediaGalleryDataSource
import io.element.android.libraries.mediaviewer.impl.gallery.MediaGalleryDataSource
import io.element.android.libraries.mediaviewer.impl.gallery.MediaGalleryMode
import io.element.android.libraries.mediaviewer.impl.gallery.aGroupedMediaItems
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemDateSeparator
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.mockk.mockk
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class MediaViewerDataSourceTest {
private val mockMediaUrl: Uri = mockk("localMediaUri")
@Test
fun `setup should start the gallery data source`() = runTest {
val startLambda = lambdaRecorder<Unit> { }
val galleryDataSource = FakeMediaGalleryDataSource(
startLambda = startLambda
)
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.setup()
startLambda.assertions().isCalledOnce()
}
@Test
fun `test dispose`() = runTest {
val sut = createMediaViewerDataSource()
sut.dispose()
}
@Test
fun `test dataFlow uninitialized, loading and error`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(AsyncData.Uninitialized)
assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java)
galleryDataSource.emitGroupedMediaItems(AsyncData.Loading())
assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java)
galleryDataSource.emitGroupedMediaItems(AsyncData.Failure(AN_EXCEPTION))
// TODO Add an error screen in the ui
assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java)
}
}
@Test
fun `test dataFlow empty`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(),
fileItems = listOf(),
)
)
)
val result = awaitItem()
assertThat(result).hasSize(1)
assertThat(result.first()).isEqualTo(MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS))
}
}
@Test
fun `test dataFlow loading items`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(
aMediaItemLoadingIndicator(
direction = Timeline.PaginationDirection.BACKWARDS,
),
aMediaItemLoadingIndicator(
direction = Timeline.PaginationDirection.FORWARDS,
),
),
fileItems = listOf(),
)
)
)
val result = awaitItem()
assertThat(result).containsExactly(
MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS),
MediaViewerPageData.Loading(Timeline.PaginationDirection.FORWARDS),
)
}
}
@Test
fun `test dataFlow with data galleryMode image`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryMode = MediaGalleryMode.Images,
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = listOf(aMediaItemFile(eventId = AN_EVENT_ID_2)),
)
)
)
val result = awaitItem()
assertThat(result).hasSize(1)
assertThat((result.first() as MediaViewerPageData.MediaViewerData).eventId).isEqualTo(AN_EVENT_ID)
}
}
@Test
fun `test dataFlow with data galleryMode files`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryMode = MediaGalleryMode.Files,
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = listOf(aMediaItemFile(eventId = AN_EVENT_ID_2)),
)
)
)
val result = awaitItem()
assertThat(result).hasSize(1)
assertThat((result.first() as MediaViewerPageData.MediaViewerData).eventId).isEqualTo(AN_EVENT_ID_2)
}
}
@Test
fun `test dataFlow - date separator are filtered out`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemDateSeparator(), aMediaItemImage(), aMediaItemDateSeparator()),
fileItems = emptyList(),
)
)
)
val result = awaitItem()
assertThat(result).hasSize(1)
}
}
@Test
fun `loadMore invokes the gallery data source loadMore`() = runTest {
val loadMoreLambda = lambdaRecorder<Timeline.PaginationDirection, Unit> { }
val galleryDataSource = FakeMediaGalleryDataSource(
loadMoreLambda = loadMoreLambda
)
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.loadMore(Timeline.PaginationDirection.BACKWARDS)
loadMoreLambda.assertions().isCalledOnce().with(value(Timeline.PaginationDirection.BACKWARDS))
}
@Test
fun `test dataFlow with data galleryMode image and load media`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
)
)
)
val result = awaitItem()
val mediaViewerData = result.first() as MediaViewerPageData.MediaViewerData
assertThat(mediaViewerData.downloadedMedia.value).isEqualTo(AsyncData.Uninitialized)
sut.loadMedia(mediaViewerData)
assertThat(mediaViewerData.downloadedMedia.value.isSuccess()).isTrue()
}
}
@Test
fun `test dataFlow with data galleryMode image and load media with failure then success`() = runTest {
val galleryDataSource = FakeMediaGalleryDataSource()
val mediaLoader = FakeMatrixMediaLoader()
val sut = createMediaViewerDataSource(
galleryDataSource = galleryDataSource,
mediaLoader = mediaLoader,
)
sut.dataFlow().test {
galleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
)
)
)
val result = awaitItem()
val mediaViewerData = result.first() as MediaViewerPageData.MediaViewerData
assertThat(mediaViewerData.downloadedMedia.value).isEqualTo(AsyncData.Uninitialized)
mediaLoader.shouldFail = true
sut.loadMedia(mediaViewerData)
assertThat(mediaViewerData.downloadedMedia.value.isFailure()).isTrue()
// clear the error
sut.clearLoadingError(mediaViewerData)
assertThat(mediaViewerData.downloadedMedia.value).isEqualTo(AsyncData.Uninitialized)
// load again with success
mediaLoader.shouldFail = false
sut.loadMedia(mediaViewerData)
assertThat(mediaViewerData.downloadedMedia.value.isSuccess()).isTrue()
}
}
@Test
fun clearLoadingError() {
}
private fun TestScope.createMediaViewerDataSource(
galleryMode: MediaGalleryMode = MediaGalleryMode.Images,
galleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource(),
mediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
localMediaFactory: LocalMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
) = MediaViewerDataSource(
galleryMode = galleryMode,
dispatcher = testCoroutineDispatchers().computation,
galleryDataSource = galleryDataSource,
mediaLoader = mediaLoader,
localMediaFactory = localMediaFactory,
)
}