PM-18570 Update Owner Selection Field to Bottom Sheet Selector (#4810)

This commit is contained in:
Phil Cappelli
2025-03-04 17:24:31 -05:00
committed by GitHub
parent 39787f9bf0
commit 35e585a60e
7 changed files with 347 additions and 130 deletions

View File

@@ -22,7 +22,6 @@ import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextSelectionB
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
import com.x8bit.bitwarden.ui.platform.components.coachmark.CoachMarkScope import com.x8bit.bitwarden.ui.platform.components.coachmark.CoachMarkScope
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
@@ -33,7 +32,6 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonH
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
import kotlinx.collections.immutable.toImmutableList
/** /**
* The top level content UI state for the [VaultAddEditScreen]. * The top level content UI state for the [VaultAddEditScreen].
@@ -169,15 +167,10 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
if (isAddItemMode && state.common.hasOrganizations) { if (isAddItemMode && state.common.hasOrganizations) {
val collections = state.common.selectedOwner?.collections.orEmpty() val collections = state.common.selectedOwner?.collections.orEmpty()
item { item {
BitwardenMultiSelectButton( BitwardenTextSelectionButton(
label = stringResource(id = R.string.owner), label = stringResource(id = R.string.owner),
options = state.common.availableOwners.map { it.name }.toImmutableList(),
selectedOption = state.common.selectedOwner?.name, selectedOption = state.common.selectedOwner?.name,
onOptionSelected = { selectedOwnerName -> onClick = commonTypeHandlers.onPresentOwnerOptions,
commonTypeHandlers.onOwnerSelected(
state.common.availableOwners.first { it.name == selectedOwnerName },
)
},
cardStyle = if (collections.isNotEmpty()) { cardStyle = if (collections.isNotEmpty()) {
CardStyle.Middle() CardStyle.Middle()
} else { } else {

View File

@@ -44,6 +44,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.base.util.toListItemCardStyle
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
@@ -420,10 +421,11 @@ fun VaultAddEditScreen(
.imePadding() .imePadding()
.fillMaxSize(), .fillMaxSize(),
) )
FolderSelectionBottomSheet(
state = viewState.common, BottomSheetViews(
bottomSheetState = state.bottomSheetState,
viewState = viewState.common,
handlers = commonTypeHandlers, handlers = commonTypeHandlers,
showBottomSheet = state.shouldShowFolderSelectionBottomSheet,
) )
} }
@@ -549,12 +551,39 @@ private fun VaultAddEditItemDialogs(
} }
} }
@Composable
private fun BottomSheetViews(
bottomSheetState: VaultAddEditState.BottomSheetState?,
viewState: VaultAddEditState.ViewState.Content.Common,
handlers: VaultAddEditCommonHandlers,
modifier: Modifier = Modifier,
) {
when (bottomSheetState) {
is VaultAddEditState.BottomSheetState.FolderSelection -> {
FolderSelectionBottomSheet(
state = viewState,
handlers = handlers,
modifier = modifier,
)
}
is VaultAddEditState.BottomSheetState.OwnerSelection -> {
OwnerSelectionBottomSheet(
state = viewState,
handlers = handlers,
modifier = modifier,
)
}
null -> Unit
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun FolderSelectionBottomSheet( private fun FolderSelectionBottomSheet(
state: VaultAddEditState.ViewState.Content.Common, state: VaultAddEditState.ViewState.Content.Common,
handlers: VaultAddEditCommonHandlers, handlers: VaultAddEditCommonHandlers,
showBottomSheet: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var selectedOptionState by rememberSaveable { var selectedOptionState by rememberSaveable {
@@ -562,12 +591,12 @@ private fun FolderSelectionBottomSheet(
} }
BitwardenModalBottomSheet( BitwardenModalBottomSheet(
sheetTitle = stringResource(R.string.folders), sheetTitle = stringResource(R.string.folders),
onDismiss = handlers.onDismissFolderSelectionSheet, onDismiss = handlers.onDismissBottomSheet,
topBarActions = { animatedOnDismiss -> topBarActions = { animatedOnDismiss ->
BitwardenTextButton( BitwardenTextButton(
label = stringResource(R.string.save), label = stringResource(R.string.save),
onClick = { onClick = {
handlers.onDismissFolderSelectionSheet() handlers.onDismissBottomSheet()
state state
.availableFolders .availableFolders
.firstOrNull { .firstOrNull {
@@ -584,7 +613,6 @@ private fun FolderSelectionBottomSheet(
isEnabled = selectedOptionState.isNotBlank(), isEnabled = selectedOptionState.isNotBlank(),
) )
}, },
showBottomSheet = showBottomSheet,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = modifier.statusBarsPadding(), modifier = modifier.statusBarsPadding(),
) { ) {
@@ -698,3 +726,98 @@ private fun FolderSelectionBottomSheetContent(
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun OwnerSelectionBottomSheet(
state: VaultAddEditState.ViewState.Content.Common,
handlers: VaultAddEditCommonHandlers,
modifier: Modifier = Modifier,
) {
var selectedOptionState by rememberSaveable {
mutableStateOf(state.selectedOwner?.name.orEmpty())
}
BitwardenModalBottomSheet(
sheetTitle = stringResource(R.string.owner),
onDismiss = handlers.onDismissBottomSheet,
topBarActions = { animatedOnDismiss ->
BitwardenTextButton(
label = stringResource(R.string.save),
onClick = {
handlers.onDismissBottomSheet()
state
.availableOwners
.firstOrNull {
it.name == selectedOptionState
}
?.run {
handlers.onOwnerSelected(this.id)
}
animatedOnDismiss()
},
isEnabled = selectedOptionState.isNotBlank(),
)
},
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = modifier.statusBarsPadding(),
) {
OwnerSelectionBottomSheetContent(
options = state.availableOwners.map { it.name }.toImmutableList(),
selectedOption = selectedOptionState,
onOptionSelected = {
selectedOptionState = it
},
)
}
}
@Composable
private fun OwnerSelectionBottomSheetContent(
options: ImmutableList<String>,
selectedOption: String,
onOptionSelected: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier
.standardHorizontalMargin(),
) {
item {
Spacer(modifier = Modifier.height(12.dp))
}
itemsIndexed(options) { index, option ->
Row(
modifier = Modifier
.fillMaxWidth()
.cardStyle(
cardStyle = options.toListItemCardStyle(index = index),
onClick = {
onOptionSelected(option)
},
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = option,
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyLarge,
modifier = Modifier.padding(horizontal = 16.dp),
)
BitwardenRadioButton(
isSelected = selectedOption == option,
onClick = {
onOptionSelected(option)
},
)
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
item {
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
}

View File

@@ -179,13 +179,13 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
}, },
dialog = dialogState, dialog = dialogState,
bottomSheetState = null,
totpData = totpData, totpData = totpData,
// Set special conditions for autofill and fido2 save // Set special conditions for autofill and fido2 save
shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null, shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
shouldExitOnSave = shouldExitOnSave, shouldExitOnSave = shouldExitOnSave,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldClearSpecialCircumstance = autofillSelectionData == null, shouldClearSpecialCircumstance = autofillSelectionData == null,
shouldShowFolderSelectionBottomSheet = false,
) )
}, },
) { ) {
@@ -352,9 +352,6 @@ class VaultAddEditViewModel @Inject constructor(
} }
is VaultAddEditAction.Common.FolderChange -> handleFolderTextInputChange(action) is VaultAddEditAction.Common.FolderChange -> handleFolderTextInputChange(action)
VaultAddEditAction.Common.DismissFolderSelectionBottomSheet -> {
handleDismissFolderSelectionBottomSheet()
}
VaultAddEditAction.Common.SelectOrAddFolderForItem -> { VaultAddEditAction.Common.SelectOrAddFolderForItem -> {
handleSelectOrAddFolderForItem() handleSelectOrAddFolderForItem()
@@ -363,6 +360,14 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditAction.Common.AddNewFolder -> { is VaultAddEditAction.Common.AddNewFolder -> {
handleAddNewFolder(action) handleAddNewFolder(action)
} }
VaultAddEditAction.Common.SelectOwnerForItem -> {
handleSelectOwnerForItem()
}
VaultAddEditAction.Common.DismissBottomSheet -> {
handleDismissBottomSheet()
}
} }
} }
@@ -722,15 +727,27 @@ class VaultAddEditViewModel @Inject constructor(
showFido2ErrorDialog() showFido2ErrorDialog()
} }
private fun handleDismissFolderSelectionBottomSheet() { private fun handleSelectOrAddFolderForItem() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = false) it.copy(
bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection,
)
} }
} }
private fun handleSelectOrAddFolderForItem() { private fun handleSelectOwnerForItem() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = true) it.copy(
bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection,
)
}
}
private fun handleDismissBottomSheet() {
mutableStateFlow.update {
it.copy(
bottomSheetState = null,
)
} }
} }
@@ -882,7 +899,7 @@ class VaultAddEditViewModel @Inject constructor(
action: VaultAddEditAction.Common.OwnershipChange, action: VaultAddEditAction.Common.OwnershipChange,
) { ) {
updateCommonContent { commonContent -> updateCommonContent { commonContent ->
commonContent.copy(selectedOwnerId = action.ownership.id) commonContent.copy(selectedOwnerId = action.ownerId)
} }
} }
@@ -2035,7 +2052,7 @@ data class VaultAddEditState(
val cipherType: VaultItemCipherType, val cipherType: VaultItemCipherType,
val viewState: ViewState, val viewState: ViewState,
val dialog: DialogState?, val dialog: DialogState?,
val shouldShowFolderSelectionBottomSheet: Boolean, val bottomSheetState: BottomSheetState?,
val shouldShowCloseButton: Boolean = true, val shouldShowCloseButton: Boolean = true,
// Internal // Internal
val shouldExitOnSave: Boolean = false, val shouldExitOnSave: Boolean = false,
@@ -2489,6 +2506,23 @@ data class VaultAddEditState(
val collections: List<VaultCollection>, val collections: List<VaultCollection>,
) : Parcelable ) : Parcelable
/**
* Displays a bottom sheet.
*/
sealed class BottomSheetState : Parcelable {
/**
* Displays a folder selection bottom sheet.
*/
@Parcelize
data object FolderSelection : BottomSheetState()
/**
* Displays a owner selection bottom sheet.
*/
@Parcelize
data object OwnerSelection : BottomSheetState()
}
/** /**
* Displays a dialog. * Displays a dialog.
*/ */
@@ -2755,11 +2789,11 @@ sealed class VaultAddEditAction {
data class NotesTextChange(val notes: String) : Common() data class NotesTextChange(val notes: String) : Common()
/** /**
* Fired when the ownership text input is changed. * Fired when the owner text input is changed.
* *
* @property ownership The new ownership text. * @property ownerId The new owner id.
*/ */
data class OwnershipChange(val ownership: VaultAddEditState.Owner) : Common() data class OwnershipChange(val ownerId: String?) : Common()
/** /**
* Represents the action to add a new custom field. * Represents the action to add a new custom field.
@@ -2881,9 +2915,14 @@ sealed class VaultAddEditAction {
data object SelectOrAddFolderForItem : Common() data object SelectOrAddFolderForItem : Common()
/** /**
* The user has dismissed the folder selection bottom sheet. * The user has clicked on owner selection card for the item.
*/ */
data object DismissFolderSelectionBottomSheet : Common() data object SelectOwnerForItem : Common()
/**
* The user has dismissed the current bottom sheet.
*/
data object DismissBottomSheet : Common()
/** /**
* The user has selected to add a new folder to associate with the item. * The user has selected to add a new folder to associate with the item.

View File

@@ -17,12 +17,18 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCollection
* @property onToggleMasterPasswordReprompt Handles the action when the master password * @property onToggleMasterPasswordReprompt Handles the action when the master password
* reprompt toggle is changed. * reprompt toggle is changed.
* @property onNotesTextChange Handles the action when the notes text is changed. * @property onNotesTextChange Handles the action when the notes text is changed.
* @property onOwnerSelected Handles the action when a owner is selected. * @property onPresentOwnerOptions Handles showing the list of ownership options.
* @property onOwnerSelected Handles the action when a owner is select.
* @property onTooltipClick Handles the action when the tooltip button is clicked. * @property onTooltipClick Handles the action when the tooltip button is clicked.
* @property onAddNewCustomFieldClick Handles the action when the add new custom field * @property onAddNewCustomFieldClick Handles the action when the add new custom field
* button is clicked. * button is clicked.
* @property onCustomFieldValueChange Handles the action when the field's value changes. * @property onCustomFieldValueChange Handles the action when the field's value changes.
* @property onCollectionSelect Handles the action when a collection is selected. * @property onCollectionSelect Handles the action when a collection is selected.
* @property onHiddenFieldVisibilityChange Handles the action when the hidden field visibility
* @property onSelectOrAddFolderForItem Handles the action when a folder is selected.
* @property onChangeToExistingFolder Handles the action when the folder is changed.
* @property onOnAddFolder Handles the action when a new folder is added.
* @property onDismissBottomSheet Handles when the current bottom sheet is dismissed.
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList")
data class VaultAddEditCommonHandlers( data class VaultAddEditCommonHandlers(
@@ -30,7 +36,8 @@ data class VaultAddEditCommonHandlers(
val onToggleFavorite: (Boolean) -> Unit, val onToggleFavorite: (Boolean) -> Unit,
val onToggleMasterPasswordReprompt: (Boolean) -> Unit, val onToggleMasterPasswordReprompt: (Boolean) -> Unit,
val onNotesTextChange: (String) -> Unit, val onNotesTextChange: (String) -> Unit,
val onOwnerSelected: (VaultAddEditState.Owner) -> Unit, val onPresentOwnerOptions: () -> Unit,
val onOwnerSelected: (String?) -> Unit,
val onTooltipClick: () -> Unit, val onTooltipClick: () -> Unit,
val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit, val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit,
val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit,
@@ -38,9 +45,9 @@ data class VaultAddEditCommonHandlers(
val onCollectionSelect: (VaultCollection) -> Unit, val onCollectionSelect: (VaultCollection) -> Unit,
val onHiddenFieldVisibilityChange: (Boolean) -> Unit, val onHiddenFieldVisibilityChange: (Boolean) -> Unit,
val onSelectOrAddFolderForItem: () -> Unit, val onSelectOrAddFolderForItem: () -> Unit,
val onDismissFolderSelectionSheet: () -> Unit,
val onChangeToExistingFolder: (String?) -> Unit, val onChangeToExistingFolder: (String?) -> Unit,
val onOnAddFolder: (String) -> Unit, val onOnAddFolder: (String) -> Unit,
val onDismissBottomSheet: () -> Unit,
) { ) {
@Suppress("UndocumentedPublicClass") @Suppress("UndocumentedPublicClass")
companion object { companion object {
@@ -62,6 +69,11 @@ data class VaultAddEditCommonHandlers(
VaultAddEditAction.Common.SelectOrAddFolderForItem, VaultAddEditAction.Common.SelectOrAddFolderForItem,
) )
}, },
onPresentOwnerOptions = {
viewModel.trySendAction(
VaultAddEditAction.Common.SelectOwnerForItem,
)
},
onToggleFavorite = { isFavorite -> onToggleFavorite = { isFavorite ->
viewModel.trySendAction( viewModel.trySendAction(
VaultAddEditAction.Common.ToggleFavorite(isFavorite), VaultAddEditAction.Common.ToggleFavorite(isFavorite),
@@ -79,9 +91,11 @@ data class VaultAddEditCommonHandlers(
VaultAddEditAction.Common.NotesTextChange(newNotes), VaultAddEditAction.Common.NotesTextChange(newNotes),
) )
}, },
onOwnerSelected = { newOwnership -> onOwnerSelected = {
viewModel.trySendAction( viewModel.trySendAction(
VaultAddEditAction.Common.OwnershipChange(newOwnership), VaultAddEditAction.Common.OwnershipChange(
ownerId = it,
),
) )
}, },
onTooltipClick = { onTooltipClick = {
@@ -124,11 +138,6 @@ data class VaultAddEditCommonHandlers(
VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = it), VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = it),
) )
}, },
onDismissFolderSelectionSheet = {
viewModel.trySendAction(
VaultAddEditAction.Common.DismissFolderSelectionBottomSheet,
)
},
onChangeToExistingFolder = { onChangeToExistingFolder = {
viewModel.trySendAction( viewModel.trySendAction(
VaultAddEditAction.Common.FolderChange( VaultAddEditAction.Common.FolderChange(
@@ -143,6 +152,11 @@ data class VaultAddEditCommonHandlers(
), ),
) )
}, },
onDismissBottomSheet = {
viewModel.trySendAction(
VaultAddEditAction.Common.DismissBottomSheet,
)
},
) )
} }
} }

View File

@@ -2326,7 +2326,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
} }
@Test @Test
fun `clicking a Ownership option should send OwnershipChange action`() { fun `clicking a Ownership option should send SelectOwnerForItem action`() {
updateStateWithOwners() updateStateWithOwners()
// Opens the menu // Opens the menu
@@ -2336,26 +2336,82 @@ class VaultAddEditScreenTest : BaseComposeTest() {
) )
.performClick() .performClick()
// Choose the option from the menu
composeTestRule
.onAllNodesWithText(text = "mockOwnerName-2")
.onLast()
.performScrollTo()
.performClick()
verify { verify {
viewModel.trySendAction( viewModel.trySendAction(
VaultAddEditAction.Common.OwnershipChange( VaultAddEditAction.Common.SelectOwnerForItem,
VaultAddEditState.Owner(
id = "mockOwnerId-2",
name = "mockOwnerName-2",
collections = DEFAULT_COLLECTIONS,
),
),
) )
} }
} }
@Test
fun `should show owner selection bottom sheet when state updates to OwnerSelection`() {
mutableStateFlow.update {
it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection)
}
composeTestRule
.onNodeWithText("Owner")
.assertIsDisplayed()
}
@Test
fun `DismissOwnerSelectionBottomSheet action sent when bottom sheet close button click`() {
mutableStateFlow.update {
it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection)
}
composeTestRule
.onNodeWithText("Owner")
.assertIsDisplayed()
composeTestRule
.onAllNodesWithContentDescription("Close")
.filterToOne(hasAnySibling(hasText("Owner")))
.assertIsDisplayed()
.performSemanticsAction(SemanticsActions.OnClick)
dispatcher.advanceTimeByAndRunCurrent(1000L)
verify {
viewModel.trySendAction(VaultAddEditAction.Common.DismissBottomSheet)
}
}
@Test
fun `Selecting option and clicking save on owner sheet sends OwnershipChange action`() {
val ownerId = "1234"
val ownerName = "name"
mutableStateFlow.update { currentState ->
updateCommonContent(currentState) {
copy(
availableOwners =
listOf(
VaultAddEditState.Owner(
id = ownerId,
name = ownerName,
collections = DEFAULT_COLLECTIONS,
),
),
)
}
.copy(bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection)
}
composeTestRule
.onNodeWithText(ownerName)
.performSemanticsAction(SemanticsActions.OnClick)
composeTestRule
.onAllNodesWithText("Save")
.filterToOne(hasAnySibling(hasText("Owner")))
.assertIsDisplayed()
.performSemanticsAction(SemanticsActions.OnClick)
verify {
viewModel.trySendAction(VaultAddEditAction.Common.OwnershipChange(ownerId = ownerId))
}
}
@Test @Test
fun `the Ownership control should display the text provided by the state`() { fun `the Ownership control should display the text provided by the state`() {
updateStateWithOwners() updateStateWithOwners()
@@ -2504,9 +2560,9 @@ class VaultAddEditScreenTest : BaseComposeTest() {
} }
@Test @Test
fun `should show folder selection bottom sheet when state updates to true`() { fun `should show folder selection bottom sheet when state updates to FolderSelection`() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = true) it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection)
} }
composeTestRule composeTestRule
@@ -2521,7 +2577,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
@Test @Test
fun `DismissFolderSelectionBottomSheet action sent when bottom sheet close button click`() { fun `DismissFolderSelectionBottomSheet action sent when bottom sheet close button click`() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = true) it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection)
} }
composeTestRule composeTestRule
@@ -2537,7 +2593,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
dispatcher.advanceTimeByAndRunCurrent(1000L) dispatcher.advanceTimeByAndRunCurrent(1000L)
verify { verify {
viewModel.trySendAction(VaultAddEditAction.Common.DismissFolderSelectionBottomSheet) viewModel.trySendAction(VaultAddEditAction.Common.DismissBottomSheet)
} }
} }
@@ -2545,7 +2601,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
@Test @Test
fun `Clicking add folder button in bottom sheet hides add button and replaced with TextField`() { fun `Clicking add folder button in bottom sheet hides add button and replaced with TextField`() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = true) it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection)
} }
composeTestRule composeTestRule
@@ -2567,7 +2623,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
@Test @Test
fun `Editing the add folder text and clicking save send AddFolder action`() { fun `Editing the add folder text and clicking save send AddFolder action`() {
mutableStateFlow.update { mutableStateFlow.update {
it.copy(shouldShowFolderSelectionBottomSheet = true) it.copy(bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection)
} }
val newFolderName = "newFolderName" val newFolderName = "newFolderName"
@@ -2614,7 +2670,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
), ),
) )
} }
.copy(shouldShowFolderSelectionBottomSheet = true) .copy(bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection)
} }
composeTestRule composeTestRule
@@ -2790,39 +2846,6 @@ class VaultAddEditScreenTest : BaseComposeTest() {
.assertTextContains("NewNote") .assertTextContains("NewNote")
} }
@Test
fun `Ownership option should send OwnershipChange action`() {
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
updateStateWithOwners()
// Opens the menu
composeTestRule
.onNodeWithContentDescriptionAfterScroll(
label = "placeholder@email.com. Owner",
)
.performClick()
// Choose the option from the menu
composeTestRule
.onAllNodesWithText(text = "mockOwnerName-2")
.onLast()
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
VaultAddEditAction.Common.OwnershipChange(
VaultAddEditState.Owner(
id = "mockOwnerId-2",
name = "mockOwnerName-2",
collections = DEFAULT_COLLECTIONS,
),
),
)
}
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `in ItemType_SecureNotes the Ownership control should display the text provided by the state`() { fun `in ItemType_SecureNotes the Ownership control should display the text provided by the state`() {
@@ -4002,9 +4025,9 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = VaultAddEditState.DialogState.Generic(message = "test".asText()), dialog = VaultAddEditState.DialogState.Generic(message = "test".asText()),
bottomSheetState = null,
vaultAddEditType = VaultAddEditType.AddItem, vaultAddEditType = VaultAddEditType.AddItem,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_LOGIN = VaultAddEditState( private val DEFAULT_STATE_LOGIN = VaultAddEditState(
@@ -4016,8 +4039,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_IDENTITY = VaultAddEditState( private val DEFAULT_STATE_IDENTITY = VaultAddEditState(
@@ -4029,8 +4052,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_CARD = VaultAddEditState( private val DEFAULT_STATE_CARD = VaultAddEditState(
@@ -4042,8 +4065,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS = VaultAddEditState( private val DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS = VaultAddEditState(
@@ -4063,10 +4086,10 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
vaultAddEditType = VaultAddEditType.AddItem, vaultAddEditType = VaultAddEditType.AddItem,
cipherType = VaultItemCipherType.SECURE_NOTE, cipherType = VaultItemCipherType.SECURE_NOTE,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_SECURE_NOTES = VaultAddEditState( private val DEFAULT_STATE_SECURE_NOTES = VaultAddEditState(
@@ -4078,8 +4101,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val DEFAULT_STATE_SSH_KEYS = VaultAddEditState( private val DEFAULT_STATE_SSH_KEYS = VaultAddEditState(
@@ -4091,8 +4114,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
isIndividualVaultDisabled = false, isIndividualVaultDisabled = false,
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
private val ALTERED_COLLECTIONS = listOf( private val ALTERED_COLLECTIONS = listOf(

View File

@@ -207,11 +207,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
type = VaultAddEditState.ViewState.Content.ItemType.Login(), type = VaultAddEditState.ViewState.Content.ItemType.Login(),
), ),
dialog = null, dialog = null,
bottomSheetState = null,
totpData = null, totpData = null,
shouldShowCloseButton = true, shouldShowCloseButton = true,
shouldExitOnSave = false, shouldExitOnSave = false,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
) )
val viewModel = createAddVaultItemViewModel( val viewModel = createAddVaultItemViewModel(
savedStateHandle = createSavedStateHandleWithState( savedStateHandle = createSavedStateHandleWithState(
@@ -296,8 +296,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
type = VaultAddEditState.ViewState.Content.ItemType.Login(), type = VaultAddEditState.ViewState.Content.ItemType.Login(),
), ),
dialog = null, dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldShowFolderSelectionBottomSheet = false,
), ),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
@@ -3217,7 +3217,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
viewModel.trySendAction(action) viewModel.trySendAction(action)
val expectedState = vaultAddItemInitialState.copy( val expectedState = vaultAddItemInitialState.copy(
shouldShowFolderSelectionBottomSheet = true, bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection,
) )
assertEquals(expectedState, viewModel.stateFlow.value) assertEquals(expectedState, viewModel.stateFlow.value)
@@ -3226,18 +3226,18 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test @Test
fun `DismissFolderSelectionBottomSheet should update state to hide bottom sheet`() = fun `DismissFolderSelectionBottomSheet should update state to hide bottom sheet`() =
runTest { runTest {
val action = VaultAddEditAction.Common.DismissFolderSelectionBottomSheet val action = VaultAddEditAction.Common.DismissBottomSheet
viewModel.trySendAction(VaultAddEditAction.Common.SelectOrAddFolderForItem) viewModel.trySendAction(VaultAddEditAction.Common.SelectOrAddFolderForItem)
assertEquals( assertEquals(
vaultAddItemInitialState.copy( vaultAddItemInitialState.copy(
shouldShowFolderSelectionBottomSheet = true, bottomSheetState = VaultAddEditState.BottomSheetState.FolderSelection,
), ),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
val expectedState = vaultAddItemInitialState.copy( val expectedState = vaultAddItemInitialState.copy(
shouldShowFolderSelectionBottomSheet = false, bottomSheetState = null,
) )
viewModel.trySendAction(action) viewModel.trySendAction(action)
assertEquals( assertEquals(
@@ -3406,13 +3406,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test @Test
fun `OwnershipChange should update ownership`() = runTest { fun `OwnershipChange should update ownership`() = runTest {
val action = VaultAddEditAction.Common.OwnershipChange( val action = VaultAddEditAction.Common.OwnershipChange("mockId-1")
ownership = VaultAddEditState.Owner(
id = "mockId-1",
name = "a@b.com",
collections = emptyList(),
),
)
viewModel.trySendAction(action) viewModel.trySendAction(action)
@@ -3428,6 +3422,42 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(expectedState, viewModel.stateFlow.value) assertEquals(expectedState, viewModel.stateFlow.value)
} }
@Test
fun `SelectOwnerForItem should update state to show bottom sheet`() = runTest {
val action = VaultAddEditAction.Common.SelectOwnerForItem
viewModel.trySendAction(action)
val expectedState = vaultAddItemInitialState.copy(
bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection,
)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `DismissOwnerSelectionBottomSheet should update state to hide bottom sheet`() =
runTest {
val action = VaultAddEditAction.Common.DismissBottomSheet
viewModel.trySendAction(VaultAddEditAction.Common.SelectOwnerForItem)
assertEquals(
vaultAddItemInitialState.copy(
bottomSheetState = VaultAddEditState.BottomSheetState.OwnerSelection,
),
viewModel.stateFlow.value,
)
val expectedState = vaultAddItemInitialState.copy(
bottomSheetState = null,
)
viewModel.trySendAction(action)
assertEquals(
expectedState,
viewModel.stateFlow.value,
)
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `AddNewCustomFieldClick should allow a user to add a custom boolean field in Secure notes item`() = fun `AddNewCustomFieldClick should allow a user to add a custom boolean field in Secure notes item`() =
@@ -4437,6 +4467,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
shouldExitOnSave: Boolean = false, shouldExitOnSave: Boolean = false,
typeContentViewState: VaultAddEditState.ViewState.Content.ItemType = createLoginTypeContentViewState(), typeContentViewState: VaultAddEditState.ViewState.Content.ItemType = createLoginTypeContentViewState(),
dialogState: VaultAddEditState.DialogState? = null, dialogState: VaultAddEditState.DialogState? = null,
bottomSheetState: VaultAddEditState.BottomSheetState? = null,
totpData: TotpData? = null, totpData: TotpData? = null,
shouldClearSpecialCircumstance: Boolean = true, shouldClearSpecialCircumstance: Boolean = true,
): VaultAddEditState = ): VaultAddEditState =
@@ -4449,11 +4480,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
type = typeContentViewState, type = typeContentViewState,
), ),
dialog = dialogState, dialog = dialogState,
bottomSheetState = bottomSheetState,
shouldExitOnSave = shouldExitOnSave, shouldExitOnSave = shouldExitOnSave,
totpData = totpData, totpData = totpData,
shouldShowCoachMarkTour = false, shouldShowCoachMarkTour = false,
shouldClearSpecialCircumstance = shouldClearSpecialCircumstance, shouldClearSpecialCircumstance = shouldClearSpecialCircumstance,
shouldShowFolderSelectionBottomSheet = false,
) )
@Suppress("LongParameterList") @Suppress("LongParameterList")
@@ -4643,19 +4674,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
) )
private fun ownershipChangeAction(): VaultAddEditAction.Common.OwnershipChange = private fun ownershipChangeAction(): VaultAddEditAction.Common.OwnershipChange =
VaultAddEditAction.Common.OwnershipChange( VaultAddEditAction.Common.OwnershipChange("organizationId")
ownership = VaultAddEditState.Owner(
id = "organizationId",
name = "organizationName",
collections = listOf(
VaultCollection(
id = "mockId-1",
name = "mockName-1",
isSelected = false,
),
),
),
)
private fun collectionSelectAction(): VaultAddEditAction.Common.CollectionSelect = private fun collectionSelectAction(): VaultAddEditAction.Common.CollectionSelect =
VaultAddEditAction.Common.CollectionSelect( VaultAddEditAction.Common.CollectionSelect(

View File

@@ -311,6 +311,12 @@ tasks {
getByName("sonar") { getByName("sonar") {
dependsOn("check") dependsOn("check")
} }
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
jvmTarget = libs.versions.jvmTarget.get()
}
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>().configureEach {
jvmTarget = libs.versions.jvmTarget.get()
}
} }
private fun renameFile(path: String, newName: String) { private fun renameFile(path: String, newName: String) {