Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e196412f authored by David Saff's avatar David Saff Committed by Android (Google) Code Review
Browse files

Merge "Introduce currentValue(kosmos) for testing StateFlows" into main

parents a56ddab6 893cd1b6
Loading
Loading
Loading
Loading
+74 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.kosmos

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class GeneralKosmosTest : SysuiTestCase() {
    @Test
    fun stateCurrentValueMutableStateFlow() = runTest {
        val source = MutableStateFlow(1)
        val mapped =
            source
                .map { it * 2 }
                .stateIn(
                    scope = backgroundScope,
                    started = SharingStarted.WhileSubscribed(),
                    initialValue = source.value * 2,
                )
        assertThat(currentValue(mapped)).isEqualTo(2)

        source.value = 3
        assertThat(currentValue(mapped)).isEqualTo(6)
    }

    @Test
    fun stateCurrentValueOnEmittedFlow() = runTest {
        val source = flow {
            emit(1)
            emit(2)
        }
        val mapped =
            source
                .map { it * 2 }
                .stateIn(
                    scope = backgroundScope,
                    started = SharingStarted.WhileSubscribed(),
                    initialValue = 2,
                )
        assertThat(currentValue(mapped)).isEqualTo(4)
    }

    @Test
    fun currentValueIsNull() = runTest {
        val source = MutableStateFlow<Int?>(null)
        assertThat(currentValue(source)).isEqualTo(null)
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.currentValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -77,6 +78,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -399,9 +401,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
     * Note that this doesn't assert what the current scene is in the UI.
     */
    private fun Kosmos.assertCurrentScene(expected: SceneKey) {
        testScope.runCurrent()
        assertWithMessage("Current scene mismatch!")
            .that(sceneContainerViewModel.currentScene.value)
            .that(currentValue(sceneContainerViewModel.currentScene))
            .isEqualTo(expected)
    }

+27 −0
Original line number Diff line number Diff line
@@ -8,7 +8,10 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
import com.android.systemui.util.mockito.mock
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -58,3 +61,27 @@ fun Kosmos.runCurrent() = testScope.runCurrent()
fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)

fun <T> Kosmos.collectValues(flow: Flow<T>): FlowValue<List<T>> = testScope.collectValues(flow)

/**
 * Retrieve the current value of this [StateFlow] safely. Needs a [TestScope] in order to make sure
 * that all pending tasks have run before returning a value. Tests that directly access
 * [StateFlow.value] may be incorrect, since the value returned may be stale if the current test
 * dispatcher is a [StandardTestDispatcher].
 *
 * If you want to assert on a [Flow] that is not a [StateFlow], please use
 * [TestScope.collectLastValue], to make sure that the desired value is captured when emitted.
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T {
    val values = mutableListOf<T>()
    val job = backgroundScope.launch { stateFlow.collect(values::add) }
    runCurrent()
    job.cancel()
    // StateFlow should always have at least one value
    return values.last()
}

/** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */
fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T {
    return testScope.currentValue(stateFlow)
}