diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt index 1608c36f8..f228092a4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt @@ -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 diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt index b048c7856..ab2f78c42 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt @@ -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( @@ -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. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt index 55eec449e..62cb42f13 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt @@ -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 diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt index 946d0e811..0ca727efb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt @@ -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( 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. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt index d969e4525..779934119 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt @@ -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) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt index 29a32a0ee..506692840 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt @@ -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. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountSwitcher.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountSwitcher.kt index 2f9fe79d2..f69510978 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountSwitcher.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountSwitcher.kt @@ -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 = { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt index 3d5c2c5f3..f25523268 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt @@ -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, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index 6d452c3f6..431383b56 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -38,7 +38,7 @@ import javax.inject.Inject @HiltViewModel class VaultViewModel @Inject constructor( private val authRepository: AuthRepository, - vaultRepository: VaultRepository, + private val vaultRepository: VaultRepository, ) : BaseViewModel( 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. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt index dea2591b1..314e03a2d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt @@ -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() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt index dbcfda3dc..affe0004c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt @@ -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 { + 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 { + 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, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt index 20f18da3e..ab1c465b1 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt @@ -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() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt index fa87eec04..e4e423aa6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt @@ -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 { + 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 { + 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 { + 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, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt index d27a18484..644ee6963 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt @@ -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 diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModelTest.kt index 47b5c354b..db492600e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModelTest.kt @@ -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() + 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 { + 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 { + 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() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/util/BitwardenAccountSwitcherTestHelpers.kt b/app/src/test/java/com/x8bit/bitwarden/ui/util/BitwardenAccountSwitcherTestHelpers.kt index 03bd9462c..b6e715dd9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/util/BitwardenAccountSwitcherTestHelpers.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/util/BitwardenAccountSwitcherTestHelpers.kt @@ -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. * diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt b/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt index 8137565d3..00420c6cd 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt @@ -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. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt index a3d300862..cbd95212b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt @@ -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) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index 462df70a7..3611d6b7e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -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 { + 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 { + 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`() =