BIT-1157: Add error state for Vault Screen (#393)

This commit is contained in:
Brian Yencho 2023-12-14 13:15:55 -06:00 committed by Álison Fernandes
parent 09fbd5d4e9
commit 65eb6ab5f8
2 changed files with 122 additions and 35 deletions

View file

@ -24,6 +24,7 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.util.toViewState
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -223,9 +224,11 @@ class VaultViewModel @Inject constructor(
} }
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) { private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
// TODO update state to error state BIT-1157 mutableStateFlow.updateToErrorStateOrDialog(
mutableStateFlow.update { it.copy(viewState = VaultState.ViewState.NoItems) } vaultData = vaultData.data,
sendEvent(VaultEvent.ShowToast(message = "Vault error state not yet implemented")) errorTitle = R.string.an_error_has_occurred.asText(),
errorMessage = R.string.generic_error_message.asText(),
)
} }
private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) { private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) {
@ -237,27 +240,11 @@ class VaultViewModel @Inject constructor(
} }
private fun vaultNoNetworkReceive(vaultData: DataState.NoNetwork<VaultData>) { private fun vaultNoNetworkReceive(vaultData: DataState.NoNetwork<VaultData>) {
val title = R.string.internet_connection_required_title.asText() mutableStateFlow.updateToErrorStateOrDialog(
val message = R.string.internet_connection_required_message.asText() vaultData = vaultData.data,
if (vaultData.data != null) { errorTitle = R.string.internet_connection_required_title.asText(),
mutableStateFlow.update { errorMessage = R.string.internet_connection_required_message.asText(),
it.copy( )
viewState = vaultData.data.toViewState(),
dialog = VaultState.DialogState.Error(
title = title,
message = message,
),
)
}
} else {
mutableStateFlow.update {
it.copy(
viewState = VaultState.ViewState.Error(
message = message,
),
)
}
}
} }
private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) { private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) {
@ -265,6 +252,7 @@ class VaultViewModel @Inject constructor(
mutableStateFlow.update { it.copy(viewState = vaultData.data.toViewState()) } mutableStateFlow.update { it.copy(viewState = vaultData.data.toViewState()) }
sendEvent(VaultEvent.ShowToast(message = "Refreshing")) sendEvent(VaultEvent.ShowToast(message = "Refreshing"))
} }
//endregion VaultAction Handlers //endregion VaultAction Handlers
} }
@ -658,3 +646,27 @@ sealed class VaultAction {
) : Internal() ) : Internal()
} }
} }
private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
vaultData: VaultData?,
errorTitle: Text,
errorMessage: Text,
) {
this.update {
if (vaultData != null) {
it.copy(
viewState = vaultData.toViewState(),
dialog = VaultState.DialogState.Error(
title = errorTitle,
message = errorMessage,
),
)
} else {
it.copy(
viewState = VaultState.ViewState.Error(
message = errorMessage,
),
)
}
}
}

View file

@ -27,6 +27,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@Suppress("LargeClass")
class VaultViewModelTest : BaseViewModelTest() { class VaultViewModelTest : BaseViewModelTest() {
private val mutableUserStateFlow = private val mutableUserStateFlow =
@ -331,27 +332,101 @@ class VaultViewModelTest : BaseViewModelTest() {
} }
@Test @Test
fun `vaultDataStateFlow Error should show toast and update state to NoItems`() = runTest { fun `vaultDataStateFlow Error without data should update state to Error`() = runTest {
mutableVaultDataStateFlow.tryEmit( mutableVaultDataStateFlow.tryEmit(
value = DataState.Error( value = DataState.Error(error = IllegalStateException()),
error = IllegalStateException(),
),
) )
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.eventFlow.test {
assertEquals(
VaultEvent.ShowToast("Vault error state not yet implemented"),
awaitItem(),
)
}
assertEquals( assertEquals(
createMockVaultState(viewState = VaultState.ViewState.NoItems), createMockVaultState(
viewState = VaultState.ViewState.Error(
message = R.string.generic_error_message.asText(),
),
),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
} }
@Suppress("MaxLineLength")
@Test
fun `vaultDataStateFlow Error with items should update state to Content and show an error dialog`() =
runTest {
mutableVaultDataStateFlow.tryEmit(
value = DataState.Error(
error = IllegalStateException(),
data = VaultData(
cipherViewList = listOf(createMockCipherView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
folderViewList = listOf(createMockFolderView(number = 1)),
),
),
)
val viewModel = createViewModel()
assertEquals(
createMockVaultState(
viewState = VaultState.ViewState.Content(
loginItemsCount = 1,
cardItemsCount = 0,
identityItemsCount = 0,
secureNoteItemsCount = 0,
favoriteItems = listOf(),
folderItems = listOf(
VaultState.ViewState.FolderItem(
id = "mockId-1",
name = "mockName-1".asText(),
itemCount = 1,
),
),
collectionItems = listOf(
VaultState.ViewState.CollectionItem(
id = "mockId-1",
name = "mockName-1",
itemCount = 1,
),
),
noFolderItems = listOf(),
trashItemsCount = 0,
),
dialog = VaultState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
),
viewModel.stateFlow.value,
)
}
@Suppress("MaxLineLength")
@Test
fun `vaultDataStateFlow Error with empty items should update state to NoItems and show an error dialog`() =
runTest {
mutableVaultDataStateFlow.tryEmit(
value = DataState.Error(
error = IllegalStateException(),
data = VaultData(
cipherViewList = emptyList(),
collectionViewList = emptyList(),
folderViewList = emptyList(),
),
),
)
val viewModel = createViewModel()
assertEquals(
createMockVaultState(
viewState = VaultState.ViewState.NoItems,
dialog = VaultState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
),
viewModel.stateFlow.value,
)
}
@Test @Test
fun `vaultDataStateFlow NoNetwork without data should update state to Error`() = runTest { fun `vaultDataStateFlow NoNetwork without data should update state to Error`() = runTest {
mutableVaultDataStateFlow.tryEmit( mutableVaultDataStateFlow.tryEmit(