From 2ff3f3e23d681c2f100150ff532d6529a86036fa Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 7 Jul 2025 16:43:08 -0500 Subject: [PATCH] PM-23503: Update Move to Organization toasts to be snackbars (#5489) --- .../manager/snackbar/SnackbarRelay.kt | 1 + .../feature/addedit/VaultAddEditScreen.kt | 8 +++++ .../feature/addedit/VaultAddEditViewModel.kt | 36 ++++++++++++++++++- .../vault/feature/item/VaultItemViewModel.kt | 21 ++++++++++- .../VaultMoveToOrganizationScreen.kt | 7 ---- .../VaultMoveToOrganizationViewModel.kt | 24 ++++++------- .../feature/addedit/VaultAddEditScreenTest.kt | 10 ++++++ .../addedit/VaultAddEditViewModelTest.kt | 21 +++++++++++ .../vault/feature/item/VaultItemScreenTest.kt | 10 ++++++ .../feature/item/VaultItemViewModelTest.kt | 17 +++++++++ .../VaultMoveToOrganizationViewModelTest.kt | 29 +++++++++++---- 11 files changed, 156 insertions(+), 28 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelay.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelay.kt index db89dc33f6..c71f166459 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelay.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelay.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.Serializable @Serializable enum class SnackbarRelay { CIPHER_DELETED, + CIPHER_MOVED_TO_ORGANIZATION, CIPHER_RESTORED, LOGIN_APPROVAL, LOGINS_IMPORTED, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index d9108df741..3c778e9b81 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -68,6 +68,8 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialo import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.radio.BitwardenRadioButton import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost +import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager @@ -124,6 +126,7 @@ fun VaultAddEditScreen( orderedList = AddEditItemCoachMark.entries, ) val scope = rememberCoroutineScope() + val snackbarHostState = rememberBitwardenSnackbarHostState() EventsEffect(viewModel = viewModel) { event -> when (event) { is VaultAddEditEvent.NavigateToQrCodeScan -> { @@ -189,6 +192,8 @@ fun VaultAddEditScreen( ) } } + + is VaultAddEditEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data) } } @@ -394,6 +399,9 @@ fun VaultAddEditScreen( }, ) }, + snackbarHost = { + BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) + }, ) { when (val viewState = state.viewState) { is VaultAddEditState.ViewState.Content -> { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index a1c2d4d6b7..e3cec5540c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -53,7 +53,10 @@ import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType @@ -108,12 +111,13 @@ private const val KEY_STATE = "state" @Suppress("TooManyFunctions", "LargeClass", "LongParameterList", "LongMethod") class VaultAddEditViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + generatorRepository: GeneratorRepository, + snackbarRelayManager: SnackbarRelayManager, private val authRepository: AuthRepository, private val clipboardManager: BitwardenClipboardManager, private val policyManager: PolicyManager, private val vaultRepository: VaultRepository, private val bitwardenCredentialManager: BitwardenCredentialManager, - generatorRepository: GeneratorRepository, private val settingsRepository: SettingsRepository, private val specialCircumstanceManager: SpecialCircumstanceManager, private val resourceManager: ResourceManager, @@ -255,6 +259,12 @@ class VaultAddEditViewModel @Inject constructor( } .onEach(::sendAction) .launchIn(viewModelScope) + + snackbarRelayManager + .getSnackbarDataFlow(SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION) + .map { VaultAddEditAction.Internal.SnackbarDataReceived(it) } + .onEach(::sendAction) + .launchIn(viewModelScope) } override fun handleAction(action: VaultAddEditAction) { @@ -1548,9 +1558,19 @@ class VaultAddEditViewModel @Inject constructor( is VaultAddEditAction.Internal.AvailableFoldersReceive -> { handleAvailableFoldersReceive(action) } + + is VaultAddEditAction.Internal.SnackbarDataReceived -> { + handleSnackbarDataReceived(action) + } } } + private fun handleSnackbarDataReceived( + action: VaultAddEditAction.Internal.SnackbarDataReceived, + ) { + sendEvent(VaultAddEditEvent.ShowSnackbar(action.data)) + } + private fun handleAvailableFoldersReceive( action: VaultAddEditAction.Internal.AvailableFoldersReceive, ) { @@ -2715,6 +2735,13 @@ data class VaultAddEditState( * Each subclass of this sealed class denotes a distinct event that can occur. */ sealed class VaultAddEditEvent { + /** + * Shows a snackbar with the given [data]. + */ + data class ShowSnackbar( + val data: BitwardenSnackbarData, + ) : VaultAddEditEvent(), BackgroundEvent + /** * Shows a toast with the given [message]. */ @@ -3372,6 +3399,13 @@ sealed class VaultAddEditAction { val generatorResult: GeneratorResult, ) : Internal() + /** + * Indicates that snackbar data has been received. + */ + data class SnackbarDataReceived( + val data: BitwardenSnackbarData, + ) : Internal() + /** * Indicates that the vault item data has been received. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt index 94b09e3483..51124c5ee0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt @@ -9,6 +9,7 @@ import com.bitwarden.core.data.repository.util.combineDataStates import com.bitwarden.core.data.repository.util.mapNullable import com.bitwarden.core.util.persistentListOfNotNull import com.bitwarden.data.repository.util.baseIconUrl +import com.bitwarden.ui.platform.base.BackgroundEvent import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.util.Text @@ -216,6 +217,12 @@ class VaultItemViewModel @Inject constructor( .map { VaultItemAction.Internal.IsIconLoadingDisabledUpdateReceive(it) } .onEach(::sendAction) .launchIn(viewModelScope) + + snackbarRelayManager + .getSnackbarDataFlow(SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION) + .map { VaultItemAction.Internal.SnackbarDataReceived(it) } + .onEach(::sendAction) + .launchIn(viewModelScope) } override fun handleAction(action: VaultItemAction) { @@ -937,6 +944,7 @@ class VaultItemViewModel @Inject constructor( when (action) { is VaultItemAction.Internal.CopyValue -> handleCopyValue(action) is VaultItemAction.Internal.PasswordBreachReceive -> handlePasswordBreachReceive(action) + is VaultItemAction.Internal.SnackbarDataReceived -> handleSnackbarDataReceived(action) is VaultItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action) is VaultItemAction.Internal.DeleteCipherReceive -> handleDeleteCipherReceive(action) is VaultItemAction.Internal.RestoreCipherReceive -> handleRestoreCipherReceive(action) @@ -958,6 +966,10 @@ class VaultItemViewModel @Inject constructor( clipboardManager.setText(action.value) } + private fun handleSnackbarDataReceived(action: VaultItemAction.Internal.SnackbarDataReceived) { + sendEvent(VaultItemEvent.ShowSnackbar(action.data)) + } + private fun handlePasswordBreachReceive( action: VaultItemAction.Internal.PasswordBreachReceive, ) { @@ -1784,7 +1796,7 @@ sealed class VaultItemEvent { */ data class ShowSnackbar( val data: BitwardenSnackbarData, - ) : VaultItemEvent() { + ) : VaultItemEvent(), BackgroundEvent { constructor( message: Text, messageHeader: Text? = null, @@ -2107,6 +2119,13 @@ sealed class VaultItemAction { val result: BreachCountResult, ) : Internal() + /** + * Indicates that snackbar data has been received. + */ + data class SnackbarDataReceived( + val data: BitwardenSnackbarData, + ) : Internal() + /** * Indicates that the vault item data has been received. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationScreen.kt index d64b52ecae..271154d5d2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationScreen.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.vault.feature.movetoorganization -import android.widget.Toast import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults @@ -10,7 +9,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel @@ -37,14 +35,9 @@ fun VaultMoveToOrganizationScreen( onNavigateBack: () -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val context = LocalContext.current - val resources = context.resources EventsEffect(viewModel = viewModel) { event -> when (event) { is VaultMoveToOrganizationEvent.NavigateBack -> onNavigateBack() - is VaultMoveToOrganizationEvent.ShowToast -> { - Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show() - } } } VaultMoveToOrganizationScaffold( diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt index 5cc44ba143..8919c683b5 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt @@ -16,6 +16,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.toViewState import com.x8bit.bitwarden.ui.vault.model.VaultCollection import dagger.hilt.android.lifecycle.HiltViewModel @@ -37,8 +40,9 @@ private const val KEY_STATE = "state" @Suppress("MaxLineLength", "TooManyFunctions") class VaultMoveToOrganizationViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val vaultRepository: VaultRepository, authRepository: AuthRepository, + private val snackbarRelayManager: SnackbarRelayManager, + private val vaultRepository: VaultRepository, ) : BaseViewModel( initialState = savedStateHandle[KEY_STATE] ?: run { @@ -165,8 +169,11 @@ class VaultMoveToOrganizationViewModel @Inject constructor( } is ShareCipherResult.Success -> { + snackbarRelayManager.sendSnackbarData( + data = BitwardenSnackbarData(message = action.message), + relay = SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION, + ) sendEvent(VaultMoveToOrganizationEvent.NavigateBack) - sendEvent(VaultMoveToOrganizationEvent.ShowToast(action.successToast)) } } } @@ -302,7 +309,7 @@ class VaultMoveToOrganizationViewModel @Inject constructor( cipherView = cipherView, collectionIds = collectionIds, ), - successToast = R.string.item_updated.asText(), + message = R.string.item_updated.asText(), ) } else { VaultMoveToOrganizationAction.Internal.ShareCipherResultReceive( @@ -312,7 +319,7 @@ class VaultMoveToOrganizationViewModel @Inject constructor( cipherView = cipherView, collectionIds = collectionIds, ), - successToast = R.string.moved_item_to_org.asText( + message = R.string.moved_item_to_org.asText( requireNotNull(contentState.cipherToMove).name, contentState.selectedOrganization.name, ), @@ -444,13 +451,6 @@ sealed class VaultMoveToOrganizationEvent { * Navigates back to the previous screen. */ data object NavigateBack : VaultMoveToOrganizationEvent() - - /** - * Show a toast with the given message. - * - * @property text the text to display. - */ - data class ShowToast(val text: Text) : VaultMoveToOrganizationEvent() } /** @@ -508,7 +508,7 @@ sealed class VaultMoveToOrganizationAction { */ data class ShareCipherResultReceive( val shareCipherResult: ShareCipherResult, - val successToast: Text, + val message: Text, ) : Internal() } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 79eb1ee448..43a2b34f73 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -54,6 +54,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager @@ -138,6 +139,15 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { } } + @Test + fun `on ShowSnackbar should display snackbar content`() { + val message = "message" + val data = BitwardenSnackbarData(message = message.asText()) + composeTestRule.onNodeWithText(text = message).assertDoesNotExist() + mutableEventFlow.tryEmit(VaultAddEditEvent.ShowSnackbar(data = data)) + composeTestRule.onNodeWithText(text = message).assertIsDisplayed() + } + @Test fun `on ExitApp event should call the exitApplication of ExitManager`() { mutableEventFlow.tryEmit(VaultAddEditEvent.ExitApp) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index ae6debf9f7..6664e8d3e7 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -71,7 +71,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType @@ -197,6 +199,13 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { private val featureFlagManager: FeatureFlagManager = mockk { every { getFeatureFlag(key = FlagKey.RestrictCipherItemDeletion) } returns false } + private val mutableSnackbarDataFlow: MutableSharedFlow = + bufferedMutableSharedFlow() + private val snackbarRelayManager: SnackbarRelayManager = mockk { + every { + getSnackbarDataFlow(relay = any(), relays = anyVararg()) + } returns mutableSnackbarDataFlow + } @BeforeEach fun setup() { @@ -501,6 +510,16 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } } + @Test + fun `snackbar relay emission should send ShowSnackbar`() = runTest { + val viewModel = createAddVaultItemViewModel() + val snackbarData = mockk() + viewModel.eventFlow.test { + mutableSnackbarDataFlow.emit(snackbarData) + assertEquals(VaultAddEditEvent.ShowSnackbar(snackbarData), awaitItem()) + } + } + @Test fun `AttachmentsClick should emit NavigateToAttachments`() = runTest { val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) @@ -3474,6 +3493,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { bitwardenCredentialManager = bitwardenCredentialManager, generatorRepository = generatorRepository, settingsRepository = settingsRepository, + snackbarRelayManager = snackbarRelayManager, specialCircumstanceManager = specialCircumstanceManager, resourceManager = resourceManager, clock = fixedClock, @@ -4860,6 +4880,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { bitwardenCredentialManager = bitwardenCredentialManager, generatorRepository = generatorRepo, settingsRepository = settingsRepository, + snackbarRelayManager = snackbarRelayManager, specialCircumstanceManager = specialCircumstanceManager, resourceManager = bitwardenResourceManager, clock = clock, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index e8fe204823..44a341be1d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -38,6 +38,7 @@ import com.bitwarden.ui.util.onNodeWithTextAfterScroll import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData @@ -137,6 +138,15 @@ class VaultItemScreenTest : BitwardenComposeTest() { assertEquals(id, onNavigateToPasswordHistoryId) } + @Test + fun `on ShowSnackbar should display snackbar content`() { + val message = "message" + val data = BitwardenSnackbarData(message = message.asText()) + composeTestRule.onNodeWithText(text = message).assertDoesNotExist() + mutableEventFlow.tryEmit(VaultItemEvent.ShowSnackbar(data = data)) + composeTestRule.onNodeWithText(text = message).assertIsDisplayed() + } + @Test fun `on close click should send CloseClick`() { composeTestRule.onNodeWithContentDescription(label = "Close").performClick() diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt index ff310c26bb..27e7147a8c 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt @@ -4,6 +4,7 @@ import android.net.Uri import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.bitwarden.core.data.repository.model.DataState +import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.data.repository.model.Environment import com.bitwarden.data.repository.util.baseIconUrl import com.bitwarden.network.model.OrganizationType @@ -63,6 +64,7 @@ import io.mockk.runs import io.mockk.unmockkStatic import io.mockk.verify import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach @@ -120,7 +122,12 @@ class VaultItemViewModelTest : BaseViewModelTest() { private val featureFlagManager: FeatureFlagManager = mockk { every { getFeatureFlag(key = FlagKey.RestrictCipherItemDeletion) } returns false } + private val mutableSnackbarDataFlow: MutableSharedFlow = + bufferedMutableSharedFlow() private val snackbarRelayManager: SnackbarRelayManager = mockk { + every { + getSnackbarDataFlow(relay = any(), relays = anyVararg()) + } returns mutableSnackbarDataFlow every { sendSnackbarData(data = any(), relay = any()) } just runs } @@ -173,6 +180,16 @@ class VaultItemViewModelTest : BaseViewModelTest() { } } + @Test + fun `snackbar relay emission should send ShowSnackbar`() = runTest { + val viewModel = createViewModel(DEFAULT_STATE) + val snackbarData = mockk() + viewModel.eventFlow.test { + mutableSnackbarDataFlow.emit(snackbarData) + assertEquals(VaultItemEvent.ShowSnackbar(snackbarData), awaitItem()) + } + } + @Nested inner class CommonActions { private lateinit var viewModel: VaultItemViewModel diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt index 5026a0b8d6..8879c55782 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt @@ -20,14 +20,20 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult +import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList import com.x8bit.bitwarden.ui.vault.model.VaultCollection 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.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach @@ -57,6 +63,10 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { every { userStateFlow } returns mutableUserStateFlow } + private val snackbarRelayManager: SnackbarRelayManager = mockk { + every { sendSnackbarData(data = any(), relay = any()) } just runs + } + @BeforeEach fun setup() { mockkStatic(SavedStateHandle::toVaultMoveToOrganizationArgs) @@ -394,14 +404,16 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { VaultMoveToOrganizationEvent.NavigateBack, awaitItem(), ) - assertEquals( - VaultMoveToOrganizationEvent.ShowToast( - text = R.string.moved_item_to_org.asText( + } + verify { + snackbarRelayManager.sendSnackbarData( + data = BitwardenSnackbarData( + message = R.string.moved_item_to_org.asText( "mockName-1", "mockOrganizationName-1", ), ), - awaitItem(), + relay = SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION, ) } coVerify { @@ -439,9 +451,11 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { VaultMoveToOrganizationEvent.NavigateBack, awaitItem(), ) - assertEquals( - VaultMoveToOrganizationEvent.ShowToast(R.string.item_updated.asText()), - awaitItem(), + } + verify { + snackbarRelayManager.sendSnackbarData( + data = BitwardenSnackbarData(message = R.string.item_updated.asText()), + relay = SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION, ) } coVerify { @@ -461,6 +475,7 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { savedStateHandle = savedStateHandle, authRepository = authRepo, vaultRepository = vaultRepo, + snackbarRelayManager = snackbarRelayManager, ) private fun createSavedStateHandleWithState(