mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 17:36:01 +03:00
BIT-1157: Add error state for Vault Screen (#393)
This commit is contained in:
parent
09fbd5d4e9
commit
65eb6ab5f8
2 changed files with 122 additions and 35 deletions
|
@ -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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue