PM-10630 setup autofill UI and interactions set up (#3891)

This commit is contained in:
Dave Severns 2024-09-10 14:10:23 -04:00 committed by GitHub
parent b94a1adda9
commit 8dce8cd576
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 812 additions and 0 deletions

View file

@ -0,0 +1,30 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val SETUP_AUTO_FILL_ROUTE = "setup_auto_fill"
/**
* Navigate to the setup auto-fill screen.
*/
fun NavController.navigateToSetupAutoFillScreen(navOptions: NavOptions? = null) {
this.navigate(SETUP_AUTO_FILL_ROUTE, navOptions)
}
/**
* Add the setup auto-fil screen to the nav graph.
*/
fun NavGraphBuilder.setupAutoFillDestination(
onNavigateToCompleteSetup: () -> Unit,
) {
composableWithPushTransitions(
route = SETUP_AUTO_FILL_ROUTE,
) {
SetupAutoFillScreen(
onNavigateToCompleteSetup = onNavigateToCompleteSetup,
)
}
}

View file

@ -0,0 +1,174 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
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 javax.inject.Inject
/**
* View model for the Auto-fill setup screen.
*/
@HiltViewModel
class SetupAutoFillViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
) :
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
initialState = SetupAutoFillState(dialogState = null, autofillEnabled = false),
) {
init {
settingsRepository
.isAutofillEnabledStateFlow
.map {
SetupAutoFillAction.Internal.AutofillEnabledUpdateReceive(isAutofillEnabled = it)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: SetupAutoFillAction) {
when (action) {
is SetupAutoFillAction.AutofillServiceChanged -> handleAutofillServiceChanged(action)
SetupAutoFillAction.ContinueClick -> handleContinueClick()
SetupAutoFillAction.DismissDialog -> handleDismissDialog()
SetupAutoFillAction.TurnOnLaterClick -> handleTurnOnLaterClick()
SetupAutoFillAction.AutoFillServiceFallback -> handleAutoFillServiceFallback()
SetupAutoFillAction.TurnOnLaterConfirmClick -> handleTurnOnLaterConfirmClick()
is SetupAutoFillAction.Internal.AutofillEnabledUpdateReceive -> {
handleAutofillEnabledUpdateReceive(action)
}
}
}
private fun handleAutofillEnabledUpdateReceive(
action: SetupAutoFillAction.Internal.AutofillEnabledUpdateReceive,
) {
mutableStateFlow.update {
it.copy(autofillEnabled = action.isAutofillEnabled)
}
}
private fun handleAutoFillServiceFallback() {
mutableStateFlow.update {
it.copy(dialogState = SetupAutoFillDialogState.AutoFillFallbackDialog)
}
}
private fun handleTurnOnLaterClick() {
mutableStateFlow.update {
it.copy(dialogState = SetupAutoFillDialogState.TurnOnLaterDialog)
}
}
private fun handleDismissDialog() {
mutableStateFlow.update {
it.copy(dialogState = null)
}
}
private fun handleTurnOnLaterConfirmClick() {
sendEvent(SetupAutoFillEvent.NavigateToCompleteSetup)
}
private fun handleContinueClick() {
sendEvent(SetupAutoFillEvent.NavigateToCompleteSetup)
}
private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) {
if (action.autofillEnabled) {
sendEvent(SetupAutoFillEvent.NavigateToAutofillSettings)
} else {
settingsRepository.disableAutofill()
}
}
}
/**
* UI State for the Auto-fill setup screen.
*/
data class SetupAutoFillState(
val dialogState: SetupAutoFillDialogState?,
val autofillEnabled: Boolean,
)
/**
* Dialog states for the Auto-fill setup screen.
*/
sealed class SetupAutoFillDialogState {
/**
* Represents the turn on later dialog.
*/
data object TurnOnLaterDialog : SetupAutoFillDialogState()
/**
* Represents the autofill fallback dialog.
*/
data object AutoFillFallbackDialog : SetupAutoFillDialogState()
}
/**
* UI Events for the Auto-fill setup screen.
*/
sealed class SetupAutoFillEvent {
/**
* Navigate to the complete setup screen.
*/
data object NavigateToCompleteSetup : SetupAutoFillEvent()
/**
* Navigate to the autofill settings screen.
*/
data object NavigateToAutofillSettings : SetupAutoFillEvent()
}
/**
* UI Actions for the Auto-fill setup screen.
*/
sealed class SetupAutoFillAction {
/**
* Dismiss the current dialog.
*/
data object DismissDialog : SetupAutoFillAction()
/**
* Move on to the next set-up step.
*/
data object ContinueClick : SetupAutoFillAction()
/**
* Turn autofill on later has been clicked.
*/
data object TurnOnLaterClick : SetupAutoFillAction()
/**
* Turn autofill on later has been confirmed.
*/
data object TurnOnLaterConfirmClick : SetupAutoFillAction()
/**
* Autofill service selection has changed.
*
* @param autofillEnabled Whether autofill is enabled.
*/
data class AutofillServiceChanged(val autofillEnabled: Boolean) : SetupAutoFillAction()
/**
* Autofill service fallback has occurred.
*/
data object AutoFillServiceFallback : SetupAutoFillAction()
/**
* Internal actions not send through UI.
*/
sealed class Internal : SetupAutoFillAction() {
/**
* An update for changes in the [isAutofillEnabled] value.
*/
data class AutofillEnabledUpdateReceive(val isAutofillEnabled: Boolean) : Internal()
}
}

View file

@ -0,0 +1,217 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.handlers.rememberSetupAutoFillHandler
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenWideSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/**
* Top level composable for the Auto-fill setup screen.
*/
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetupAutoFillScreen(
onNavigateToCompleteSetup: () -> Unit,
intentManager: IntentManager = LocalIntentManager.current,
viewModel: SetupAutoFillViewModel = hiltViewModel(),
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val handler = rememberSetupAutoFillHandler(viewModel = viewModel)
EventsEffect(viewModel = viewModel) { event ->
when (event) {
SetupAutoFillEvent.NavigateToCompleteSetup -> onNavigateToCompleteSetup()
SetupAutoFillEvent.NavigateToAutofillSettings -> {
val showFallback = !intentManager.startSystemAutofillSettingsActivity()
if (showFallback) {
handler.sendAutoFillServiceFallback.invoke()
}
}
}
}
when (state.dialogState) {
is SetupAutoFillDialogState.AutoFillFallbackDialog -> {
BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = null,
message = R.string.bitwarden_autofill_go_to_settings.asText(),
),
onDismissRequest = handler.onDismissDialog,
)
}
is SetupAutoFillDialogState.TurnOnLaterDialog -> {
BitwardenTwoButtonDialog(
title = stringResource(R.string.turn_on_autofill_later),
message = stringResource(R.string.return_to_complete_this_step_anytime_in_settings),
confirmButtonText = stringResource(id = R.string.confirm),
dismissButtonText = stringResource(id = R.string.cancel),
onConfirmClick = handler.onConfirmTurnOnLaterClick,
onDismissClick = handler.onDismissDialog,
onDismissRequest = handler.onDismissDialog,
)
}
null -> Unit
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.account_setup),
scrollBehavior = scrollBehavior,
navigationIcon = null,
)
},
) { innerPadding ->
SetupAutoFillContent(
autofillEnabled = state.autofillEnabled,
onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) },
onContinueClick = handler.onContinueClick,
onTurnOnLaterClick = handler.onTurnOnLaterClick,
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
.fillMaxSize(),
)
}
}
@Suppress("LongMethod")
@Composable
private fun SetupAutoFillContent(
autofillEnabled: Boolean,
onAutofillServiceChanged: (Boolean) -> Unit,
onContinueClick: () -> Unit,
onTurnOnLaterClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
) {
// Animated Image placeholder TODO PM-10843
Image(
painter = rememberVectorPainter(id = R.drawable.account_setup),
contentDescription = null,
modifier = Modifier
.standardHorizontalMargin()
.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.turn_on_autofill),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
modifier = Modifier
.standardHorizontalMargin()
.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.use_autofill_to_log_into_your_accounts),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.standardHorizontalMargin()
.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(24.dp))
BitwardenWideSwitch(
label = stringResource(
R.string.autofill_services,
),
isChecked = autofillEnabled,
onCheckedChange = onAutofillServiceChanged,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(24.dp))
BitwardenFilledButton(
label = stringResource(id = R.string.continue_text),
onClick = onContinueClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(12.dp))
BitwardenTextButton(
label = stringResource(R.string.turn_on_later),
onClick = onTurnOnLaterClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
@Preview(showBackground = true)
@Composable
private fun SetupAutoFillContentDisabled_preview() {
BitwardenTheme {
SetupAutoFillContent(
autofillEnabled = false,
onAutofillServiceChanged = {},
onContinueClick = {},
onTurnOnLaterClick = {},
)
}
}
@Preview(showBackground = true)
@Composable
private fun SetupAutoFillContentEnabled_preview() {
BitwardenTheme {
SetupAutoFillContent(
autofillEnabled = true,
onAutofillServiceChanged = {},
onContinueClick = {},
onTurnOnLaterClick = {},
)
}
}

View file

@ -0,0 +1,52 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupAutoFillAction
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupAutoFillViewModel
/**
* Handler for the Auto-fill setup screen.
*/
data class SetupAutoFillHandler(
val onAutofillServiceChanged: (Boolean) -> Unit,
val onContinueClick: () -> Unit,
val onTurnOnLaterClick: () -> Unit,
val onDismissDialog: () -> Unit,
val onConfirmTurnOnLaterClick: () -> Unit,
val sendAutoFillServiceFallback: () -> Unit,
) {
companion object {
/**
* Convenience function for creating a [SetupAutoFillHandler] with a
* [SetupAutoFillViewModel].
*/
fun create(viewModel: SetupAutoFillViewModel): SetupAutoFillHandler = SetupAutoFillHandler(
onAutofillServiceChanged = {
viewModel.trySendAction(
SetupAutoFillAction.AutofillServiceChanged(
it,
),
)
},
onContinueClick = { viewModel.trySendAction(SetupAutoFillAction.ContinueClick) },
onTurnOnLaterClick = { viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterClick) },
onDismissDialog = { viewModel.trySendAction(SetupAutoFillAction.DismissDialog) },
onConfirmTurnOnLaterClick = {
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
},
sendAutoFillServiceFallback = {
viewModel.trySendAction(SetupAutoFillAction.AutoFillServiceFallback)
},
)
}
}
/**
* Convenience function for creating a [SetupAutoFillHandler] in a [Composable] scope.
*/
@Composable
fun rememberSetupAutoFillHandler(viewModel: SetupAutoFillViewModel): SetupAutoFillHandler =
remember(viewModel) {
SetupAutoFillHandler.create(viewModel)
}

View file

@ -995,4 +995,9 @@ Do you want to switch to this account?</string>
<string name="authenticator_sync">Authenticator Sync</string>
<string name="allow_bitwarden_authenticator_syncing">Allow Bitwarden Authenticator Syncing</string>
<string name="there_was_an_issue_validating_the_registration_token">There was an issue validating the registration token.</string>
<string name="turn_on_autofill">Turn on autofill</string>
<string name="use_autofill_to_log_into_your_accounts">Use autofill to log into your accounts with a single tap.</string>
<string name="turn_on_later">Turn on later</string>
<string name="turn_on_autofill_later">Turn on autofill later?</string>
<string name="return_to_complete_this_step_anytime_in_settings">You can return to complete this step anytime in Settings.</string>
</resources>

View file

@ -0,0 +1,114 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
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
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class SetupAutoFillViewModelTest : BaseViewModelTest() {
private val mutableAutoFillEnabledStateFlow = MutableStateFlow(false)
private val settingsRepository = mockk<SettingsRepository>(relaxed = true) {
every { isAutofillEnabledStateFlow } returns mutableAutoFillEnabledStateFlow
every { disableAutofill() } just runs
}
@Test
fun `handleAutofillEnabledUpdateReceive updates autofillEnabled state`() {
val viewModel = createViewModel()
assertFalse(viewModel.stateFlow.value.autofillEnabled)
mutableAutoFillEnabledStateFlow.value = true
assertTrue(viewModel.stateFlow.value.autofillEnabled)
}
@Test
fun `handleAutofillServiceChanged with autofillEnabled true navigates to autofill settings`() =
runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SetupAutoFillAction.AutofillServiceChanged(true))
assertEquals(
SetupAutoFillEvent.NavigateToAutofillSettings,
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `handleAutofillServiceChanged with autofillEnabled false disables autofill`() =
runTest {
val viewModel = createViewModel()
mutableAutoFillEnabledStateFlow.value = true
assertTrue(viewModel.stateFlow.value.autofillEnabled)
viewModel.eventFlow.test {
viewModel.trySendAction(SetupAutoFillAction.AutofillServiceChanged(false))
expectNoEvents()
}
verify { settingsRepository.disableAutofill() }
}
@Test
fun `handleContinueClick sends NavigateToCompleteSetup event`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
assertEquals(SetupAutoFillEvent.NavigateToCompleteSetup, awaitItem())
}
}
@Test
fun `handleTurnOnLater click sets dialogState to TurnOnLaterDialog`() {
val viewModel = createViewModel()
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterClick)
assertEquals(
SetupAutoFillDialogState.TurnOnLaterDialog,
viewModel.stateFlow.value.dialogState,
)
}
@Test
fun `handleTurnOnLaterConfirmClick sends NavigateToCompleteSetup event`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
assertEquals(SetupAutoFillEvent.NavigateToCompleteSetup, awaitItem())
}
}
@Test
fun `handleDismissDialog sets dialogState to null`() {
val viewModel = createViewModel()
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterClick)
assertEquals(
SetupAutoFillDialogState.TurnOnLaterDialog,
viewModel.stateFlow.value.dialogState,
)
viewModel.trySendAction(SetupAutoFillAction.DismissDialog)
assertNull(viewModel.stateFlow.value.dialogState)
}
@Test
fun `handleAutoFillServiceFallback sets dialogState to AutoFillFallbackDialog`() {
val viewModel = createViewModel()
viewModel.trySendAction(SetupAutoFillAction.AutoFillServiceFallback)
assertEquals(
SetupAutoFillDialogState.AutoFillFallbackDialog,
viewModel.stateFlow.value.dialogState,
)
}
private fun createViewModel() = SetupAutoFillViewModel(settingsRepository)
}

View file

@ -0,0 +1,220 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class SetupAutofillScreenTest : BaseComposeTest() {
private var onNavigateToCompleteSetupCalled = false
private val mutableEventFlow = bufferedMutableSharedFlow<SetupAutoFillEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val viewModel = mockk<SetupAutoFillViewModel>(relaxed = true) {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
private val intentManager = mockk<IntentManager>(relaxed = true)
@Before
fun setup() {
composeTestRule.setContent {
SetupAutoFillScreen(
onNavigateToCompleteSetup = { onNavigateToCompleteSetupCalled = true },
intentManager = intentManager,
viewModel = viewModel,
)
}
}
@Test
fun `Turning on autofill should send AutofillServiceChanged with value of true`() {
composeTestRule
.onNodeWithText("Auto-fill services")
.performClick()
verify {
viewModel.trySendAction(SetupAutoFillAction.AutofillServiceChanged(true))
}
}
@Test
fun `Turning off autofill should send AutofillServiceChanged with value of false`() {
mutableStateFlow.update {
it.copy(autofillEnabled = true)
}
composeTestRule
.onNodeWithText("Auto-fill services", ignoreCase = true)
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(SetupAutoFillAction.AutofillServiceChanged(false))
}
}
@Test
fun `Continue click should send correct action`() {
composeTestRule
.onNodeWithText("Continue")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
}
}
@Test
fun `Turn on later click should send correct action`() {
composeTestRule
.onNodeWithText("Turn on later")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterClick)
}
}
@Test
fun `NavigateToAutoFillSettings should start system autofill settings activity`() {
every { intentManager.startSystemAutofillSettingsActivity() } returns true
mutableEventFlow.tryEmit(SetupAutoFillEvent.NavigateToAutofillSettings)
verify {
intentManager.startSystemAutofillSettingsActivity()
}
}
@Suppress("MaxLineLength")
@Test
fun `NavigateToAutoFillSettings should send AutoFillServiceFallback action when intent fails`() {
every { intentManager.startSystemAutofillSettingsActivity() } returns false
mutableEventFlow.tryEmit(SetupAutoFillEvent.NavigateToAutofillSettings)
verify { viewModel.trySendAction(SetupAutoFillAction.AutoFillServiceFallback) }
}
@Test
fun `NavigateToCompleteSetup should call onNavigateToCompleteSetup`() {
mutableEventFlow.tryEmit(SetupAutoFillEvent.NavigateToCompleteSetup)
assertTrue(onNavigateToCompleteSetupCalled)
}
@Test
fun `Show autofill fallback dialog when dialog state is AutoFillFallbackDialog`() {
mutableStateFlow.update {
it.copy(
dialogState = SetupAutoFillDialogState.AutoFillFallbackDialog,
)
}
composeTestRule
.onNode(isDialog())
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText(
"We were unable to automatically open the Android autofill",
substring = true,
)
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Suppress("MaxLineLength")
@Test
fun `When autofill fallback dialog is dismissed, sends action to dismiss dialog and is removed when state is null`() {
mutableStateFlow.update {
it.copy(
dialogState = SetupAutoFillDialogState.AutoFillFallbackDialog,
)
}
composeTestRule
.onNode(isDialog())
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify { viewModel.trySendAction(SetupAutoFillAction.DismissDialog) }
mutableStateFlow.update {
it.copy(
dialogState = null,
)
}
composeTestRule.assertNoDialogExists()
}
@Test
fun `Show turn on later dialog when dialog state is TurnOnLaterDialog`() {
mutableStateFlow.update {
it.copy(
dialogState = SetupAutoFillDialogState.TurnOnLaterDialog,
)
}
composeTestRule
.onNode(isDialog())
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Turn on autofill later?")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `On confirm click on TurnOnLaterDialog, sends action to turn on later`() {
mutableStateFlow.update {
it.copy(
dialogState = SetupAutoFillDialogState.TurnOnLaterDialog,
)
}
composeTestRule
.onNode(isDialog())
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Confirm")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify { viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick) }
}
@Suppress("MaxLineLength")
@Test
fun `When turn on later dialog is dismissed, sends action to dismiss dialog and is removed when state is null`() {
mutableStateFlow.update {
it.copy(
dialogState = SetupAutoFillDialogState.TurnOnLaterDialog,
)
}
composeTestRule
.onNode(isDialog())
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify { viewModel.trySendAction(SetupAutoFillAction.DismissDialog) }
mutableStateFlow.update {
it.copy(
dialogState = null,
)
}
composeTestRule.assertNoDialogExists()
}
}
private val DEFAULT_STATE = SetupAutoFillState(dialogState = null, autofillEnabled = false)