mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
BIT-1207: Fully implement account switcher lock and logout (#362)
This commit is contained in:
parent
b7578b8f96
commit
1adf58aca8
19 changed files with 588 additions and 43 deletions
|
@ -49,7 +49,6 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
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.showNotYetImplementedToast
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
|
@ -187,13 +186,11 @@ fun LandingScreen(
|
|||
onSwitchAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
|
||||
},
|
||||
onLockAccountClick = {
|
||||
// TODO: Implement lock functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLockAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.LockAccountClick(it)) }
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
// TODO: Implement logout functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLogoutAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.LogoutAccountClick(it)) }
|
||||
},
|
||||
onAddAccountClick = {
|
||||
// Not available
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -25,9 +26,11 @@ private const val KEY_STATE = "state"
|
|||
/**
|
||||
* Manages application state for the initial landing screen.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class LandingViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<LandingState, LandingEvent, LandingAction>(
|
||||
|
@ -75,6 +78,8 @@ class LandingViewModel @Inject constructor(
|
|||
|
||||
override fun handleAction(action: LandingAction) {
|
||||
when (action) {
|
||||
is LandingAction.LockAccountClick -> handleLockAccountClicked(action)
|
||||
is LandingAction.LogoutAccountClick -> handleLogoutAccountClicked(action)
|
||||
is LandingAction.SwitchAccountClick -> handleSwitchAccountClicked(action)
|
||||
is LandingAction.ConfirmSwitchToMatchingAccountClick -> {
|
||||
handleConfirmSwitchToMatchingAccountClicked(action)
|
||||
|
@ -92,6 +97,14 @@ class LandingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLockAccountClicked(action: LandingAction.LockAccountClick) {
|
||||
vaultRepository.lockVaultIfNecessary(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleLogoutAccountClicked(action: LandingAction.LogoutAccountClick) {
|
||||
authRepository.logout(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClicked(action: LandingAction.SwitchAccountClick) {
|
||||
authRepository.switchAccount(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
@ -247,6 +260,23 @@ sealed class LandingEvent {
|
|||
* Models actions for the landing screen.
|
||||
*/
|
||||
sealed class LandingAction {
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to lock
|
||||
* the associated account's vault.
|
||||
*/
|
||||
data class LockAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : LandingAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to log out
|
||||
* of that account.
|
||||
*/
|
||||
data class LogoutAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : LandingAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to switch
|
||||
* to it.
|
||||
|
|
|
@ -39,7 +39,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
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.showNotYetImplementedToast
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
|
||||
|
@ -147,13 +146,11 @@ fun LoginScreen(
|
|||
onSwitchAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.SwitchAccountClick(it)) }
|
||||
},
|
||||
onLockAccountClick = {
|
||||
// TODO: Implement lock functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLockAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.LockAccountClick(it)) }
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
// TODO: Implement logout functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLogoutAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.LogoutAccountClick(it)) }
|
||||
},
|
||||
onAddAccountClick = {
|
||||
// Not available
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
|
@ -35,6 +36,7 @@ private const val KEY_STATE = "state"
|
|||
class LoginViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
|
@ -68,6 +70,8 @@ class LoginViewModel @Inject constructor(
|
|||
|
||||
override fun handleAction(action: LoginAction) {
|
||||
when (action) {
|
||||
is LoginAction.LockAccountClick -> handleLockAccountClicked(action)
|
||||
is LoginAction.LogoutAccountClick -> handleLogoutAccountClicked(action)
|
||||
is LoginAction.SwitchAccountClick -> handleSwitchAccountClicked(action)
|
||||
is LoginAction.CloseButtonClick -> handleCloseButtonClicked()
|
||||
LoginAction.LoginButtonClick -> handleLoginButtonClicked()
|
||||
|
@ -86,6 +90,14 @@ class LoginViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLockAccountClicked(action: LoginAction.LockAccountClick) {
|
||||
vaultRepository.lockVaultIfNecessary(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleLogoutAccountClicked(action: LoginAction.LogoutAccountClick) {
|
||||
authRepository.logout(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClicked(action: LoginAction.SwitchAccountClick) {
|
||||
authRepository.switchAccount(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
@ -234,6 +246,23 @@ sealed class LoginEvent {
|
|||
* Models actions for the login screen.
|
||||
*/
|
||||
sealed class LoginAction {
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to lock
|
||||
* the associated account's vault.
|
||||
*/
|
||||
data class LockAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : LoginAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to log out
|
||||
* of that account.
|
||||
*/
|
||||
data class LogoutAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : LoginAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to switch
|
||||
* to it.
|
||||
|
|
|
@ -32,7 +32,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
|
@ -197,13 +196,11 @@ fun VaultUnlockScreen(
|
|||
onSwitchAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultUnlockAction.SwitchAccountClick(it)) }
|
||||
},
|
||||
onLockAccountClick = {
|
||||
// TODO: Implement lock functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLockAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultUnlockAction.LockAccountClick(it)) }
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
// TODO: Implement logout functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
onLogoutAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultUnlockAction.LogoutAccountClick(it)) }
|
||||
},
|
||||
onAddAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultUnlockAction.AddAccountClick) }
|
||||
|
|
|
@ -31,6 +31,7 @@ private const val KEY_STATE = "state"
|
|||
/**
|
||||
* Manages application state for the initial vault unlock screen.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class VaultUnlockViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
|
@ -79,6 +80,8 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
VaultUnlockAction.DismissDialog -> handleDismissDialog()
|
||||
VaultUnlockAction.ConfirmLogoutClick -> handleConfirmLogoutClick()
|
||||
is VaultUnlockAction.PasswordInputChanged -> handlePasswordInputChanged(action)
|
||||
is VaultUnlockAction.LockAccountClick -> handleLockAccountClick(action)
|
||||
is VaultUnlockAction.LogoutAccountClick -> handleLogoutAccountClick(action)
|
||||
is VaultUnlockAction.SwitchAccountClick -> handleSwitchAccountClick(action)
|
||||
VaultUnlockAction.UnlockClick -> handleUnlockClick()
|
||||
is VaultUnlockAction.Internal.ReceiveVaultUnlockResult -> {
|
||||
|
@ -109,6 +112,14 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLockAccountClick(action: VaultUnlockAction.LockAccountClick) {
|
||||
vaultRepo.lockVaultIfNecessary(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleLogoutAccountClick(action: VaultUnlockAction.LogoutAccountClick) {
|
||||
authRepository.logout(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClick(action: VaultUnlockAction.SwitchAccountClick) {
|
||||
authRepository.switchAccount(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
@ -253,6 +264,22 @@ sealed class VaultUnlockAction {
|
|||
val passwordInput: String,
|
||||
) : VaultUnlockAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to lock
|
||||
* the associated account's vault.
|
||||
*/
|
||||
data class LockAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : VaultUnlockAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to log out
|
||||
* of that account.
|
||||
*/
|
||||
data class LogoutAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : VaultUnlockAction()
|
||||
|
||||
/**
|
||||
* The user has clicked the an account to switch too.
|
||||
*/
|
||||
|
|
|
@ -108,8 +108,14 @@ fun BitwardenAccountSwitcher(
|
|||
LockOrLogoutDialog(
|
||||
accountSummary = requireNotNull(lockOrLogoutAccount),
|
||||
onDismissRequest = { lockOrLogoutAccount = null },
|
||||
onLockAccountClick = onLockAccountClick,
|
||||
onLogoutAccountClick = onLogoutAccountClick,
|
||||
onLockAccountClick = {
|
||||
onLockAccountClick(it)
|
||||
lockOrLogoutAccount = null
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
onLogoutAccountClick(it)
|
||||
lockOrLogoutAccount = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -306,12 +312,14 @@ private fun LockOrLogoutDialog(
|
|||
title = "${accountSummary.email}\n${accountSummary.environmentLabel}",
|
||||
onDismissRequest = onDismissRequest,
|
||||
selectionItems = {
|
||||
BitwardenBasicDialogRow(
|
||||
text = stringResource(id = R.string.lock),
|
||||
onClick = {
|
||||
onLockAccountClick(accountSummary)
|
||||
},
|
||||
)
|
||||
if (accountSummary.isVaultUnlocked) {
|
||||
BitwardenBasicDialogRow(
|
||||
text = stringResource(id = R.string.lock),
|
||||
onClick = {
|
||||
onLockAccountClick(accountSummary)
|
||||
},
|
||||
)
|
||||
}
|
||||
BitwardenBasicDialogRow(
|
||||
text = stringResource(id = R.string.log_out),
|
||||
onClick = {
|
||||
|
|
|
@ -28,7 +28,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
||||
|
@ -87,6 +86,12 @@ fun VaultScreen(
|
|||
searchIconClickAction = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.SearchIconClick) }
|
||||
},
|
||||
accountLockClickAction = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.LockAccountClick(it)) }
|
||||
},
|
||||
accountLogoutClickAction = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.LogoutAccountClick(it)) }
|
||||
},
|
||||
accountSwitchClickAction = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.SwitchAccountClick(it)) }
|
||||
},
|
||||
|
@ -128,6 +133,8 @@ private fun VaultScreenScaffold(
|
|||
state: VaultState,
|
||||
addItemClickAction: () -> Unit,
|
||||
searchIconClickAction: () -> Unit,
|
||||
accountLockClickAction: (AccountSummary) -> Unit,
|
||||
accountLogoutClickAction: (AccountSummary) -> Unit,
|
||||
accountSwitchClickAction: (AccountSummary) -> Unit,
|
||||
addAccountClickAction: () -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
|
@ -219,14 +226,8 @@ private fun VaultScreenScaffold(
|
|||
isVisible = accountMenuVisible,
|
||||
accountSummaries = state.accountSummaries.toImmutableList(),
|
||||
onSwitchAccountClick = accountSwitchClickAction,
|
||||
onLockAccountClick = {
|
||||
// TODO: Implement lock functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
// TODO: Implement logout functionality (BIT-1207)
|
||||
showNotYetImplementedToast(context)
|
||||
},
|
||||
onLockAccountClick = accountLockClickAction,
|
||||
onLogoutAccountClick = accountLogoutClickAction,
|
||||
onAddAccountClick = addAccountClickAction,
|
||||
onDismissRequest = { updateAccountMenuVisibility(false) },
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
|
|
|
@ -38,7 +38,7 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class VaultViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||
initialState = run {
|
||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||
|
@ -87,6 +87,8 @@ class VaultViewModel @Inject constructor(
|
|||
is VaultAction.IdentityGroupClick -> handleIdentityClick()
|
||||
is VaultAction.LoginGroupClick -> handleLoginClick()
|
||||
is VaultAction.SearchIconClick -> handleSearchIconClick()
|
||||
is VaultAction.LockAccountClick -> handleLockAccountClick(action)
|
||||
is VaultAction.LogoutAccountClick -> handleLogoutAccountClick(action)
|
||||
is VaultAction.SwitchAccountClick -> handleSwitchAccountClick(action)
|
||||
is VaultAction.AddAccountClick -> handleAddAccountClick()
|
||||
is VaultAction.SecureNoteGroupClick -> handleSecureNoteClick()
|
||||
|
@ -128,6 +130,14 @@ class VaultViewModel @Inject constructor(
|
|||
sendEvent(VaultEvent.NavigateToVaultSearchScreen)
|
||||
}
|
||||
|
||||
private fun handleLockAccountClick(action: VaultAction.LockAccountClick) {
|
||||
vaultRepository.lockVaultIfNecessary(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleLogoutAccountClick(action: VaultAction.LogoutAccountClick) {
|
||||
authRepository.logout(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClick(action: VaultAction.SwitchAccountClick) {
|
||||
val isSwitchingAccounts =
|
||||
when (authRepository.switchAccount(userId = action.accountSummary.userId)) {
|
||||
|
@ -461,6 +471,22 @@ sealed class VaultAction {
|
|||
*/
|
||||
data object SearchIconClick : VaultAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to lock
|
||||
* the associated account's vault.
|
||||
*/
|
||||
data class LockAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : VaultAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked on the given [accountSummary] information in order to log out
|
||||
* of that account.
|
||||
*/
|
||||
data class LogoutAccountClick(
|
||||
val accountSummary: AccountSummary,
|
||||
) : VaultAction()
|
||||
|
||||
/**
|
||||
* User clicked an account in the account switcher.
|
||||
*/
|
||||
|
|
|
@ -22,10 +22,15 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
|||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
||||
import com.x8bit.bitwarden.ui.util.performLockAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLogoutAccountClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -114,6 +119,60 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `account long click in the account switcher should show the lock-or-logout dialog and close the switcher`() {
|
||||
// Show the account switcher
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule.performAccountLongClick(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
|
||||
composeTestRule.assertLockOrLogoutDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `lock button click in the lock-or-logout dialog should send LockAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLockAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(LandingAction.LockAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(LandingAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `continue button should be enabled or disabled according to the state`() {
|
||||
composeTestRule.onNodeWithText("Continue").assertIsEnabled()
|
||||
|
|
|
@ -7,13 +7,16 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummary
|
||||
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
|
||||
|
@ -22,7 +25,12 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
class LandingViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val authRepository: AuthRepository = mockk(relaxed = true)
|
||||
private val authRepository: AuthRepository = mockk(relaxed = true) {
|
||||
every { logout(any()) } just runs
|
||||
}
|
||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||
every { lockVaultIfNecessary(any()) } just runs
|
||||
}
|
||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||
|
||||
@Test
|
||||
|
@ -88,6 +96,32 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LockAccountClick should call lockVaultIfNecessary for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(LandingAction.LockAccountClick(accountSummary))
|
||||
|
||||
verify { vaultRepository.lockVaultIfNecessary(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LogoutAccountClick should call logout for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(LandingAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SwitchAccountClick should call switchAccount for the given account`() {
|
||||
val matchingAccountUserId = "matchingAccountUserId"
|
||||
|
@ -314,6 +348,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
every { rememberedEmailAddress } returns rememberedEmail
|
||||
every { userStateFlow } returns MutableStateFlow(userState)
|
||||
},
|
||||
vaultRepository = vaultRepository,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
|
|
@ -18,10 +18,15 @@ import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
|||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
||||
import com.x8bit.bitwarden.ui.util.performLockAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLogoutAccountClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -103,6 +108,60 @@ class LoginScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `account long click in the account switcher should show the lock-or-logout dialog and close the switcher`() {
|
||||
// Show the account switcher
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule.performAccountLongClick(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
|
||||
composeTestRule.assertLockOrLogoutDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `lock button click in the lock-or-logout dialog should send LockAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLockAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(LoginAction.LockAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(LoginAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close button click should send CloseButtonClick action`() {
|
||||
composeTestRule.onNodeWithContentDescription("Close").performClick()
|
||||
|
|
|
@ -12,17 +12,22 @@ import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -43,6 +48,10 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
private val authRepository: AuthRepository = mockk(relaxed = true) {
|
||||
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { logout(any()) } just runs
|
||||
}
|
||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||
every { lockVaultIfNecessary(any()) } just runs
|
||||
}
|
||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||
|
||||
|
@ -140,6 +149,45 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LockAccountClick should call lockVaultIfNecessary for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(LoginAction.LockAccountClick(accountSummary))
|
||||
|
||||
verify { vaultRepository.lockVaultIfNecessary(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LogoutAccountClick should call logout for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(LoginAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SwitchAccountClick should call switchAccount for the given account`() {
|
||||
val matchingAccountUserId = "matchingAccountUserId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns matchingAccountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(LoginAction.SwitchAccountClick(accountSummary))
|
||||
|
||||
verify { authRepository.switchAccount(userId = matchingAccountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CloseButtonClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -316,6 +364,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
LoginViewModel(
|
||||
authRepository = authRepository,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
||||
|
|
|
@ -15,11 +15,16 @@ import androidx.compose.ui.test.performScrollTo
|
|||
import androidx.compose.ui.test.performTextInput
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.performAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
||||
import com.x8bit.bitwarden.ui.util.performAddAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLockAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLogoutAccountClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -92,6 +97,52 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `account long click in the account switcher should show the lock-or-logout dialog and close the switcher`() {
|
||||
// Show the account switcher
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule.performAccountLongClick(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
|
||||
composeTestRule.assertLockOrLogoutDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `lock button click in the lock-or-logout dialog should send LockAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLockAccountClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultUnlockAction.LockAccountClick(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultUnlockAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout click in the overflow menu should show the logout confirmation dialog`() {
|
||||
// Confirm neither the popup nor the dialog are showing
|
||||
|
|
|
@ -36,9 +36,12 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
every { specialCircumstance } returns null
|
||||
every { specialCircumstance = any() } just runs
|
||||
every { logout() } just runs
|
||||
every { logout(any()) } just runs
|
||||
every { switchAccount(any()) } returns SwitchAccountResult.AccountSwitched
|
||||
}
|
||||
private val vaultRepository = mockk<VaultRepository>()
|
||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||
every { lockVaultIfNecessary(any()) } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when not set`() {
|
||||
|
@ -167,6 +170,32 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LockAccountClick should call lockVaultIfNecessary for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultUnlockAction.LockAccountClick(accountSummary))
|
||||
|
||||
verify { vaultRepository.lockVaultIfNecessary(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LogoutAccountClick should call logout for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultUnlockAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SwitchAccountClick should switch to the given account`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package com.x8bit.bitwarden.ui.util
|
||||
|
||||
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.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
|
||||
private const val ACCOUNT = "Account"
|
||||
|
@ -43,6 +49,35 @@ fun ComposeContentTestRule.assertSwitcherIsNotDisplayed(
|
|||
this.onNodeWithText(ADD_ACCOUNT).assertDoesNotExist()
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the "lock or logout" dialog is currently displayed with information from the given
|
||||
* [accountSummary].
|
||||
*/
|
||||
fun ComposeContentTestRule.assertLockOrLogoutDialogIsDisplayed(
|
||||
accountSummary: AccountSummary,
|
||||
) {
|
||||
this.waitForIdle()
|
||||
this
|
||||
.onNode(isDialog())
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText("Lock")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText(accountSummary.email, substring = true)
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText(accountSummary.environmentLabel, substring = true)
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the given [accountSummary] in the account switcher.
|
||||
*/
|
||||
|
@ -52,6 +87,37 @@ fun ComposeContentTestRule.performAccountClick(
|
|||
this.onNodeWithText(accountSummary.email).performClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Long clicks on the given [accountSummary] in the account switcher.
|
||||
*/
|
||||
fun ComposeContentTestRule.performAccountLongClick(
|
||||
accountSummary: AccountSummary,
|
||||
) {
|
||||
this.onNodeWithText(accountSummary.email).performTouchInput {
|
||||
this.longClick()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the "Lock" button in the "lock or logout" dialog.
|
||||
*/
|
||||
fun ComposeContentTestRule.performLockAccountClick() {
|
||||
this
|
||||
.onAllNodesWithText("Lock")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the "Lock" button in the "lock or logout" dialog.
|
||||
*/
|
||||
fun ComposeContentTestRule.performLogoutAccountClick() {
|
||||
this
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the account switcher.
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.compose.ui.test.SemanticsNodeInteractionCollection
|
|||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.hasScrollToNodeAction
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
|
@ -28,6 +29,15 @@ val isProgressBar: SemanticsMatcher
|
|||
?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that no dialog currently exists.
|
||||
*/
|
||||
fun ComposeContentTestRule.assertNoDialogExists() {
|
||||
this
|
||||
.onNode(isDialog())
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that asserts that the node does not exist in the scrollable list.
|
||||
*/
|
||||
|
|
|
@ -12,11 +12,16 @@ import androidx.compose.ui.test.performScrollToNode
|
|||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.performAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
||||
import com.x8bit.bitwarden.ui.util.performAddAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLockAccountClick
|
||||
import com.x8bit.bitwarden.ui.util.performLogoutAccountClick
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
@ -109,6 +114,48 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `account long click in the account switcher should show the lock-or-logout dialog and close the switcher`() {
|
||||
// Show the account switcher
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule.performAccountLongClick(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
|
||||
composeTestRule.assertLockOrLogoutDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `lock button click in the lock-or-logout dialog should send LockAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLockAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultAction.LockAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `search icon click should send SearchIconClick action`() {
|
||||
mutableStateFlow.update { it.copy(viewState = VaultState.ViewState.NoItems) }
|
||||
|
|
|
@ -40,6 +40,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { specialCircumstance } returns null
|
||||
every { specialCircumstance = any() } just runs
|
||||
every { logout(any()) } just runs
|
||||
every { switchAccount(any()) } answers { switchAccountResult }
|
||||
}
|
||||
|
||||
|
@ -47,6 +48,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
mockk {
|
||||
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
||||
every { sync() } returns Unit
|
||||
every { lockVaultIfNecessary(any()) } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -144,6 +146,32 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LockAccountClick should call lockVaultIfNecessary for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultAction.LockAccountClick(accountSummary))
|
||||
|
||||
verify { vaultRepository.lockVaultIfNecessary(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LogoutAccountClick should call logout for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on SwitchAccountClick when result is NoChange should try to switch to the given account and set isSwitchingAccounts to false`() =
|
||||
|
|
Loading…
Reference in a new issue