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