diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt index 041b5df2e5..460cd0cca0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt @@ -79,6 +79,8 @@ fun VaultContent( label = favoriteItem.name(), supportingLabel = favoriteItem.supportingLabel?.invoke(), onClick = { vaultHandlers.vaultItemClick(favoriteItem) }, + overflowOptions = favoriteItem.overflowOptions, + onOverflowOptionClick = vaultHandlers.overflowOptionClick, modifier = Modifier .fillMaxWidth() .padding( @@ -227,12 +229,13 @@ fun VaultContent( ) } items(state.noFolderItems) { noFolderItem -> - VaultEntryListItem( startIcon = noFolderItem.startIcon, label = noFolderItem.name(), supportingLabel = noFolderItem.supportingLabel?.invoke(), onClick = { vaultHandlers.vaultItemClick(noFolderItem) }, + overflowOptions = noFolderItem.overflowOptions, + onOverflowOptionClick = vaultHandlers.overflowOptionClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultEntryListItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultEntryListItem.kt index 792796e1cf..5f32427f1d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultEntryListItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultEntryListItem.kt @@ -1,16 +1,15 @@ package com.x8bit.bitwarden.ui.vault.feature.vault -import android.widget.Toast import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem import com.x8bit.bitwarden.ui.platform.components.SelectionItemData import com.x8bit.bitwarden.ui.platform.components.model.IconData import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme -import kotlinx.collections.immutable.persistentListOf +import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction +import kotlinx.collections.immutable.toImmutableList /** * A Composable function that displays a row item for different types of vault entries. @@ -18,6 +17,8 @@ import kotlinx.collections.immutable.persistentListOf * @param label The primary text label to display for the item. * @param supportingLabel An optional secondary text label to display beneath the primary label. * @param onClick The lambda to be invoked when the item is clicked. + * @param overflowOptions List of options to display for the item. + * @param onOverflowOptionClick The lambda to be invoked when an overflow option is clicked. * @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier. * This allows the caller to specify things like padding, size, etc. */ @@ -26,25 +27,25 @@ fun VaultEntryListItem( startIcon: IconData, label: String, onClick: () -> Unit, + overflowOptions: List, + onOverflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit, modifier: Modifier = Modifier, supportingLabel: String? = null, ) { - val context = LocalContext.current BitwardenListItem( modifier = modifier, label = label, supportingLabel = supportingLabel, startIcon = startIcon, onClick = onClick, - selectionDataList = persistentListOf( - SelectionItemData( - text = "Not yet implemented", - onClick = { - // TODO: Provide dialog-based implementation (BIT-1353 - BIT-1356) - Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show() - }, - ), - ), + selectionDataList = overflowOptions + .map { option -> + SelectionItemData( + text = option.title(), + onClick = { onOverflowOptionClick(option) }, + ) + } + .toImmutableList(), ) } @@ -57,6 +58,8 @@ private fun VaultEntryListItem_preview() { label = "Example Login", supportingLabel = "Username", onClick = {}, + overflowOptions = emptyList(), + onOverflowOptionClick = {}, modifier = Modifier, ) } 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 abcae5a880..328bc91513 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 @@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect @@ -51,7 +52,9 @@ import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager +import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import kotlinx.collections.immutable.persistentListOf @@ -73,6 +76,7 @@ fun VaultScreen( onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit, onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit, exitManager: ExitManager = LocalExitManager.current, + intentManager: IntentManager = LocalIntentManager.current, ) { val state by viewModel.stateFlow.collectAsState() val context = LocalContext.current @@ -102,6 +106,8 @@ fun VaultScreen( onNavigateToVaultItemListingScreen(event.itemListingType) } + is VaultEvent.NavigateToUrl -> intentManager.launchUri(event.url.toUri()) + VaultEvent.NavigateOutOfApp -> exitManager.exitApplication() is VaultEvent.ShowToast -> { Toast 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 757b6493fd..99b7443c67 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 @@ -7,6 +7,7 @@ import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState +import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl @@ -19,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.concat import com.x8bit.bitwarden.ui.platform.base.util.hexToColor import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary import com.x8bit.bitwarden.ui.platform.components.model.IconData +import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials @@ -43,6 +45,7 @@ import javax.inject.Inject @Suppress("TooManyFunctions") @HiltViewModel class VaultViewModel @Inject constructor( + private val clipboardManager: BitwardenClipboardManager, private val authRepository: AuthRepository, private val vaultRepository: VaultRepository, private val settingsRepository: SettingsRepository, @@ -128,6 +131,7 @@ class VaultViewModel @Inject constructor( is VaultAction.TryAgainClick -> handleTryAgainClick() is VaultAction.DialogDismiss -> handleDialogDismiss() is VaultAction.RefreshPull -> handleRefreshPull() + is VaultAction.OverflowOptionClick -> handleOverflowOptionClick(action) is VaultAction.Internal -> handleInternalAction(action) } } @@ -270,6 +274,82 @@ class VaultViewModel @Inject constructor( vaultRepository.sync() } + private fun handleOverflowOptionClick(action: VaultAction.OverflowOptionClick) { + when (val overflowAction = action.overflowAction) { + is ListingItemOverflowAction.VaultAction.CopyNoteClick -> { + handleCopyNoteClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyNumberClick -> { + handleCopyNumberClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyPasswordClick -> { + handleCopyPasswordClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick -> { + handleCopySecurityCodeClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyUsernameClick -> { + handleCopyUsernameClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.EditClick -> { + handleEditClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.LaunchClick -> { + handleLaunchClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.ViewClick -> { + handleViewClick(overflowAction) + } + } + } + + private fun handleCopyNoteClick(action: ListingItemOverflowAction.VaultAction.CopyNoteClick) { + clipboardManager.setText(action.notes) + } + + private fun handleCopyNumberClick( + action: ListingItemOverflowAction.VaultAction.CopyNumberClick, + ) { + clipboardManager.setText(action.number) + } + + private fun handleCopyPasswordClick( + action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, + ) { + clipboardManager.setText(action.password) + } + + private fun handleCopySecurityCodeClick( + action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, + ) { + clipboardManager.setText(action.securityCode) + } + + private fun handleCopyUsernameClick( + action: ListingItemOverflowAction.VaultAction.CopyUsernameClick, + ) { + clipboardManager.setText(action.username) + } + + private fun handleEditClick(action: ListingItemOverflowAction.VaultAction.EditClick) { + sendEvent(VaultEvent.NavigateToEditVaultItem(action.cipherId)) + } + + private fun handleLaunchClick(action: ListingItemOverflowAction.VaultAction.LaunchClick) { + sendEvent(VaultEvent.NavigateToUrl(action.url)) + } + + private fun handleViewClick(action: ListingItemOverflowAction.VaultAction.ViewClick) { + sendEvent(VaultEvent.NavigateToVaultItem(action.cipherId)) + } + private fun handleInternalAction(action: VaultAction.Internal) { when (action) { is VaultAction.Internal.PullToRefreshEnableReceive -> { @@ -584,6 +664,11 @@ data class VaultState( */ abstract val supportingLabel: Text? + /** + * The overflow options to be displayed for the vault item. + */ + abstract val overflowOptions: List + /** * Represents a login item within the vault. * @@ -594,6 +679,7 @@ data class VaultState( override val id: String, override val name: Text, override val startIcon: IconData = IconData.Local(R.drawable.ic_login_item), + override val overflowOptions: List, val username: Text?, ) : VaultItem() { override val supportingLabel: Text? get() = username @@ -610,6 +696,7 @@ data class VaultState( override val id: String, override val name: Text, override val startIcon: IconData = IconData.Local(R.drawable.ic_card_item), + override val overflowOptions: List, val brand: Text? = null, val lastFourDigits: Text? = null, ) : VaultItem() { @@ -636,6 +723,7 @@ data class VaultState( override val id: String, override val name: Text, override val startIcon: IconData = IconData.Local(R.drawable.ic_identity_item), + override val overflowOptions: List, val firstName: Text?, ) : VaultItem() { override val supportingLabel: Text? get() = firstName @@ -650,6 +738,7 @@ data class VaultState( override val id: String, override val name: Text, override val startIcon: IconData = IconData.Local(R.drawable.ic_secure_note_item), + override val overflowOptions: List, ) : VaultItem() { override val supportingLabel: Text? get() = null } @@ -726,6 +815,13 @@ sealed class VaultEvent { val itemListingType: VaultItemListingType, ) : VaultEvent() + /** + * Navigates to the given [url]. + */ + data class NavigateToUrl( + val url: String, + ) : VaultEvent() + /** * Navigate to the verification code screen. */ @@ -874,6 +970,13 @@ sealed class VaultAction { */ data object TryAgainClick : VaultAction() + /** + * User clicked an overflow action. + */ + data class OverflowOptionClick( + val overflowAction: ListingItemOverflowAction.VaultAction, + ) : VaultAction() + /** * Models actions that the [VaultViewModel] itself might send. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt index d7cfea5372..1641f080ea 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault.handlers import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary +import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.vault.VaultAction import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState import com.x8bit.bitwarden.ui.vault.feature.vault.VaultViewModel @@ -31,6 +32,7 @@ data class VaultHandlers( val trashClick: () -> Unit, val tryAgainClick: () -> Unit, val dialogDismiss: () -> Unit, + val overflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit, ) { companion object { /** @@ -74,6 +76,9 @@ data class VaultHandlers( trashClick = { viewModel.trySendAction(VaultAction.TrashClick) }, tryAgainClick = { viewModel.trySendAction(VaultAction.TryAgainClick) }, dialogDismiss = { viewModel.trySendAction(VaultAction.DialogDismiss) }, + overflowOptionClick = { + viewModel.trySendAction(VaultAction.OverflowOptionClick(it)) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt index b16a01d433..00a537e2a1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt @@ -10,6 +10,7 @@ import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.components.model.IconData +import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType @@ -156,11 +157,13 @@ private fun CipherView.toVaultItemOrNull( isIconLoadingDisabled = isIconLoadingDisabled, baseIconUrl = baseIconUrl, ), + overflowOptions = toOverflowActions(), ) CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote( id = id, name = name.asText(), + overflowOptions = toOverflowActions(), ) CipherType.CARD -> VaultState.ViewState.VaultItem.Card( @@ -170,12 +173,14 @@ private fun CipherView.toVaultItemOrNull( lastFourDigits = card?.number ?.takeLast(4) ?.asText(), + overflowOptions = toOverflowActions(), ) CipherType.IDENTITY -> VaultState.ViewState.VaultItem.Identity( id = id, name = name.asText(), firstName = identity?.firstName?.asText(), + overflowOptions = toOverflowActions(), ) } } 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 cdd42f9a6c..9eba116f24 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 @@ -15,6 +15,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode +import androidx.core.net.toUri import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl @@ -23,6 +24,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.platform.manager.exit.ExitManager +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed import com.x8bit.bitwarden.ui.util.assertNoDialogExists @@ -63,6 +65,7 @@ class VaultScreenTest : BaseComposeTest() { private var onNavigateToVerificationCodeScreen = false private var onNavigateToSearchScreen = false private val exitManager = mockk(relaxed = true) + private val intentManager = mockk(relaxed = true) private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) @@ -84,6 +87,7 @@ class VaultScreenTest : BaseComposeTest() { onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true }, onNavigateToSearchVault = { onNavigateToSearchScreen = true }, exitManager = exitManager, + intentManager = intentManager, ) } } @@ -653,6 +657,15 @@ class VaultScreenTest : BaseComposeTest() { assertEquals(VaultItemListingType.Folder(mockFolderId), onNavigateToVaultItemListingType) } + @Test + fun `NavigateToUrl event should call launchUri`() { + val url = "www.test.com" + mutableEventFlow.tryEmit(VaultEvent.NavigateToUrl(url)) + verify(exactly = 1) { + intentManager.launchUri(url.toUri()) + } + } + @Test fun `NavigateOutOfApp event should call exitApplication on the ExitManager`() { mutableEventFlow.tryEmit(VaultEvent.NavigateOutOfApp) @@ -722,6 +735,7 @@ class VaultScreenTest : BaseComposeTest() { id = "12345", name = itemText.asText(), username = username.asText(), + overflowOptions = emptyList(), ) mutableStateFlow.update { it.copy( @@ -842,6 +856,7 @@ class VaultScreenTest : BaseComposeTest() { id = "12345", name = itemText.asText(), username = userName.asText(), + overflowOptions = emptyList(), ) mutableStateFlow.update { it.copy( 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 a82ff5a7a2..0d19608d97 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 @@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.Organization import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState +import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.model.Environment @@ -19,6 +20,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData 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.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.toViewState @@ -38,6 +40,10 @@ import org.junit.jupiter.api.Test @Suppress("LargeClass") class VaultViewModelTest : BaseViewModelTest() { + private val clipboardManager: BitwardenClipboardManager = mockk { + every { setText(any()) } just runs + } + private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false) private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false) @@ -1055,8 +1061,133 @@ class VaultViewModelTest : BaseViewModelTest() { assertTrue(viewModel.stateFlow.value.isIconLoadingDisabled) } + @Test + fun `OverflowOptionClick Vault CopyNoteClick should call setText on the ClipboardManager`() = + runTest { + val notes = "notes" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNoteClick(notes = notes), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(notes) + } + } + + @Test + fun `OverflowOptionClick Vault CopyNumberClick should call setText on the ClipboardManager`() = + runTest { + val number = "12345-4321-9876-6789" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNumberClick(number = number), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(number) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = + runTest { + val password = "passTheWord" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = password), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(password) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = + runTest { + val securityCode = "234" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = securityCode, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(securityCode) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyUsernameClick should call setText on the ClipboardManager`() = + runTest { + val username = "bitwarden" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyUsernameClick( + username = username, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(username) + } + } + + @Test + fun `OverflowOptionClick Vault EditClick should emit NavigateToEditVaultItem`() = runTest { + val cipherId = "cipherId-1234" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.EditClick(cipherId = cipherId), + ), + ) + assertEquals(VaultEvent.NavigateToEditVaultItem(cipherId), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault LaunchClick should emit NavigateToUrl`() = runTest { + val url = "www.test.com" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.LaunchClick(url = url), + ), + ) + assertEquals(VaultEvent.NavigateToUrl(url), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault ViewClick should emit NavigateToUrl`() = runTest { + val cipherId = "cipherId-9876" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = cipherId), + ), + ) + assertEquals(VaultEvent.NavigateToVaultItem(cipherId), awaitItem()) + } + } + private fun createViewModel(): VaultViewModel = VaultViewModel( + clipboardManager = clipboardManager, authRepository = authRepository, settingsRepository = settingsRepository, vaultRepository = vaultRepository,