BIT-1488: Track changes in autofill status and send users to settings (#663)

This commit is contained in:
Brian Yencho 2024-01-18 11:17:56 -06:00 committed by Álison Fernandes
parent 3851f88828
commit 94108bcb5d
11 changed files with 324 additions and 7 deletions

View file

@ -1,5 +1,7 @@
package com.x8bit.bitwarden.data.autofill.di
import android.content.Context
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilder
import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilderImpl
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
@ -14,7 +16,9 @@ import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides dependencies within the autofill package.
@ -22,6 +26,13 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
object AutofillModule {
@Singleton
@Provides
fun providesAutofillManager(
@ApplicationContext context: Context,
): AutofillManager = context.getSystemService(AutofillManager::class.java)
@Provides
fun providesAutofillParser(): AutofillParser = AutofillParserImpl()

View file

@ -67,6 +67,16 @@ interface SettingsRepository {
*/
var isApprovePasswordlessLoginsEnabled: Boolean
/**
* Emits updates whenever there is a change in the app's status for supporting autofill.
*/
val isAutofillEnabledStateFlow: StateFlow<Boolean>
/**
* Disables autofill if it is currently enabled.
*/
fun disableAutofill()
/**
* Sets default values for various settings for the given [userId] if necessary. This is
* typically used when logging into a new account.

View file

@ -1,7 +1,9 @@
package com.x8bit.bitwarden.data.platform.repository
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
@ -12,7 +14,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@ -21,6 +26,8 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class SettingsRepositoryImpl(
private val autofillManager: AutofillManager,
private val appForegroundManager: AppForegroundManager,
private val authDiskSource: AuthDiskSource,
private val settingsDiskSource: SettingsDiskSource,
private val vaultSdkSource: VaultSdkSource,
@ -30,6 +37,13 @@ class SettingsRepositoryImpl(
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
private val isAutofillEnabledAndSupported: Boolean
get() = autofillManager.isEnabled &&
autofillManager.hasEnabledAutofillServices() &&
autofillManager.isAutofillSupported
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(isAutofillEnabledAndSupported)
override var appLanguage: AppLanguage
get() = settingsDiskSource.appLanguage ?: AppLanguage.DEFAULT
set(value) {
@ -134,6 +148,20 @@ class SettingsRepositoryImpl(
isApprovePasswordlessLoginsEnabled = value,
)
}
override val isAutofillEnabledStateFlow: StateFlow<Boolean> =
mutableIsAutofillEnabledStateFlow.asStateFlow()
init {
observeAutofillEnabledChanges()
}
override fun disableAutofill() {
autofillManager.disableAutofillServices()
// Manually indicate that autofill is no longer supported without needing a foreground state
// change.
mutableIsAutofillEnabledStateFlow.value = false
}
override fun setDefaultsIfNecessary(userId: String) {
// Set Vault Settings defaults
@ -260,6 +288,15 @@ class SettingsRepositoryImpl(
)
}
}
private fun observeAutofillEnabledChanges() {
appForegroundManager
.appForegroundStateFlow
.onEach {
mutableIsAutofillEnabledStateFlow.value = isAutofillEnabledAndSupported
}
.launchIn(unconfinedScope)
}
}
/**

View file

@ -1,8 +1,10 @@
package com.x8bit.bitwarden.data.platform.repository.di
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepositoryImpl
@ -38,12 +40,16 @@ object PlatformRepositoryModule {
@Provides
@Singleton
fun provideSettingsRepository(
autofillManager: AutofillManager,
appForegroundManager: AppForegroundManager,
authDiskSource: AuthDiskSource,
settingsDiskSource: SettingsDiskSource,
vaultSdkSource: VaultSdkSource,
dispatcherManager: DispatcherManager,
): SettingsRepository =
SettingsRepositoryImpl(
autofillManager = autofillManager,
appForegroundManager = appForegroundManager,
authDiskSource = authDiskSource,
settingsDiskSource = settingsDiskSource,
vaultSdkSource = vaultSdkSource,

View file

@ -30,6 +30,9 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog
@ -37,6 +40,8 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
/**
* Displays the auto-fill screen.
@ -47,20 +52,38 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
fun AutoFillScreen(
onNavigateBack: () -> Unit,
viewModel: AutoFillViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current,
) {
val state by viewModel.stateFlow.collectAsState()
val context = LocalContext.current
val resources = context.resources
var shouldShowAutofillFallbackDialog by rememberSaveable { mutableStateOf(false) }
EventsEffect(viewModel = viewModel) { event ->
when (event) {
AutoFillEvent.NavigateBack -> onNavigateBack.invoke()
AutoFillEvent.NavigateToAutofillSettings -> {
val isSuccess = intentManager.startSystemAutofillSettingsActivity()
shouldShowAutofillFallbackDialog = !isSuccess
}
is AutoFillEvent.ShowToast -> {
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
}
}
}
if (shouldShowAutofillFallbackDialog) {
BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = null,
message = R.string.bitwarden_autofill_go_to_settings.asText(),
),
onDismissRequest = { shouldShowAutofillFallbackDialog = false },
)
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold(
modifier = Modifier

View file

@ -10,6 +10,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.parcelize.Parcelize
@ -29,7 +30,7 @@ class AutoFillViewModel @Inject constructor(
initialState = savedStateHandle[KEY_STATE]
?: AutoFillState(
isAskToAddLoginEnabled = false,
isAutoFillServicesEnabled = false,
isAutoFillServicesEnabled = settingsRepository.isAutofillEnabledStateFlow.value,
isCopyTotpAutomaticallyEnabled = false,
isUseInlineAutoFillEnabled = settingsRepository.isInlineAutofillEnabled,
uriDetectionMethod = AutoFillState.UriDetectionMethod.DEFAULT,
@ -40,6 +41,14 @@ class AutoFillViewModel @Inject constructor(
stateFlow
.onEach { savedStateHandle[KEY_STATE] = it }
.launchIn(viewModelScope)
settingsRepository
.isAutofillEnabledStateFlow
.map {
AutoFillAction.Internal.AutofillEnabledUpdateReceive(isAutofillEnabled = it)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: AutoFillAction): Unit = when (action) {
@ -49,6 +58,9 @@ class AutoFillViewModel @Inject constructor(
is AutoFillAction.CopyTotpAutomaticallyClick -> handleCopyTotpAutomaticallyClick(action)
is AutoFillAction.UriDetectionMethodSelect -> handleUriDetectionMethodSelect(action)
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
is AutoFillAction.Internal.AutofillEnabledUpdateReceive -> {
handleAutofillEnabledUpdateReceive(action)
}
}
private fun handleAskToAddLoginClick(action: AutoFillAction.AskToAddLoginClick) {
@ -58,9 +70,11 @@ class AutoFillViewModel @Inject constructor(
}
private fun handleAutoFillServicesClick(action: AutoFillAction.AutoFillServicesClick) {
// TODO BIT-828: Persist selection
sendEvent(AutoFillEvent.ShowToast("Not yet implemented.".asText()))
mutableStateFlow.update { it.copy(isAutoFillServicesEnabled = action.isEnabled) }
if (action.isEnabled) {
sendEvent(AutoFillEvent.NavigateToAutofillSettings)
} else {
settingsRepository.disableAutofill()
}
}
private fun handleBackClick() {
@ -87,6 +101,14 @@ class AutoFillViewModel @Inject constructor(
it.copy(uriDetectionMethod = action.uriDetectionMethod)
}
}
private fun handleAutofillEnabledUpdateReceive(
action: AutoFillAction.Internal.AutofillEnabledUpdateReceive,
) {
mutableStateFlow.update {
it.copy(isAutoFillServicesEnabled = action.isAutofillEnabled)
}
}
}
/**
@ -123,6 +145,11 @@ sealed class AutoFillEvent {
*/
data object NavigateBack : AutoFillEvent()
/**
* Navigates to the system autofill settings selection screen.
*/
data object NavigateToAutofillSettings : AutoFillEvent()
/**
* Displays a toast with the given [Text].
*/
@ -174,4 +201,17 @@ sealed class AutoFillAction {
data class UseInlineAutofillClick(
val isEnabled: Boolean,
) : AutoFillAction()
/**
* Internal actions.
*/
sealed class Internal : AutoFillAction() {
/**
* An update for changes in the [isAutofillEnabled] value.
*/
data class AutofillEnabledUpdateReceive(
val isAutofillEnabled: Boolean,
) : Internal()
}
}

View file

@ -26,6 +26,12 @@ interface IntentManager {
*/
fun startCustomTabsActivity(uri: Uri)
/**
* Attempts to start the system autofill settings activity. The return value indicates whether
* or not this was successful.
*/
fun startSystemAutofillSettingsActivity(): Boolean
/**
* Start an activity to view the given [uri] in an external browser.
*/

View file

@ -1,12 +1,14 @@
package com.x8bit.bitwarden.ui.platform.manager.intent
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.MediaStore
import android.provider.Settings
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
@ -79,6 +81,18 @@ class IntentManagerImpl(
.launchUrl(context, uri)
}
override fun startSystemAutofillSettingsActivity(): Boolean =
try {
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
.apply {
data = Uri.parse("package:${context.packageName}")
}
context.startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
false
}
override fun launchUri(uri: Uri) {
val newUri = if (uri.scheme == null) {
uri.buildUpon().scheme("https").build()

View file

@ -1,11 +1,14 @@
package com.x8bit.bitwarden.data.platform.repository
import android.view.autofill.AutofillManager
import app.cash.turbine.test
import com.bitwarden.core.DerivePinKeyResponse
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.data.platform.util.asSuccess
@ -14,7 +17,12 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLang
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
@ -23,11 +31,25 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class SettingsRepositoryTest {
private val autofillManager: AutofillManager = mockk {
every { hasEnabledAutofillServices() } answers { isAutofillEnabledAndSupported }
every { isAutofillSupported } answers { isAutofillEnabledAndSupported }
every { isEnabled } answers { isAutofillEnabledAndSupported }
every { disableAutofillServices() } just runs
}
private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED)
private val appForegroundManager: AppForegroundManager = mockk {
every { appForegroundStateFlow } returns mutableAppForegroundStateFlow
}
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val fakeSettingsDiskSource = FakeSettingsDiskSource()
private val vaultSdkSource: VaultSdkSource = mockk()
private var isAutofillEnabledAndSupported = false
private val settingsRepository = SettingsRepositoryImpl(
autofillManager = autofillManager,
appForegroundManager = appForegroundManager,
authDiskSource = fakeAuthDiskSource,
settingsDiskSource = fakeSettingsDiskSource,
vaultSdkSource = vaultSdkSource,
@ -389,6 +411,53 @@ class SettingsRepositoryTest {
)
}
@Suppress("MaxLineLength")
@Test
fun `isAutofillEnabledStateFlow should emit updates if necessary when the app foreground state changes and disableAutofill is called`() =
runTest {
settingsRepository.isAutofillEnabledStateFlow.test {
assertFalse(awaitItem())
// An update is received when both the autofill state and foreground state change
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertTrue(awaitItem())
// An update is not received when only the foreground state changes
mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED
expectNoEvents()
// An update is not received when only the autofill state changes
isAutofillEnabledAndSupported = false
expectNoEvents()
// An update is received after both states have changed
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertFalse(awaitItem())
// Calling disableAutofill will result in an emission of false
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED
assertTrue(awaitItem())
settingsRepository.disableAutofill()
assertFalse(awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `disableAutofill should trigger an emission of false from isAutofillEnabledStateFlow and disable autofill with the OS`() {
// Start in a state where autofill is enabled
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertTrue(settingsRepository.isAutofillEnabledStateFlow.value)
settingsRepository.disableAutofill()
assertFalse(settingsRepository.isAutofillEnabledStateFlow.value)
verify { autofillManager.disableAutofillServices() }
}
@Test
fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest {
val userId = "userId"

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.filterToOne
@ -13,6 +14,8 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@ -24,6 +27,7 @@ import org.junit.Test
class AutoFillScreenTest : BaseComposeTest() {
private var isSystemSettingsRequestSuccess = false
private var onNavigateBackCalled = false
private val mutableEventFlow = bufferedMutableSharedFlow<AutoFillEvent>()
@ -32,6 +36,9 @@ class AutoFillScreenTest : BaseComposeTest() {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
private val intentManager: IntentManager = mockk {
every { startSystemAutofillSettingsActivity() } answers { isSystemSettingsRequestSuccess }
}
@Before
fun setUp() {
@ -39,10 +46,63 @@ class AutoFillScreenTest : BaseComposeTest() {
AutoFillScreen(
onNavigateBack = { onNavigateBackCalled = true },
viewModel = viewModel,
intentManager = intentManager,
)
}
}
@Suppress("MaxLineLength")
@Test
fun `on NavigateToAutofillSettings should attempt to navigate to system settings and not show the fallback dialog when result is a success`() {
isSystemSettingsRequestSuccess = true
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
verify {
intentManager.startSystemAutofillSettingsActivity()
}
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on NavigateToAutofillSettings should attempt to navigate to system settings and show the fallback dialog when result is not a success`() {
isSystemSettingsRequestSuccess = false
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
verify {
intentManager.startSystemAutofillSettingsActivity()
}
composeTestRule
.onAllNodesWithText(
"We were unable to automatically open the Android autofill settings menu for " +
"you. You can navigate to the autofill settings menu manually from Android " +
"Settings>System>Languages and input>Advanced>Autofill service.",
)
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on autofill settings fallback dialog Ok click should dismiss the dialog`() {
isSystemSettingsRequestSuccess = false
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
.performClick()
composeTestRule.assertNoDialogExists()
}
@Test
fun `on auto fill services toggle should send AutoFillServicesClick`() {
composeTestRule

View file

@ -10,15 +10,19 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AutoFillViewModelTest : BaseViewModelTest() {
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(false)
private val settingsRepository: SettingsRepository = mockk() {
every { isInlineAutofillEnabled } returns true
every { isInlineAutofillEnabled = any() } just runs
every { isAutofillEnabledStateFlow } returns mutableIsAutofillEnabledStateFlow
every { disableAutofill() } just runs
}
@Test
@ -29,6 +33,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
@Test
fun `initial state should be correct when set`() {
mutableIsAutofillEnabledStateFlow.value = true
val state = DEFAULT_STATE.copy(
isAutoFillServicesEnabled = true,
uriDetectionMethod = AutoFillState.UriDetectionMethod.REGULAR_EXPRESSION,
@ -37,6 +42,26 @@ class AutoFillViewModelTest : BaseViewModelTest() {
assertEquals(state, viewModel.stateFlow.value)
}
@Test
fun `changes in autofill enabled status should update the state`() {
val viewModel = createViewModel()
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
mutableIsAutofillEnabledStateFlow.value = true
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = true),
viewModel.stateFlow.value,
)
mutableIsAutofillEnabledStateFlow.value = false
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = false),
viewModel.stateFlow.value,
)
}
@Test
fun `on AskToAddLoginClick should emit ShowToast`() = runTest {
val viewModel = createViewModel()
@ -51,14 +76,30 @@ class AutoFillViewModelTest : BaseViewModelTest() {
}
@Test
fun `on AutoFillServicesClick should emit ShowToast`() = runTest {
fun `on AutoFillServicesClick with false should disable autofill`() {
val viewModel = createViewModel()
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(false))
verify {
settingsRepository.disableAutofill()
}
assertEquals(
DEFAULT_STATE,
viewModel.stateFlow.value,
)
}
@Test
fun `on AutoFillServicesClick with true should emit NavigateToAutofillSettings`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true))
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
assertEquals(
AutoFillEvent.NavigateToAutofillSettings,
awaitItem(),
)
}
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = true),
DEFAULT_STATE,
viewModel.stateFlow.value,
)
}