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

Commit 893cd1b6 authored by David Saff's avatar David Saff
Browse files

Introduce currentValue(kosmos) for testing StateFlows

Bug: 342622417
Test: SceneFrameworkIntegrationTest
Flag: TEST_ONLY
Change-Id: I2526727a2667184d5ebaf543d20614e631c57755
parent 96558c13
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)
}