summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt49
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt5
4 files changed, 64 insertions, 60 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index e56b965d9402..3187cca6ca45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -37,7 +37,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -48,9 +47,9 @@ 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
+import com.android.systemui.kosmos.verifyCurrent
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -77,12 +76,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
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
-import org.mockito.Mockito.verify
/**
* Integration test cases for the Scene Framework.
@@ -137,10 +133,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneContainerViewModel.activateIn(testScope)
assertWithMessage("Initial scene key mismatch!")
- .that(sceneContainerViewModel.currentScene.value)
+ .that(currentValue(sceneContainerViewModel.currentScene))
.isEqualTo(sceneContainerConfig.initialSceneKey)
assertWithMessage("Initial scene container visibility mismatch!")
- .that(sceneContainerViewModel.isVisible)
+ .that(currentValue { sceneContainerViewModel.isVisible })
.isTrue()
}
@@ -337,7 +333,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(bouncerActionButton)
.isNotNull()
kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
- runCurrent()
// TODO(b/369765704): Assert that an activity was started once we use ActivityStarter.
}
@@ -358,9 +353,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(bouncerActionButton)
.isNotNull()
kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
- runCurrent()
- verify(mockTelecomManager).showInCallScreen(any())
+ verifyCurrent(mockTelecomManager).showInCallScreen(any())
}
@Test
@@ -413,7 +407,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
* the UI must gradually transition between scenes.
*/
private fun Kosmos.getCurrentSceneInUi(): SceneKey {
- return when (val state = transitionState.value) {
+ return when (val state = currentValue(transitionState)) {
is ObservableTransitionState.Idle -> state.currentScene
is ObservableTransitionState.Transition.ChangeScene -> state.fromScene
is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
@@ -436,7 +430,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
// is not an observable that can trigger a new evaluation.
fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
- testScope.runCurrent()
}
/** Emulates a phone call in progress. */
@@ -447,7 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
setIsInCall(true)
setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
}
- testScope.runCurrent()
}
/**
@@ -480,24 +472,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
isInitiatedByUserInput = false,
isUserInputOngoing = flowOf(false),
)
- testScope.runCurrent()
// Report progress of transition.
- while (progressFlow.value < 1f) {
+ while (currentValue(progressFlow) < 1f) {
progressFlow.value += 0.2f
- testScope.runCurrent()
}
// End the transition and report the change.
transitionState.value = ObservableTransitionState.Idle(to)
fakeSceneDataSource.unpause(force = true)
- testScope.runCurrent()
assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
- .that(sceneContainerViewModel.isVisible)
+ .that(currentValue { sceneContainerViewModel.isVisible })
.isEqualTo(expectedVisible)
- assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
+ assertThat(currentValue(sceneContainerViewModel.currentScene)).isEqualTo(to)
bouncerSceneJob =
if (to == Scenes.Bouncer) {
@@ -510,7 +499,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
bouncerSceneJob?.cancel()
null
}
- testScope.runCurrent()
}
/**
@@ -556,13 +544,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
)
powerInteractor.setAwakeForTest()
- testScope.runCurrent()
}
/** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
private fun Kosmos.unlockDevice() {
assertWithMessage("Cannot unlock a device that's already unlocked!")
- .that(deviceEntryInteractor.isUnlocked.value)
+ .that(currentValue(deviceEntryInteractor.isUnlocked))
.isFalse()
emulateUserDrivenTransition(Scenes.Bouncer)
@@ -595,7 +582,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
pinBouncerViewModel.onPinButtonClicked(digit)
}
pinBouncerViewModel.onAuthenticateButtonClicked()
- testScope.runCurrent()
}
/**
@@ -625,26 +611,23 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
pinBouncerViewModel.onAuthenticateButtonClicked()
fakeMobileConnectionsRepository.isAnySimSecure.value = false
- testScope.runCurrent()
setAuthMethod(authMethodAfterSimUnlock, enableLockscreen)
- testScope.runCurrent()
}
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
private fun Kosmos.wakeUpDevice() {
- val wakefulnessModel = powerInteractor.detailedWakefulness.value
+ val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
assertWithMessage("Cannot wake up device as it's already awake!")
.that(wakefulnessModel.isAwake())
.isFalse()
powerInteractor.setAwakeForTest()
- testScope.runCurrent()
}
/** Changes device wakefulness state from awake to asleep, going through intermediary states. */
private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) {
- val wakefulnessModel = powerInteractor.detailedWakefulness.value
+ val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
assertWithMessage("Cannot put device to sleep as it's already asleep!")
.that(wakefulnessModel.isAwake())
.isTrue()
@@ -659,22 +642,18 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
)
.toLong()
)
- } else {
- testScope.runCurrent()
}
}
/** Emulates the dismissal of the IME (soft keyboard). */
private fun Kosmos.dismissIme() {
- (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
- it.onImeDismissed()
- testScope.runCurrent()
- }
+ (currentValue(bouncerSceneContentViewModel.authMethodViewModel)
+ as? PasswordBouncerViewModel)
+ ?.let { it.onImeDismissed() }
}
private fun Kosmos.introduceLockedSim() {
setAuthMethod(AuthenticationMethodModel.Sim)
fakeMobileConnectionsRepository.isAnySimSecure.value = true
- testScope.runCurrent()
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 7ee9d84d84fb..669162d30b80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -17,6 +17,7 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.mockito.kotlin.verify
var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
@@ -82,6 +83,32 @@ fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T {
}
/** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */
+fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn)
+
+/**
+ * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of
+ * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue]
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.currentValue(fn: () -> T): T {
+ runCurrent()
+ return fn()
+}
+
+/** Retrieve the result of [fn] after running all pending tasks. See `TestScope.currentValue(fn)` */
fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T {
return testScope.currentValue(stateFlow)
}
+
+/** Safely verify that a mock has been called after the test scope has caught up */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.verifyCurrent(mock: T): T {
+ runCurrent()
+ return verify(mock)
+}
+
+/**
+ * Safely verify that a mock has been called after the test scope has caught up. See
+ * `TestScope.verifyCurrent`
+ */
+fun <T> Kosmos.verifyCurrent(mock: T) = testScope.verifyCurrent(mock)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index f52572a9e42d..f5eebb46c2ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -19,13 +19,13 @@ package com.android.systemui.scene.shared.model
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.kosmos.currentValue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
-class FakeSceneDataSource(
- initialSceneKey: SceneKey,
-) : SceneDataSource {
+class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) : SceneDataSource {
private val _currentScene = MutableStateFlow(initialSceneKey)
override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
@@ -33,18 +33,20 @@ class FakeSceneDataSource(
private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet())
override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow()
- var isPaused = false
- private set
+ private var _isPaused = false
+ val isPaused
+ get() = testScope.currentValue { _isPaused }
- var pendingScene: SceneKey? = null
- private set
+ private var _pendingScene: SceneKey? = null
+ val pendingScene
+ get() = testScope.currentValue { _pendingScene }
var pendingOverlays: Set<OverlayKey>? = null
private set
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
- if (isPaused) {
- pendingScene = toScene
+ if (_isPaused) {
+ _pendingScene = toScene
} else {
_currentScene.value = toScene
}
@@ -55,7 +57,7 @@ class FakeSceneDataSource(
}
override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
- if (isPaused) {
+ if (_isPaused) {
pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay
} else {
_currentOverlays.value += overlay
@@ -63,7 +65,7 @@ class FakeSceneDataSource(
}
override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
- if (isPaused) {
+ if (_isPaused) {
pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay
} else {
_currentOverlays.value -= overlay
@@ -82,9 +84,9 @@ class FakeSceneDataSource(
* last one will be remembered.
*/
fun pause() {
- check(!isPaused) { "Can't pause what's already paused!" }
+ check(!_isPaused) { "Can't pause what's already paused!" }
- isPaused = true
+ _isPaused = true
}
/**
@@ -100,15 +102,12 @@ class FakeSceneDataSource(
*
* If [expectedScene] is provided, will assert that it's indeed the latest called.
*/
- fun unpause(
- force: Boolean = false,
- expectedScene: SceneKey? = null,
- ) {
- check(force || isPaused) { "Can't unpause what's already not paused!" }
-
- isPaused = false
- pendingScene?.let { _currentScene.value = it }
- pendingScene = null
+ fun unpause(force: Boolean = false, expectedScene: SceneKey? = null) {
+ check(force || _isPaused) { "Can't unpause what's already not paused!" }
+
+ _isPaused = false
+ _pendingScene?.let { _currentScene.value = it }
+ _pendingScene = null
pendingOverlays?.let { _currentOverlays.value = it }
pendingOverlays = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
index f5196866ae6f..7eebfc305682 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
@@ -19,13 +19,12 @@ package com.android.systemui.scene.shared.model
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.initialSceneKey
import com.android.systemui.scene.sceneContainerConfig
val Kosmos.fakeSceneDataSource by Fixture {
- FakeSceneDataSource(
- initialSceneKey = initialSceneKey,
- )
+ FakeSceneDataSource(initialSceneKey = initialSceneKey, testScope = testScope)
}
val Kosmos.sceneDataSourceDelegator by Fixture {