mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-458, BIT-459: Add Folder Saving/Editing/Deleting (#832)
This commit is contained in:
parent
a88f28e5bc
commit
0a6b0f8dc7
4 changed files with 557 additions and 25 deletions
|
@ -109,14 +109,16 @@ fun FolderAddEditScreen(
|
|||
{ viewModel.trySendAction(FolderAddEditAction.SaveClick) }
|
||||
},
|
||||
)
|
||||
BitwardenOverflowActionItem(
|
||||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = { shouldShowConfirmationDialog = true },
|
||||
if (state.shouldShowOverflowMenu) {
|
||||
BitwardenOverflowActionItem(
|
||||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = { shouldShowConfirmationDialog = true },
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
@ -3,10 +3,14 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -16,6 +20,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -65,6 +70,14 @@ class FolderAddEditViewModel @Inject constructor(
|
|||
is FolderAddEditAction.NameTextChange -> handleNameTextChange(action)
|
||||
is FolderAddEditAction.SaveClick -> handleSaveClick()
|
||||
is FolderAddEditAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
is FolderAddEditAction.Internal.CreateFolderResultReceive ->
|
||||
handleCreateFolderResultReceive(action)
|
||||
|
||||
is FolderAddEditAction.Internal.UpdateFolderResultReceive ->
|
||||
handleCreateFolderResultReceive(action)
|
||||
|
||||
is FolderAddEditAction.Internal.DeleteFolderResultReceive ->
|
||||
handleDeleteResultReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,12 +85,71 @@ class FolderAddEditViewModel @Inject constructor(
|
|||
sendEvent(FolderAddEditEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleSaveClick() {
|
||||
sendEvent(FolderAddEditEvent.ShowToast("Not yet implemented.".asText()))
|
||||
private fun handleSaveClick() = onContent { content ->
|
||||
if (content.folderName.isEmpty()) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
message = R.string.validation_field_required
|
||||
.asText(R.string.name.asText()),
|
||||
),
|
||||
)
|
||||
}
|
||||
return@onContent
|
||||
}
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Loading(
|
||||
R.string.saving.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
when (val folderAddEditType = state.folderAddEditType) {
|
||||
FolderAddEditType.AddItem -> {
|
||||
val result = vaultRepository.createFolder(
|
||||
FolderView(
|
||||
name = content.folderName,
|
||||
id = folderAddEditType.folderId,
|
||||
revisionDate = DateTime.now(),
|
||||
),
|
||||
)
|
||||
sendAction(FolderAddEditAction.Internal.CreateFolderResultReceive(result))
|
||||
}
|
||||
|
||||
is FolderAddEditType.EditItem -> {
|
||||
val result = vaultRepository.updateFolder(
|
||||
folderAddEditType.folderId,
|
||||
FolderView(
|
||||
name = content.folderName,
|
||||
id = folderAddEditType.folderId,
|
||||
revisionDate = DateTime.now(),
|
||||
),
|
||||
)
|
||||
sendAction(FolderAddEditAction.Internal.UpdateFolderResultReceive(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteClick() {
|
||||
sendEvent(FolderAddEditEvent.ShowToast("Not yet implemented.".asText()))
|
||||
val folderId = state.folderAddEditType.folderId ?: return
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Loading(
|
||||
R.string.deleting.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
val result =
|
||||
vaultRepository.deleteFolder(folderId = folderId)
|
||||
sendAction(FolderAddEditAction.Internal.DeleteFolderResultReceive(result))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDismissDialog() {
|
||||
|
@ -160,6 +232,84 @@ class FolderAddEditViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCreateFolderResultReceive(
|
||||
action: FolderAddEditAction.Internal.UpdateFolderResultReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = null)
|
||||
}
|
||||
|
||||
when (action.result) {
|
||||
is UpdateFolderResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is UpdateFolderResult.Success -> {
|
||||
sendEvent(FolderAddEditEvent.ShowToast(R.string.folder_updated.asText()))
|
||||
sendEvent(FolderAddEditEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCreateFolderResultReceive(
|
||||
action: FolderAddEditAction.Internal.CreateFolderResultReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = null)
|
||||
}
|
||||
|
||||
when (action.result) {
|
||||
is CreateFolderResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is CreateFolderResult.Success -> {
|
||||
sendEvent(FolderAddEditEvent.ShowToast(R.string.folder_created.asText()))
|
||||
sendEvent(FolderAddEditEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteResultReceive(
|
||||
action: FolderAddEditAction.Internal.DeleteFolderResultReceive,
|
||||
) {
|
||||
when (action.result) {
|
||||
DeleteFolderResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DeleteFolderResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialog = null) }
|
||||
sendEvent(FolderAddEditEvent.ShowToast(R.string.folder_deleted.asText()))
|
||||
sendEvent(event = FolderAddEditEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun onContent(
|
||||
crossinline block: (FolderAddEditState.ViewState.Content) -> Unit,
|
||||
) {
|
||||
(state.viewState as? FolderAddEditState.ViewState.Content)?.let(block)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,13 +326,19 @@ data class FolderAddEditState(
|
|||
val dialog: DialogState?,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Helper to determine whether we show the overflow menu.
|
||||
*/
|
||||
val shouldShowOverflowMenu: Boolean
|
||||
get() = folderAddEditType is FolderAddEditType.EditItem
|
||||
|
||||
/**
|
||||
* Helper to determine the screen display name.
|
||||
*/
|
||||
val screenDisplayName: Text
|
||||
get() = when (folderAddEditType) {
|
||||
FolderAddEditType.AddItem -> R.string.add_item.asText()
|
||||
is FolderAddEditType.EditItem -> R.string.edit_item.asText()
|
||||
FolderAddEditType.AddItem -> R.string.add_folder.asText()
|
||||
is FolderAddEditType.EditItem -> R.string.edit_folder.asText()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -289,6 +445,21 @@ sealed class FolderAddEditAction {
|
|||
*/
|
||||
sealed class Internal : FolderAddEditAction() {
|
||||
|
||||
/**
|
||||
* The result for deleting a folder has been received.
|
||||
*/
|
||||
data class DeleteFolderResultReceive(val result: DeleteFolderResult) : Internal()
|
||||
|
||||
/**
|
||||
* The result for updating a folder has been received.
|
||||
*/
|
||||
data class UpdateFolderResultReceive(val result: UpdateFolderResult) : Internal()
|
||||
|
||||
/**
|
||||
* The result for creating a folder has been received.
|
||||
*/
|
||||
data class CreateFolderResultReceive(val result: CreateFolderResult) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the vault items data has been received.
|
||||
*/
|
||||
|
|
|
@ -64,9 +64,34 @@ class FolderAddEditScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `overflow menu should only be displayed in edit mode`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.assertIsNotDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE_EDIT.copy(
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = "TestName",
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking overflow menu and delete, and cancel should dismiss the dialog`() {
|
||||
val deleteText = "Do you really want to delete? This cannot be undone."
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE_EDIT.copy(
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = "TestName",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Open the overflow menu
|
||||
composeTestRule
|
||||
|
@ -96,6 +121,14 @@ class FolderAddEditScreenTest : BaseComposeTest() {
|
|||
fun `clicking overflow menu and delete, and delete confirmation again should send a DeleteClick Action`() {
|
||||
val deleteText = "Do you really want to delete? This cannot be undone."
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE_EDIT.copy(
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = "TestName",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
|
|
|
@ -7,10 +7,15 @@ import com.bitwarden.core.FolderView
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.model.FolderAddEditType
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -78,13 +83,343 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `DeleteClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
fun `DeleteClick with DeleteFolderResult Success should emit toast and navigate back`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = DEFAULT_STATE.copy(
|
||||
folderAddEditType = FolderAddEditType.EditItem((DEFAULT_EDIT_ITEM_ID)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.deleteFolder(folderId = DEFAULT_EDIT_ITEM_ID)
|
||||
} returns DeleteFolderResult.Success
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FolderAddEditAction.DeleteClick)
|
||||
assertEquals(FolderAddEditEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
FolderAddEditEvent.ShowToast(R.string.folder_deleted.asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
FolderAddEditEvent.NavigateBack,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `DeleteClick with DeleteFolderResult Success should show dialog, and remove it once an item is deleted`() =
|
||||
runTest {
|
||||
val stateWithDialog = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.EditItem((DEFAULT_EDIT_ITEM_ID)),
|
||||
dialog = FolderAddEditState.DialogState.Loading(
|
||||
R.string.deleting.asText(),
|
||||
),
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithoutDialog = stateWithDialog.copy(
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = stateWithoutDialog,
|
||||
),
|
||||
)
|
||||
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.deleteFolder(folderId = DEFAULT_EDIT_ITEM_ID)
|
||||
} returns DeleteFolderResult.Success
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
viewModel.trySendAction(FolderAddEditAction.DeleteClick)
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
assertEquals(stateWithDialog, awaitItem())
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `DeleteClick should not call deleteFolder if no folderId is present`() =
|
||||
runTest {
|
||||
val state = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.AddItem,
|
||||
dialog = null,
|
||||
viewState = FolderAddEditState.ViewState.Error(
|
||||
R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = state,
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(FolderAddEditAction.DeleteClick)
|
||||
|
||||
coVerify(exactly = 0) {
|
||||
vaultRepository.deleteFolder(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeleteClick with DeleteFolderResult Failure should show an error dialog`() =
|
||||
runTest {
|
||||
val stateWithDialog = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.EditItem((DEFAULT_EDIT_ITEM_ID)),
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
R.string.generic_error_message.asText(),
|
||||
),
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithoutDialog = stateWithDialog.copy(
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = stateWithoutDialog,
|
||||
),
|
||||
)
|
||||
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.deleteFolder(folderId = DEFAULT_EDIT_ITEM_ID)
|
||||
} returns DeleteFolderResult.Error
|
||||
|
||||
viewModel.trySendAction(FolderAddEditAction.DeleteClick)
|
||||
|
||||
assertEquals(
|
||||
stateWithDialog,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with empty name should show an error dialog`() =
|
||||
runTest {
|
||||
val stateWithoutName = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.AddItem,
|
||||
dialog = null,
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = "",
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithDialog = stateWithoutName.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
R.string.validation_field_required
|
||||
.asText(R.string.name.asText()),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithoutName,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(stateWithoutName, viewModel.stateFlow.value)
|
||||
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
|
||||
assertEquals(stateWithDialog, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in add mode, SaveClick createFolder success should show dialog, and remove it once an item is saved`() =
|
||||
runTest {
|
||||
val stateWithDialog = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.AddItem,
|
||||
dialog = FolderAddEditState.DialogState.Loading(
|
||||
R.string.saving.asText(),
|
||||
),
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithoutDialog = stateWithDialog.copy(
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithoutDialog,
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.createFolder(any())
|
||||
} returns CreateFolderResult.Success(mockk())
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
assertEquals(stateWithDialog, awaitItem())
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in add mode, SaveClick createFolder error should show an error dialog`() = runTest {
|
||||
val state = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.AddItem,
|
||||
dialog = null,
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = state,
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.createFolder(any())
|
||||
} returns CreateFolderResult.Error
|
||||
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
|
||||
assertEquals(
|
||||
state.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in edit mode, SaveClick should show dialog, and remove it once an item is saved`() =
|
||||
runTest {
|
||||
val stateWithDialog = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
|
||||
dialog = FolderAddEditState.DialogState.Loading(
|
||||
R.string.saving.asText(),
|
||||
),
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithoutDialog = stateWithDialog.copy(
|
||||
folderAddEditType = FolderAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
|
||||
dialog = null,
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = stateWithoutDialog,
|
||||
),
|
||||
)
|
||||
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.updateFolder(any(), any())
|
||||
} returns UpdateFolderResult.Success(mockk())
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
assertEquals(stateWithDialog, awaitItem())
|
||||
assertEquals(stateWithoutDialog, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in edit mode, SaveClick updateFolder error should show an error dialog`() = runTest {
|
||||
val state = FolderAddEditState(
|
||||
folderAddEditType = FolderAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
|
||||
dialog = null,
|
||||
viewState = FolderAddEditState.ViewState.Content(
|
||||
folderName = DEFAULT_FOLDER_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = state,
|
||||
),
|
||||
)
|
||||
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.updateFolder(any(), any())
|
||||
} returns UpdateFolderResult.Error(errorMessage = null)
|
||||
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
|
||||
assertEquals(
|
||||
state.copy(
|
||||
dialog = FolderAddEditState.DialogState.Error(
|
||||
R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -121,15 +456,6 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FolderAddEditAction.SaveClick)
|
||||
assertEquals(FolderAddEditEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `folderStateFlow Error should update state to error`() {
|
||||
val viewModel = createViewModel(
|
||||
|
|
Loading…
Add table
Reference in a new issue