From e397c036e49ad4e443d13c7729e217f9fb071ed9 Mon Sep 17 00:00:00 2001 From: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:39:55 -0500 Subject: [PATCH] PM-14353 : Clean up consumed snackbar on quick resubmission due to state based nav. (#4235) --- .../manager/snackbar/SnackbarRelayManager.kt | 5 +++ .../snackbar/SnackbarRelayManagerImpl.kt | 6 +++ .../ui/vault/feature/vault/VaultViewModel.kt | 5 ++- .../snackbar/SnackbarRelayManagerTest.kt | 38 ++++++++++++++----- .../vault/feature/vault/VaultViewModelTest.kt | 31 ++++++++++++--- 5 files changed, 69 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManager.kt index cf341c3c4..1b7e809b9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManager.kt @@ -19,4 +19,9 @@ interface SnackbarRelayManager { * the [relay] to receive the data from. */ fun getSnackbarDataFlow(relay: SnackbarRelay): Flow + + /** + * Clears the buffer for the given [relay]. + */ + fun clearRelayBuffer(relay: SnackbarRelay) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerImpl.kt index 83cfae46f..22ef37765 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerImpl.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.manager.snackbar import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filterNotNull @@ -26,6 +27,11 @@ class SnackbarRelayManagerImpl : SnackbarRelayManager { } .filterNotNull() + @OptIn(ExperimentalCoroutinesApi::class) + override fun clearRelayBuffer(relay: SnackbarRelay) { + getSnackbarDataFlowInternal(relay).resetReplayCache() + } + private fun getSnackbarDataFlowInternal( relay: SnackbarRelay, ): MutableSharedFlow = 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 99b7e710c..dff036ac9 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 @@ -73,8 +73,8 @@ class VaultViewModel @Inject constructor( private val settingsRepository: SettingsRepository, private val vaultRepository: VaultRepository, private val firstTimeActionManager: FirstTimeActionManager, + private val snackbarRelayManager: SnackbarRelayManager, featureFlagManager: FeatureFlagManager, - snackbarRelayManager: SnackbarRelayManager, ) : BaseViewModel( initialState = run { val userState = requireNotNull(authRepository.userStateFlow.value) @@ -283,6 +283,9 @@ class VaultViewModel @Inject constructor( SwitchAccountResult.AccountSwitched -> true SwitchAccountResult.NoChange -> false } + if (isSwitchingAccounts) { + snackbarRelayManager.clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY) + } mutableStateFlow.update { it.copy(isSwitchingAccounts = isSwitchingAccounts) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerTest.kt index 49e6639c2..46d821325 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/manager/snackbar/SnackbarRelayManagerTest.kt @@ -66,16 +66,16 @@ class SnackbarRelayManagerTest { fun `When multiple consumers are registered to the same relay, send data to all consumers`() = runTest { val relayManager = SnackbarRelayManagerImpl() - val relay1 = SnackbarRelay.MY_VAULT_RELAY + val relay = SnackbarRelay.MY_VAULT_RELAY val expectedData = BitwardenSnackbarData(message = "Test message".asText()) turbineScope { - val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) - relayManager.sendSnackbarData(data = expectedData, relay = relay1) + val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) + relayManager.sendSnackbarData(data = expectedData, relay = relay) assertEquals( expectedData, consumer1.awaitItem(), ) - val consumer2 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) + val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) assertEquals( expectedData, consumer2.awaitItem(), @@ -85,20 +85,40 @@ class SnackbarRelayManagerTest { @Suppress("MaxLineLength") @Test - fun `When multiple consumers are register to the same relay, and one is completed before the other the second consumer registers should not receive any emissions`() = + fun `When multiple consumers are registered to the same relay, and one is completed before the other the second consumer registers should not receive any emissions`() = runTest { val relayManager = SnackbarRelayManagerImpl() - val relay1 = SnackbarRelay.MY_VAULT_RELAY + val relay = SnackbarRelay.MY_VAULT_RELAY val expectedData = BitwardenSnackbarData(message = "Test message".asText()) turbineScope { - val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) - relayManager.sendSnackbarData(data = expectedData, relay = relay1) + val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) + relayManager.sendSnackbarData(data = expectedData, relay = relay) assertEquals( expectedData, consumer1.awaitItem(), ) consumer1.cancel() - val consumer2 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) + val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) + consumer2.expectNoEvents() + } + } + + @Suppress("MaxLineLength") + @Test + fun `When multiple consumers register to the same relay, and clearRelayBuffer is called, the second consumer should not receive any emissions`() = + runTest { + val relayManager = SnackbarRelayManagerImpl() + val relay = SnackbarRelay.MY_VAULT_RELAY + val expectedData = BitwardenSnackbarData(message = "Test message".asText()) + turbineScope { + val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) + relayManager.sendSnackbarData(data = expectedData, relay = relay) + assertEquals( + expectedData, + consumer1.awaitItem(), + ) + relayManager.clearRelayBuffer(relay) + val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope) consumer2.expectNoEvents() } } 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 fc86a0cf2..f5d4fda3f 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 @@ -36,7 +36,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary 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.SnackbarRelayManagerImpl +import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager 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 @@ -49,6 +49,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -66,7 +67,12 @@ class VaultViewModelTest : BaseViewModelTest() { ZoneOffset.UTC, ) - private val snackbarRelayManager = SnackbarRelayManagerImpl() + private val mutableSnackbarDataFlow = MutableStateFlow(null) + private val snackbarRelayManager: SnackbarRelayManager = mockk { + every { getSnackbarDataFlow(SnackbarRelay.MY_VAULT_RELAY) } returns mutableSnackbarDataFlow + .filterNotNull() + every { clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY) } just runs + } private val clipboardManager: BitwardenClipboardManager = mockk { every { setText(any()) } just runs @@ -1802,15 +1808,28 @@ class VaultViewModelTest : BaseViewModelTest() { fun `when SnackbarRelay flow updates, snackbar is shown`() = runTest { val viewModel = createViewModel() val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText()) + mutableSnackbarDataFlow.update { expectedSnackbarData } viewModel.eventFlow.test { - snackbarRelayManager.sendSnackbarData( - data = expectedSnackbarData, - relay = SnackbarRelay.MY_VAULT_RELAY, - ) assertEquals(VaultEvent.ShowSnackbar(expectedSnackbarData), awaitItem()) } } + @Test + fun `when account switch action is handled, clear snackbar relay buffer should be called`() = + runTest { + val viewModel = createViewModel() + switchAccountResult = SwitchAccountResult.AccountSwitched + viewModel.trySendAction( + VaultAction.SwitchAccountClick( + accountSummary = mockk() { + every { userId } returns "updatedUserId" + }, + ), + ) + verify(exactly = 1) { + snackbarRelayManager.clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY) + } + } private fun createViewModel(): VaultViewModel = VaultViewModel( authRepository = authRepository,