PM-14353 : Clean up consumed snackbar on quick resubmission due to state based nav. (#4235)

This commit is contained in:
Dave Severns 2024-11-06 14:39:55 -05:00 committed by GitHub
parent 29384596d4
commit e397c036e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 16 deletions

View file

@ -19,4 +19,9 @@ interface SnackbarRelayManager {
* the [relay] to receive the data from. * the [relay] to receive the data from.
*/ */
fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData> fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData>
/**
* Clears the buffer for the given [relay].
*/
fun clearRelayBuffer(relay: SnackbarRelay)
} }

View file

@ -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.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
@ -26,6 +27,11 @@ class SnackbarRelayManagerImpl : SnackbarRelayManager {
} }
.filterNotNull() .filterNotNull()
@OptIn(ExperimentalCoroutinesApi::class)
override fun clearRelayBuffer(relay: SnackbarRelay) {
getSnackbarDataFlowInternal(relay).resetReplayCache()
}
private fun getSnackbarDataFlowInternal( private fun getSnackbarDataFlowInternal(
relay: SnackbarRelay, relay: SnackbarRelay,
): MutableSharedFlow<BitwardenSnackbarData?> = ): MutableSharedFlow<BitwardenSnackbarData?> =

View file

@ -73,8 +73,8 @@ class VaultViewModel @Inject constructor(
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val vaultRepository: VaultRepository, private val vaultRepository: VaultRepository,
private val firstTimeActionManager: FirstTimeActionManager, private val firstTimeActionManager: FirstTimeActionManager,
private val snackbarRelayManager: SnackbarRelayManager,
featureFlagManager: FeatureFlagManager, featureFlagManager: FeatureFlagManager,
snackbarRelayManager: SnackbarRelayManager,
) : BaseViewModel<VaultState, VaultEvent, VaultAction>( ) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
initialState = run { initialState = run {
val userState = requireNotNull(authRepository.userStateFlow.value) val userState = requireNotNull(authRepository.userStateFlow.value)
@ -283,6 +283,9 @@ class VaultViewModel @Inject constructor(
SwitchAccountResult.AccountSwitched -> true SwitchAccountResult.AccountSwitched -> true
SwitchAccountResult.NoChange -> false SwitchAccountResult.NoChange -> false
} }
if (isSwitchingAccounts) {
snackbarRelayManager.clearRelayBuffer(SnackbarRelay.MY_VAULT_RELAY)
}
mutableStateFlow.update { mutableStateFlow.update {
it.copy(isSwitchingAccounts = isSwitchingAccounts) it.copy(isSwitchingAccounts = isSwitchingAccounts)
} }

View file

@ -66,16 +66,16 @@ class SnackbarRelayManagerTest {
fun `When multiple consumers are registered to the same relay, send data to all consumers`() = fun `When multiple consumers are registered to the same relay, send data to all consumers`() =
runTest { runTest {
val relayManager = SnackbarRelayManagerImpl() val relayManager = SnackbarRelayManagerImpl()
val relay1 = SnackbarRelay.MY_VAULT_RELAY val relay = SnackbarRelay.MY_VAULT_RELAY
val expectedData = BitwardenSnackbarData(message = "Test message".asText()) val expectedData = BitwardenSnackbarData(message = "Test message".asText())
turbineScope { turbineScope {
val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
relayManager.sendSnackbarData(data = expectedData, relay = relay1) relayManager.sendSnackbarData(data = expectedData, relay = relay)
assertEquals( assertEquals(
expectedData, expectedData,
consumer1.awaitItem(), consumer1.awaitItem(),
) )
val consumer2 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) val consumer2 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
assertEquals( assertEquals(
expectedData, expectedData,
consumer2.awaitItem(), consumer2.awaitItem(),
@ -85,20 +85,40 @@ class SnackbarRelayManagerTest {
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @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 { runTest {
val relayManager = SnackbarRelayManagerImpl() val relayManager = SnackbarRelayManagerImpl()
val relay1 = SnackbarRelay.MY_VAULT_RELAY val relay = SnackbarRelay.MY_VAULT_RELAY
val expectedData = BitwardenSnackbarData(message = "Test message".asText()) val expectedData = BitwardenSnackbarData(message = "Test message".asText())
turbineScope { turbineScope {
val consumer1 = relayManager.getSnackbarDataFlow(relay1).testIn(backgroundScope) val consumer1 = relayManager.getSnackbarDataFlow(relay).testIn(backgroundScope)
relayManager.sendSnackbarData(data = expectedData, relay = relay1) relayManager.sendSnackbarData(data = expectedData, relay = relay)
assertEquals( assertEquals(
expectedData, expectedData,
consumer1.awaitItem(), consumer1.awaitItem(),
) )
consumer1.cancel() 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() consumer2.expectNoEvents()
} }
} }

View file

@ -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.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData 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.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.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData 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.model.VaultFilterType
@ -49,6 +49,7 @@ import io.mockk.mockk
import io.mockk.runs import io.mockk.runs
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
@ -66,7 +67,12 @@ class VaultViewModelTest : BaseViewModelTest() {
ZoneOffset.UTC, ZoneOffset.UTC,
) )
private val snackbarRelayManager = SnackbarRelayManagerImpl() private val mutableSnackbarDataFlow = MutableStateFlow<BitwardenSnackbarData?>(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 { private val clipboardManager: BitwardenClipboardManager = mockk {
every { setText(any<String>()) } just runs every { setText(any<String>()) } just runs
@ -1802,15 +1808,28 @@ class VaultViewModelTest : BaseViewModelTest() {
fun `when SnackbarRelay flow updates, snackbar is shown`() = runTest { fun `when SnackbarRelay flow updates, snackbar is shown`() = runTest {
val viewModel = createViewModel() val viewModel = createViewModel()
val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText()) val expectedSnackbarData = BitwardenSnackbarData(message = "test message".asText())
mutableSnackbarDataFlow.update { expectedSnackbarData }
viewModel.eventFlow.test { viewModel.eventFlow.test {
snackbarRelayManager.sendSnackbarData(
data = expectedSnackbarData,
relay = SnackbarRelay.MY_VAULT_RELAY,
)
assertEquals(VaultEvent.ShowSnackbar(expectedSnackbarData), awaitItem()) 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 = private fun createViewModel(): VaultViewModel =
VaultViewModel( VaultViewModel(
authRepository = authRepository, authRepository = authRepository,