PM-37232: Bug: Update error states for the Send and VaultAddEdit Screens (#7098)

This commit is contained in:
David Perez authored and GitHub committed 2026-06-24 19:23:02 +00:00
1 parent b6a0838dba
commit 141ec500b8
4 files changed
+149 -88

No files matched your search

@@ -265,11 +265,19 @@ class SendViewModel @Inject constructor(
private fun handleSendDataReceive(action: SendAction.Internal.SendDataReceive) {
when (val dataState = action.sendDataState) {
is DataState.Error -> {
mutableStateFlow.update {
it.copy(
viewState = SendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
mutableStateFlow.update { state ->
state.copy(
viewState = dataState
.data
?.toViewState(
baseWebSendUrl = environmentRepo
.environment
.environmentUrlData
.baseWebSendUrl,
)
?: SendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
dialogState = null,
isRefreshing = false,
)
@@ -279,9 +287,7 @@ class SendViewModel @Inject constructor(
is DataState.NoNetwork,
is DataState.Loaded,
-> {
val data = dataState
.data
?: SendData(sendViewList = emptyList())
val data = dataState.data ?: SendData(sendViewList = emptyList())
mutableStateFlow.update {
it.copy(
viewState = data.toViewState(
@@ -2164,26 +2164,29 @@ class VaultAddEditViewModel @Inject constructor(
private fun handleVaultDataReceive(action: VaultAddEditAction.Internal.VaultDataReceive) {
when (val vaultDataState = action.vaultData) {
is DataState.Error -> {
mutableStateFlow.update {
it.copy(
viewState = VaultAddEditState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
)
}
}
is DataState.Loaded -> {
viewModelScope.launch {
sendAction(
VaultAddEditAction.Internal.DetermineContentStateResultReceive(
vaultAddEditState = state.determineContentState(
vaultData = vaultDataState.data,
userData = action.userData,
),
),
)
}
vaultDataState
.data?.let {
viewModelScope.launch {
sendAction(
VaultAddEditAction.Internal.DetermineContentStateResultReceive(
vaultAddEditState = state.determineContentState(
vaultData = it,
userData = action.userData,
),
),
)
}
}
?: run {
// No data to display, so let's just show the error state
mutableStateFlow.update {
it.copy(
viewState = VaultAddEditState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
)
}
}
}
DataState.Loading -> {
@@ -2196,24 +2199,14 @@ class VaultAddEditViewModel @Inject constructor(
}
}
is DataState.NoNetwork -> {
is DataState.NoNetwork,
is DataState.Pending,
is DataState.Loaded,
-> {
viewModelScope.launch {
sendAction(
VaultAddEditAction.Internal.DetermineContentStateResultReceive(
state.determineContentState(
vaultData = vaultDataState.data,
userData = action.userData,
),
),
)
}
}
is DataState.Pending -> {
viewModelScope.launch {
sendAction(
VaultAddEditAction.Internal.DetermineContentStateResultReceive(
state.determineContentState(
vaultAddEditState = state.determineContentState(
vaultData = vaultDataState.data,
userData = action.userData,
),
@@ -48,6 +48,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.milliseconds
@Suppress("LargeClass")
class SendViewModelTest : BaseViewModelTest() {
@@ -550,26 +551,58 @@ class SendViewModelTest : BaseViewModelTest() {
assertEquals(initialState.copy(dialogState = null), viewModel.stateFlow.value)
}
@Suppress("MaxLineLength")
@Test
fun `VaultRepository SendData Error should update view state to Error`() = runTest {
val dialogState = SendState.DialogState.Loading(BitwardenString.syncing.asText())
val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState))
fun `VaultRepository SendData Error should update view state to Error when there is no data`() =
runTest {
val dialogState = SendState.DialogState.Loading(BitwardenString.syncing.asText())
val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState))
viewModel.eventFlow.test {
mutableSendDataFlow.value = DataState.Error(Throwable("Fail"))
viewModel.eventFlow.test {
mutableSendDataFlow.value = DataState.Error(Throwable("Fail"))
}
assertEquals(
DEFAULT_STATE.copy(
viewState = SendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
dialogState = null,
isRefreshing = false,
),
viewModel.stateFlow.value,
)
}
assertEquals(
DEFAULT_STATE.copy(
viewState = SendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
@Suppress("MaxLineLength")
@Test
fun `VaultRepository SendData Error should update view state to data view state when there is data`() =
runTest {
val dialogState = SendState.DialogState.Loading(BitwardenString.syncing.asText())
val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState))
val viewState = mockk<SendState.ViewState.Content>()
val sendData = mockk<SendData> {
every {
toViewState(Environment.Us.environmentUrlData.baseWebSendUrl)
} returns viewState
}
viewModel.eventFlow.test {
mutableSendDataFlow.value = DataState.Error(
error = Throwable("Fail"),
data = sendData,
)
}
assertEquals(
DEFAULT_STATE.copy(
viewState = viewState,
dialogState = null,
isRefreshing = false,
),
dialogState = null,
isRefreshing = false,
),
viewModel.stateFlow.value,
)
}
viewModel.stateFlow.value,
)
}
@Test
fun `VaultRepository SendData Loaded should update view state`() = runTest {
@@ -656,7 +689,7 @@ class SendViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
viewModel.trySendAction(SendAction.RefreshPull)
advanceTimeBy(300)
advanceTimeBy(300.milliseconds)
verify(exactly = 1) {
vaultRepo.sync(forced = false)
}
@@ -671,7 +704,7 @@ class SendViewModelTest : BaseViewModelTest() {
} returns false
viewModel.trySendAction(SendAction.RefreshPull)
advanceTimeBy(300)
advanceTimeBy(300.milliseconds)
assertEquals(
DEFAULT_STATE.copy(
isRefreshing = false,
@@ -545,6 +545,40 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `vault data Error with data should determine the content state from the available data`() =
runTest {
val stateWithName = createVaultAddItemState(
commonContentViewState = createCommonContentViewState(name = "mockName-1"),
)
mutableVaultDataFlow.value = DataState.Error(
error = Throwable("Fail"),
data = createVaultData(),
)
val viewModel = createAddVaultItemViewModel(
createSavedStateHandleWithState(
state = stateWithName,
vaultAddEditType = VaultAddEditType.AddItem,
vaultItemCipherType = VaultItemCipherType.LOGIN,
),
)
assertEquals(stateWithName, viewModel.stateFlow.value)
}
@Test
fun `vault data Error without data should update view state to Error`() = runTest {
mutableVaultDataFlow.value = DataState.Error(error = Throwable("Fail"))
val viewModel = createAddVaultItemViewModel()
assertEquals(
VaultAddEditState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
viewModel.stateFlow.value.viewState,
)
}
@Test
fun `CloseClick should emit NavigateBack`() = runTest {
val viewModel = createAddVaultItemViewModel()
@@ -6526,40 +6560,36 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
initialState: VaultAddEditState,
type: CustomFieldType,
) {
lateinit var expectedCustomField: VaultAddEditState.Custom
lateinit var action: VaultAddEditAction.Common
lateinit var expectedState: VaultAddEditState.ViewState.Content
when (type) {
val expectedCustomField: VaultAddEditState.Custom = when (type) {
CustomFieldType.LINKED -> {
expectedCustomField = VaultAddEditState.Custom.LinkedField(
"TestId 4",
"Linked Field",
VaultLinkedFieldType.PASSWORD,
VaultAddEditState.Custom.LinkedField(
itemId = "TestId 4",
name = "Linked Field",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
)
}
CustomFieldType.HIDDEN -> {
expectedCustomField = VaultAddEditState.Custom.HiddenField(
"TestId 2",
"Test Hidden",
"Updated Test Text",
VaultAddEditState.Custom.HiddenField(
itemId = "TestId 2",
name = "Test Hidden",
value = "Updated Test Text",
)
}
CustomFieldType.BOOLEAN -> {
expectedCustomField = VaultAddEditState.Custom.BooleanField(
"TestId 3",
"Boolean Field",
false,
VaultAddEditState.Custom.BooleanField(
itemId = "TestId 3",
name = "Boolean Field",
value = false,
)
}
CustomFieldType.TEXT -> {
expectedCustomField = VaultAddEditState.Custom.TextField(
"TestId 1",
"Test Text",
"Updated Test Text",
VaultAddEditState.Custom.TextField(
itemId = "TestId 1",
name = "Test Text",
value = "Updated Test Text",
)
}
}
@@ -6574,13 +6604,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
val currentContentState =
(viewModel.stateFlow.value.viewState as VaultAddEditState.ViewState.Content)
action = VaultAddEditAction.Common.CustomFieldValueChange(expectedCustomField)
expectedState = currentContentState
.copy(
common = currentContentState.common.copy(
customFieldData = listOf(expectedCustomField),
),
)
val action = VaultAddEditAction.Common.CustomFieldValueChange(expectedCustomField)
val expectedState = currentContentState.copy(
common = currentContentState.common.copy(
customFieldData = listOf(expectedCustomField),
),
)
viewModel.trySendAction(action)