mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 04:19:54 +03:00
BIT-666: Create UI for Secure Note-type item creation (#319)
This commit is contained in:
parent
4ce89abbbf
commit
c729d7da1b
5 changed files with 996 additions and 37 deletions
|
@ -2,10 +2,12 @@ package com.x8bit.bitwarden.ui.vault.feature.additem
|
|||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
@ -55,7 +57,6 @@ fun VaultAddItemScreen(
|
|||
onNavigateBack: () -> Unit,
|
||||
viewModel: VaultAddItemViewModel = hiltViewModel(),
|
||||
) {
|
||||
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val scrollState = rememberScrollState()
|
||||
val context = LocalContext.current
|
||||
|
@ -74,10 +75,15 @@ fun VaultAddItemScreen(
|
|||
VaultAddLoginItemTypeHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val secureNotesTypeHandlers = remember(viewModel) {
|
||||
VaultAddSecureNotesItemTypeHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
|
@ -139,8 +145,11 @@ fun VaultAddItemScreen(
|
|||
// TODO(BIT-667): Create UI for identity-type item creation
|
||||
}
|
||||
|
||||
VaultAddItemState.ItemType.SecureNotes -> {
|
||||
// TODO(BIT-666): Create UI for secure notes type item creation
|
||||
is VaultAddItemState.ItemType.SecureNotes -> {
|
||||
AddSecureNotesTypeItemContent(
|
||||
state = selectedType,
|
||||
secureNotesTypeHandlers = secureNotesTypeHandlers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +182,7 @@ private fun TypeOptionsItem(
|
|||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun AddLoginTypeItemContent(
|
||||
private fun ColumnScope.AddLoginTypeItemContent(
|
||||
state: VaultAddItemState.ItemType.Login,
|
||||
loginItemTypeHandlers: VaultAddLoginItemTypeHandlers,
|
||||
) {
|
||||
|
@ -340,6 +349,7 @@ private fun AddLoginTypeItemContent(
|
|||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = state.notes,
|
||||
onValueChange = loginItemTypeHandlers.onNotesTextChange,
|
||||
|
@ -384,3 +394,125 @@ private fun AddLoginTypeItemContent(
|
|||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ColumnScope.AddSecureNotesTypeItemContent(
|
||||
state: VaultAddItemState.ItemType.SecureNotes,
|
||||
secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.name),
|
||||
value = state.name,
|
||||
onValueChange = secureNotesTypeHandlers.onNameTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.folder),
|
||||
options = state.availableFolders.map { it.invoke() }.toImmutableList(),
|
||||
selectedOption = state.folderName.invoke(),
|
||||
onOptionSelected = secureNotesTypeHandlers.onFolderTextChange,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.favorite),
|
||||
isChecked = state.favorite,
|
||||
onCheckedChange = secureNotesTypeHandlers.onToggleFavorite,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenSwitchWithActions(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = state.masterPasswordReprompt,
|
||||
onCheckedChange = secureNotesTypeHandlers.onToggleMasterPasswordReprompt,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
actions = {
|
||||
IconButton(onClick = secureNotesTypeHandlers.onTooltipClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_tooltip),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.notes),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = state.notes,
|
||||
onValueChange = secureNotesTypeHandlers.onNotesTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.custom_fields),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.new_custom_field),
|
||||
onClick = secureNotesTypeHandlers.onAddNewCustomFieldClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.ownership),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.who_owns_this_item),
|
||||
options = state.availableOwners.toImmutableList(),
|
||||
selectedOption = state.ownership,
|
||||
onOptionSelected = secureNotesTypeHandlers.onOwnershipTextChange,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
|
|
|
@ -7,9 +7,10 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.Card.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.Identity.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.SecureNotes.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -63,6 +64,10 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
handleAddLoginTypeAction(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType -> {
|
||||
handleAddSecureNoteTypeAction(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.Internal.CreateCipherResultReceive -> {
|
||||
handleCreateCipherResultReceive(action)
|
||||
}
|
||||
|
@ -75,13 +80,29 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
|
||||
private fun handleSaveClick() {
|
||||
viewModelScope.launch {
|
||||
sendAction(
|
||||
action = VaultAddItemAction.Internal.CreateCipherResultReceive(
|
||||
createCipherResult = vaultRepository.createCipher(
|
||||
cipherView = stateFlow.value.selectedType.toCipherView(),
|
||||
),
|
||||
),
|
||||
)
|
||||
when (state.selectedType) {
|
||||
is VaultAddItemState.ItemType.Login -> {
|
||||
sendAction(
|
||||
action = VaultAddItemAction.Internal.CreateCipherResultReceive(
|
||||
createCipherResult = vaultRepository.createCipher(
|
||||
cipherView = stateFlow.value.selectedType.toCipherView(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.ItemType.SecureNotes -> {
|
||||
// TODO Add Saving of SecureNotes (BIT-509)
|
||||
}
|
||||
|
||||
VaultAddItemState.ItemType.Card -> {
|
||||
// TODO Add Saving of SecureNotes (BIT-668)
|
||||
}
|
||||
|
||||
VaultAddItemState.ItemType.Identity -> {
|
||||
// TODO Add Saving of SecureNotes (BIT-508)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,6 +133,30 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddSecureNotesItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.SecureNotes(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddCardItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.Card,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddIdentityItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.Identity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Type Option Handlers
|
||||
|
||||
//region Add Login Item Type Handlers
|
||||
|
@ -191,30 +236,6 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddCardItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.Card,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddIdentityItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.Identity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchToAddSecureNotesItem() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
selectedType = VaultAddItemState.ItemType.SecureNotes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNameTextInputChange(
|
||||
action: VaultAddItemAction.ItemType.LoginType.NameTextChange,
|
||||
) {
|
||||
|
@ -369,6 +390,114 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
|
||||
//endregion Add Login Item Type Handlers
|
||||
|
||||
//region Secure Note Item Type Handlers
|
||||
|
||||
private fun handleAddSecureNoteTypeAction(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType,
|
||||
) {
|
||||
when (action) {
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.NameTextChange -> {
|
||||
handleSecureNoteNameTextInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.FolderChange -> {
|
||||
handleSecureNoteFolderTextInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite -> {
|
||||
handleSecureNoteToggleFavorite(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt -> {
|
||||
handleSecureNoteToggleMasterPasswordReprompt(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange -> {
|
||||
handleSecureNoteNotesTextInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange -> {
|
||||
handleSecureNoteOwnershipTextInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.TooltipClick -> {
|
||||
handleSecureNoteTooltipClick()
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick -> {
|
||||
handleSecureNoteAddNewCustomFieldClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteNameTextInputChange(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.NameTextChange,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(name = action.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteFolderTextInputChange(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.FolderChange,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(folderName = action.folderName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteToggleFavorite(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(favorite = action.isFavorite)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteToggleMasterPasswordReprompt(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(masterPasswordReprompt = action.isMasterPasswordReprompt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteNotesTextInputChange(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(notes = action.note)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteOwnershipTextInputChange(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange,
|
||||
) {
|
||||
updateSecureNoteType { secureNoteType ->
|
||||
secureNoteType.copy(ownership = action.ownership)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteTooltipClick() {
|
||||
// TODO Add the text for the prompt (BIT-1079)
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Not yet implemented",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleSecureNoteAddNewCustomFieldClick() {
|
||||
// TODO Implement custom text fields (BIT-529)
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Not yet implemented",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
//endregion Secure Notes Item Type Handlers
|
||||
|
||||
//region Internal Type Handlers
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -408,6 +537,23 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun updateSecureNoteType(
|
||||
crossinline block: (
|
||||
VaultAddItemState.ItemType.SecureNotes,
|
||||
) -> VaultAddItemState.ItemType.SecureNotes,
|
||||
) {
|
||||
mutableStateFlow.update { currentState ->
|
||||
val currentSelectedType = currentState.selectedType
|
||||
if (currentSelectedType !is VaultAddItemState.ItemType.SecureNotes) {
|
||||
return@update currentState
|
||||
}
|
||||
|
||||
val updatedSecureNote = block(currentSelectedType)
|
||||
|
||||
currentState.copy(selectedType = updatedSecureNote)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Utility Functions
|
||||
|
||||
companion object {
|
||||
|
@ -540,9 +686,28 @@ data class VaultAddItemState(
|
|||
* @property displayStringResId Resource ID for the display string of the secure notes type.
|
||||
*/
|
||||
@Parcelize
|
||||
data object SecureNotes : ItemType() {
|
||||
data class SecureNotes(
|
||||
val name: String = "",
|
||||
val folderName: Text = DEFAULT_FOLDER,
|
||||
val favorite: Boolean = false,
|
||||
val masterPasswordReprompt: Boolean = false,
|
||||
val notes: String = "",
|
||||
val ownership: String = DEFAULT_OWNERSHIP,
|
||||
val availableFolders: List<Text> = listOf(
|
||||
"Folder 1".asText(),
|
||||
"Folder 2".asText(),
|
||||
"Folder 3".asText(),
|
||||
),
|
||||
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
|
||||
) : ItemType() {
|
||||
|
||||
override val displayStringResId: Int
|
||||
get() = ItemTypeOption.SECURE_NOTES.labelRes
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
|
||||
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -703,6 +868,66 @@ sealed class VaultAddItemAction {
|
|||
*/
|
||||
data object AddNewCustomFieldClick : LoginType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents actions specific to the SecureNotes type.
|
||||
*/
|
||||
sealed class SecureNotesType : ItemType() {
|
||||
/**
|
||||
* Fired when the name text input is changed.
|
||||
*
|
||||
* @property name The new name text.
|
||||
*/
|
||||
data class NameTextChange(val name: String) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the folder text input is changed.
|
||||
*
|
||||
* @property folderName The new folder text.
|
||||
*/
|
||||
data class FolderChange(val folderName: Text) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the Favorite toggle is changed.
|
||||
*
|
||||
* @property isFavorite The new state of the Favorite toggle.
|
||||
*/
|
||||
data class ToggleFavorite(val isFavorite: Boolean) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the Master Password Reprompt toggle is changed.
|
||||
*
|
||||
* @property isMasterPasswordReprompt The new state of the Master
|
||||
* Password Re-prompt toggle.
|
||||
*/
|
||||
data class ToggleMasterPasswordReprompt(
|
||||
val isMasterPasswordReprompt: Boolean,
|
||||
) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the note text input is changed.
|
||||
*
|
||||
* @property note The new note text.
|
||||
*/
|
||||
data class NotesTextChange(val note: String) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the ownership text input is changed.
|
||||
*
|
||||
* @property ownership The new ownership text.
|
||||
*/
|
||||
data class OwnershipChange(val ownership: String) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Represents the action to open tooltip
|
||||
*/
|
||||
data object TooltipClick : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Represents the action to add a new custom field.
|
||||
*/
|
||||
data object AddNewCustomFieldClick : SecureNotesType()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
* within the context of adding secure note items to a vault.
|
||||
*
|
||||
* @property onNameTextChange Handles the action when the name text is changed.
|
||||
* @property onFolderTextChange Handles the action when the folder text is changed.
|
||||
* @property onToggleFavorite Handles the action when the favorite toggle is changed.
|
||||
* @property onToggleMasterPasswordReprompt Handles the action when the master password
|
||||
* reprompt toggle is changed.
|
||||
* @property onNotesTextChange Handles the action when the notes text is changed.
|
||||
* @property onOwnershipTextChange Handles the action when the ownership text is changed.
|
||||
* @property onTooltipClick Handles the action when the tooltip button is clicked.
|
||||
* @property onAddNewCustomFieldClick Handles the action when the add new custom field
|
||||
* button is clicked.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class VaultAddSecureNotesItemTypeHandlers(
|
||||
val onNameTextChange: (String) -> Unit,
|
||||
val onFolderTextChange: (String) -> Unit,
|
||||
val onToggleFavorite: (Boolean) -> Unit,
|
||||
val onToggleMasterPasswordReprompt: (Boolean) -> Unit,
|
||||
val onNotesTextChange: (String) -> Unit,
|
||||
val onOwnershipTextChange: (String) -> Unit,
|
||||
val onTooltipClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Creates an instance of [VaultAddSecureNotesItemTypeHandlers] by binding actions
|
||||
* to the provided [VaultAddItemViewModel].
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun create(viewModel: VaultAddItemViewModel): VaultAddSecureNotesItemTypeHandlers {
|
||||
return VaultAddSecureNotesItemTypeHandlers(
|
||||
onNameTextChange = { newName ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.NameTextChange(newName),
|
||||
)
|
||||
},
|
||||
onFolderTextChange = { newFolder ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.FolderChange(
|
||||
newFolder.asText(),
|
||||
),
|
||||
)
|
||||
},
|
||||
onToggleFavorite = { isFavorite ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite(isFavorite),
|
||||
)
|
||||
},
|
||||
onToggleMasterPasswordReprompt = { isMasterPasswordReprompt ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt(
|
||||
isMasterPasswordReprompt,
|
||||
),
|
||||
)
|
||||
},
|
||||
onNotesTextChange = { newNotes ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange(newNotes),
|
||||
)
|
||||
},
|
||||
onOwnershipTextChange = { newOwnership ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange(newOwnership),
|
||||
)
|
||||
},
|
||||
onTooltipClick = {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.TooltipClick,
|
||||
)
|
||||
},
|
||||
onAddNewCustomFieldClick = {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.test.performScrollTo
|
|||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.emptyFlow
|
|||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Test
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultAddItemScreenTest : BaseComposeTest() {
|
||||
private val mutableStateFlow = MutableStateFlow(
|
||||
VaultAddItemState(
|
||||
|
@ -625,6 +627,341 @@ class VaultAddItemScreenTest : BaseComposeTest() {
|
|||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state changing Name text field should trigger NameTextChange`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Name")
|
||||
.performTextInput(text = "TestName")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.NameTextChange(name = "TestName"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the name control should display the text provided by the state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Name")
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(name = "NewName") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Name")
|
||||
.assertTextContains("NewName")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state clicking a Folder Option should send FolderChange action`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Folder, No Folder")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Folder 1")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.FolderChange("Folder 1".asText()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the folder control should display the text provided by the state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Folder, No Folder")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(folderName = "Folder 2".asText()) }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Folder, Folder 2")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state, toggling the favorite toggle should send ToggleFavorite action`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Favorite")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite(
|
||||
isFavorite = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the favorite toggle should be enabled or disabled according to state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Favorite")
|
||||
.assertIsOff()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(favorite = true) }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Favorite")
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state, toggling the Master password re-prompt toggle should send ToggleMasterPasswordReprompt action`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Master password re-prompt")
|
||||
.performScrollTo()
|
||||
.performTouchInput {
|
||||
click(position = Offset(x = 1f, y = center.y))
|
||||
}
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt(
|
||||
isMasterPasswordReprompt = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the master password re-prompt toggle should be enabled or disabled according to state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Master password re-prompt")
|
||||
.assertIsOff()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(masterPasswordReprompt = true) }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Master password re-prompt")
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state, toggling the Master password re-prompt tooltip button should send TooltipClick action`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Master password re-prompt help")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.TooltipClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state changing Notes text field should trigger NotesTextChange`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasSetTextAction() and hasText("Notes"))
|
||||
.performScrollTo()
|
||||
.performTextInput("TestNotes")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange("TestNotes"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the Notes control should display the text provided by the state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasSetTextAction() and hasText("Notes"))
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(notes = "NewNote") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasSetTextAction() and hasText("Notes"))
|
||||
.assertTextContains("NewNote")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state clicking New Custom Field button should trigger AddNewCustomFieldClick`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "New custom field")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state clicking a Ownership option should send OwnershipChange action`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "a@b.com")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange("a@b.com"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the Ownership control should display the text provided by the state`() {
|
||||
mutableStateFlow.value =
|
||||
VaultAddItemState(selectedType = VaultAddItemState.ItemType.SecureNotes())
|
||||
|
||||
composeTestRule.setContent {
|
||||
VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {})
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateSecureNotesType(currentState) { copy(ownership = "Owner 2") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Who owns this item?, Owner 2")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
private fun updateLoginType(
|
||||
|
@ -638,5 +975,17 @@ class VaultAddItemScreenTest : BaseComposeTest() {
|
|||
return currentState.copy(selectedType = updatedType)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun updateSecureNotesType(
|
||||
currentState: VaultAddItemState,
|
||||
transform: VaultAddItemState.ItemType.SecureNotes.() -> VaultAddItemState.ItemType.SecureNotes,
|
||||
): VaultAddItemState {
|
||||
val updatedType = when (val currentType = currentState.selectedType) {
|
||||
is VaultAddItemState.ItemType.SecureNotes -> currentType.transform()
|
||||
else -> currentType
|
||||
}
|
||||
return currentState.copy(selectedType = updatedType)
|
||||
}
|
||||
|
||||
//endregion Helper functions
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import app.cash.turbine.test
|
|||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -59,6 +61,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(VaultAddItemEvent.ShowToast("Save Item Failure"), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TypeOptionSelect LOGIN should switch to LoginItem`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
@ -348,6 +351,149 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class VaultAddSecureNotesTypeItemActions {
|
||||
private lateinit var viewModel: VaultAddItemViewModel
|
||||
private lateinit var initialState: VaultAddItemState
|
||||
private lateinit var initialSavedStateHandle: SavedStateHandle
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
initialState = createVaultAddSecureNotesItemState()
|
||||
initialSavedStateHandle = createSavedStateHandleWithState(initialState)
|
||||
viewModel = VaultAddItemViewModel(
|
||||
savedStateHandle = initialSavedStateHandle,
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NameTextChange should update name in SecureNotesItem`() = runTest {
|
||||
val action = VaultAddItemAction.ItemType.SecureNotesType.NameTextChange("newName")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(name = "newName")
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `FolderChange should update folder in SecureNotesItem`() = runTest {
|
||||
val action = VaultAddItemAction.ItemType.SecureNotesType.FolderChange(
|
||||
"newFolder".asText(),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(folderName = "newFolder".asText())
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ToggleFavorite should update favorite in SecureNotesItem`() = runTest {
|
||||
val action = VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite(true)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(favorite = true)
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt in SecureNotesItem`() =
|
||||
runTest {
|
||||
val action =
|
||||
VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt(
|
||||
isMasterPasswordReprompt = true,
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(masterPasswordReprompt = true)
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `NotesTextChange should update notes in SecureNotesItem`() = runTest {
|
||||
val action =
|
||||
VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange(note = "newNotes")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(notes = "newNotes")
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `OwnershipChange should update ownership in SecureNotesItem`() = runTest {
|
||||
val action =
|
||||
VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange(ownership = "newOwner")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedSecureNotesItem =
|
||||
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
|
||||
.copy(ownership = "newOwner")
|
||||
|
||||
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TooltipClick should emit ShowToast with 'Tooltip' message`() = runTest {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
.actionChannel
|
||||
.trySend(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.TooltipClick,
|
||||
)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Not yet implemented"), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddNewCustomFieldClick should emit ShowToast with 'Add New Custom Field' message`() =
|
||||
runTest {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
.actionChannel
|
||||
.trySend(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Not yet implemented"), awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createVaultAddLoginItemState(
|
||||
name: String = "",
|
||||
|
@ -374,6 +520,26 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createVaultAddSecureNotesItemState(
|
||||
name: String = "",
|
||||
folder: Text = "No Folder".asText(),
|
||||
favorite: Boolean = false,
|
||||
masterPasswordReprompt: Boolean = false,
|
||||
notes: String = "",
|
||||
ownership: String = "placeholder@email.com",
|
||||
): VaultAddItemState =
|
||||
VaultAddItemState(
|
||||
selectedType = VaultAddItemState.ItemType.SecureNotes(
|
||||
name = name,
|
||||
folderName = folder,
|
||||
favorite = favorite,
|
||||
masterPasswordReprompt = masterPasswordReprompt,
|
||||
notes = notes,
|
||||
ownership = ownership,
|
||||
),
|
||||
)
|
||||
|
||||
private fun createSavedStateHandleWithState(state: VaultAddItemState) =
|
||||
SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
|
|
Loading…
Add table
Reference in a new issue