BIT-363, BIT-1323: Add time interval options to session timeout menu (#529)

This commit is contained in:
Brian Yencho 2024-01-08 09:19:55 -06:00 committed by Álison Fernandes
parent 7cfdddaa81
commit e69c4eb29e
8 changed files with 428 additions and 52 deletions

View file

@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.di
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -27,4 +29,13 @@ object PlatformDiskModule {
sharedPreferences = sharedPreferences,
json = json,
)
@Provides
@Singleton
fun provideSettingsDiskSource(
sharedPreferences: SharedPreferences,
): SettingsDiskSource =
SettingsDiskSourceImpl(
sharedPreferences = sharedPreferences,
)
}

View file

@ -2,9 +2,12 @@ package com.x8bit.bitwarden.data.platform.repository.di
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.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepositoryImpl
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepositoryImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -30,4 +33,17 @@ object PlatformRepositoryModule {
authDiskSource = authDiskSource,
dispatcherManager = dispatcherManager,
)
@Provides
@Singleton
fun provideSettingsRepository(
authDiskSource: AuthDiskSource,
settingsDiskSource: SettingsDiskSource,
dispatcherManager: DispatcherManager,
): SettingsRepository =
SettingsRepositoryImpl(
authDiskSource = authDiskSource,
settingsDiskSource = settingsDiskSource,
dispatcherManager = dispatcherManager,
)
}

View file

@ -18,7 +18,9 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@ -28,6 +30,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.Text
@ -40,9 +43,11 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
import com.x8bit.bitwarden.ui.platform.util.displayLabel
/**
* Displays the account security screen.
@ -198,19 +203,13 @@ fun AccountSecurityScreen(
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout),
onClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick) }
SessionTimeoutRow(
selectedVaultTimeoutType = state.vaultTimeoutType,
onVaultTimeoutTypeSelect = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.VaultTimeoutTypeSelect(it)) }
},
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = state.sessionTimeout(),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
)
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout_action),
onClick = remember(viewModel) {
@ -286,6 +285,70 @@ fun AccountSecurityScreen(
}
}
@Suppress("LongMethod")
@Composable
private fun SessionTimeoutRow(
selectedVaultTimeoutType: VaultTimeout.Type,
onVaultTimeoutTypeSelect: (VaultTimeout.Type) -> Unit,
modifier: Modifier = Modifier,
) {
var shouldShowSelectionDialog by remember { mutableStateOf(false) }
var shouldShowNeverTimeoutConfirmationDialog by remember { mutableStateOf(false) }
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout),
onClick = { shouldShowSelectionDialog = true },
modifier = modifier,
) {
Text(
text = selectedVaultTimeoutType.displayLabel(),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
when {
shouldShowSelectionDialog -> {
val vaultTimeoutOptions = VaultTimeout.Type.entries
BitwardenSelectionDialog(
title = stringResource(id = R.string.session_timeout),
onDismissRequest = { shouldShowSelectionDialog = false },
) {
vaultTimeoutOptions.forEach { vaultTimeoutOption ->
BitwardenSelectionRow(
text = vaultTimeoutOption.displayLabel,
onClick = {
shouldShowSelectionDialog = false
val selectedType =
vaultTimeoutOptions.first { it == vaultTimeoutOption }
if (selectedType == VaultTimeout.Type.NEVER) {
shouldShowNeverTimeoutConfirmationDialog = true
} else {
onVaultTimeoutTypeSelect(selectedType)
}
},
isSelected = selectedVaultTimeoutType == vaultTimeoutOption,
)
}
}
}
shouldShowNeverTimeoutConfirmationDialog -> {
BitwardenTwoButtonDialog(
title = stringResource(id = R.string.warning),
message = stringResource(id = R.string.never_lock_warning),
confirmButtonText = stringResource(id = R.string.ok),
dismissButtonText = stringResource(id = R.string.cancel),
onConfirmClick = {
shouldShowNeverTimeoutConfirmationDialog = false
onVaultTimeoutTypeSelect(VaultTimeout.Type.NEVER)
},
onDismissClick = { shouldShowNeverTimeoutConfirmationDialog = false },
onDismissRequest = { shouldShowNeverTimeoutConfirmationDialog = false },
)
}
}
}
@Composable
private fun FingerPrintPhraseDialog(
fingerprintPhrase: Text,
@ -349,7 +412,10 @@ private fun SessionTimeoutActionDialog(
BitwardenSelectionRow(
text = option.text,
isSelected = option == selectedSessionTimeoutAction,
onClick = { onActionSelect(SessionTimeoutAction.values().first { it == option }) },
onClick = {
onActionSelect(
SessionTimeoutAction.values().first { it == option })
},
)
}
}

View file

@ -5,6 +5,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
@ -26,6 +28,7 @@ private const val KEY_STATE = "state"
class AccountSecurityViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val vaultRepository: VaultRepository,
private val settingsRepository: SettingsRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<AccountSecurityState, AccountSecurityEvent, AccountSecurityAction>(
initialState = savedStateHandle[KEY_STATE]
@ -35,7 +38,7 @@ class AccountSecurityViewModel @Inject constructor(
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = settingsRepository.vaultTimeout.type,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
),
) {
@ -58,12 +61,12 @@ class AccountSecurityViewModel @Inject constructor(
is AccountSecurityAction.LoginRequestToggle -> handleLoginRequestToggle(action)
AccountSecurityAction.LogoutClick -> handleLogoutClick()
AccountSecurityAction.PendingLoginRequestsClick -> handlePendingLoginRequestsClick()
is AccountSecurityAction.VaultTimeoutTypeSelect -> handleVaultTimeoutTypeSelect(action)
is AccountSecurityAction.SessionTimeoutActionSelect -> {
handleSessionTimeoutActionSelect(action)
}
AccountSecurityAction.SessionTimeoutActionClick -> handleSessionTimeoutActionClick()
AccountSecurityAction.SessionTimeoutClick -> handleSessionTimeoutClick()
AccountSecurityAction.TwoStepLoginClick -> handleTwoStepLoginClick()
is AccountSecurityAction.UnlockWithBiometricToggle -> {
handleUnlockWithBiometricToggled(action)
@ -119,6 +122,30 @@ class AccountSecurityViewModel @Inject constructor(
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
}
private fun handleVaultTimeoutTypeSelect(action: AccountSecurityAction.VaultTimeoutTypeSelect) {
val vaultTimeoutType = action.vaultTimeoutType
mutableStateFlow.update {
it.copy(
vaultTimeoutType = action.vaultTimeoutType,
)
}
val vaultTimeout = when (vaultTimeoutType) {
VaultTimeout.Type.IMMEDIATELY -> VaultTimeout.Immediately
VaultTimeout.Type.ONE_MINUTE -> VaultTimeout.OneMinute
VaultTimeout.Type.FIVE_MINUTES -> VaultTimeout.FiveMinutes
VaultTimeout.Type.THIRTY_MINUTES -> VaultTimeout.ThirtyMinutes
VaultTimeout.Type.ONE_HOUR -> VaultTimeout.OneHour
VaultTimeout.Type.FOUR_HOURS -> VaultTimeout.FourHours
VaultTimeout.Type.ON_APP_RESTART -> VaultTimeout.OnAppRestart
VaultTimeout.Type.NEVER -> VaultTimeout.Never
VaultTimeout.Type.CUSTOM -> VaultTimeout.Custom(vaultTimeoutInMinutes = 0)
}
settingsRepository.vaultTimeout = vaultTimeout
// TODO: Finish implementing vault timeouts (BIT-1120)
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
}
private fun handleSessionTimeoutActionSelect(
action: AccountSecurityAction.SessionTimeoutActionSelect,
) {
@ -136,11 +163,6 @@ class AccountSecurityViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = AccountSecurityDialog.SessionTimeoutAction) }
}
private fun handleSessionTimeoutClick() {
// TODO BIT-462: Implement session timeout
sendEvent(AccountSecurityEvent.ShowToast("Display session timeout dialog.".asText()))
}
private fun handleTwoStepLoginClick() {
// TODO BIT-468: Implement two-step login
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
@ -171,7 +193,7 @@ data class AccountSecurityState(
val isApproveLoginRequestsEnabled: Boolean,
val isUnlockWithBiometricsEnabled: Boolean,
val isUnlockWithPinEnabled: Boolean,
val sessionTimeout: Text,
val vaultTimeoutType: VaultTimeout.Type,
val sessionTimeoutAction: SessionTimeoutAction,
) : Parcelable
@ -295,6 +317,13 @@ sealed class AccountSecurityAction {
*/
data object PendingLoginRequestsClick : AccountSecurityAction()
/**
* User selected a [vaultTimeoutType].
*/
data class VaultTimeoutTypeSelect(
val vaultTimeoutType: VaultTimeout.Type,
) : AccountSecurityAction()
/**
* User selected a [SessionTimeoutAction].
*/
@ -307,11 +336,6 @@ sealed class AccountSecurityAction {
*/
data object SessionTimeoutActionClick : AccountSecurityAction()
/**
* User clicked session timeout.
*/
data object SessionTimeoutClick : AccountSecurityAction()
/**
* User clicked two-step login.
*/

View file

@ -0,0 +1,23 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
/**
* Provides a human-readable display label for the given [VaultTimeout.Type].
*/
val VaultTimeout.Type.displayLabel: Text
get() = when (this) {
VaultTimeout.Type.IMMEDIATELY -> R.string.immediately
VaultTimeout.Type.ONE_MINUTE -> R.string.one_minute
VaultTimeout.Type.FIVE_MINUTES -> R.string.five_minutes
VaultTimeout.Type.THIRTY_MINUTES -> R.string.thirty_minutes
VaultTimeout.Type.ONE_HOUR -> R.string.one_hour
VaultTimeout.Type.FOUR_HOURS -> R.string.four_hours
VaultTimeout.Type.ON_APP_RESTART -> R.string.on_restart
VaultTimeout.Type.NEVER -> R.string.never
VaultTimeout.Type.CUSTOM -> R.string.custom
}
.asText()

View file

@ -15,10 +15,12 @@ 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.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@ -124,29 +126,217 @@ class AccountSecurityScreenTest : BaseComposeTest() {
composeTestRule.onNodeWithText("Unlock with PIN code").assertIsOn()
}
@Test
fun `on session timeout click should send SessionTimeoutClick`() {
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
verify { viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick) }
}
@Test
fun `session timeout should be updated on or off according to state`() {
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.assertTextEquals("Session timeout", "15 Minutes")
mutableStateFlow.update { it.copy(sessionTimeout = "30 Minutes".asText()) }
.assertTextEquals("Session timeout", "30 minutes")
mutableStateFlow.update { it.copy(vaultTimeoutType = VaultTimeout.Type.FOUR_HOURS) }
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.assertTextEquals("Session timeout", "30 Minutes")
.assertTextEquals("Session timeout", "4 hours")
}
@Test
fun `on session timeout click should show a selection dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Immediately")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("1 minute")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("5 minutes")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("30 minutes")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("1 hour")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("4 hours")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("On app restart")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Custom")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on session timeout selection dialog cancel click should close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout selection non-Never timeout type click should send VaultTimeoutTypeSelect and close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("4 hours")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.FOUR_HOURS),
)
}
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout selection Never timeout type click should show a confirmation dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText(
"Setting your lock options to “Never” keeps your vault available to anyone with " +
"access to your device. If you use this option, you should ensure that you " +
"keep your device properly protected.",
)
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on session timeout Never confirmation dialog Cancel click should close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify(exactly = 0) { viewModel.trySendAction(any()) }
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout Never confirmation dialog Ok click should close the dialog and emit VaultTimeoutTypeSelect`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.NEVER),
)
}
composeTestRule.assertNoDialogExists()
}
@Test
@ -337,7 +527,7 @@ class AccountSecurityScreenTest : BaseComposeTest() {
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = VaultTimeout.Type.THIRTY_MINUTES,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
)
}

View file

@ -3,6 +3,8 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
@ -119,6 +121,30 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `on VaultTimeoutTypeSelect should update the selection and emit ShowToast()`() = runTest {
val settingsRepository = mockk<SettingsRepository>() {
every { vaultTimeout = any() } just runs
}
val viewModel = createViewModel(settingsRepository = settingsRepository)
viewModel.eventFlow.test {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.FOUR_HOURS),
)
assertEquals(
AccountSecurityEvent.ShowToast("Not yet implemented.".asText()),
awaitItem(),
)
}
assertEquals(
DEFAULT_STATE.copy(
vaultTimeoutType = VaultTimeout.Type.FOUR_HOURS,
),
viewModel.stateFlow.value,
)
verify { settingsRepository.vaultTimeout = VaultTimeout.FourHours }
}
@Test
fun `on SessionTimeoutActionSelect should update session timeout action`() = runTest {
val viewModel = createViewModel()
@ -148,18 +174,6 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `on SessionTimeoutClick should emit ShowToast`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick)
assertEquals(
AccountSecurityEvent.ShowToast("Display session timeout dialog.".asText()),
awaitItem(),
)
}
}
@Test
fun `on TwoStepLoginClick should emit NavigateToTwoStepLogin`() = runTest {
val viewModel = createViewModel()
@ -235,12 +249,14 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
private fun createViewModel(
authRepository: AuthRepository = mockk(relaxed = true),
vaultRepository: VaultRepository = mockk(relaxed = true),
settingsRepository: SettingsRepository = mockk(relaxed = true),
savedStateHandle: SavedStateHandle = SavedStateHandle().apply {
set("state", DEFAULT_STATE)
},
): AccountSecurityViewModel = AccountSecurityViewModel(
authRepository = authRepository,
vaultRepository = vaultRepository,
settingsRepository = settingsRepository,
savedStateHandle = savedStateHandle,
)
@ -251,7 +267,7 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = VaultTimeout.Type.THIRTY_MINUTES,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
)
}

View file

@ -0,0 +1,30 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.asText
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class VaultTimeoutExtensionsTest {
@Test
fun `displayLabel should return the correct value for each type`() {
mapOf(
VaultTimeout.Type.IMMEDIATELY to R.string.immediately.asText(),
VaultTimeout.Type.ONE_MINUTE to R.string.one_minute.asText(),
VaultTimeout.Type.FIVE_MINUTES to R.string.five_minutes.asText(),
VaultTimeout.Type.THIRTY_MINUTES to R.string.thirty_minutes.asText(),
VaultTimeout.Type.ONE_HOUR to R.string.one_hour.asText(),
VaultTimeout.Type.FOUR_HOURS to R.string.four_hours.asText(),
VaultTimeout.Type.ON_APP_RESTART to R.string.on_restart.asText(),
VaultTimeout.Type.NEVER to R.string.never.asText(),
VaultTimeout.Type.CUSTOM to R.string.custom.asText(),
)
.forEach { (type, label) ->
assertEquals(
label,
type.displayLabel,
)
}
}
}