mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 20:09:59 +03:00
BIT-1135: Add confirmation dialog to lock-or-logout dialog (#364)
This commit is contained in:
parent
62ab43dfd5
commit
e3a0832777
9 changed files with 194 additions and 19 deletions
|
@ -86,6 +86,7 @@ private const val MAXIMUM_ACCOUNT_LIMIT = 5
|
|||
* sync with the associated app bar.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun BitwardenAccountSwitcher(
|
||||
isVisible: Boolean,
|
||||
|
@ -104,19 +105,37 @@ fun BitwardenAccountSwitcher(
|
|||
var isVisibleActual by remember { mutableStateOf(isVisible) }
|
||||
|
||||
var lockOrLogoutAccount by remember { mutableStateOf<AccountSummary?>(null) }
|
||||
if (lockOrLogoutAccount != null && !isVisibleActual) {
|
||||
LockOrLogoutDialog(
|
||||
accountSummary = requireNotNull(lockOrLogoutAccount),
|
||||
onDismissRequest = { lockOrLogoutAccount = null },
|
||||
onLockAccountClick = {
|
||||
onLockAccountClick(it)
|
||||
lockOrLogoutAccount = null
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
onLogoutAccountClick(it)
|
||||
lockOrLogoutAccount = null
|
||||
},
|
||||
)
|
||||
var logoutConfirmationAccount by remember { mutableStateOf<AccountSummary?>(null) }
|
||||
when {
|
||||
isVisibleActual -> {
|
||||
// Can not show dialogs when the switcher itself is visible
|
||||
}
|
||||
|
||||
lockOrLogoutAccount != null -> {
|
||||
LockOrLogoutDialog(
|
||||
accountSummary = requireNotNull(lockOrLogoutAccount),
|
||||
onDismissRequest = { lockOrLogoutAccount = null },
|
||||
onLockAccountClick = {
|
||||
onLockAccountClick(it)
|
||||
lockOrLogoutAccount = null
|
||||
},
|
||||
onLogoutAccountClick = {
|
||||
lockOrLogoutAccount = null
|
||||
logoutConfirmationAccount = it
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
logoutConfirmationAccount != null -> {
|
||||
BitwardenLogoutConfirmationDialog(
|
||||
accountSummary = requireNotNull(logoutConfirmationAccount),
|
||||
onDismissRequest = { logoutConfirmationAccount = null },
|
||||
onConfirmClick = {
|
||||
onLogoutAccountClick(requireNotNull(logoutConfirmationAccount))
|
||||
logoutConfirmationAccount = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = modifier) {
|
||||
|
|
|
@ -3,21 +3,29 @@ package com.x8bit.bitwarden.ui.platform.components
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
|
||||
/**
|
||||
* A reusable dialog for confirming whether or not the user wants to log out.
|
||||
*
|
||||
* @param onDismissRequest A callback for when the dialog is requesting dismissal.
|
||||
* @param onConfirmClick A callback for when the log out confirmation button is clicked.
|
||||
* @param accountSummary Optional account information that may be used to provide additional
|
||||
* information.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenLogoutConfirmationDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirmClick: () -> Unit,
|
||||
accountSummary: AccountSummary? = null,
|
||||
) {
|
||||
val baseConfirmationMessage = stringResource(id = R.string.logout_confirmation)
|
||||
val message = accountSummary
|
||||
?.let { "$baseConfirmationMessage\n\n${it.email}\n${it.environmentLabel}" }
|
||||
?: baseConfirmationMessage
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.log_out),
|
||||
message = stringResource(id = R.string.logout_confirmation),
|
||||
message = message,
|
||||
confirmButtonText = stringResource(id = R.string.yes),
|
||||
onConfirmClick = onConfirmClick,
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
|
|
|
@ -136,6 +136,9 @@ class VaultViewModel @Inject constructor(
|
|||
|
||||
private fun handleLogoutAccountClick(action: VaultAction.LogoutAccountClick) {
|
||||
authRepository.logout(userId = action.accountSummary.userId)
|
||||
mutableStateFlow.update {
|
||||
it.copy(isSwitchingAccounts = action.accountSummary.isActive)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClick(action: VaultAction.SwitchAccountClick) {
|
||||
|
|
|
@ -23,6 +23,7 @@ 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.assertLogoutConfirmationDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
|
@ -31,6 +32,7 @@ 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 com.x8bit.bitwarden.ui.util.performLogoutAccountConfirmationClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -158,7 +160,7 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
fun `logout button click in the lock-or-logout dialog should show the logout confirmation dialog and hide the lock-or-logout dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
|
@ -169,6 +171,25 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.assertLogoutConfirmationDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the logout confirmation dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the logout confirmation dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.performLogoutAccountConfirmationClick()
|
||||
|
||||
verify { viewModel.trySendAction(LandingAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ 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.assertLogoutConfirmationDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
|
@ -27,6 +28,7 @@ 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 com.x8bit.bitwarden.ui.util.performLogoutAccountConfirmationClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -147,7 +149,7 @@ class LoginScreenTest : BaseComposeTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
fun `logout button click in the lock-or-logout dialog should show the logout confirmation dialog and hide the lock-or-logout dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
|
@ -158,6 +160,25 @@ class LoginScreenTest : BaseComposeTest() {
|
|||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.assertLogoutConfirmationDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the logout confirmation dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the logout confirmation dialog
|
||||
val accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY)
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = accountSummaries)
|
||||
}
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.performLogoutAccountConfirmationClick()
|
||||
|
||||
verify { viewModel.trySendAction(LoginAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ 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.assertLogoutConfirmationDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
|
@ -25,6 +26,7 @@ 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.util.performLogoutAccountConfirmationClick
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -130,13 +132,28 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
fun `logout button click in the lock-or-logout dialog should show the logout confirmation dialog and hide the lock-or-logout dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.assertLogoutConfirmationDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the logout confirmation dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the logout confirmation dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.performLogoutAccountConfirmationClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultUnlockAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
|
|
|
@ -78,6 +78,35 @@ fun ComposeContentTestRule.assertLockOrLogoutDialogIsDisplayed(
|
|||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the logoung confirmation dialog is currently displayed with information from the given
|
||||
* [accountSummary].
|
||||
*/
|
||||
fun ComposeContentTestRule.assertLogoutConfirmationDialogIsDisplayed(
|
||||
accountSummary: AccountSummary,
|
||||
) {
|
||||
this.waitForIdle()
|
||||
this
|
||||
.onNode(isDialog())
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
this
|
||||
.onAllNodesWithText("Are you sure you want to log out?", substring = true)
|
||||
.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.
|
||||
*/
|
||||
|
@ -118,6 +147,16 @@ fun ComposeContentTestRule.performLogoutAccountClick() {
|
|||
.performClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the "Yes" button in the logout confirmation dialog to confirm the logout.
|
||||
*/
|
||||
fun ComposeContentTestRule.performLogoutAccountConfirmationClick() {
|
||||
this
|
||||
.onAllNodesWithText("Yes")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the account switcher.
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ 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.assertLogoutConfirmationDialogIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||
|
@ -22,6 +23,7 @@ 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.util.performLogoutAccountConfirmationClick
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
@ -145,13 +147,28 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the lock-or-logout dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
fun `logout button click in the lock-or-logout dialog should show the logout confirmation dialog and hide the lock-or-logout dialog`() {
|
||||
// Show the lock-or-logout dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.assertLogoutConfirmationDialogIsDisplayed(
|
||||
accountSummary = ACTIVE_ACCOUNT_SUMMARY,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout button click in the logout confirmation dialog should send LogoutAccountClick action and close the dialog`() {
|
||||
// Show the logout confirmation dialog
|
||||
composeTestRule.performAccountIconClick()
|
||||
composeTestRule.performAccountLongClick(ACTIVE_ACCOUNT_SUMMARY)
|
||||
composeTestRule.performLogoutAccountClick()
|
||||
|
||||
composeTestRule.performLogoutAccountConfirmationClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultAction.LogoutAccountClick(ACTIVE_ACCOUNT_SUMMARY)) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
|
|
@ -151,6 +151,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
every { isActive } returns false
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
|
@ -159,16 +160,45 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
verify { vaultRepository.lockVaultIfNecessary(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on LogoutAccountClick should call logout for the given account`() {
|
||||
fun `on LogoutAccountClick for an active account should call logout for the given account and set isSwitchingAccounts to true`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
every { isActive } returns true
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
isSwitchingAccounts = true,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on LogoutAccountClick for an inactive account should call logout for the given account and set isSwitchingAccounts to false`() {
|
||||
val accountUserId = "userId"
|
||||
val accountSummary = mockk<AccountSummary> {
|
||||
every { userId } returns accountUserId
|
||||
every { isActive } returns false
|
||||
}
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultAction.LogoutAccountClick(accountSummary))
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
isSwitchingAccounts = false,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify { authRepository.logout(userId = accountUserId) }
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue