PM-18315 add UI when 3pa is available for each chrome channel which s… (#4758)

This commit is contained in:
Dave Severns
2025-02-21 15:09:57 -05:00
committed by GitHub
parent 86e5789f30
commit 892f817b2a
9 changed files with 412 additions and 6 deletions

View File

@@ -47,6 +47,7 @@ import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.ChromeAutofillSettingsCard
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.util.displayLabel
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import kotlinx.collections.immutable.toImmutableList
@@ -95,6 +96,11 @@ fun AutoFillScreen(
}
AutoFillEvent.NavigateToSetupAutofill -> onNavigateToSetupAutofill()
is AutoFillEvent.NavigateToChromeAutofillSettings -> {
intentManager.startChromeAutofillSettingsActivity(
releaseChannel = event.releaseChannel,
)
}
}
}
@@ -139,7 +145,7 @@ fun AutoFillScreen(
actionText = stringResource(R.string.get_started),
onActionClick = remember(viewModel) {
{
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick)
}
},
onDismissClick = remember(viewModel) {
@@ -196,6 +202,20 @@ fun AutoFillScreen(
)
Spacer(modifier = Modifier.height(height = 8.dp))
}
if (state.chromeAutofillSettingsOptions.isNotEmpty()) {
ChromeAutofillSettingsCard(
options = state.chromeAutofillSettingsOptions,
onOptionClicked = remember(viewModel) {
{
viewModel.trySendAction(AutoFillAction.ChromeAutofillSelected(it))
}
},
enabled = state.isAutoFillServicesEnabled,
)
Spacer(modifier = Modifier.height(8.dp))
}
if (state.showPasskeyManagementRow) {
BitwardenExternalLinkRow(
text = stringResource(id = R.string.passkey_management),

View File

@@ -5,13 +5,20 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -28,6 +35,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class AutoFillViewModel @Inject constructor(
authRepository: AuthRepository,
chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
private val savedStateHandle: SavedStateHandle,
private val settingsRepository: SettingsRepository,
private val firstTimeActionManager: FirstTimeActionManager,
@@ -50,6 +58,7 @@ class AutoFillViewModel @Inject constructor(
defaultUriMatchType = settingsRepository.defaultUriMatchType,
showAutofillActionCard = false,
activeUserId = userId,
chromeAutofillSettingsOptions = persistentListOf(),
)
},
) {
@@ -81,6 +90,12 @@ class AutoFillViewModel @Inject constructor(
.map { AutoFillAction.Internal.UpdateShowAutofillActionCard(it.showSetupAutofillCard) }
.onEach(::sendAction)
.launchIn(viewModelScope)
chromeThirdPartyAutofillEnabledManager
.chromeThirdPartyAutofillStatusFlow
.map { AutoFillAction.Internal.ChromeAutofillStatusReceive(status = it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: AutoFillAction) = when (action) {
@@ -94,8 +109,9 @@ class AutoFillViewModel @Inject constructor(
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
AutoFillAction.PasskeyManagementClick -> handlePasskeyManagementClick()
is AutoFillAction.Internal -> handleInternalAction(action)
AutoFillAction.AutoFillActionCardCtaClick -> handleAutoFillActionCardCtClick()
AutoFillAction.AutofillActionCardCtaClick -> handleAutofillActionCardCtaClick()
AutoFillAction.DismissShowAutofillActionCard -> handleDismissShowAutofillActionCard()
is AutoFillAction.ChromeAutofillSelected -> handleChromeAutofillSelected(action)
}
private fun handleInternalAction(action: AutoFillAction.Internal) {
@@ -111,14 +127,34 @@ class AutoFillViewModel @Inject constructor(
is AutoFillAction.Internal.UpdateShowAutofillActionCard -> {
handleUpdateShowAutofillActionCard(action)
}
is AutoFillAction.Internal.ChromeAutofillStatusReceive -> {
handleChromeAutofillStatusReceive(action)
}
}
}
private fun handleChromeAutofillStatusReceive(
action: AutoFillAction.Internal.ChromeAutofillStatusReceive,
) {
mutableStateFlow.update {
it.copy(
chromeAutofillSettingsOptions = action
.status
.toChromeAutoFillSettingsOptions(),
)
}
}
private fun handleChromeAutofillSelected(action: AutoFillAction.ChromeAutofillSelected) {
sendEvent(AutoFillEvent.NavigateToChromeAutofillSettings(action.releaseChannel))
}
private fun handleDismissShowAutofillActionCard() {
dismissShowAutofillActionCard()
}
private fun handleAutoFillActionCardCtClick() {
private fun handleAutofillActionCardCtaClick() {
sendEvent(AutoFillEvent.NavigateToSetupAutofill)
}
@@ -216,6 +252,7 @@ data class AutoFillState(
val defaultUriMatchType: UriMatchType,
val showAutofillActionCard: Boolean,
val activeUserId: String,
val chromeAutofillSettingsOptions: ImmutableList<ChromeAutofillSettingsOption>,
) : Parcelable {
/**
@@ -226,6 +263,19 @@ data class AutoFillState(
get() = isAutoFillServicesEnabled
}
@Suppress("MaxLineLength")
private fun ChromeThirdPartyAutofillStatus.toChromeAutoFillSettingsOptions(): ImmutableList<ChromeAutofillSettingsOption> =
persistentListOfNotNull(
ChromeAutofillSettingsOption.Stable(
enabled = this.stableStatusData.isThirdPartyEnabled,
)
.takeIf { this.stableStatusData.isAvailable },
ChromeAutofillSettingsOption.Beta(
enabled = this.betaChannelStatusData.isThirdPartyEnabled,
)
.takeIf { this.betaChannelStatusData.isAvailable },
)
/**
* Models events for the auto-fill screen.
*/
@@ -262,6 +312,13 @@ sealed class AutoFillEvent {
val text: Text,
) : AutoFillEvent()
/**
* Navigate to the Autofill settings of the specified [releaseChannel].
*/
data class NavigateToChromeAutofillSettings(
val releaseChannel: ChromeReleaseChannel,
) : AutoFillEvent()
/**
* Navigates to the setup autofill screen.
*/
@@ -335,7 +392,12 @@ sealed class AutoFillAction {
/**
* User has clicked the CTA on the autofill action card.
*/
data object AutoFillActionCardCtaClick : AutoFillAction()
data object AutofillActionCardCtaClick : AutoFillAction()
/**
* User has clicked one of the chrome autofill options.
*/
data class ChromeAutofillSelected(val releaseChannel: ChromeReleaseChannel) : AutoFillAction()
/**
* Internal actions.
@@ -359,5 +421,12 @@ sealed class AutoFillAction {
* An update for changes in the [showAutofillActionCard] value from the settings repository.
*/
data class UpdateShowAutofillActionCard(val showAutofillActionCard: Boolean) : Internal()
/**
* Received updated [ChromeThirdPartyAutofillStatus] data.
*/
data class ChromeAutofillStatusReceive(
val status: ChromeThirdPartyAutofillStatus,
) : Internal()
}
}

View File

@@ -0,0 +1,94 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* Card for displaying a list of [ChromeAutofillSettingsOption]s and whether they are
* currently enabled.
*
* @param options List of data to display in the card, if the list is empty nothing will be drawn.
* @param onOptionClicked Lambda that is invoked when an option row is clicked and passes back the
* [ChromeReleaseChannel] for that option.
* @param enabled Whether to show the switches for each option as enabled.
*/
@Composable
fun ChromeAutofillSettingsCard(
options: ImmutableList<ChromeAutofillSettingsOption>,
onOptionClicked: (ChromeReleaseChannel) -> Unit,
enabled: Boolean,
modifier: Modifier = Modifier,
) {
if (options.isEmpty()) return
Column(modifier = modifier) {
options.forEachIndexed { index, option ->
BitwardenSwitch(
label = option.optionText(),
isChecked = option.isEnabled,
onCheckedChange = {
onOptionClicked(option.chromeReleaseChannel)
},
cardStyle = if (index == 0) {
CardStyle.Top(
dividerPadding = 16.dp,
)
} else {
CardStyle.Middle(
dividerPadding = 16.dp,
)
},
enabled = enabled,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
Text(
text = stringResource(
R.string.improves_login_filling_for_supported_websites_on_chrome,
),
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.cardStyle(
cardStyle = CardStyle.Bottom,
paddingHorizontal = 16.dp,
)
.defaultMinSize(minHeight = 48.dp),
)
}
}
@Preview
@Composable
private fun ChromeAutofillSettingsCard_preview() {
BitwardenTheme {
ChromeAutofillSettingsCard(
options = persistentListOf(
ChromeAutofillSettingsOption.Stable(enabled = false),
ChromeAutofillSettingsOption.Beta(enabled = true),
),
enabled = true,
onOptionClicked = {},
)
}
}

View File

@@ -0,0 +1,42 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model
import android.os.Parcelable
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import kotlinx.parcelize.Parcelize
/**
* Models an option for an option for each type of supported version of Chrome to enable
* third party autofill. Each [ChromeAutofillSettingsOption] contains the associated
* [ChromeReleaseChannel], the [optionText] to display in any UI component, and
* whether or not the third party autofill [isEnabled].
*/
@Parcelize
sealed class ChromeAutofillSettingsOption(val isEnabled: Boolean) : Parcelable {
abstract val chromeReleaseChannel: ChromeReleaseChannel
abstract val optionText: Text
/**
* Represents the stable Chrome release channel.
*/
@Parcelize
data class Stable(val enabled: Boolean) : ChromeAutofillSettingsOption(isEnabled = enabled) {
override val chromeReleaseChannel: ChromeReleaseChannel
get() = ChromeReleaseChannel.STABLE
override val optionText: Text
get() = R.string.use_chrome_autofill_integration.asText()
}
/**
* Represents the beta Chrome release channel.
*/
@Parcelize
data class Beta(val enabled: Boolean) : ChromeAutofillSettingsOption(isEnabled = enabled) {
override val chromeReleaseChannel: ChromeReleaseChannel
get() = ChromeReleaseChannel.BETA
override val optionText: Text
get() = R.string.use_chrome_beta_autofill_integration.asText()
}
}

View File

@@ -9,6 +9,7 @@ import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import kotlinx.parcelize.Parcelize
/**
@@ -48,6 +49,11 @@ interface IntentManager {
*/
fun startCredentialManagerSettings(context: Context)
/**
* Starts the Chrome autofill settings activity for the provided [ChromeReleaseChannel].
*/
fun startChromeAutofillSettingsActivity(releaseChannel: ChromeReleaseChannel): Boolean
/**
* Start an activity to view the given [uri] in an external browser.
*/

View File

@@ -26,6 +26,7 @@ import androidx.credentials.CredentialManager
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.MainActivity
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
@@ -133,6 +134,22 @@ class IntentManagerImpl(
}
}
override fun startChromeAutofillSettingsActivity(
releaseChannel: ChromeReleaseChannel,
): Boolean = try {
val intent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
.apply {
addCategory(Intent.CATEGORY_DEFAULT)
addCategory(Intent.CATEGORY_APP_BROWSER)
addCategory(Intent.CATEGORY_PREFERENCE)
setPackage(releaseChannel.packageName)
}
context.startActivity(intent)
true
} catch (_: ActivityNotFoundException) {
false
}
override fun launchUri(uri: Uri) {
if (uri.scheme.equals(other = "androidapp", ignoreCase = true)) {
val packageName = uri.toString().removePrefix(prefix = "androidapp://")

View File

@@ -1221,4 +1221,7 @@ Do you want to switch to this account?</string>
<string name="passkey_operation_failed_because_user_verification_attempts_exceeded">Passkey operation failed because user verification attempts exceeded.</string>
<string name="passkey_operation_failed_because_no_item_was_selected">Passkey operation failed because no item was selected.</string>
<string name="self_host_server_url">Self-host server URL</string>
<string name="use_chrome_autofill_integration">Use Chrome autofill integration</string>
<string name="use_chrome_beta_autofill_integration">Use Chrome autofill integration (Beta)</string>
<string name="improves_login_filling_for_supported_websites_on_chrome">Improves login filling for supported websites on Chrome. Once enabled, youll be directed to Chrome settings to enable third-party autofill.</string>
</resources>

View File

@@ -14,9 +14,11 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
@@ -24,6 +26,7 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.junit.Assert.assertTrue
@@ -47,6 +50,7 @@ class AutoFillScreenTest : BaseComposeTest() {
every { startSystemAutofillSettingsActivity() } answers { isSystemSettingsRequestSuccess }
every { startCredentialManagerSettings(any()) } just runs
every { startSystemAccessibilitySettingsActivity() } just runs
every { startChromeAutofillSettingsActivity(any()) } returns true
}
@Before
@@ -487,7 +491,7 @@ class AutoFillScreenTest : BaseComposeTest() {
.performScrollTo()
.performClick()
verify { viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick) }
verify { viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick) }
}
@Test
@@ -505,6 +509,83 @@ class AutoFillScreenTest : BaseComposeTest() {
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToSetupAutofill)
assertTrue(onNavigateToSetupAutoFillScreenCalled)
}
@Test
fun `ChromeAutofillSettingsCard is only displayed when there are options in the list`() {
val chromeAutofillSupportingText =
"Improves login filling for supported websites on Chrome. " +
"Once enabled, youll be directed to Chrome settings to enable " +
"third-party autofill."
composeTestRule
.onNodeWithText(chromeAutofillSupportingText)
.assertDoesNotExist()
mutableStateFlow.update {
it.copy(
chromeAutofillSettingsOptions = persistentListOf(
ChromeAutofillSettingsOption.Stable(enabled = true),
),
)
}
composeTestRule
.onNodeWithText(chromeAutofillSupportingText)
.performScrollTo()
.assertIsDisplayed()
}
@Test
fun `when Chrome autofill options are clicked the correct action is sent`() {
mutableStateFlow.update {
it.copy(
isAutoFillServicesEnabled = true,
chromeAutofillSettingsOptions = persistentListOf(
ChromeAutofillSettingsOption.Stable(enabled = true),
ChromeAutofillSettingsOption.Beta(enabled = false),
),
)
}
composeTestRule
.onNodeWithText("Use Chrome autofill integration")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText("Use Chrome autofill integration (Beta)")
.performScrollTo()
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.BETA),
)
viewModel.trySendAction(
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.STABLE),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `when NavigateToChromeAutofillSettings events are sent they invoke the intent manager with the correct release channel`() {
mutableEventFlow.tryEmit(
AutoFillEvent.NavigateToChromeAutofillSettings(
ChromeReleaseChannel.STABLE,
),
)
mutableEventFlow.tryEmit(
AutoFillEvent.NavigateToChromeAutofillSettings(
ChromeReleaseChannel.BETA,
),
)
verify(exactly = 1) {
intentManager.startChromeAutofillSettingsActivity(ChromeReleaseChannel.BETA)
intentManager.startChromeAutofillSettingsActivity(ChromeReleaseChannel.STABLE)
}
}
}
private val DEFAULT_STATE: AutoFillState = AutoFillState(
@@ -518,4 +599,5 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
defaultUriMatchType = UriMatchType.DOMAIN,
showAutofillActionCard = false,
activeUserId = "activeUserId",
chromeAutofillSettingsOptions = persistentListOf(),
)

View File

@@ -4,12 +4,17 @@ import android.os.Build
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@@ -17,6 +22,7 @@ import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.runTest
@@ -40,6 +46,12 @@ class AutoFillViewModelTest : BaseViewModelTest() {
every { storeShowAutoFillSettingBadge(any()) } just runs
}
private val mutableChromeAutofillStatusFlow = MutableStateFlow(DEFAULT_AUTOFILL_STATUS)
private val chromeThirdPartyAutofillEnabledManager =
mockk<ChromeThirdPartyAutofillEnabledManager> {
every { chromeThirdPartyAutofillStatusFlow } returns mutableChromeAutofillStatusFlow
}
private val settingsRepository: SettingsRepository = mockk {
every { isInlineAutofillEnabled } returns true
every { isInlineAutofillEnabled = any() } just runs
@@ -330,7 +342,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
mutableFirstTimeStateFlow.update { it.copy(showSetupAutofillCard = true) }
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick)
assertEquals(
AutoFillEvent.NavigateToSetupAutofill,
awaitItem(),
@@ -354,6 +366,55 @@ class AutoFillViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `when ChromeAutofillStatusReceive with updated information is processed state updates as expected`() =
runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
mutableChromeAutofillStatusFlow.update {
it.copy(
stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(isAvailable = true),
)
}
assertEquals(
DEFAULT_STATE.copy(
chromeAutofillSettingsOptions = persistentListOf(
ChromeAutofillSettingsOption.Stable(enabled = false),
),
),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `when ChromeAutofillSelected action is handled the correct NavigateToChromeAutofillSettings event is sent`() =
runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.STABLE),
)
assertEquals(
AutoFillEvent.NavigateToChromeAutofillSettings(ChromeReleaseChannel.STABLE),
awaitItem(),
)
viewModel.trySendAction(
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.BETA),
)
assertEquals(
AutoFillEvent.NavigateToChromeAutofillSettings(ChromeReleaseChannel.BETA),
awaitItem(),
)
}
}
private fun createViewModel(
state: AutoFillState? = DEFAULT_STATE,
): AutoFillViewModel = AutoFillViewModel(
@@ -361,6 +422,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
settingsRepository = settingsRepository,
authRepository = authRepository,
firstTimeActionManager = firstTimeActionManager,
chromeThirdPartyAutofillEnabledManager = chromeThirdPartyAutofillEnabledManager,
)
}
@@ -375,4 +437,15 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
defaultUriMatchType = UriMatchType.DOMAIN,
showAutofillActionCard = false,
activeUserId = "activeUserId",
chromeAutofillSettingsOptions = persistentListOf(),
)
private val DEFAULT_CHROME_AUTOFILL_DATA = ChromeThirdPartyAutoFillData(
isAvailable = false,
isThirdPartyEnabled = false,
)
private val DEFAULT_AUTOFILL_STATUS = ChromeThirdPartyAutofillStatus(
stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
)