1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package com.android.wallpaper.picker.customization.ui.viewmodel 19 20 import androidx.test.filters.SmallTest 21 import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository 22 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor 23 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination 24 import com.android.wallpaper.picker.customization.shared.model.WallpaperModel 25 import com.android.wallpaper.testing.FakeWallpaperClient 26 import com.android.wallpaper.testing.TestWallpaperPreferences 27 import com.android.wallpaper.testing.collectLastValue 28 import com.google.common.truth.Truth.assertThat 29 import com.google.common.truth.Truth.assertWithMessage 30 import kotlinx.coroutines.Dispatchers 31 import kotlinx.coroutines.ExperimentalCoroutinesApi 32 import kotlinx.coroutines.test.StandardTestDispatcher 33 import kotlinx.coroutines.test.TestScope 34 import kotlinx.coroutines.test.resetMain 35 import kotlinx.coroutines.test.runCurrent 36 import kotlinx.coroutines.test.runTest 37 import kotlinx.coroutines.test.setMain 38 import org.junit.After 39 import org.junit.Before 40 import org.junit.Test 41 import org.junit.runner.RunWith 42 import org.robolectric.RobolectricTestRunner 43 44 @OptIn(ExperimentalCoroutinesApi::class) 45 @SmallTest 46 @RunWith(RobolectricTestRunner::class) 47 class WallpaperQuickSwitchViewModelTest { 48 49 private lateinit var underTest: WallpaperQuickSwitchViewModel 50 51 private lateinit var client: FakeWallpaperClient 52 private lateinit var testScope: TestScope 53 54 @Before 55 fun setUp() { 56 client = FakeWallpaperClient() 57 58 val testDispatcher = StandardTestDispatcher() 59 Dispatchers.setMain(testDispatcher) 60 testScope = TestScope(testDispatcher) 61 val interactor = 62 WallpaperInteractor( 63 repository = 64 WallpaperRepository( 65 scope = testScope.backgroundScope, 66 client = client, 67 wallpaperPreferences = TestWallpaperPreferences(), 68 backgroundDispatcher = testDispatcher, 69 ), 70 ) 71 underTest = 72 WallpaperQuickSwitchViewModel( 73 interactor = interactor, 74 destination = WallpaperDestination.HOME, 75 coroutineScope = testScope.backgroundScope, 76 maxOptions = FakeWallpaperClient.INITIAL_RECENT_WALLPAPERS.size, 77 ) 78 } 79 80 @After 81 fun tearDown() { 82 Dispatchers.resetMain() 83 } 84 85 @Test 86 fun `initial options`() = 87 testScope.runTest { 88 val options = collectLastValue(underTest.options) 89 assertOptions( 90 observed = options(), 91 expected = expectations(), 92 ) 93 } 94 95 @Test 96 fun `updates options`() = 97 testScope.runTest { 98 val options = collectLastValue(underTest.options) 99 100 val models = 101 listOf( 102 WallpaperModel( 103 wallpaperId = "aaa", 104 placeholderColor = 1200, 105 title = "title1", 106 ), 107 WallpaperModel( 108 wallpaperId = "bbb", 109 placeholderColor = 1300, 110 title = "title2", 111 ), 112 WallpaperModel( 113 wallpaperId = "ccc", 114 placeholderColor = 1400, 115 title = "title3", 116 ), 117 ) 118 client.setRecentWallpapers(buildMap { put(WallpaperDestination.HOME, models) }) 119 120 assertOptions( 121 observed = options(), 122 expected = 123 expectations( 124 models = models, 125 ), 126 ) 127 } 128 129 @Test 130 fun `recentOptions_lastUpdatedChange_updatesOptions`() = 131 testScope.runTest { 132 val options = collectLastValue(underTest.options) 133 134 val models = 135 FakeWallpaperClient.INITIAL_RECENT_WALLPAPERS.mapIndexed { idx, wp -> 136 WallpaperModel( 137 wallpaperId = wp.wallpaperId, 138 placeholderColor = wp.placeholderColor, 139 lastUpdated = if (idx == 0) 100 else wp.lastUpdated, 140 title = "title1" 141 ) 142 } 143 client.setRecentWallpapers(buildMap { put(WallpaperDestination.HOME, models) }) 144 145 assertOptions( 146 observed = options(), 147 expected = 148 expectations( 149 models = models, 150 ), 151 ) 152 } 153 154 @Test 155 fun `switches to third option`() = 156 testScope.runTest { 157 val options = collectLastValue(underTest.options) 158 159 // Pause the client so we can examine the interim state. 160 client.pause() 161 val selectedIndex = 2 162 val optionToSelect = checkNotNull(options()?.get(selectedIndex)) 163 val onSelected = collectLastValue(optionToSelect.onSelected) 164 onSelected()?.invoke() 165 166 assertOptions( 167 observed = options(), 168 expected = 169 expectations( 170 selectingIndex = selectedIndex, 171 ), 172 ) 173 174 // Unpause the client so we can examine the final state. 175 client.unpause() 176 runCurrent() 177 assertOptions( 178 observed = options(), 179 expected = 180 expectations( 181 selectedIndex = selectedIndex, 182 ), 183 ) 184 } 185 186 private fun expectations( 187 models: List<WallpaperModel> = FakeWallpaperClient.INITIAL_RECENT_WALLPAPERS, 188 selectedIndex: Int = 0, 189 selectingIndex: Int? = null, 190 ): List<ExpectedOption> { 191 return models.mapIndexed { index, model -> 192 val nothingBeingSelected = selectingIndex == null 193 val isBeingSelected = selectingIndex == index 194 val isSelected = selectedIndex == index 195 ExpectedOption( 196 wallpaperId = model.wallpaperId, 197 placeholderColor = model.placeholderColor, 198 isLarge = isBeingSelected || (nothingBeingSelected && isSelected), 199 isSelectionIndicatorVisible = 200 isBeingSelected || (nothingBeingSelected && isSelected), 201 isSelectable = nothingBeingSelected && !isSelected, 202 ) 203 } 204 } 205 206 private fun TestScope.assertOptions( 207 observed: List<WallpaperQuickSwitchOptionViewModel>?, 208 expected: List<ExpectedOption>, 209 ) { 210 checkNotNull(observed) 211 assertThat(observed).hasSize(expected.size) 212 observed.forEachIndexed { index, option -> 213 assertWithMessage("mismatching wallpaperId for index $index.") 214 .that(option.wallpaperId) 215 .isEqualTo(expected[index].wallpaperId) 216 assertWithMessage("mismatching isLarge for index $index.") 217 .that(collectLastValue(option.isLarge)()) 218 .isEqualTo(expected[index].isLarge) 219 assertWithMessage("mismatching placeholderColor for index $index.") 220 .that(option.placeholderColor) 221 .isEqualTo(expected[index].placeholderColor) 222 assertWithMessage("mismatching isSelectionBorderVisible for index $index.") 223 .that(collectLastValue(option.isSelectionIndicatorVisible)()) 224 .isEqualTo(expected[index].isSelectionIndicatorVisible) 225 assertWithMessage("mismatching isSelectable for index $index.") 226 .that(collectLastValue(option.onSelected)() != null) 227 .isEqualTo(expected[index].isSelectable) 228 } 229 } 230 231 private data class ExpectedOption( 232 val wallpaperId: String, 233 val placeholderColor: Int, 234 val isLarge: Boolean = false, 235 val isSelectionIndicatorVisible: Boolean = false, 236 val isSelectable: Boolean = true, 237 ) 238 } 239