Merge changes Ia3b1ae8f,Ie87220a7 into tm-qpr-dev
* changes: Clean-up FooterActionsViewModelTest Introduce TestScope.collectLastValue(Flow)
This commit is contained in:
committed by
Android (Google) Code Review
commit
52c7bc24e2
@@ -41,6 +41,7 @@ import com.android.systemui.util.mockito.eq
|
|||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.nullable
|
import com.android.systemui.util.mockito.nullable
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -55,7 +56,7 @@ class FooterActionsInteractorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
utils = FooterActionsTestUtils(context, TestableLooper.get(this))
|
utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.common.shared.model.ContentDescription
|
import com.android.systemui.common.shared.model.ContentDescription
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
import com.android.systemui.qs.FakeFgsManagerController
|
import com.android.systemui.qs.FakeFgsManagerController
|
||||||
import com.android.systemui.qs.QSSecurityFooterUtils
|
import com.android.systemui.qs.QSSecurityFooterUtils
|
||||||
import com.android.systemui.qs.footer.FooterActionsTestUtils
|
import com.android.systemui.qs.footer.FooterActionsTestUtils
|
||||||
@@ -44,12 +45,9 @@ import com.android.systemui.util.mockito.mock
|
|||||||
import com.android.systemui.util.mockito.nullable
|
import com.android.systemui.util.mockito.nullable
|
||||||
import com.android.systemui.util.settings.FakeSettings
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -62,16 +60,20 @@ import org.mockito.Mockito.`when` as whenever
|
|||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@RunWithLooper
|
@RunWithLooper
|
||||||
class FooterActionsViewModelTest : SysuiTestCase() {
|
class FooterActionsViewModelTest : SysuiTestCase() {
|
||||||
|
private val testScope = TestScope()
|
||||||
private lateinit var utils: FooterActionsTestUtils
|
private lateinit var utils: FooterActionsTestUtils
|
||||||
private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
utils = FooterActionsTestUtils(context, TestableLooper.get(this))
|
utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTest(block: suspend TestScope.() -> Unit) {
|
||||||
|
testScope.runTest(testBody = block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun settingsButton() = runBlockingTest {
|
fun settingsButton() = runTest {
|
||||||
val underTest = utils.footerActionsViewModel(showPowerButton = false)
|
val underTest = utils.footerActionsViewModel(showPowerButton = false)
|
||||||
val settings = underTest.settings
|
val settings = underTest.settings
|
||||||
|
|
||||||
@@ -87,7 +89,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun powerButton() = runBlockingTest {
|
fun powerButton() = runTest {
|
||||||
// Without power button.
|
// Without power button.
|
||||||
val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
|
val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
|
||||||
assertThat(underTestWithoutPower.power).isNull()
|
assertThat(underTestWithoutPower.power).isNull()
|
||||||
@@ -114,7 +116,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun userSwitcher() = runBlockingTest {
|
fun userSwitcher() = runTest {
|
||||||
val picture: Drawable = mock()
|
val picture: Drawable = mock()
|
||||||
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
|
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
|
||||||
val settings = FakeSettings()
|
val settings = FakeSettings()
|
||||||
@@ -135,7 +137,6 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
showPowerButton = false,
|
showPowerButton = false,
|
||||||
footerActionsInteractor =
|
footerActionsInteractor =
|
||||||
utils.footerActionsInteractor(
|
utils.footerActionsInteractor(
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
userSwitcherRepository =
|
userSwitcherRepository =
|
||||||
utils.userSwitcherRepository(
|
utils.userSwitcherRepository(
|
||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
@@ -143,22 +144,12 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
userManager = userManager,
|
userManager = userManager,
|
||||||
userInfoController = userInfoController,
|
userInfoController = userInfoController,
|
||||||
userSwitcherController = userSwitcherControllerWrapper.controller,
|
userSwitcherController = userSwitcherControllerWrapper.controller,
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collect the user switcher into currentUserSwitcher.
|
// Collect the user switcher into currentUserSwitcher.
|
||||||
var currentUserSwitcher: FooterActionsButtonViewModel? = null
|
val currentUserSwitcher = collectLastValue(underTest.userSwitcher)
|
||||||
val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
|
|
||||||
fun currentUserSwitcher(): FooterActionsButtonViewModel? {
|
|
||||||
// Make sure we finish collecting the current user switcher. This is necessary because
|
|
||||||
// combined flows launch multiple coroutines in the current scope so we need to make
|
|
||||||
// sure we process all coroutines triggered by our flow collection before we make
|
|
||||||
// assertions on the current buttons.
|
|
||||||
advanceUntilIdle()
|
|
||||||
return currentUserSwitcher
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user switcher is disabled.
|
// The user switcher is disabled.
|
||||||
assertThat(currentUserSwitcher()).isNull()
|
assertThat(currentUserSwitcher()).isNull()
|
||||||
@@ -203,12 +194,10 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
// in guest mode.
|
// in guest mode.
|
||||||
userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
|
userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
|
||||||
assertThat(iconTint()).isNull()
|
assertThat(iconTint()).isNull()
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun security() = runBlockingTest {
|
fun security() = runTest {
|
||||||
val securityController = FakeSecurityController()
|
val securityController = FakeSecurityController()
|
||||||
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
|
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
|
||||||
|
|
||||||
@@ -224,22 +213,15 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
footerActionsInteractor =
|
footerActionsInteractor =
|
||||||
utils.footerActionsInteractor(
|
utils.footerActionsInteractor(
|
||||||
qsSecurityFooterUtils = qsSecurityFooterUtils,
|
qsSecurityFooterUtils = qsSecurityFooterUtils,
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
securityRepository =
|
securityRepository =
|
||||||
utils.securityRepository(
|
utils.securityRepository(
|
||||||
securityController = securityController,
|
securityController = securityController,
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collect the security model into currentSecurity.
|
// Collect the security model into currentSecurity.
|
||||||
var currentSecurity: FooterActionsSecurityButtonViewModel? = null
|
val currentSecurity = collectLastValue(underTest.security)
|
||||||
val job = launch { underTest.security.collect { currentSecurity = it } }
|
|
||||||
fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
|
|
||||||
advanceUntilIdle()
|
|
||||||
return currentSecurity
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, we always return a null SecurityButtonConfig.
|
// By default, we always return a null SecurityButtonConfig.
|
||||||
assertThat(currentSecurity()).isNull()
|
assertThat(currentSecurity()).isNull()
|
||||||
@@ -270,12 +252,10 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
security = currentSecurity()
|
security = currentSecurity()
|
||||||
assertThat(security).isNotNull()
|
assertThat(security).isNotNull()
|
||||||
assertThat(security!!.onClick).isNull()
|
assertThat(security!!.onClick).isNull()
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun foregroundServices() = runBlockingTest {
|
fun foregroundServices() = runTest {
|
||||||
val securityController = FakeSecurityController()
|
val securityController = FakeSecurityController()
|
||||||
val fgsManagerController =
|
val fgsManagerController =
|
||||||
FakeFgsManagerController(
|
FakeFgsManagerController(
|
||||||
@@ -300,21 +280,14 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
securityRepository =
|
securityRepository =
|
||||||
utils.securityRepository(
|
utils.securityRepository(
|
||||||
securityController,
|
securityController,
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
),
|
),
|
||||||
foregroundServicesRepository =
|
foregroundServicesRepository =
|
||||||
utils.foregroundServicesRepository(fgsManagerController),
|
utils.foregroundServicesRepository(fgsManagerController),
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collect the security model into currentSecurity.
|
// Collect the security model into currentSecurity.
|
||||||
var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
|
val currentForegroundServices = collectLastValue(underTest.foregroundServices)
|
||||||
val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
|
|
||||||
fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
|
|
||||||
advanceUntilIdle()
|
|
||||||
return currentForegroundServices
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't show the foreground services button if the number of running packages is not
|
// We don't show the foreground services button if the number of running packages is not
|
||||||
// > 1.
|
// > 1.
|
||||||
@@ -356,12 +329,10 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
securityController.updateState {}
|
securityController.updateState {}
|
||||||
assertThat(currentForegroundServices()?.displayText).isFalse()
|
assertThat(currentForegroundServices()?.displayText).isFalse()
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
|
fun observeDeviceMonitoringDialogRequests() = runTest {
|
||||||
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
|
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
|
||||||
val broadcastDispatcher = mock<BroadcastDispatcher>()
|
val broadcastDispatcher = mock<BroadcastDispatcher>()
|
||||||
|
|
||||||
@@ -390,7 +361,6 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
utils.footerActionsInteractor(
|
utils.footerActionsInteractor(
|
||||||
qsSecurityFooterUtils = qsSecurityFooterUtils,
|
qsSecurityFooterUtils = qsSecurityFooterUtils,
|
||||||
broadcastDispatcher = broadcastDispatcher,
|
broadcastDispatcher = broadcastDispatcher,
|
||||||
bgDispatcher = testDispatcher,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -415,7 +385,4 @@ class FooterActionsViewModelTest : SysuiTestCase() {
|
|||||||
underTest.onVisibilityChangeRequested(visible = true)
|
underTest.onVisibilityChangeRequested(visible = true)
|
||||||
assertThat(underTest.isVisible.value).isTrue()
|
assertThat(underTest.isVisible.value).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
|
|
||||||
runTest(testDispatcher) { block() }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.coroutines
|
||||||
|
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
|
|
||||||
|
/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
|
||||||
|
fun <T> TestScope.collectLastValue(
|
||||||
|
flow: Flow<T>,
|
||||||
|
context: CoroutineContext = EmptyCoroutineContext,
|
||||||
|
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||||
|
): () -> T? {
|
||||||
|
var lastValue: T? = null
|
||||||
|
backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
|
||||||
|
return {
|
||||||
|
runCurrent()
|
||||||
|
lastValue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,8 @@ import com.android.systemui.util.mockito.mock
|
|||||||
import com.android.systemui.util.settings.FakeSettings
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.android.systemui.util.settings.GlobalSettings
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Util class to create real implementations of the FooterActions repositories, viewModel and
|
* Util class to create real implementations of the FooterActions repositories, viewModel and
|
||||||
@@ -65,6 +66,7 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
|
|||||||
class FooterActionsTestUtils(
|
class FooterActionsTestUtils(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val testableLooper: TestableLooper,
|
private val testableLooper: TestableLooper,
|
||||||
|
private val scheduler: TestCoroutineScheduler,
|
||||||
) {
|
) {
|
||||||
/** Enable or disable the user switcher in the settings. */
|
/** Enable or disable the user switcher in the settings. */
|
||||||
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
|
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
|
||||||
@@ -105,7 +107,7 @@ class FooterActionsTestUtils(
|
|||||||
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
|
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
|
||||||
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
|
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
|
||||||
broadcastDispatcher: BroadcastDispatcher = mock(),
|
broadcastDispatcher: BroadcastDispatcher = mock(),
|
||||||
bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
|
bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
|
||||||
): FooterActionsInteractor {
|
): FooterActionsInteractor {
|
||||||
return FooterActionsInteractorImpl(
|
return FooterActionsInteractorImpl(
|
||||||
activityStarter,
|
activityStarter,
|
||||||
@@ -126,7 +128,7 @@ class FooterActionsTestUtils(
|
|||||||
/** Create a [SecurityRepository] to be used in tests. */
|
/** Create a [SecurityRepository] to be used in tests. */
|
||||||
fun securityRepository(
|
fun securityRepository(
|
||||||
securityController: SecurityController = FakeSecurityController(),
|
securityController: SecurityController = FakeSecurityController(),
|
||||||
bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
|
bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
|
||||||
): SecurityRepository {
|
): SecurityRepository {
|
||||||
return SecurityRepositoryImpl(
|
return SecurityRepositoryImpl(
|
||||||
securityController,
|
securityController,
|
||||||
@@ -145,7 +147,7 @@ class FooterActionsTestUtils(
|
|||||||
fun userSwitcherRepository(
|
fun userSwitcherRepository(
|
||||||
@Application context: Context = this.context.applicationContext,
|
@Application context: Context = this.context.applicationContext,
|
||||||
bgHandler: Handler = Handler(testableLooper.looper),
|
bgHandler: Handler = Handler(testableLooper.looper),
|
||||||
bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
|
bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
|
||||||
userManager: UserManager = mock(),
|
userManager: UserManager = mock(),
|
||||||
userTracker: UserTracker = FakeUserTracker(),
|
userTracker: UserTracker = FakeUserTracker(),
|
||||||
userSwitcherController: UserSwitcherController = mock(),
|
userSwitcherController: UserSwitcherController = mock(),
|
||||||
|
|||||||
Reference in New Issue
Block a user