mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 03:49:36 +03:00
[PM-12922] Disable delete if user can't manage collection (#4179)
This commit is contained in:
parent
e397c036e4
commit
87d324b063
14 changed files with 503 additions and 13 deletions
|
@ -314,7 +314,7 @@ fun VaultAddEditScreen(
|
||||||
text = stringResource(id = R.string.delete),
|
text = stringResource(id = R.string.delete),
|
||||||
onClick = { pendingDeleteCipher = true },
|
onClick = { pendingDeleteCipher = true },
|
||||||
)
|
)
|
||||||
.takeUnless { state.isAddItemMode },
|
.takeUnless { state.isAddItemMode || !state.canDelete },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -149,7 +149,9 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
attestationOptions = fido2AttestationOptions,
|
attestationOptions = fido2AttestationOptions,
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
)
|
)
|
||||||
?: totpData?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?: totpData?.toDefaultAddTypeContent(
|
||||||
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
)
|
||||||
?: VaultAddEditState.ViewState.Content(
|
?: VaultAddEditState.ViewState.Content(
|
||||||
common = VaultAddEditState.ViewState.Content.Common(),
|
common = VaultAddEditState.ViewState.Content.Common(),
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
@ -1589,6 +1591,16 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
currentAccount = userData?.activeAccount,
|
currentAccount = userData?.activeAccount,
|
||||||
vaultAddEditType = vaultAddEditType,
|
vaultAddEditType = vaultAddEditType,
|
||||||
) { currentAccount, cipherView ->
|
) { currentAccount, cipherView ->
|
||||||
|
val canDelete = vaultData.collectionViewList
|
||||||
|
.none {
|
||||||
|
val itemIsInCollection = cipherView
|
||||||
|
?.collectionIds
|
||||||
|
?.contains(it.id) == true
|
||||||
|
|
||||||
|
val canManageCollection = it.manage
|
||||||
|
|
||||||
|
itemIsInCollection && !canManageCollection
|
||||||
|
}
|
||||||
// Derive the view state from the current Cipher for Edit mode
|
// Derive the view state from the current Cipher for Edit mode
|
||||||
// or use the current state for Add
|
// or use the current state for Add
|
||||||
(cipherView
|
(cipherView
|
||||||
|
@ -1598,6 +1610,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
totpData = totpData,
|
totpData = totpData,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = clock,
|
clock = clock,
|
||||||
|
canDelete = canDelete,
|
||||||
)
|
)
|
||||||
?: viewState)
|
?: viewState)
|
||||||
.appendFolderAndOwnerData(
|
.appendFolderAndOwnerData(
|
||||||
|
@ -2026,6 +2039,15 @@ data class VaultAddEditState(
|
||||||
*/
|
*/
|
||||||
val isCloneMode: Boolean get() = vaultAddEditType is VaultAddEditType.CloneItem
|
val isCloneMode: Boolean get() = vaultAddEditType is VaultAddEditType.CloneItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to determine if the UI should allow deletion of this item.
|
||||||
|
*/
|
||||||
|
val canDelete: Boolean
|
||||||
|
get() = (viewState as? ViewState.Content)
|
||||||
|
?.common
|
||||||
|
?.canDelete
|
||||||
|
?: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum representing the main type options for the vault, such as LOGIN, CARD, etc.
|
* Enum representing the main type options for the vault, such as LOGIN, CARD, etc.
|
||||||
*
|
*
|
||||||
|
@ -2085,6 +2107,7 @@ data class VaultAddEditState(
|
||||||
* @property selectedOwnerId The ID of the owner associated with the item.
|
* @property selectedOwnerId The ID of the owner associated with the item.
|
||||||
* @property availableOwners A list of available owners.
|
* @property availableOwners A list of available owners.
|
||||||
* @property hasOrganizations Indicates if the user is part of any organizations.
|
* @property hasOrganizations Indicates if the user is part of any organizations.
|
||||||
|
* @property canDelete Indicates whether the current user can delete the item.
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Common(
|
data class Common(
|
||||||
|
@ -2101,6 +2124,7 @@ data class VaultAddEditState(
|
||||||
val selectedOwnerId: String? = null,
|
val selectedOwnerId: String? = null,
|
||||||
val availableOwners: List<Owner> = emptyList(),
|
val availableOwners: List<Owner> = emptyList(),
|
||||||
val hasOrganizations: Boolean = false,
|
val hasOrganizations: Boolean = false,
|
||||||
|
val canDelete: Boolean = true,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,13 +35,14 @@ private const val PASSKEY_CREATION_TIME_PATTERN: String = "hh:mm a"
|
||||||
/**
|
/**
|
||||||
* Transforms [CipherView] into [VaultAddEditState.ViewState].
|
* Transforms [CipherView] into [VaultAddEditState.ViewState].
|
||||||
*/
|
*/
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "LongParameterList")
|
||||||
fun CipherView.toViewState(
|
fun CipherView.toViewState(
|
||||||
isClone: Boolean,
|
isClone: Boolean,
|
||||||
isIndividualVaultDisabled: Boolean,
|
isIndividualVaultDisabled: Boolean,
|
||||||
totpData: TotpData?,
|
totpData: TotpData?,
|
||||||
resourceManager: ResourceManager,
|
resourceManager: ResourceManager,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
|
canDelete: Boolean,
|
||||||
): VaultAddEditState.ViewState =
|
): VaultAddEditState.ViewState =
|
||||||
VaultAddEditState.ViewState.Content(
|
VaultAddEditState.ViewState.Content(
|
||||||
type = when (type) {
|
type = when (type) {
|
||||||
|
@ -108,6 +109,7 @@ fun CipherView.toViewState(
|
||||||
availableOwners = emptyList(),
|
availableOwners = emptyList(),
|
||||||
hasOrganizations = false,
|
hasOrganizations = false,
|
||||||
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
||||||
|
canDelete = canDelete,
|
||||||
),
|
),
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
)
|
)
|
||||||
|
|
|
@ -226,7 +226,8 @@ fun VaultItemScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
|
.takeIf { state.canDelete },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -82,7 +82,8 @@ class VaultItemViewModel @Inject constructor(
|
||||||
vaultRepository.getVaultItemStateFlow(state.vaultItemId),
|
vaultRepository.getVaultItemStateFlow(state.vaultItemId),
|
||||||
authRepository.userStateFlow,
|
authRepository.userStateFlow,
|
||||||
vaultRepository.getAuthCodeFlow(state.vaultItemId),
|
vaultRepository.getAuthCodeFlow(state.vaultItemId),
|
||||||
) { cipherViewState, userState, authCodeState ->
|
vaultRepository.collectionsStateFlow,
|
||||||
|
) { cipherViewState, userState, authCodeState, collectionsState ->
|
||||||
val totpCodeData = authCodeState.data?.let {
|
val totpCodeData = authCodeState.data?.let {
|
||||||
TotpCodeItemData(
|
TotpCodeItemData(
|
||||||
periodSeconds = it.periodSeconds,
|
periodSeconds = it.periodSeconds,
|
||||||
|
@ -96,14 +97,30 @@ class VaultItemViewModel @Inject constructor(
|
||||||
vaultDataState = combineDataStates(
|
vaultDataState = combineDataStates(
|
||||||
cipherViewState,
|
cipherViewState,
|
||||||
authCodeState,
|
authCodeState,
|
||||||
) { _, _ ->
|
collectionsState,
|
||||||
|
) { _, _, _ ->
|
||||||
// We are only combining the DataStates to know the overall state,
|
// We are only combining the DataStates to know the overall state,
|
||||||
// we map it to the appropriate value below.
|
// we map it to the appropriate value below.
|
||||||
}
|
}
|
||||||
.mapNullable {
|
.mapNullable {
|
||||||
|
// Deletion is not allowed when the item is in a collection that the user
|
||||||
|
// does not have "manage" permission for.
|
||||||
|
val canDelete = collectionsState.data
|
||||||
|
?.none {
|
||||||
|
val itemIsInCollection = cipherViewState.data
|
||||||
|
?.collectionIds
|
||||||
|
?.contains(it.id) == true
|
||||||
|
|
||||||
|
val canManageCollection = it.manage
|
||||||
|
|
||||||
|
itemIsInCollection && !canManageCollection
|
||||||
|
}
|
||||||
|
?: true
|
||||||
|
|
||||||
VaultItemStateData(
|
VaultItemStateData(
|
||||||
cipher = cipherViewState.data,
|
cipher = cipherViewState.data,
|
||||||
totpCodeItemData = totpCodeData,
|
totpCodeItemData = totpCodeData,
|
||||||
|
canDelete = canDelete,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -915,6 +932,7 @@ class VaultItemViewModel @Inject constructor(
|
||||||
isPremiumUser = account.isPremium,
|
isPremiumUser = account.isPremium,
|
||||||
hasMasterPassword = account.hasMasterPassword,
|
hasMasterPassword = account.hasMasterPassword,
|
||||||
totpCodeItemData = this.data?.totpCodeItemData,
|
totpCodeItemData = this.data?.totpCodeItemData,
|
||||||
|
canDelete = this.data?.canDelete == true,
|
||||||
)
|
)
|
||||||
?: VaultItemState.ViewState.Error(message = errorText)
|
?: VaultItemState.ViewState.Error(message = errorText)
|
||||||
|
|
||||||
|
@ -1153,6 +1171,14 @@ data class VaultItemState(
|
||||||
?.isNotEmpty()
|
?.isNotEmpty()
|
||||||
?: false
|
?: false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the cipher can be deleted.
|
||||||
|
*/
|
||||||
|
val canDelete: Boolean
|
||||||
|
get() = viewState.asContentOrNull()
|
||||||
|
?.common
|
||||||
|
?.canDelete == true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text to display on the deletion confirmation dialog.
|
* The text to display on the deletion confirmation dialog.
|
||||||
*/
|
*/
|
||||||
|
@ -1216,6 +1242,7 @@ data class VaultItemState(
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
val currentCipher: CipherView? = null,
|
val currentCipher: CipherView? = null,
|
||||||
val attachments: List<AttachmentItem>?,
|
val attachments: List<AttachmentItem>?,
|
||||||
|
val canDelete: Boolean,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,4 +11,5 @@ import com.bitwarden.vault.CipherView
|
||||||
data class VaultItemStateData(
|
data class VaultItemStateData(
|
||||||
val cipher: CipherView?,
|
val cipher: CipherView?,
|
||||||
val totpCodeItemData: TotpCodeItemData?,
|
val totpCodeItemData: TotpCodeItemData?,
|
||||||
|
val canDelete: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,13 +32,14 @@ private const val FIDO2_CREDENTIAL_CREATION_TIME_PATTERN: String = "h:mm a"
|
||||||
/**
|
/**
|
||||||
* Transforms [VaultData] into [VaultState.ViewState].
|
* Transforms [VaultData] into [VaultState.ViewState].
|
||||||
*/
|
*/
|
||||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
@Suppress("CyclomaticComplexMethod", "LongMethod", "LongParameterList")
|
||||||
fun CipherView.toViewState(
|
fun CipherView.toViewState(
|
||||||
previousState: VaultItemState.ViewState.Content?,
|
previousState: VaultItemState.ViewState.Content?,
|
||||||
isPremiumUser: Boolean,
|
isPremiumUser: Boolean,
|
||||||
hasMasterPassword: Boolean,
|
hasMasterPassword: Boolean,
|
||||||
totpCodeItemData: TotpCodeItemData?,
|
totpCodeItemData: TotpCodeItemData?,
|
||||||
clock: Clock = Clock.systemDefaultZone(),
|
clock: Clock = Clock.systemDefaultZone(),
|
||||||
|
canDelete: Boolean,
|
||||||
): VaultItemState.ViewState =
|
): VaultItemState.ViewState =
|
||||||
VaultItemState.ViewState.Content(
|
VaultItemState.ViewState.Content(
|
||||||
common = VaultItemState.ViewState.Content.Common(
|
common = VaultItemState.ViewState.Content.Common(
|
||||||
|
@ -79,6 +80,7 @@ fun CipherView.toViewState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.orEmpty(),
|
.orEmpty(),
|
||||||
|
canDelete = canDelete,
|
||||||
),
|
),
|
||||||
type = when (type) {
|
type = when (type) {
|
||||||
CipherType.LOGIN -> {
|
CipherType.LOGIN -> {
|
||||||
|
|
|
@ -5,7 +5,11 @@ import com.bitwarden.vault.CollectionView
|
||||||
/**
|
/**
|
||||||
* Create a mock [CollectionView] with a given [number].
|
* Create a mock [CollectionView] with a given [number].
|
||||||
*/
|
*/
|
||||||
fun createMockCollectionView(number: Int, name: String? = null): CollectionView =
|
fun createMockCollectionView(
|
||||||
|
number: Int,
|
||||||
|
name: String? = null,
|
||||||
|
manage: Boolean = true,
|
||||||
|
): CollectionView =
|
||||||
CollectionView(
|
CollectionView(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
organizationId = "mockOrganizationId-$number",
|
organizationId = "mockOrganizationId-$number",
|
||||||
|
@ -13,5 +17,5 @@ fun createMockCollectionView(number: Int, name: String? = null): CollectionView
|
||||||
name = name ?: "mockName-$number",
|
name = name ?: "mockName-$number",
|
||||||
externalId = "mockExternalId-$number",
|
externalId = "mockExternalId-$number",
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
manage = true,
|
manage = manage,
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,6 +45,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||||
|
@ -1195,6 +1196,136 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `in edit mode, canDelete should be false when cipher is in a collection the user cannot manage`() =
|
||||||
|
runTest {
|
||||||
|
val cipherView = createMockCipherView(1)
|
||||||
|
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||||
|
val stateWithName = createVaultAddItemState(
|
||||||
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
commonContentViewState = createCommonContentViewState(
|
||||||
|
name = "mockName-1",
|
||||||
|
originalCipher = cipherView,
|
||||||
|
customFieldData = listOf(
|
||||||
|
VaultAddEditState.Custom.HiddenField(
|
||||||
|
itemId = "testId",
|
||||||
|
name = "mockName-1",
|
||||||
|
value = "mockValue-1",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
notes = "mockNotes-1",
|
||||||
|
canDelete = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
every {
|
||||||
|
cipherView.toViewState(
|
||||||
|
isClone = false,
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
clock = fixedClock,
|
||||||
|
canDelete = false,
|
||||||
|
)
|
||||||
|
} returns stateWithName.viewState
|
||||||
|
|
||||||
|
mutableVaultDataFlow.value = DataState.Loaded(
|
||||||
|
data = createVaultData(
|
||||||
|
cipherView = cipherView,
|
||||||
|
collectionViewList = listOf(
|
||||||
|
createMockCollectionView(
|
||||||
|
number = 1,
|
||||||
|
manage = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
createAddVaultItemViewModel(
|
||||||
|
createSavedStateHandleWithState(
|
||||||
|
state = stateWithName,
|
||||||
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
cipherView.toViewState(
|
||||||
|
isClone = false,
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
clock = fixedClock,
|
||||||
|
canDelete = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `in edit mode, canDelete should be true when cipher is in a collection the user can manage`() =
|
||||||
|
runTest {
|
||||||
|
val cipherView = createMockCipherView(1)
|
||||||
|
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||||
|
val stateWithName = createVaultAddItemState(
|
||||||
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
commonContentViewState = createCommonContentViewState(
|
||||||
|
name = "mockName-1",
|
||||||
|
originalCipher = cipherView,
|
||||||
|
customFieldData = listOf(
|
||||||
|
VaultAddEditState.Custom.HiddenField(
|
||||||
|
itemId = "testId",
|
||||||
|
name = "mockName-1",
|
||||||
|
value = "mockValue-1",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
notes = "mockNotes-1",
|
||||||
|
canDelete = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
every {
|
||||||
|
cipherView.toViewState(
|
||||||
|
isClone = false,
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
|
)
|
||||||
|
} returns stateWithName.viewState
|
||||||
|
|
||||||
|
mutableVaultDataFlow.value = DataState.Loaded(
|
||||||
|
data = createVaultData(
|
||||||
|
cipherView = cipherView,
|
||||||
|
collectionViewList = listOf(
|
||||||
|
createMockCollectionView(
|
||||||
|
number = 1,
|
||||||
|
manage = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
createAddVaultItemViewModel(
|
||||||
|
createSavedStateHandleWithState(
|
||||||
|
state = stateWithName,
|
||||||
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
cipherView.toViewState(
|
||||||
|
isClone = false,
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `in edit mode, updateCipher success should ShowToast and NavigateBack`() =
|
fun `in edit mode, updateCipher success should ShowToast and NavigateBack`() =
|
||||||
runTest {
|
runTest {
|
||||||
|
@ -1310,6 +1441,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
mutableVaultDataFlow.value = DataState.Loaded(
|
mutableVaultDataFlow.value = DataState.Loaded(
|
||||||
|
@ -1341,6 +1473,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
||||||
}
|
}
|
||||||
|
@ -1375,6 +1508,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -1437,6 +1571,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -1502,6 +1637,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
mutableVaultDataFlow.value = DataState.Loaded(
|
mutableVaultDataFlow.value = DataState.Loaded(
|
||||||
|
@ -1558,6 +1694,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
every { fido2CredentialManager.isUserVerified } returns true
|
every { fido2CredentialManager.isUserVerified } returns true
|
||||||
|
@ -1619,6 +1756,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} returns stateWithName.viewState
|
} returns stateWithName.viewState
|
||||||
every { fido2CredentialManager.isUserVerified } returns false
|
every { fido2CredentialManager.isUserVerified } returns false
|
||||||
|
@ -3982,6 +4120,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
availableOwners: List<VaultAddEditState.Owner> = createOwnerList(),
|
availableOwners: List<VaultAddEditState.Owner> = createOwnerList(),
|
||||||
selectedOwnerId: String? = null,
|
selectedOwnerId: String? = null,
|
||||||
hasOrganizations: Boolean = true,
|
hasOrganizations: Boolean = true,
|
||||||
|
canDelete: Boolean = true,
|
||||||
): VaultAddEditState.ViewState.Content.Common =
|
): VaultAddEditState.ViewState.Content.Common =
|
||||||
VaultAddEditState.ViewState.Content.Common(
|
VaultAddEditState.ViewState.Content.Common(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -3995,6 +4134,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
availableFolders = availableFolders,
|
availableFolders = availableFolders,
|
||||||
availableOwners = availableOwners,
|
availableOwners = availableOwners,
|
||||||
hasOrganizations = hasOrganizations,
|
hasOrganizations = hasOrganizations,
|
||||||
|
canDelete = canDelete,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
|
@ -75,6 +75,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -121,6 +122,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -172,6 +174,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -232,6 +235,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = mockk { every { uri } returns totp },
|
totpData = mockk { every { uri } returns totp },
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -289,6 +293,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -324,6 +329,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -368,6 +374,7 @@ class CipherViewExtensionsTest {
|
||||||
totpData = null,
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
|
|
@ -780,22 +780,39 @@ class VaultItemScreenTest : BaseComposeTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `menu Delete option click should be displayed`() {
|
fun `menu Delete option should be displayed based on state`() {
|
||||||
|
// Confirm overflow is closed on initial load
|
||||||
// Confirm dropdown version of item is absent
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onAllNodesWithText("Delete")
|
.onAllNodesWithText("Delete")
|
||||||
.filter(hasAnyAncestor(isPopup()))
|
.filter(hasAnyAncestor(isPopup()))
|
||||||
.assertCountEquals(0)
|
.assertCountEquals(0)
|
||||||
|
|
||||||
// Open the overflow menu
|
// Open the overflow menu
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNodeWithContentDescription("More")
|
.onNodeWithContentDescription("More")
|
||||||
.performClick()
|
.performClick()
|
||||||
// Click on the delete item in the dropdown
|
|
||||||
|
// Confirm Delete option is present
|
||||||
|
mutableStateFlow.update { it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE) }
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onAllNodesWithText("Delete")
|
.onAllNodesWithText("Delete")
|
||||||
.filterToOne(hasAnyAncestor(isPopup()))
|
.filterToOne(hasAnyAncestor(isPopup()))
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
|
|
||||||
|
// Confirm Delete option is not present when canDelete is false
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
viewState = DEFAULT_LOGIN_VIEW_STATE
|
||||||
|
.copy(
|
||||||
|
common = DEFAULT_COMMON
|
||||||
|
.copy(canDelete = false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onAllNodesWithText("Delete")
|
||||||
|
.filter(hasAnyAncestor(isPopup()))
|
||||||
|
.assertCountEquals(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1166,6 +1183,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Menu should display correct items when cipher is not in a collection`() {
|
fun `Menu should display correct items when cipher is not in a collection`() {
|
||||||
|
mutableStateFlow.update { it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE) }
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNodeWithContentDescription("More")
|
.onNodeWithContentDescription("More")
|
||||||
.performClick()
|
.performClick()
|
||||||
|
@ -2374,6 +2392,7 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
|
||||||
title = "test.mp4",
|
title = "test.mp4",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val DEFAULT_PASSKEY = R.string.created_xy.asText(
|
private val DEFAULT_PASSKEY = R.string.created_xy.asText(
|
||||||
|
@ -2455,6 +2474,7 @@ private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
|
||||||
requiresReprompt = true,
|
requiresReprompt = true,
|
||||||
requiresCloneConfirmation = false,
|
requiresCloneConfirmation = false,
|
||||||
attachments = emptyList(),
|
attachments = emptyList(),
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
|
private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -45,6 +45,7 @@ class CipherViewExtensionsTest {
|
||||||
totpCode = "testCode",
|
totpCode = "testCode",
|
||||||
),
|
),
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -80,6 +81,7 @@ class CipherViewExtensionsTest {
|
||||||
totpCode = "testCode",
|
totpCode = "testCode",
|
||||||
),
|
),
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -108,6 +110,7 @@ class CipherViewExtensionsTest {
|
||||||
totpCode = "testCode",
|
totpCode = "testCode",
|
||||||
),
|
),
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -136,6 +139,7 @@ class CipherViewExtensionsTest {
|
||||||
totpCode = "testCode",
|
totpCode = "testCode",
|
||||||
),
|
),
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -170,6 +174,7 @@ class CipherViewExtensionsTest {
|
||||||
totpCode = "testCode",
|
totpCode = "testCode",
|
||||||
),
|
),
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -194,6 +199,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -216,6 +222,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -237,6 +244,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -268,6 +276,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -304,6 +313,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -342,6 +352,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -364,6 +375,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
val expectedState = VaultItemState.ViewState.Content(
|
val expectedState = VaultItemState.ViewState.Content(
|
||||||
|
@ -384,6 +396,7 @@ class CipherViewExtensionsTest {
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
totpCodeItemData = null,
|
totpCodeItemData = null,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemState.ViewState.Content(
|
VaultItemState.ViewState.Content(
|
||||||
|
|
|
@ -170,6 +170,7 @@ fun createCommonContent(
|
||||||
requiresReprompt = true,
|
requiresReprompt = true,
|
||||||
requiresCloneConfirmation = false,
|
requiresCloneConfirmation = false,
|
||||||
attachments = emptyList(),
|
attachments = emptyList(),
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
VaultItemState.ViewState.Content.Common(
|
VaultItemState.ViewState.Content.Common(
|
||||||
|
@ -213,6 +214,7 @@ fun createCommonContent(
|
||||||
title = "test.mp4",
|
title = "test.mp4",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
canDelete = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue