mirror of
https://github.com/bitwarden/android.git
synced 2026-04-28 20:08:27 -05:00
BITAU-90 Add "Sync with Bitwarden App" row to settings (#239)
This commit is contained in:
@@ -102,6 +102,12 @@ android {
|
||||
lint {
|
||||
disable.add("MissingTranslation")
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
testOptions {
|
||||
// Required for Robolectric
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.authenticator.ui.platform.feature.settings
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -102,6 +103,24 @@ fun SettingsScreen(
|
||||
SettingsEvent.NavigateToPrivacyPolicy -> {
|
||||
intentManager.launchUri("https://bitwarden.com/privacy".toUri())
|
||||
}
|
||||
|
||||
SettingsEvent.NavigateToBitwardenApp -> {
|
||||
|
||||
intentManager.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"bitwarden://settings/account_security".toUri(),
|
||||
).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
SettingsEvent.NavigateToBitwardenPlayStoreListing -> {
|
||||
intentManager.launchUri(
|
||||
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +169,12 @@ fun SettingsScreen(
|
||||
viewModel.trySendAction(SettingsAction.DataClick.BackupClick)
|
||||
}
|
||||
},
|
||||
onSyncWithBitwardenClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick)
|
||||
}
|
||||
},
|
||||
shouldShowSyncWithBitwardenApp = state.showSyncWithBitwarden,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AppearanceSettings(
|
||||
@@ -237,11 +262,14 @@ private fun SecuritySettings(
|
||||
//region Data settings
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
private fun VaultSettings(
|
||||
modifier: Modifier = Modifier,
|
||||
onExportClick: () -> Unit,
|
||||
onImportClick: () -> Unit,
|
||||
onBackupClick: () -> Unit,
|
||||
onSyncWithBitwardenClick: () -> Unit,
|
||||
shouldShowSyncWithBitwardenApp: Boolean,
|
||||
) {
|
||||
BitwardenListHeaderText(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
@@ -294,6 +322,25 @@ private fun VaultSettings(
|
||||
dialogConfirmButtonText = stringResource(R.string.learn_more),
|
||||
dialogDismissButtonText = stringResource(R.string.ok),
|
||||
)
|
||||
if (shouldShowSyncWithBitwardenApp) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = R.string.sync_with_bitwarden_app),
|
||||
onClick = onSyncWithBitwardenClick,
|
||||
modifier = modifier,
|
||||
withDivider = true,
|
||||
content = {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.mirrorIfRtl()
|
||||
.size(24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_external_link),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -7,7 +7,9 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.data.platform.manager.FeatureFlagManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.model.BiometricsKeyResult
|
||||
import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
|
||||
@@ -16,6 +18,8 @@ import com.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.concat
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
|
||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -34,16 +38,21 @@ private const val KEY_STATE = "state"
|
||||
class SettingsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
clock: Clock,
|
||||
private val authenticatorBridgeManager: AuthenticatorBridgeManager,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<SettingsState, SettingsEvent, SettingsAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: createInitialState(
|
||||
clock,
|
||||
settingsRepository.appLanguage,
|
||||
settingsRepository.appTheme,
|
||||
settingsRepository.isUnlockWithBiometricsEnabled,
|
||||
settingsRepository.isCrashLoggingEnabled,
|
||||
clock = clock,
|
||||
appLanguage = settingsRepository.appLanguage,
|
||||
appTheme = settingsRepository.appTheme,
|
||||
unlockWithBiometricsEnabled = settingsRepository.isUnlockWithBiometricsEnabled,
|
||||
isSubmitCrashLogsEnabled = settingsRepository.isCrashLoggingEnabled,
|
||||
isSyncWithBitwardenFeatureEnabled =
|
||||
featureFlagManager.getFeatureFlag(LocalFeatureFlag.PasswordManagerSync),
|
||||
accountSyncState = authenticatorBridgeManager.accountSyncStateFlow.value,
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: SettingsAction) {
|
||||
@@ -131,6 +140,17 @@ class SettingsViewModel @Inject constructor(
|
||||
SettingsAction.DataClick.ExportClick -> handleExportClick()
|
||||
SettingsAction.DataClick.ImportClick -> handleImportClick()
|
||||
SettingsAction.DataClick.BackupClick -> handleBackupClick()
|
||||
SettingsAction.DataClick.SyncWithBitwardenClick -> handleSyncWithBitwardenClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSyncWithBitwardenClick() {
|
||||
when (authenticatorBridgeManager.accountSyncStateFlow.value) {
|
||||
AccountSyncState.AppNotInstalled -> {
|
||||
sendEvent(SettingsEvent.NavigateToBitwardenPlayStoreListing)
|
||||
}
|
||||
|
||||
else -> sendEvent(SettingsEvent.NavigateToBitwardenApp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,15 +248,21 @@ class SettingsViewModel @Inject constructor(
|
||||
|
||||
@Suppress("UndocumentedPublicClass")
|
||||
companion object {
|
||||
@Suppress("LongParameterList")
|
||||
private fun createInitialState(
|
||||
clock: Clock,
|
||||
appLanguage: AppLanguage,
|
||||
appTheme: AppTheme,
|
||||
unlockWithBiometricsEnabled: Boolean,
|
||||
isSubmitCrashLogsEnabled: Boolean,
|
||||
accountSyncState: AccountSyncState,
|
||||
isSyncWithBitwardenFeatureEnabled: Boolean,
|
||||
): SettingsState {
|
||||
val currentYear = Year.now(clock)
|
||||
val copyrightInfo = "© Bitwarden Inc. 2015-$currentYear".asText()
|
||||
// Show sync with Bitwarden row if feature is enabled and the OS is supported:
|
||||
val shouldShowSyncWithBitwarden = isSyncWithBitwardenFeatureEnabled &&
|
||||
accountSyncState != AccountSyncState.OsVersionNotSupported
|
||||
return SettingsState(
|
||||
appearance = SettingsState.Appearance(
|
||||
language = appLanguage,
|
||||
@@ -249,6 +275,7 @@ class SettingsViewModel @Inject constructor(
|
||||
.asText()
|
||||
.concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()),
|
||||
copyrightInfo = copyrightInfo,
|
||||
showSyncWithBitwarden = shouldShowSyncWithBitwarden,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -262,6 +289,7 @@ data class SettingsState(
|
||||
val appearance: Appearance,
|
||||
val isUnlockWithBiometricsEnabled: Boolean,
|
||||
val isSubmitCrashLogsEnabled: Boolean,
|
||||
val showSyncWithBitwarden: Boolean,
|
||||
val dialog: Dialog?,
|
||||
val version: Text,
|
||||
val copyrightInfo: Text,
|
||||
@@ -325,6 +353,16 @@ sealed class SettingsEvent {
|
||||
* Navigate to the privacy policy web page.
|
||||
*/
|
||||
data object NavigateToPrivacyPolicy : SettingsEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the Bitwarden account settings.
|
||||
*/
|
||||
data object NavigateToBitwardenApp : SettingsEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the Bitwarden Play Store listing.
|
||||
*/
|
||||
data object NavigateToBitwardenPlayStoreListing : SettingsEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,6 +414,11 @@ sealed class SettingsAction(
|
||||
* Indicates the user click backup.
|
||||
*/
|
||||
data object BackupClick : DataClick()
|
||||
|
||||
/**
|
||||
* Indicates the user clicked sync with Bitwarden.
|
||||
*/
|
||||
data object SyncWithBitwardenClick : DataClick()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,4 +124,5 @@
|
||||
<string name="download_bitwarden_card_title">Download the Bitwarden app</string>
|
||||
<string name="download_bitwarden_card_message">Store all of your logins and sync verification codes directly with the Authenticator app.</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="sync_with_bitwarden_app">Sync with Bitwarden app</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.bitwarden.authenticator.ui.platform.base
|
||||
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme
|
||||
import org.junit.Rule
|
||||
|
||||
/**
|
||||
* A base class that can be used for performing Compose-layer testing using Robolectric, Compose
|
||||
* Testing, and JUnit 4.
|
||||
*/
|
||||
abstract class BaseComposeTest : BaseRobolectricTest() {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
/**
|
||||
* instance of [OnBackPressedDispatcher] made available if testing using
|
||||
*
|
||||
* [setContentWithBackDispatcher] or [runTestWithTheme]
|
||||
*/
|
||||
var backDispatcher: OnBackPressedDispatcher? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Helper for testing a basic Composable function that only requires a Composable environment
|
||||
* with the [BitwardenTheme].
|
||||
*/
|
||||
protected fun runTestWithTheme(
|
||||
theme: AppTheme,
|
||||
test: @Composable () -> Unit,
|
||||
) {
|
||||
composeTestRule.setContent {
|
||||
AuthenticatorTheme(
|
||||
theme = theme,
|
||||
) {
|
||||
backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for testing a basic Composable function that provides access to a
|
||||
* [OnBackPressedDispatcher].
|
||||
*
|
||||
* Use if the [Composable] function being tested uses a [BackHandler]
|
||||
*/
|
||||
protected fun setContentWithBackDispatcher(test: @Composable () -> Unit) {
|
||||
composeTestRule.setContent {
|
||||
backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bitwarden.authenticator.ui.platform.base
|
||||
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.shadows.ShadowLog
|
||||
|
||||
/**
|
||||
* A base class that can be used for performing tests that use Robolectric and JUnit 4.
|
||||
*/
|
||||
@Config(
|
||||
application = HiltTestApplication::class,
|
||||
sdk = [Config.NEWEST_SDK],
|
||||
)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
abstract class BaseRobolectricTest {
|
||||
init {
|
||||
ShadowLog.stream = System.out
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.bitwarden.authenticator.ui.platform.feature.settings
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.core.net.toUri
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.authenticator.ui.platform.base.BaseComposeTest
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.concat
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.authenticator.ui.platform.manager.biometrics.BiometricsManager
|
||||
import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import org.junit.Before
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertEquals
|
||||
|
||||
class SettingsScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateToTutorialCalled = false
|
||||
private var onNaviateToExportCalled = false
|
||||
private var onNavigateToImportCalled = false
|
||||
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<SettingsEvent>()
|
||||
|
||||
val viewModel: SettingsViewModel = mockk {
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { trySendAction(any()) } just runs
|
||||
}
|
||||
|
||||
private val biometricsManager: BiometricsManager = mockk {
|
||||
every { isBiometricsSupported } returns true
|
||||
}
|
||||
private val intentManager: IntentManager = mockk()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
SettingsScreen(
|
||||
viewModel = viewModel,
|
||||
biometricsManager = biometricsManager,
|
||||
intentManager = intentManager,
|
||||
onNavigateToTutorial = { onNavigateToTutorialCalled = true },
|
||||
onNavigateToExport = { onNaviateToExportCalled = true },
|
||||
onNavigateToImport = { onNavigateToImportCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Sync with Bitwarden row should be hidden when showSyncWithBitwarden is false`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
showSyncWithBitwarden = false,
|
||||
)
|
||||
composeTestRule.onNodeWithText("Sync with Bitwarden app").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Sync with Bitwarden row click should send SyncWithBitwardenClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Sync with Bitwarden app")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToBitwardenApp receive should launch bitwarden account security deep link`() {
|
||||
every { intentManager.startActivity(any()) } just runs
|
||||
val intentSlot = slot<Intent>()
|
||||
val expectedIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"bitwarden://settings/account_security".toUri(),
|
||||
).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
mutableEventFlow.tryEmit(SettingsEvent.NavigateToBitwardenApp)
|
||||
verify { intentManager.startActivity(capture(intentSlot)) }
|
||||
assertEquals(
|
||||
expectedIntent.data,
|
||||
intentSlot.captured.data,
|
||||
)
|
||||
assertEquals(
|
||||
expectedIntent.flags,
|
||||
intentSlot.captured.flags,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToBitwardenPlayStoreListing receive launch Bitwarden Play Store URI`() {
|
||||
every { intentManager.launchUri(any()) } just runs
|
||||
mutableEventFlow.tryEmit(SettingsEvent.NavigateToBitwardenPlayStoreListing)
|
||||
verify {
|
||||
intentManager.launchUri(
|
||||
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val APP_LANGUAGE = AppLanguage.ENGLISH
|
||||
private val APP_THEME = AppTheme.DEFAULT
|
||||
private val DEFAULT_STATE = SettingsState(
|
||||
appearance = SettingsState.Appearance(
|
||||
APP_LANGUAGE,
|
||||
APP_THEME,
|
||||
),
|
||||
isSubmitCrashLogsEnabled = true,
|
||||
isUnlockWithBiometricsEnabled = true,
|
||||
showSyncWithBitwarden = true,
|
||||
dialog = null,
|
||||
version = R.string.version.asText()
|
||||
.concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()),
|
||||
copyrightInfo = "© Bitwarden Inc. 2015-2024".asText(),
|
||||
)
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.bitwarden.authenticator.ui.platform.feature.settings
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.data.platform.manager.FeatureFlagManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.concat
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
|
||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class SettingsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val authenticatorBridgeManager: AuthenticatorBridgeManager = mockk {
|
||||
every { accountSyncStateFlow } returns MutableStateFlow(AccountSyncState.Loading)
|
||||
}
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { appLanguage } returns APP_LANGUAGE
|
||||
every { appTheme } returns APP_THEME
|
||||
every { isUnlockWithBiometricsEnabled } returns true
|
||||
every { isCrashLoggingEnabled } returns true
|
||||
}
|
||||
private val clipboardManager: BitwardenClipboardManager = mockk()
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every { getFeatureFlag(LocalFeatureFlag.PasswordManagerSync) } returns true
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `initialState should be correct when saved state is null and password manager feature flag is off`() {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(LocalFeatureFlag.PasswordManagerSync)
|
||||
} returns false
|
||||
val viewModel = createViewModel(savedState = null)
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
showSyncWithBitwarden = false,
|
||||
)
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `initialState should be correct when saved state is null and password manager feature flag is on but OS version is too low`() {
|
||||
every {
|
||||
authenticatorBridgeManager.accountSyncStateFlow
|
||||
} returns MutableStateFlow(AccountSyncState.OsVersionNotSupported)
|
||||
val viewModel = createViewModel(savedState = null)
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
showSyncWithBitwarden = false,
|
||||
)
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `initialState should be correct when saved state is null and password manager feature flag is on and OS version is supported`() {
|
||||
every {
|
||||
authenticatorBridgeManager.accountSyncStateFlow
|
||||
} returns MutableStateFlow(AccountSyncState.Loading)
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(LocalFeatureFlag.PasswordManagerSync)
|
||||
} returns true
|
||||
val viewModel = createViewModel(savedState = null)
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
showSyncWithBitwarden = true,
|
||||
)
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on SyncWithBitwardenClick receive with AccountSyncState AppNotInstalled should emit NavigateToBitwardenPlayStoreListing`() =
|
||||
runTest {
|
||||
every {
|
||||
authenticatorBridgeManager.accountSyncStateFlow
|
||||
} returns MutableStateFlow(AccountSyncState.AppNotInstalled)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick)
|
||||
assertEquals(
|
||||
SettingsEvent.NavigateToBitwardenPlayStoreListing,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on SyncWithBitwardenClick receive with AccountSyncState not AppNotInstalled should emit NavigateToBitwardenApp`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick)
|
||||
assertEquals(
|
||||
SettingsEvent.NavigateToBitwardenApp,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
savedState: SettingsState? = DEFAULT_STATE,
|
||||
) = SettingsViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { this["state"] = savedState },
|
||||
clock = CLOCK,
|
||||
authenticatorBridgeManager = authenticatorBridgeManager,
|
||||
settingsRepository = settingsRepository,
|
||||
clipboardManager = clipboardManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
||||
private val APP_LANGUAGE = AppLanguage.ENGLISH
|
||||
private val APP_THEME = AppTheme.DEFAULT
|
||||
private val CLOCK = Clock.fixed(
|
||||
Instant.parse("2024-10-12T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
private val DEFAULT_STATE = SettingsState(
|
||||
appearance = SettingsState.Appearance(
|
||||
APP_LANGUAGE,
|
||||
APP_THEME,
|
||||
),
|
||||
isSubmitCrashLogsEnabled = true,
|
||||
isUnlockWithBiometricsEnabled = true,
|
||||
showSyncWithBitwarden = true,
|
||||
dialog = null,
|
||||
version = R.string.version.asText()
|
||||
.concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()),
|
||||
copyrightInfo = "© Bitwarden Inc. 2015-2024".asText(),
|
||||
)
|
||||
Reference in New Issue
Block a user