1 /*
<lambda>null2  * Copyright (C) 2017 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 package androidx.lifecycle
17 
18 import androidx.arch.core.executor.ArchTaskExecutor
19 import androidx.lifecycle.testing.TestLifecycleOwner
20 import androidx.lifecycle.util.InstantTaskExecutor
21 import com.google.common.truth.Truth.assertThat
22 import kotlinx.coroutines.ExperimentalCoroutinesApi
23 import kotlinx.coroutines.test.UnconfinedTestDispatcher
24 import org.junit.Test
25 import org.junit.runner.RunWith
26 import org.junit.runners.JUnit4
27 
28 @Suppress("unchecked_cast")
29 @RunWith(JUnit4::class)
30 class TransformationsTest {
31 
32     @OptIn(ExperimentalCoroutinesApi::class)
33     private val owner = TestLifecycleOwner(coroutineDispatcher = UnconfinedTestDispatcher())
34 
35     // region map
36     @Test
37     fun map() {
38         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
39 
40         val sourceLiveData = MutableLiveData<String>()
41         val observer = TestObserver<Int>()
42 
43         val mapLiveData = sourceLiveData.map { it.length }
44         mapLiveData.observe(owner, observer)
45         sourceLiveData.value = "four"
46 
47         assertThat(observer.values).containsExactly(4)
48     }
49 
50     @Test
51     fun map_initialValueIsSet() {
52         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
53 
54         val initialValue = "initialValue"
55         val sourceLiveData = MutableLiveData(initialValue)
56 
57         val mapLiveData = sourceLiveData.map { it }
58 
59         assertThat(mapLiveData.isInitialized).isTrue()
60         assertThat(mapLiveData.value).isEqualTo(initialValue)
61         assertThat(sourceLiveData.value).isEqualTo(initialValue)
62     }
63 
64     @Test
65     fun map_initialValueNull() {
66         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
67 
68         val sourceLiveData = MutableLiveData<String?>(null)
69         val output = "testOutput"
70 
71         val mapLiveData = sourceLiveData.map { output }
72 
73         assertThat(mapLiveData.isInitialized).isTrue()
74         assertThat(mapLiveData.value).isEqualTo(output)
75         assertThat(sourceLiveData.value).isNull()
76     }
77 
78     @Test
79     fun map_createsOnBackgroundThread() {
80         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
81 
82         val initialValue = "value"
83         val sourceLiveData = MutableLiveData(initialValue)
84 
85         val mapLiveData = sourceLiveData.map { "mapped $it" }
86 
87         assertThat(mapLiveData.isInitialized).isTrue()
88         assertThat(mapLiveData.value).isEqualTo("mapped $initialValue")
89         assertThat(sourceLiveData.value).isEqualTo(initialValue)
90     }
91 
92     @Test
93     fun map_observesOnBackgroundThread_throwsException() {
94         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
95 
96         val sourceLiveData = MutableLiveData("value")
97         val observer = TestObserver<String>()
98 
99         val mapLiveData = sourceLiveData.map { "mapped $it" }
100         val error = runCatching { mapLiveData.observe(owner, observer) }.exceptionOrNull()
101 
102         with(assertThat(error)) {
103             isInstanceOf(IllegalStateException::class.java)
104             hasMessageThat().isEqualTo("Cannot invoke observe on a background thread")
105         }
106     }
107 
108     @Test
109     fun map_noObsoleteValue() {
110         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
111 
112         val sourceLiveData = MutableLiveData<Int>()
113         val mapLiveData = sourceLiveData.map { value: Int -> value * value }
114         val observer = TestObserver<Int>()
115 
116         mapLiveData.value = 1
117         mapLiveData.observeForever(observer)
118         mapLiveData.removeObserver(observer)
119         sourceLiveData.value = 2
120         mapLiveData.observeForever(observer)
121 
122         assertThat(observer.values).containsExactly(1, 4)
123     }
124 
125     // endregion
126 
127     // region switchMap
128     @Test
129     fun switchMap() {
130         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
131 
132         val sourceLiveData = MutableLiveData<Int>()
133         val firstLiveData = MutableLiveData<String>()
134         val secondLiveData = MutableLiveData<String>()
135         val observer = TestObserver<String>()
136 
137         val switchLiveData =
138             sourceLiveData.switchMap { input -> if (input == 1) firstLiveData else secondLiveData }
139         switchLiveData.observe(owner, observer)
140 
141         firstLiveData.value = "first"
142         sourceLiveData.value = 1
143         secondLiveData.value = "second"
144         sourceLiveData.value = 2
145         firstLiveData.value = "failure"
146 
147         assertThat(observer.values).containsExactly("first", "second")
148     }
149 
150     @Test
151     fun switchMap_initialValueSet() {
152         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
153 
154         val initialValue = "initialValue"
155         val sourceLiveData = MutableLiveData(true)
156         val anotherLiveData = MutableLiveData(initialValue)
157 
158         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
159 
160         assertThat(switchMapLiveData.isInitialized).isTrue()
161         assertThat(switchMapLiveData.value).isEqualTo(initialValue)
162         assertThat(anotherLiveData.value).isEqualTo(initialValue)
163     }
164 
165     @Test
166     fun switchMap_noInitialValue_notInitialized() {
167         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
168 
169         val sourceLiveData = MutableLiveData(true)
170         val anotherLiveData = MutableLiveData<String>()
171 
172         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
173 
174         assertThat(switchMapLiveData.isInitialized).isFalse()
175     }
176 
177     @Test
178     fun switchMap_initialValueNull() {
179         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
180 
181         val sourceLiveData = MutableLiveData<String?>(null)
182         val anotherLiveData = MutableLiveData<String?>()
183 
184         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
185 
186         assertThat(switchMapLiveData.isInitialized).isFalse()
187     }
188 
189     @Test
190     fun switchMap_sameLiveData() {
191         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
192 
193         val initialValue = "initialValue"
194         val modifiedValue = "modifiedValue"
195         val observer = TestObserver<String?>()
196         val sourceLiveData = MutableLiveData(true)
197         val anotherLiveData = MutableLiveData(initialValue)
198 
199         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
200         switchMapLiveData.observe(owner, observer)
201 
202         anotherLiveData.value = modifiedValue
203 
204         assertThat(switchMapLiveData.value).isEqualTo(modifiedValue)
205         assertThat(observer.values).containsExactly(initialValue, modifiedValue)
206     }
207 
208     @Test
209     fun switchMap_noRedispatch() {
210         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
211 
212         val sourceLiveData = MutableLiveData<Int>()
213         val anotherLiveData = MutableLiveData<String>()
214         val observer = TestObserver<String>()
215 
216         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
217         switchMapLiveData.observe(owner, observer)
218 
219         anotherLiveData.value = "first"
220         sourceLiveData.value = 1
221         sourceLiveData.value = 2
222 
223         assertThat(observer.values).containsExactly("first")
224     }
225 
226     @Test
227     fun switchMap_toNull() {
228         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
229 
230         val sourceLiveData = MutableLiveData<Int>()
231         val anotherLiveData = MutableLiveData<String>()
232         val observer = TestObserver<String>()
233 
234         val switchMapLiveData =
235             sourceLiveData.switchMap { input: Int -> if (input == 1) anotherLiveData else null }
236         switchMapLiveData.observe(owner, observer)
237 
238         anotherLiveData.value = "first"
239         sourceLiveData.value = 1
240         sourceLiveData.value = 2
241 
242         assertThat(anotherLiveData.hasObservers()).isFalse()
243         assertThat(observer.values).containsExactly("first")
244     }
245 
246     @Test
247     fun switchMap_createsOnBackgroundThread() {
248         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
249 
250         val initialValue = "initialValue"
251         val sourceLiveData = MutableLiveData(true)
252         val anotherLiveData = MutableLiveData(initialValue)
253 
254         val switchMapLiveData = sourceLiveData.switchMap { anotherLiveData }
255 
256         assertThat(switchMapLiveData.isInitialized).isTrue()
257         assertThat(switchMapLiveData.value).isEqualTo(initialValue)
258     }
259 
260     @Test
261     fun switchMap_observesOnBackgroundThread_throwsException() {
262         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
263 
264         val initialValue = "initialValue"
265         val sourceLiveData = MutableLiveData(true)
266         val anotherLiveData = MutableLiveData(initialValue)
267         val observer = TestObserver<String>()
268 
269         val mapLiveData = sourceLiveData.switchMap { anotherLiveData }
270         val error = runCatching { mapLiveData.observe(owner, observer) }.exceptionOrNull()
271 
272         with(assertThat(error)) {
273             isInstanceOf(IllegalStateException::class.java)
274             hasMessageThat().isEqualTo("Cannot invoke observe on a background thread")
275         }
276     }
277 
278     // endregion
279 
280     // region distinctUntilChanged
281     @Test
282     fun distinctUntilChanged_initialValueIsSet() {
283         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
284 
285         val sourceLiveData = MutableLiveData("value")
286         val observer = TestObserver<String>()
287 
288         val distinctLiveData = sourceLiveData.distinctUntilChanged()
289         distinctLiveData.observe(owner, observer)
290 
291         assertThat(observer.values).containsExactly("value")
292         assertThat(distinctLiveData.value).isEqualTo("value")
293     }
294 
295     @Test
296     fun distinctUntilChanged_onInitialNullValue_triggersObserver() {
297         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
298 
299         val sourceLiveData = MutableLiveData<String?>(null)
300         val observer = TestObserver<String?>()
301 
302         val distinctLiveData = sourceLiveData.distinctUntilChanged()
303         distinctLiveData.observe(owner, observer)
304 
305         assertThat(observer.values).containsExactly(null)
306         assertThat(distinctLiveData.value).isNull()
307     }
308 
309     @Test
310     fun distinctUntilChanged_initialNullValue() {
311         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
312 
313         val sourceLiveData = MutableLiveData<String>()
314         val observer = TestObserver<String>()
315 
316         val distinctLiveData = sourceLiveData.distinctUntilChanged()
317         distinctLiveData.observe(owner, observer)
318 
319         assertThat(distinctLiveData.value).isNull()
320     }
321 
322     @Test
323     fun distinctUntilChanged_filtersValueRepetitions() {
324         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
325 
326         val sourceLiveData = MutableLiveData<String>()
327         val observer = TestObserver<String>()
328 
329         val distinctLiveData = sourceLiveData.distinctUntilChanged()
330         distinctLiveData.observe(owner, observer)
331 
332         sourceLiveData.value = "new value"
333         sourceLiveData.value = "new value"
334         sourceLiveData.value = "newer value"
335 
336         assertThat(observer.values).containsExactly("new value", "newer value")
337     }
338 
339     @Test
340     fun distinctUntilChanged_createsOnBackgroundThread() {
341         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
342 
343         val sourceLiveData = MutableLiveData("value")
344         val distinctLiveData = sourceLiveData.distinctUntilChanged()
345 
346         assertThat(distinctLiveData.value).isEqualTo("value")
347     }
348 
349     @Test
350     fun distinctUntilChanged_observesOnMainThread() {
351         ArchTaskExecutor.getInstance().setDelegate(InstantTaskExecutor())
352 
353         val sourceLiveData = MutableLiveData("value")
354         val observer = TestObserver<String>()
355 
356         val distinctLiveData = sourceLiveData.distinctUntilChanged()
357         distinctLiveData.observe(owner, observer)
358 
359         assertThat(observer.values.size).isEqualTo(1)
360         assertThat(distinctLiveData.value).isEqualTo("value")
361     }
362 
363     @Test
364     fun distinctUntilChanged_observesOnBackgroundThread_throwsException() {
365         ArchTaskExecutor.getInstance().setDelegate(InstantTaskOnBackgroundTaskExecutor())
366 
367         val sourceLiveData = MutableLiveData("value")
368         val observer = TestObserver<String>()
369 
370         val distinctLiveData = sourceLiveData.distinctUntilChanged()
371         val error = runCatching { distinctLiveData.observe(owner, observer) }.exceptionOrNull()
372 
373         with(assertThat(error)) {
374             isInstanceOf(IllegalStateException::class.java)
375             hasMessageThat().isEqualTo("Cannot invoke observe on a background thread")
376         }
377     }
378 
379     // endregion
380 
381     private class TestObserver<T>(val values: MutableList<T> = mutableListOf()) : Observer<T> {
382         override fun onChanged(value: T) {
383             values += value
384         }
385     }
386 
387     private class InstantTaskOnBackgroundTaskExecutor : InstantTaskExecutor() {
388         override fun isMainThread(): Boolean = false
389     }
390 }
391