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