[PM-12922] Disable delete if user can't manage collection (#4179)

This commit is contained in:
Patrick Honkonen 2024-11-06 18:42:06 -05:00 committed by GitHub
parent e397c036e4
commit 87d324b063
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 503 additions and 13 deletions

View file

@ -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 },
), ),
) )
}, },

View file

@ -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 {
/** /**

View file

@ -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,
) )

View file

@ -226,7 +226,8 @@ fun VaultItemScreen(
) )
} }
}, },
), )
.takeIf { state.canDelete },
), ),
) )
}, },

View file

@ -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 {
/** /**

View file

@ -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,
) )

View file

@ -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 -> {

View file

@ -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,
) )

View file

@ -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")

View file

@ -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(

View file

@ -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 =

View file

@ -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(

View file

@ -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,
) )
} }