Merge changes Ia3b1ae8f,Ie87220a7 into tm-qpr-dev

* changes:
  Clean-up FooterActionsViewModelTest
  Introduce TestScope.collectLastValue(Flow)
This commit is contained in:
Jordan Demeulenaere
2022-12-02 09:30:12 +00:00
committed by Android (Google) Code Review
4 changed files with 64 additions and 54 deletions

View File

@@ -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

View File

@@ -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() }
} }

View File

@@ -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
}
}

View File

@@ -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(),