diff options
Diffstat (limited to 'tests')
11 files changed, 506 insertions, 180 deletions
diff --git a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java index 24b7fb12..a8b8b2e9 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java +++ b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java @@ -118,8 +118,12 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.intentresolver.chooser.DisplayResolveInfo; +import com.android.intentresolver.contentpreview.FakeThumbnailLoader; import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.contentpreview.ImageLoaderModule; +import com.android.intentresolver.contentpreview.PreviewCacheSize; +import com.android.intentresolver.contentpreview.PreviewMaxConcurrency; +import com.android.intentresolver.contentpreview.ThumbnailLoader; import com.android.intentresolver.data.repository.FakeUserRepository; import com.android.intentresolver.data.repository.UserRepository; import com.android.intentresolver.data.repository.UserRepositoryModule; @@ -272,6 +276,17 @@ public class ChooserActivityTest { @BindValue final ImageLoader mImageLoader = mFakeImageLoader; + @BindValue + @PreviewCacheSize + int mPreviewCacheSize = 16; + + @BindValue + @PreviewMaxConcurrency + int mPreviewMaxConcurrency = 4; + + @BindValue + ThumbnailLoader mThumbnailLoader = new FakeThumbnailLoader(); + @Before public void setUp() { // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt b/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt new file mode 100644 index 00000000..d3fdf17d --- /dev/null +++ b/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.contentpreview + +import android.graphics.Bitmap +import android.net.Uri + +/** Fake implementation of [ThumbnailLoader] for use in testing. */ +class FakeThumbnailLoader : ThumbnailLoader { + + val fakeInvoke = mutableMapOf<Uri, suspend () -> Bitmap?>() + val invokeCalls = mutableListOf<Uri>() + var unfinishedInvokeCount = 0 + + override suspend fun invoke(uri: Uri): Bitmap? { + invokeCalls.add(uri) + unfinishedInvokeCount++ + val result = fakeInvoke[uri]?.invoke() + unfinishedInvokeCount-- + return result + } +} diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt index 659c178c..8f7c59de 100644 --- a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt +++ b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.intentresolver.backgroundDispatcher import com.android.intentresolver.contentResolver import com.android.intentresolver.contentpreview.HeadlineGenerator import com.android.intentresolver.contentpreview.ImageLoader +import com.android.intentresolver.contentpreview.mimetypeClassifier import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.activityResultRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.cursorPreviewsRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.pendingSelectionCallbackRepository @@ -97,6 +98,7 @@ val Kosmos.selectionInteractor selectionsRepo = previewSelectionsRepository, targetIntentModifier = targetIntentModifier, updateTargetIntentInteractor = updateTargetIntentInteractor, + mimeTypeClassifier = mimetypeClassifier, ) val Kosmos.setCursorPreviewsInteractor diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt new file mode 100644 index 00000000..331f9f64 --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.contentpreview + +import android.graphics.Bitmap +import android.net.Uri +import com.google.common.truth.Truth.assertThat +import kotlin.math.ceil +import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class CachingImagePreviewImageLoaderTest { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + private val testJobTime = 100.milliseconds + private val testCacheSize = 4 + private val testMaxConcurrency = 2 + private val testTimeToFillCache = + testJobTime * ceil((testCacheSize).toFloat() / testMaxConcurrency.toFloat()).roundToInt() + private val testUris = + List(5) { Uri.fromParts("TestScheme$it", "TestSsp$it", "TestFragment$it") } + private val testTimeToLoadAllUris = + testJobTime * ceil((testUris.size).toFloat() / testMaxConcurrency.toFloat()).roundToInt() + private val testBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8) + private val fakeThumbnailLoader = + FakeThumbnailLoader().apply { + testUris.forEach { + fakeInvoke[it] = { + delay(testJobTime) + testBitmap + } + } + } + + private val imageLoader = + CachingImagePreviewImageLoader( + scope = testScope.backgroundScope, + bgDispatcher = testDispatcher, + thumbnailLoader = fakeThumbnailLoader, + cacheSize = testCacheSize, + maxConcurrency = testMaxConcurrency, + ) + + @Test + fun loadImage_notCached_callsThumbnailLoader() = + testScope.runTest { + // Arrange + var result: Bitmap? = null + + // Act + imageLoader.loadImage(testScope, testUris[0]) { result = it } + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) + assertThat(result).isSameInstanceAs(testBitmap) + } + + @Test + fun loadImage_cached_usesCachedValue() = + testScope.runTest { + // Arrange + imageLoader.loadImage(testScope, testUris[0]) {} + advanceTimeBy(testJobTime) + runCurrent() + fakeThumbnailLoader.invokeCalls.clear() + var result: Bitmap? = null + + // Act + imageLoader.loadImage(testScope, testUris[0]) { result = it } + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() + assertThat(result).isSameInstanceAs(testBitmap) + } + + @Test + fun loadImage_error_returnsNull() = + testScope.runTest { + // Arrange + fakeThumbnailLoader.fakeInvoke[testUris[0]] = { + delay(testJobTime) + throw RuntimeException("Test exception") + } + var result: Bitmap? = testBitmap + + // Act + imageLoader.loadImage(testScope, testUris[0]) { result = it } + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) + assertThat(result).isNull() + } + + @Test + fun loadImage_uncached_limitsConcurrency() = + testScope.runTest { + // Arrange + val results = mutableListOf<Bitmap?>() + assertThat(testUris.size).isGreaterThan(testMaxConcurrency) + + // Act + testUris.take(testMaxConcurrency + 1).forEach { uri -> + imageLoader.loadImage(testScope, uri) { results.add(it) } + } + + // Assert + assertThat(results).isEmpty() + advanceTimeBy(testJobTime) + runCurrent() + assertThat(results).hasSize(testMaxConcurrency) + advanceTimeBy(testJobTime) + runCurrent() + assertThat(results).hasSize(testMaxConcurrency + 1) + assertThat(results) + .containsExactlyElementsIn(List(testMaxConcurrency + 1) { testBitmap }) + } + + @Test + fun loadImage_cacheEvicted_cancelsLoadAndReturnsNull() = + testScope.runTest { + // Arrange + val results = MutableList<Bitmap?>(testUris.size) { null } + assertThat(testUris.size).isGreaterThan(testCacheSize) + + // Act + imageLoader.loadImage(testScope, testUris[0]) { results[0] = it } + runCurrent() + testUris.indices.drop(1).take(testCacheSize).forEach { i -> + imageLoader.loadImage(testScope, testUris[i]) { results[i] = it } + } + advanceTimeBy(testTimeToFillCache) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(testUris) + assertThat(results) + .containsExactlyElementsIn( + List(testUris.size) { index -> if (index == 0) null else testBitmap } + ) + .inOrder() + assertThat(fakeThumbnailLoader.unfinishedInvokeCount).isEqualTo(1) + } + + @Test + fun prePopulate_fillsCache() = + testScope.runTest { + // Arrange + val fullCacheUris = testUris.take(testCacheSize) + assertThat(fullCacheUris).hasSize(testCacheSize) + + // Act + imageLoader.prePopulate(fullCacheUris) + advanceTimeBy(testTimeToFillCache) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(fullCacheUris) + + // Act + fakeThumbnailLoader.invokeCalls.clear() + imageLoader.prePopulate(fullCacheUris) + advanceTimeBy(testTimeToFillCache) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() + } + + @Test + fun prePopulate_greaterThanCacheSize_fillsCacheThenDropsRemaining() = + testScope.runTest { + // Arrange + assertThat(testUris.size).isGreaterThan(testCacheSize) + + // Act + imageLoader.prePopulate(testUris) + advanceTimeBy(testTimeToLoadAllUris) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls) + .containsExactlyElementsIn(testUris.take(testCacheSize)) + + // Act + fakeThumbnailLoader.invokeCalls.clear() + imageLoader.prePopulate(testUris) + advanceTimeBy(testTimeToLoadAllUris) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() + } + + @Test + fun prePopulate_fewerThatCacheSize_loadsTheGiven() = + testScope.runTest { + // Arrange + val unfilledCacheUris = testUris.take(testMaxConcurrency) + assertThat(unfilledCacheUris.size).isLessThan(testCacheSize) + + // Act + imageLoader.prePopulate(unfilledCacheUris) + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(unfilledCacheUris) + + // Act + fakeThumbnailLoader.invokeCalls.clear() + imageLoader.prePopulate(unfilledCacheUris) + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() + } + + @Test + fun invoke_uncached_alwaysCallsTheThumbnailLoader() = + testScope.runTest { + // Arrange + + // Act + imageLoader.invoke(testUris[0], caching = false) + imageLoader.invoke(testUris[0], caching = false) + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0], testUris[0]) + } + + @Test + fun invoke_cached_usesTheCacheWhenPossible() = + testScope.runTest { + // Arrange + + // Act + imageLoader.invoke(testUris[0], caching = true) + imageLoader.invoke(testUris[0], caching = true) + advanceTimeBy(testJobTime) + runCurrent() + + // Assert + assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) + } +} diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt index ff699373..0036e803 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt @@ -111,8 +111,12 @@ class CursorPreviewsInteractorTest { @Test fun initialCursorLoad() = runTestWithDeps( + cursor = (0 until 10), + cursorStartPosition = 2, cursorSizes = mapOf(0 to (200 x 100)), - metadatSizes = mapOf(0 to (300 x 100), 3 to (400 x 100)) + metadatSizes = mapOf(0 to (300 x 100), 3 to (400 x 100)), + pageSize = 2, + maxLoadedPages = 3, ) { deps -> backgroundScope.launch { cursorPreviewsInteractor.launch(deps.cursor, deps.initialPreviews) @@ -120,31 +124,29 @@ class CursorPreviewsInteractorTest { runCurrent() assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.startIdx).isEqualTo(0) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreRight).isNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels) - .containsExactly( - PreviewModel( - uri = Uri.fromParts("scheme0", "ssp0", "fragment0"), - mimeType = "image/bitmap", - aspectRatio = 2f, - ), - PreviewModel( - uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = "image/bitmap" - ), - PreviewModel( - uri = Uri.fromParts("scheme2", "ssp2", "fragment2"), - mimeType = "image/bitmap" - ), - PreviewModel( - uri = Uri.fromParts("scheme3", "ssp3", "fragment3"), - mimeType = "image/bitmap", - aspectRatio = 4f, - ), - ) - .inOrder() + with(cursorPreviewsRepository.previewsModel.value!!) { + assertThat(previewModels) + .containsExactlyElementsIn( + List(6) { + PreviewModel( + uri = Uri.fromParts("scheme$it", "ssp$it", "fragment$it"), + mimeType = "image/bitmap", + aspectRatio = + when (it) { + 0 -> 2f + 3 -> 4f + else -> 1f + } + ) + } + ) + .inOrder() + assertThat(startIdx).isEqualTo(0) + assertThat(loadMoreLeft).isNull() + assertThat(loadMoreRight).isNotNull() + assertThat(leftTriggerIndex).isEqualTo(2) + assertThat(rightTriggerIndex).isEqualTo(4) + } } @Test @@ -181,39 +183,6 @@ class CursorPreviewsInteractorTest { } @Test - fun loadMoreLeft_keepRight() = - runTestWithDeps( - initialSelection = listOf(24), - cursor = (0 until 48), - pageSize = 16, - maxLoadedPages = 2, - ) { deps -> - backgroundScope.launch { - cursorPreviewsInteractor.launch(deps.cursor, deps.initialPreviews) - } - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(16) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNotNull() - - cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft!!.invoke() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(32) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme0", "ssp0", "fragment0")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNull() - } - - @Test fun loadMoreRight_evictLeft() = runTestWithDeps( initialSelection = listOf(24), @@ -246,38 +215,6 @@ class CursorPreviewsInteractorTest { } @Test - fun loadMoreRight_keepLeft() = - runTestWithDeps( - initialSelection = listOf(24), - cursor = (0 until 48), - pageSize = 16, - maxLoadedPages = 2, - ) { deps -> - backgroundScope.launch { - cursorPreviewsInteractor.launch(deps.cursor, deps.initialPreviews) - } - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(16) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreRight).isNotNull() - - cursorPreviewsRepository.previewsModel.value!!.loadMoreRight!!.invoke() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(32) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme47", "ssp47", "fragment47")) - } - - @Test fun noMoreRight_appendUnclaimedFromInitialSelection() = runTestWithDeps( initialSelection = listOf(24, 50), diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt index 735bcb1d..d04c984f 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt @@ -53,7 +53,7 @@ class FetchPreviewsInteractorTest { cursor: Iterable<Int> = (0 until 4), cursorStartPosition: Int = cursor.count() / 2, pageSize: Int = 16, - maxLoadedPages: Int = 3, + maxLoadedPages: Int = 8, previewSizes: Map<Int, Size> = emptyMap(), block: KosmosTestScope.() -> Unit, ) { @@ -113,7 +113,7 @@ class FetchPreviewsInteractorTest { .isEqualTo( PreviewsModel( previewModels = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), mimeType = "image/bitmap", @@ -127,6 +127,8 @@ class FetchPreviewsInteractorTest { startIdx = 1, loadMoreLeft = null, loadMoreRight = null, + leftTriggerIndex = 0, + rightTriggerIndex = 1, ) ) } @@ -202,38 +204,6 @@ class FetchPreviewsInteractorTest { } @Test - fun loadMoreLeft_keepRight() = - runTest( - initialSelection = listOf(24), - cursor = (0 until 48), - pageSize = 16, - maxLoadedPages = 2, - ) { - backgroundScope.launch { fetchPreviewsInteractor.activate() } - fakeCursorResolver.complete() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(16) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNotNull() - - cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft!!.invoke() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(32) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme0", "ssp0", "fragment0")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNull() - } - - @Test fun loadMoreRight_evictLeft() = runTest( initialSelection = listOf(24), @@ -265,37 +235,6 @@ class FetchPreviewsInteractorTest { } @Test - fun loadMoreRight_keepLeft() = - runTest( - initialSelection = listOf(24), - cursor = (0 until 48), - pageSize = 16, - maxLoadedPages = 2, - ) { - backgroundScope.launch { fetchPreviewsInteractor.activate() } - fakeCursorResolver.complete() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(16) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme31", "ssp31", "fragment31")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreRight).isNotNull() - - cursorPreviewsRepository.previewsModel.value!!.loadMoreRight!!.invoke() - runCurrent() - - assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels).hasSize(32) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.first().uri) - .isEqualTo(Uri.fromParts("scheme16", "ssp16", "fragment16")) - assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels.last().uri) - .isEqualTo(Uri.fromParts("scheme47", "ssp47", "fragment47")) - } - - @Test fun noMoreRight_appendUnclaimedFromInitialSelection() = runTest( initialSelection = listOf(24, 50), diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt index a3c65570..0275a9c3 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt @@ -64,7 +64,7 @@ class SelectablePreviewInteractorTest { assertThat(underTest.isSelected.first()).isFalse() previewSelectionsRepository.selections.value = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = "image/bitmap" diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt index be5ddfe5..14b9c49c 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt @@ -39,7 +39,7 @@ class SelectablePreviewsInteractorTest { cursorPreviewsRepository.previewsModel.value = PreviewsModel( previewModels = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = "image/bitmap", @@ -52,9 +52,11 @@ class SelectablePreviewsInteractorTest { startIdx = 0, loadMoreLeft = null, loadMoreRight = null, + leftTriggerIndex = 0, + rightTriggerIndex = 1, ) previewSelectionsRepository.selections.value = - setOf( + listOf( PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null), ) targetIntentModifier = TargetIntentModifier { error("unexpected invocation") } @@ -94,7 +96,7 @@ class SelectablePreviewsInteractorTest { @Test fun keySet_reflectsRepositoryUpdate() = runKosmosTest { previewSelectionsRepository.selections.value = - setOf( + listOf( PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null), ) targetIntentModifier = TargetIntentModifier { error("unexpected invocation") } @@ -114,7 +116,7 @@ class SelectablePreviewsInteractorTest { cursorPreviewsRepository.previewsModel.value = PreviewsModel( previewModels = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = "image/bitmap", @@ -127,8 +129,10 @@ class SelectablePreviewsInteractorTest { startIdx = 5, loadMoreLeft = null, loadMoreRight = { loadRequested = true }, + leftTriggerIndex = 0, + rightTriggerIndex = 1, ) - previewSelectionsRepository.selections.value = emptySet() + previewSelectionsRepository.selections.value = emptyList() runCurrent() assertThat(previews.value).isNotNull() diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt new file mode 100644 index 00000000..a50efebf --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor + +import android.content.Intent +import android.net.Uri +import com.android.intentresolver.contentpreview.mimetypeClassifier +import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.previewSelectionsRepository +import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel +import com.android.intentresolver.util.runKosmosTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class SelectionInteractorTest { + @Test + fun singleSelection_removalPrevented() = runKosmosTest { + val initialPreview = + PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null) + previewSelectionsRepository.selections.value = listOf(initialPreview) + + val underTest = + SelectionInteractor( + previewSelectionsRepository, + { Intent() }, + updateTargetIntentInteractor, + mimetypeClassifier, + ) + + assertThat(underTest.selections.value).containsExactly(initialPreview) + + // Shouldn't do anything! + underTest.unselect(initialPreview) + + assertThat(underTest.selections.value).containsExactly(initialPreview) + } + + @Test + fun multipleSelections_removalAllowed() = runKosmosTest { + val first = PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null) + val second = + PreviewModel(uri = Uri.fromParts("scheme2", "ssp2", "fragment2"), mimeType = null) + previewSelectionsRepository.selections.value = listOf(first, second) + + val underTest = + SelectionInteractor( + previewSelectionsRepository, + { Intent() }, + updateTargetIntentInteractor, + mimetypeClassifier + ) + + underTest.unselect(first) + + assertThat(underTest.selections.value).containsExactly(second) + } +} diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt index 5aac7b55..a165b41e 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt @@ -34,8 +34,8 @@ class SetCursorPreviewsInteractorTest { fun setPreviews_noAdditionalData() = runKosmosTest { val loadState = setCursorPreviewsInteractor.setPreviews( - previewsByKey = - setOf( + previews = + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null, @@ -44,6 +44,8 @@ class SetCursorPreviewsInteractorTest { startIndex = 100, hasMoreLeft = false, hasMoreRight = false, + leftTriggerIndex = 0, + rightTriggerIndex = 0, ) assertThat(loadState.first()).isNull() @@ -69,8 +71,8 @@ class SetCursorPreviewsInteractorTest { val loadState = setCursorPreviewsInteractor .setPreviews( - previewsByKey = - setOf( + previews = + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null, @@ -79,6 +81,8 @@ class SetCursorPreviewsInteractorTest { startIndex = 100, hasMoreLeft = true, hasMoreRight = true, + leftTriggerIndex = 0, + rightTriggerIndex = 0, ) .stateIn(backgroundScope) diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt index bd3d88f8..ec4a9c3e 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt @@ -25,6 +25,7 @@ import android.graphics.drawable.Icon import android.net.Uri import com.android.intentresolver.FakeImageLoader import com.android.intentresolver.contentpreview.HeadlineGenerator +import com.android.intentresolver.contentpreview.mimetypeClassifier import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.activityResultRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.cursorPreviewsRepository @@ -39,6 +40,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.payloadToggleImageLoader import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.selectablePreviewsInteractor import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.selectionInteractor +import com.android.intentresolver.contentpreview.payloadtoggle.shared.ContentType import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel import com.android.intentresolver.data.model.ChooserRequest @@ -68,23 +70,24 @@ class ShareouselViewModelTest { actionsInteractor = customActionsInteractor, headlineGenerator = headlineGenerator, chooserRequestInteractor = chooserRequestInteractor, + mimeTypeClassifier = mimetypeClassifier, selectionInteractor = selectionInteractor, scope = viewModelScope, ) } @Test - fun headline() = runTest { - assertThat(shareouselViewModel.headline.first()).isEqualTo("IMAGES: 1") + fun headline_images() = runTest { + assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 1") previewSelectionsRepository.selections.value = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = null, + mimeType = "image/png", ), PreviewModel( uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = null, + mimeType = "image/jpeg", ) ) runCurrent() @@ -92,6 +95,40 @@ class ShareouselViewModelTest { } @Test + fun headline_videos() = runTest { + previewSelectionsRepository.selections.value = + listOf( + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = "video/mpeg", + ), + PreviewModel( + uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), + mimeType = "video/mpeg", + ) + ) + runCurrent() + assertThat(shareouselViewModel.headline.first()).isEqualTo("VIDEOS: 2") + } + + @Test + fun headline_mixed() = runTest { + previewSelectionsRepository.selections.value = + listOf( + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = "image/jpeg", + ), + PreviewModel( + uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), + mimeType = "video/mpeg", + ) + ) + runCurrent() + assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 2") + } + + @Test fun metadataText() = runTest { val request = ChooserRequest( @@ -112,19 +149,21 @@ class ShareouselViewModelTest { cursorPreviewsRepository.previewsModel.value = PreviewsModel( previewModels = - setOf( + listOf( PreviewModel( uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = null, + mimeType = "image/png", ), PreviewModel( uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = null, + mimeType = "video/mpeg", ) ), startIdx = 1, loadMoreLeft = null, loadMoreRight = null, + leftTriggerIndex = 0, + rightTriggerIndex = 1, ) runCurrent() @@ -140,15 +179,17 @@ class ShareouselViewModelTest { .inOrder() val previewVm = - shareouselViewModel.preview( + shareouselViewModel.preview.invoke( PreviewModel( uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = null - ) + mimeType = "video/mpeg" + ), + /* index = */ 1, ) assertWithMessage("preview bitmap is null").that(previewVm.bitmap.first()).isNotNull() assertThat(previewVm.isSelected.first()).isFalse() + assertThat(previewVm.contentType).isEqualTo(ContentType.Video) previewVm.setSelected(true) @@ -208,7 +249,7 @@ class ShareouselViewModelTest { this.pendingIntentSender = pendingIntentSender this.targetIntentModifier = targetIntentModifier previewSelectionsRepository.selections.value = - setOf(PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)) + listOf(PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)) payloadToggleImageLoader = FakeImageLoader( initialBitmaps = @@ -234,9 +275,9 @@ class ShareouselViewModelTest { override fun getFilesWithTextHeadline(text: CharSequence, count: Int): String = error("not supported") - override fun getVideosHeadline(count: Int): String = error("not supported") + override fun getVideosHeadline(count: Int): String = "VIDEOS: $count" - override fun getFilesHeadline(count: Int): String = error("not supported") + override fun getFilesHeadline(count: Int): String = "FILES: $count" } // instantiate the view model, and then runCurrent() so that it is fully hydrated before // starting the test |