mirror of
https://github.com/bitwarden/android.git
synced 2024-11-26 19:36:18 +03:00
BIT-1309: Ownership in the add item screen (#831)
This commit is contained in:
parent
f1a799955c
commit
b3f23ab172
30 changed files with 1095 additions and 286 deletions
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.api
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import okhttp3.MultipartBody
|
||||
|
@ -16,6 +17,7 @@ import retrofit2.http.Path
|
|||
/**
|
||||
* Defines raw calls under the /ciphers API with authentication applied.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface CiphersApi {
|
||||
|
||||
/**
|
||||
|
@ -24,6 +26,14 @@ interface CiphersApi {
|
|||
@POST("ciphers")
|
||||
suspend fun createCipher(@Body body: CipherJsonRequest): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Create a cipher that belongs to an organization.
|
||||
*/
|
||||
@POST("ciphers/create")
|
||||
suspend fun createCipherInOrganization(
|
||||
@Body body: CreateCipherInOrganizationJsonRequest,
|
||||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Associates an attachment with a cipher.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a create cipher in organization request.
|
||||
*
|
||||
* @property cipher The cipher to create.
|
||||
* @property collectionIds A list of collection ids associated with the cipher.
|
||||
*/
|
||||
@Serializable
|
||||
data class CreateCipherInOrganizationJsonRequest(
|
||||
@SerialName("Cipher")
|
||||
val cipher: CipherJsonRequest,
|
||||
|
||||
@SerialName("CollectionIds")
|
||||
val collectionIds: List<String>,
|
||||
)
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
|
@ -10,12 +11,20 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherRespo
|
|||
/**
|
||||
* Provides an API for querying ciphers endpoints.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface CiphersService {
|
||||
/**
|
||||
* Attempt to create a cipher.
|
||||
*/
|
||||
suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher that belongs to an organization.
|
||||
*/
|
||||
suspend fun createCipherInOrganization(
|
||||
body: CreateCipherInOrganizationJsonRequest,
|
||||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Attempt to upload an attachment file.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
|
@ -21,6 +22,7 @@ import java.time.ZoneOffset
|
|||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class CiphersServiceImpl(
|
||||
private val azureApi: AzureApi,
|
||||
private val ciphersApi: CiphersApi,
|
||||
|
@ -30,6 +32,10 @@ class CiphersServiceImpl(
|
|||
override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> =
|
||||
ciphersApi.createCipher(body = body)
|
||||
|
||||
override suspend fun createCipherInOrganization(
|
||||
body: CreateCipherInOrganizationJsonRequest,
|
||||
): Result<SyncResponseJson.Cipher> = ciphersApi.createCipherInOrganization(body = body)
|
||||
|
||||
override suspend fun createAttachment(
|
||||
cipherId: String,
|
||||
body: AttachmentJsonRequest,
|
||||
|
|
|
@ -200,6 +200,14 @@ interface VaultRepository : VaultLockManager {
|
|||
*/
|
||||
suspend fun createCipher(cipherView: CipherView): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher that belongs to an organization.
|
||||
*/
|
||||
suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create an attachment for the given [cipherView].
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,7 @@ import com.x8bit.bitwarden.data.platform.util.asFailure
|
|||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
|
@ -584,6 +585,32 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult {
|
||||
val userId = activeUserId ?: return CreateCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService
|
||||
.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = cipher.toEncryptedNetworkCipher(),
|
||||
collectionIds = collectionIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess { vaultDiskSource.saveCipher(userId = userId, cipher = it) }
|
||||
.fold(
|
||||
onFailure = { CreateCipherResult.Error },
|
||||
onSuccess = { CreateCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult {
|
||||
val userId = activeUserId ?: return DeleteCipherResult.Error
|
||||
return ciphersService
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package com.x8bit.bitwarden.ui.vault.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
|
||||
/**
|
||||
* A set of switches that a user can select [Collection]s with.
|
||||
*/
|
||||
fun LazyListScope.collectionItemsSelector(
|
||||
collectionList: List<VaultCollection>?,
|
||||
onCollectionSelect: (VaultCollection) -> Unit,
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.collections),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (collectionList?.isNotEmpty() == true) {
|
||||
items(collectionList) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenWideSwitch(
|
||||
label = it.name,
|
||||
isChecked = it.isSelected,
|
||||
onCheckedChange = { _ ->
|
||||
onCollectionSelect(it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_collections_to_list),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.vault.components.collectionItemsSelector
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
|
@ -151,9 +152,18 @@ fun LazyListScope.vaultAddEditCardItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.folder),
|
||||
options = commonState.availableFolders.map { it.invoke() }.toImmutableList(),
|
||||
selectedOption = commonState.folderName.invoke(),
|
||||
onOptionSelected = commonHandlers.onFolderTextChange,
|
||||
options = commonState
|
||||
.availableFolders
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedFolder?.name,
|
||||
onOptionSelected = { selectedFolderName ->
|
||||
commonHandlers.onFolderSelected(
|
||||
commonState
|
||||
.availableFolders
|
||||
.first { it.name == selectedFolderName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -272,14 +282,29 @@ fun LazyListScope.vaultAddEditCardItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.who_owns_this_item),
|
||||
options = commonState.availableOwners.toImmutableList(),
|
||||
selectedOption = commonState.ownership,
|
||||
onOptionSelected = commonHandlers.onOwnershipTextChange,
|
||||
options = commonState
|
||||
.availableOwners
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedOwner?.name,
|
||||
onOptionSelected = { selectedOwnerName ->
|
||||
commonHandlers.onOwnerSelected(
|
||||
commonState
|
||||
.availableOwners
|
||||
.first { it.name == selectedOwnerName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
if (commonState.selectedOwnerId != null) {
|
||||
collectionItemsSelector(
|
||||
collectionList = commonState.selectedOwner?.collections,
|
||||
onCollectionSelect = commonHandlers.onCollectionSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.vault.components.collectionItemsSelector
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
|
@ -260,9 +261,18 @@ fun LazyListScope.vaultAddEditIdentityItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.folder),
|
||||
options = commonState.availableFolders.map { it.invoke() }.toImmutableList(),
|
||||
selectedOption = commonState.folderName.invoke(),
|
||||
onOptionSelected = commonTypeHandlers.onFolderTextChange,
|
||||
options = commonState
|
||||
.availableFolders
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedFolder?.name,
|
||||
onOptionSelected = { selectedFolderName ->
|
||||
commonTypeHandlers.onFolderSelected(
|
||||
commonState
|
||||
.availableFolders
|
||||
.first { it.name == selectedFolderName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -394,14 +404,29 @@ fun LazyListScope.vaultAddEditIdentityItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.who_owns_this_item),
|
||||
options = commonState.availableOwners.toImmutableList(),
|
||||
selectedOption = commonState.ownership,
|
||||
onOptionSelected = commonTypeHandlers.onOwnershipTextChange,
|
||||
options = commonState
|
||||
.availableOwners
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedOwner?.name,
|
||||
onOptionSelected = { selectedOwnerName ->
|
||||
commonTypeHandlers.onOwnerSelected(
|
||||
commonState
|
||||
.availableOwners
|
||||
.first { it.name == selectedOwnerName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
if (commonState.selectedOwnerId != null) {
|
||||
collectionItemsSelector(
|
||||
collectionList = commonState.selectedOwner?.collections,
|
||||
onCollectionSelect = commonTypeHandlers.onCollectionSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
|
|
@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.vault.components.collectionItemsSelector
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
|
@ -199,9 +200,18 @@ fun LazyListScope.vaultAddEditLoginItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.folder),
|
||||
options = commonState.availableFolders.map { it.invoke() }.toImmutableList(),
|
||||
selectedOption = commonState.folderName.invoke(),
|
||||
onOptionSelected = commonActionHandler.onFolderTextChange,
|
||||
options = commonState
|
||||
.availableFolders
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedFolder?.name,
|
||||
onOptionSelected = { selectedFolderName ->
|
||||
commonActionHandler.onFolderSelected(
|
||||
commonState
|
||||
.availableFolders
|
||||
.first { it.name == selectedFolderName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -316,14 +326,30 @@ fun LazyListScope.vaultAddEditLoginItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.who_owns_this_item),
|
||||
options = commonState.availableOwners.toImmutableList(),
|
||||
selectedOption = commonState.ownership,
|
||||
onOptionSelected = commonActionHandler.onOwnershipTextChange,
|
||||
options = commonState
|
||||
.availableOwners
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedOwner?.name,
|
||||
onOptionSelected = { selectedOwnerName ->
|
||||
commonActionHandler.onOwnerSelected(
|
||||
commonState
|
||||
.availableOwners
|
||||
.first { it.name == selectedOwnerName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.selectedOwnerId != null) {
|
||||
collectionItemsSelector(
|
||||
collectionList = commonState.selectedOwner?.collections,
|
||||
onCollectionSelect = commonActionHandler.onCollectionSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.vault.components.collectionItemsSelector
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
@ -59,11 +60,19 @@ fun LazyListScope.vaultAddEditSecureNotesItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.folder),
|
||||
options = commonState.availableFolders.map { it.invoke() }.toImmutableList(),
|
||||
selectedOption = commonState.folderName.invoke(),
|
||||
onOptionSelected = commonTypeHandlers.onFolderTextChange,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
options = commonState
|
||||
.availableFolders
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedFolder?.name,
|
||||
onOptionSelected = { selectedFolderName ->
|
||||
commonTypeHandlers.onFolderSelected(
|
||||
commonState
|
||||
.availableFolders
|
||||
.first { it.name == selectedFolderName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -175,14 +184,30 @@ fun LazyListScope.vaultAddEditSecureNotesItems(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.who_owns_this_item),
|
||||
options = commonState.availableOwners.toImmutableList(),
|
||||
selectedOption = commonState.ownership,
|
||||
onOptionSelected = commonTypeHandlers.onOwnershipTextChange,
|
||||
options = commonState
|
||||
.availableOwners
|
||||
.map { it.name }
|
||||
.toImmutableList(),
|
||||
selectedOption = commonState.selectedOwner?.name,
|
||||
onOptionSelected = { selectedOwnerName ->
|
||||
commonTypeHandlers.onOwnerSelected(
|
||||
commonState
|
||||
.availableOwners
|
||||
.first { it.name == selectedOwnerName },
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.selectedOwnerId != null) {
|
||||
collectionItemsSelector(
|
||||
collectionList = commonState.selectedOwner?.collections,
|
||||
onCollectionSelect = commonTypeHandlers.onCollectionSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.bitwarden.core.CipherView
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
|
@ -20,6 +21,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -30,12 +32,15 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
|
|||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.appendFolderAndOwnerData
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDefaultAddTypeContent
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.validateCipherOrReturnErrorState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -109,18 +114,17 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
//region Initialization and Overrides
|
||||
|
||||
init {
|
||||
state
|
||||
.vaultAddEditType
|
||||
.vaultItemId
|
||||
?.let { itemId ->
|
||||
vaultRepository
|
||||
.getVaultItemStateFlow(itemId)
|
||||
// We'll stop getting updates as soon as we get some loaded data.
|
||||
.takeUntilLoaded()
|
||||
.map { VaultAddEditAction.Internal.VaultDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
vaultRepository
|
||||
.vaultDataStateFlow
|
||||
.takeUntilLoaded()
|
||||
.map { vaultDataState ->
|
||||
VaultAddEditAction.Internal.VaultDataReceive(
|
||||
vaultData = vaultDataState,
|
||||
userData = authRepository.userStateFlow.value,
|
||||
)
|
||||
}
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
vaultRepository
|
||||
.totpCodeFlow
|
||||
|
@ -186,6 +190,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
is VaultAddEditAction.Common.CustomFieldActionSelect -> handleCustomFieldActionSelected(
|
||||
action,
|
||||
)
|
||||
is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,6 +251,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun handleSaveClick() = onContent { content ->
|
||||
if (content.common.name.isBlank()) {
|
||||
mutableStateFlow.update {
|
||||
|
@ -258,6 +264,19 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
return@onContent
|
||||
} else if (
|
||||
content.common.selectedOwnerId != null &&
|
||||
content.common.selectedOwner?.collections?.all { !it.isSelected } == true
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultAddEditState.DialogState.Generic(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.select_one_collection.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return@onContent
|
||||
}
|
||||
|
||||
mutableStateFlow.update {
|
||||
|
@ -271,7 +290,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
when (val vaultAddEditType = state.vaultAddEditType) {
|
||||
VaultAddEditType.AddItem -> {
|
||||
val result = vaultRepository.createCipher(cipherView = content.toCipherView())
|
||||
val result = content.createCipherForAddAndCloneItemStates()
|
||||
sendAction(VaultAddEditAction.Internal.CreateCipherResultReceive(result))
|
||||
}
|
||||
|
||||
|
@ -284,7 +303,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is VaultAddEditType.CloneItem -> {
|
||||
val result = vaultRepository.createCipher(cipherView = content.toCipherView())
|
||||
val result = content.createCipherForAddAndCloneItemStates()
|
||||
sendAction(VaultAddEditAction.Internal.CreateCipherResultReceive(result))
|
||||
}
|
||||
}
|
||||
|
@ -437,7 +456,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
action: VaultAddEditAction.Common.FolderChange,
|
||||
) {
|
||||
updateCommonContent { commonContent ->
|
||||
commonContent.copy(folderName = action.folder)
|
||||
commonContent.copy(selectedFolderId = action.folder.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,7 +488,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
action: VaultAddEditAction.Common.OwnershipChange,
|
||||
) {
|
||||
updateCommonContent { commonContent ->
|
||||
commonContent.copy(ownership = action.ownership)
|
||||
commonContent.copy(selectedOwnerId = action.ownership.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,6 +509,22 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleCollectionSelect(
|
||||
action: VaultAddEditAction.Common.CollectionSelect,
|
||||
) {
|
||||
updateCommonContent { currentCommonContentState ->
|
||||
currentCommonContentState.copy(
|
||||
availableOwners = currentCommonContentState
|
||||
.availableOwners
|
||||
.toUpdatedOwners(
|
||||
selectedCollectionId = action.collection.id,
|
||||
selectedOwnerId = currentCommonContentState.selectedOwnerId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Common Handlers
|
||||
|
||||
//region Add Login Item Type Handlers
|
||||
|
@ -1000,7 +1035,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
|
||||
@Suppress("LongMethod")
|
||||
private fun handleVaultDataReceive(action: VaultAddEditAction.Internal.VaultDataReceive) {
|
||||
when (val vaultDataState = action.vaultDataState) {
|
||||
when (val vaultDataState = action.vaultData) {
|
||||
is DataState.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
|
@ -1012,17 +1047,10 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is DataState.Loaded -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = vaultDataState
|
||||
.data
|
||||
?.toViewState(
|
||||
isClone = it.isCloneMode,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
?: VaultAddEditState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.determineContentState(
|
||||
vaultData = vaultDataState.data,
|
||||
userData = action.userData,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1046,23 +1074,43 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is DataState.Pending -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = vaultDataState
|
||||
.data
|
||||
?.toViewState(
|
||||
isClone = it.isCloneMode,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
?: VaultAddEditState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.determineContentState(
|
||||
vaultData = vaultDataState.data,
|
||||
userData = action.userData,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun VaultAddEditState.determineContentState(
|
||||
vaultData: VaultData,
|
||||
userData: UserState?,
|
||||
): VaultAddEditState =
|
||||
copy(
|
||||
viewState = vaultData.cipherViewList
|
||||
.find { it.id == vaultAddEditType.vaultItemId }
|
||||
.validateCipherOrReturnErrorState(
|
||||
currentAccount = userData?.activeAccount,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
) { currentAccount, cipherView ->
|
||||
// Derive the view state from the current Cipher for Edit mode
|
||||
// or use the current state for Add
|
||||
(cipherView?.toViewState(
|
||||
isClone = isCloneMode,
|
||||
resourceManager = resourceManager,
|
||||
) ?: viewState)
|
||||
.appendFolderAndOwnerData(
|
||||
folderViewList = vaultData.folderViewList,
|
||||
collectionViewList = vaultData.collectionViewList
|
||||
.filter { !it.readOnly },
|
||||
activeAccount = currentAccount,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun handleVaultTotpCodeReceive(action: VaultAddEditAction.Internal.TotpCodeReceive) {
|
||||
when (action.totpResult) {
|
||||
is TotpCodeResult.Success -> {
|
||||
|
@ -1243,6 +1291,46 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private suspend fun VaultAddEditState.ViewState.Content.createCipherForAddAndCloneItemStates(): CreateCipherResult {
|
||||
return common.selectedOwner?.collections
|
||||
?.filter { it.isSelected }
|
||||
?.map { it.id }
|
||||
?.let {
|
||||
vaultRepository.createCipherInOrganization(
|
||||
cipherView = toCipherView(),
|
||||
collectionIds = it,
|
||||
)
|
||||
}
|
||||
?: vaultRepository.createCipher(cipherView = toCipherView())
|
||||
}
|
||||
|
||||
private fun List<VaultAddEditState.Owner>.toUpdatedOwners(
|
||||
selectedOwnerId: String?,
|
||||
selectedCollectionId: String,
|
||||
): List<VaultAddEditState.Owner> =
|
||||
map { owner ->
|
||||
if (owner.id != selectedOwnerId) return@map owner
|
||||
owner.copy(
|
||||
collections = owner
|
||||
.collections
|
||||
.toUpdatedCollections(selectedCollectionId = selectedCollectionId),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<VaultCollection>.toUpdatedCollections(
|
||||
selectedCollectionId: String,
|
||||
): List<VaultCollection> =
|
||||
map { collection ->
|
||||
collection.copy(
|
||||
isSelected = if (selectedCollectionId == collection.id) {
|
||||
!collection.isSelected
|
||||
} else {
|
||||
collection.isSelected
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
//endregion Utility Functions
|
||||
}
|
||||
|
||||
|
@ -1343,9 +1431,9 @@ data class VaultAddEditState(
|
|||
* @property favorite Indicates whether this item is marked as a favorite.
|
||||
* @property customFieldData Additional custom fields associated with the item.
|
||||
* @property notes Any additional notes or comments associated with the item.
|
||||
* @property folderName The folder that this item belongs to.
|
||||
* @property selectedFolderId The ID of the folder that this item belongs to.
|
||||
* @property availableFolders The list of folders that this item could be added too.
|
||||
* @property ownership The ownership email associated with the item.
|
||||
* @property selectedOwnerId The ID of the owner associated with the item.
|
||||
* @property availableOwners A list of available owners.
|
||||
*/
|
||||
@Parcelize
|
||||
|
@ -1357,21 +1445,23 @@ data class VaultAddEditState(
|
|||
val favorite: Boolean = false,
|
||||
val customFieldData: List<Custom> = emptyList(),
|
||||
val notes: String = "",
|
||||
val folderName: Text = DEFAULT_FOLDER,
|
||||
val availableFolders: List<Text> = listOf(
|
||||
"Folder 1".asText(),
|
||||
"Folder 2".asText(),
|
||||
"Folder 3".asText(),
|
||||
),
|
||||
// TODO: Update this property to get available owners from the data layer (BIT-501)
|
||||
val ownership: String = DEFAULT_OWNERSHIP,
|
||||
// TODO: Update this property to get available owners from the data layer (BIT-501)
|
||||
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
|
||||
val selectedFolderId: String? = null,
|
||||
val availableFolders: List<Folder> = emptyList(),
|
||||
val selectedOwnerId: String? = null,
|
||||
val availableOwners: List<Owner> = emptyList(),
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
|
||||
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to provide the currently selected owner.
|
||||
*/
|
||||
val selectedOwner: Owner?
|
||||
get() = availableOwners.find { it.id == selectedOwnerId }
|
||||
|
||||
/**
|
||||
* Helper to provide the currently selected folder.
|
||||
*/
|
||||
val selectedFolder: Folder?
|
||||
get() = availableFolders.find { it.id == selectedFolderId }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1547,6 +1637,32 @@ data class VaultAddEditState(
|
|||
) : Custom()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models a folder that can be chosen by the user.
|
||||
*
|
||||
* @property id the folder id.
|
||||
* @property name the folder name.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Folder(
|
||||
val id: String?,
|
||||
val name: String,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Models an owner that can be chosen by the user.
|
||||
*
|
||||
* @property id the id of the owner (nullable).
|
||||
* @property name the name of the owner.
|
||||
* @property collections the collections of the owner.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Owner(
|
||||
val id: String?,
|
||||
val name: String,
|
||||
val collections: List<VaultCollection>,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Displays a dialog.
|
||||
*/
|
||||
|
@ -1691,7 +1807,7 @@ sealed class VaultAddEditAction {
|
|||
*
|
||||
* @property folder The new folder text.
|
||||
*/
|
||||
data class FolderChange(val folder: Text) : Common()
|
||||
data class FolderChange(val folder: VaultAddEditState.Folder) : Common()
|
||||
|
||||
/**
|
||||
* Fired when the Favorite toggle is changed.
|
||||
|
@ -1720,7 +1836,7 @@ sealed class VaultAddEditAction {
|
|||
*
|
||||
* @property ownership The new ownership text.
|
||||
*/
|
||||
data class OwnershipChange(val ownership: String) : Common()
|
||||
data class OwnershipChange(val ownership: VaultAddEditState.Owner) : Common()
|
||||
|
||||
/**
|
||||
* Represents the action to add a new custom field.
|
||||
|
@ -1747,6 +1863,15 @@ sealed class VaultAddEditAction {
|
|||
* Represents the action to open tooltip
|
||||
*/
|
||||
data object TooltipClick : Common()
|
||||
|
||||
/**
|
||||
* The user has selected a collection.
|
||||
*
|
||||
* @property collection the collection selected.
|
||||
*/
|
||||
data class CollectionSelect(
|
||||
val collection: VaultCollection,
|
||||
) : Common()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2039,7 +2164,8 @@ sealed class VaultAddEditAction {
|
|||
* Indicates that the vault item data has been received.
|
||||
*/
|
||||
data class VaultDataReceive(
|
||||
val vaultDataState: DataState<CipherView?>,
|
||||
val vaultData: DataState<VaultData>,
|
||||
val userData: UserState?,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,40 +1,42 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
|
||||
/**
|
||||
* A collection of handler functions for managing actions common
|
||||
* within the context of adding items to a vault.
|
||||
*
|
||||
* @property onNameTextChange Handles the action when the name text is changed.
|
||||
* @property onFolderTextChange Handles the action when the folder text is changed.
|
||||
* @property onFolderSelected Handles the action when a folder is selected.
|
||||
* @property onToggleFavorite Handles the action when the favorite toggle is changed.
|
||||
* @property onToggleMasterPasswordReprompt Handles the action when the master password
|
||||
* reprompt toggle is changed.
|
||||
* @property onNotesTextChange Handles the action when the notes text is changed.
|
||||
* @property onOwnershipTextChange Handles the action when the ownership text is changed.
|
||||
* @property onOwnerSelected Handles the action when a owner is selected.
|
||||
* @property onTooltipClick Handles the action when the tooltip button is clicked.
|
||||
* @property onAddNewCustomFieldClick Handles the action when the add new custom field
|
||||
* button is clicked.
|
||||
* @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.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
data class VaultAddEditCommonHandlers(
|
||||
val onNameTextChange: (String) -> Unit,
|
||||
val onFolderTextChange: (String) -> Unit,
|
||||
val onFolderSelected: (VaultAddEditState.Folder) -> Unit,
|
||||
val onToggleFavorite: (Boolean) -> Unit,
|
||||
val onToggleMasterPasswordReprompt: (Boolean) -> Unit,
|
||||
val onNotesTextChange: (String) -> Unit,
|
||||
val onOwnershipTextChange: (String) -> Unit,
|
||||
val onOwnerSelected: (VaultAddEditState.Owner) -> Unit,
|
||||
val onTooltipClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit,
|
||||
val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit,
|
||||
val onCustomFieldActionSelect: (CustomFieldAction, VaultAddEditState.Custom) -> Unit,
|
||||
val onCollectionSelect: (VaultCollection) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
||||
|
@ -50,10 +52,10 @@ data class VaultAddEditCommonHandlers(
|
|||
VaultAddEditAction.Common.NameTextChange(newName),
|
||||
)
|
||||
},
|
||||
onFolderTextChange = { newFolder ->
|
||||
onFolderSelected = { newFolder ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.FolderChange(
|
||||
newFolder.asText(),
|
||||
newFolder,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
@ -74,7 +76,7 @@ data class VaultAddEditCommonHandlers(
|
|||
VaultAddEditAction.Common.NotesTextChange(newNotes),
|
||||
)
|
||||
},
|
||||
onOwnershipTextChange = { newOwnership ->
|
||||
onOwnerSelected = { newOwnership ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.OwnershipChange(newOwnership),
|
||||
)
|
||||
|
@ -107,6 +109,13 @@ data class VaultAddEditCommonHandlers(
|
|||
),
|
||||
)
|
||||
},
|
||||
onCollectionSelect = { selectedCollection ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.CollectionSelect(
|
||||
collection = selectedCollection,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType.Companion.fromId
|
||||
import com.x8bit.bitwarden.ui.vault.model.findVaultCardBrandWithNameOrNull
|
||||
|
@ -76,17 +83,107 @@ fun CipherView.toViewState(
|
|||
favorite = this.favorite,
|
||||
masterPasswordReprompt = this.reprompt == CipherRepromptType.PASSWORD,
|
||||
notes = this.notes.orEmpty(),
|
||||
// TODO: Update these properties to pull folder from data layer (BIT-501)
|
||||
folderName = this.folderId?.asText() ?: R.string.folder_none.asText(),
|
||||
availableFolders = emptyList(),
|
||||
// TODO: Update this property to pull owner from data layer (BIT-501)
|
||||
ownership = "",
|
||||
// TODO: Update this property to pull available owners from data layer (BIT-501)
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds Folder and Owner data to [VaultAddEditState.ViewState].
|
||||
*/
|
||||
fun VaultAddEditState.ViewState.appendFolderAndOwnerData(
|
||||
folderViewList: List<FolderView>,
|
||||
collectionViewList: List<CollectionView>,
|
||||
activeAccount: UserState.Account,
|
||||
resourceManager: ResourceManager,
|
||||
): VaultAddEditState.ViewState {
|
||||
return (this as? VaultAddEditState.ViewState.Content)?.let { currentContentState ->
|
||||
currentContentState.copy(
|
||||
common = currentContentState.common.copy(
|
||||
selectedFolderId = folderViewList.toSelectedFolderId(
|
||||
cipherView = currentContentState.common.originalCipher,
|
||||
),
|
||||
availableFolders = folderViewList.toAvailableFolders(
|
||||
resourceManager = resourceManager,
|
||||
),
|
||||
selectedOwnerId = activeAccount.toSelectedOwnerId(
|
||||
cipherView = currentContentState.common.originalCipher,
|
||||
),
|
||||
availableOwners = activeAccount.toAvailableOwners(
|
||||
collectionViewList = collectionViewList,
|
||||
),
|
||||
),
|
||||
)
|
||||
} ?: this
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a [CipherView] otherwise returning a [VaultAddEditState.ViewState.Error].
|
||||
*/
|
||||
fun CipherView?.validateCipherOrReturnErrorState(
|
||||
currentAccount: UserState.Account?,
|
||||
vaultAddEditType: VaultAddEditType,
|
||||
lambda: (
|
||||
currentAccount: UserState.Account,
|
||||
cipherView: CipherView?,
|
||||
) -> VaultAddEditState.ViewState,
|
||||
): VaultAddEditState.ViewState =
|
||||
if (currentAccount == null ||
|
||||
(vaultAddEditType is VaultAddEditType.EditItem && this == null)
|
||||
) {
|
||||
VaultAddEditState.ViewState.Error(R.string.generic_error_message.asText())
|
||||
} else {
|
||||
lambda(currentAccount, this)
|
||||
}
|
||||
|
||||
private fun List<FolderView>.toSelectedFolderId(cipherView: CipherView?): String? =
|
||||
cipherView
|
||||
?.folderId
|
||||
?.takeIf { id -> id in map { it.id } }
|
||||
|
||||
private fun List<FolderView>.toAvailableFolders(
|
||||
resourceManager: ResourceManager,
|
||||
): List<VaultAddEditState.Folder> =
|
||||
listOf(
|
||||
VaultAddEditState.Folder(
|
||||
id = null,
|
||||
name = resourceManager.getString(R.string.folder_none),
|
||||
),
|
||||
)
|
||||
.plus(
|
||||
map { VaultAddEditState.Folder(name = it.name, id = it.id.toString()) },
|
||||
)
|
||||
|
||||
private fun UserState.Account.toSelectedOwnerId(cipherView: CipherView?): String? =
|
||||
cipherView
|
||||
?.organizationId
|
||||
?.takeIf { id -> id in organizations.map { it.id } }
|
||||
|
||||
private fun UserState.Account.toAvailableOwners(
|
||||
collectionViewList: List<CollectionView>,
|
||||
): List<VaultAddEditState.Owner> =
|
||||
listOf(VaultAddEditState.Owner(name = email, id = null, collections = emptyList()))
|
||||
.plus(
|
||||
organizations.map {
|
||||
VaultAddEditState.Owner(
|
||||
name = it.name.orEmpty(),
|
||||
id = it.id,
|
||||
collections = collectionViewList
|
||||
.filter { collection ->
|
||||
collection.organizationId == it.id &&
|
||||
collection.id != null
|
||||
}
|
||||
.map { collection ->
|
||||
VaultCollection(
|
||||
id = collection.id.orEmpty(),
|
||||
name = collection.name,
|
||||
isSelected = false,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun FieldView.toCustomField() =
|
||||
when (this.type) {
|
||||
FieldType.TEXT -> VaultAddEditState.Custom.TextField(
|
||||
|
|
|
@ -70,7 +70,6 @@ class VaultItemViewModel @Inject constructor(
|
|||
verificationCode = it.code,
|
||||
)
|
||||
}
|
||||
|
||||
VaultItemAction.Internal.VaultDataReceive(
|
||||
userState = userState,
|
||||
vaultDataState = combineDataStates(
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -16,9 +15,9 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.vault.components.collectionItemsSelector
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
|
@ -29,7 +28,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
fun VaultMoveToOrganizationContent(
|
||||
state: VaultMoveToOrganizationState.ViewState.Content,
|
||||
organizationSelect: (VaultMoveToOrganizationState.ViewState.Content.Organization) -> Unit,
|
||||
collectionSelect: (VaultMoveToOrganizationState.ViewState.Content.Collection) -> Unit,
|
||||
collectionSelect: (VaultCollection) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
|
@ -70,45 +69,9 @@ fun VaultMoveToOrganizationContent(
|
|||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.collections),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
if (state.selectedOrganization.collections.isNotEmpty()) {
|
||||
items(state.selectedOrganization.collections) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenWideSwitch(
|
||||
label = it.name,
|
||||
isChecked = it.isSelected,
|
||||
onCheckedChange = { _ ->
|
||||
collectionSelect(it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_collections_to_list),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
collectionItemsSelector(
|
||||
collectionList = state.selectedOrganization.collections,
|
||||
onCollectionSelect = collectionSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
|
||||
/**
|
||||
* Displays the vault move to organization screen.
|
||||
|
@ -78,7 +79,7 @@ private fun VaultMoveToOrganizationScaffold(
|
|||
moveClick: () -> Unit,
|
||||
dismissClick: () -> Unit,
|
||||
organizationSelect: (VaultMoveToOrganizationState.ViewState.Content.Organization) -> Unit,
|
||||
collectionSelect: (VaultMoveToOrganizationState.ViewState.Content.Collection) -> Unit,
|
||||
collectionSelect: (VaultCollection) -> Unit,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
when (val dialog = state.dialogState) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.map
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
|
@ -17,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -57,14 +59,14 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
|
|||
) { cipherViewState, collectionsState, userState ->
|
||||
VaultMoveToOrganizationAction.Internal.VaultDataReceive(
|
||||
vaultData = combineDataStates(
|
||||
dataState1 = cipherViewState,
|
||||
dataState1 = cipherViewState.map { Unit },
|
||||
dataState2 = collectionsState,
|
||||
dataState3 = DataState.Loaded(userState),
|
||||
) { ciphersData, collectionsData, userData ->
|
||||
dataState3 = DataState.Loaded(userState).map { Unit },
|
||||
) { _, collectionsData, _ ->
|
||||
Triple(
|
||||
first = ciphersData,
|
||||
first = cipherViewState.data,
|
||||
second = collectionsData,
|
||||
third = userData,
|
||||
third = userState,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -378,21 +380,7 @@ data class VaultMoveToOrganizationState(
|
|||
data class Organization(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val collections: List<Collection>,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Models a collection.
|
||||
*
|
||||
* @property id the collection id.
|
||||
* @property name the collection name.
|
||||
* @property isSelected if the collection is selected or not.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Collection(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val isSelected: Boolean,
|
||||
val collections: List<VaultCollection>,
|
||||
) : Parcelable
|
||||
}
|
||||
|
||||
|
@ -457,7 +445,7 @@ sealed class VaultMoveToOrganizationAction {
|
|||
* @property collection the collection to select.
|
||||
*/
|
||||
data class CollectionSelect(
|
||||
val collection: VaultMoveToOrganizationState.ViewState.Content.Collection,
|
||||
val collection: VaultCollection,
|
||||
) : VaultMoveToOrganizationAction()
|
||||
|
||||
/**
|
||||
|
@ -495,9 +483,9 @@ private fun List<VaultMoveToOrganizationState.ViewState.Content.Organization>.to
|
|||
)
|
||||
}
|
||||
|
||||
private fun List<VaultMoveToOrganizationState.ViewState.Content.Collection>.toUpdatedCollections(
|
||||
private fun List<VaultCollection>.toUpdatedCollections(
|
||||
selectedCollectionId: String,
|
||||
): List<VaultMoveToOrganizationState.ViewState.Content.Collection> =
|
||||
): List<VaultCollection> =
|
||||
map { collection ->
|
||||
collection.copy(
|
||||
isSelected = if (selectedCollectionId == collection.id) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.VaultMoveToOrganizationState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
|
||||
/**
|
||||
* Transforms a triple of [CipherView] (nullable), list of [CollectionView],
|
||||
|
@ -46,7 +47,7 @@ fun Triple<CipherView?, List<CollectionView>, UserState?>.toViewState():
|
|||
collection.id != null
|
||||
}
|
||||
.map { collection ->
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
VaultCollection(
|
||||
id = collection.id.orEmpty(),
|
||||
name = collection.name,
|
||||
isSelected = currentCipher
|
||||
|
|
|
@ -49,10 +49,8 @@ fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
|
|||
name = common.name,
|
||||
notes = common.notes.orNullIfBlank(),
|
||||
favorite = common.favorite,
|
||||
// TODO Use real folder ID (BIT-528)
|
||||
folderId = common.originalCipher?.folderId,
|
||||
// TODO Use real organization ID (BIT-780)
|
||||
organizationId = common.originalCipher?.organizationId,
|
||||
folderId = common.selectedFolderId,
|
||||
organizationId = common.selectedOwnerId,
|
||||
reprompt = common.toCipherRepromptType(),
|
||||
fields = common.customFieldData.map { it.toFieldView() },
|
||||
)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Models a collection.
|
||||
*
|
||||
* @property id the collection id.
|
||||
* @property name the collection name.
|
||||
* @property isSelected if the collection is selected or not.
|
||||
*/
|
||||
@Parcelize
|
||||
data class VaultCollection(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val isSelected: Boolean,
|
||||
) : Parcelable
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
|||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
|
@ -63,6 +64,21 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createCipherInOrganization should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_CIPHER_SUCCESS_JSON))
|
||||
val result = ciphersService.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1),
|
||||
collectionIds = listOf("12345"),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
createMockCipher(number = 1),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createAttachment should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_ATTACHMENT_SUCCESS_JSON))
|
||||
|
|
|
@ -37,6 +37,7 @@ import com.x8bit.bitwarden.data.platform.util.asFailure
|
|||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson
|
||||
|
@ -1704,6 +1705,115 @@ class VaultRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createCipherInOrganization with no active user should return CreateCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = null
|
||||
|
||||
val result = vaultRepository.createCipherInOrganization(
|
||||
cipherView = mockk(),
|
||||
collectionIds = mockk(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createCipherInOrganization with encryptCipher failure should return CreateCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createCipherInOrganization(
|
||||
cipherView = mockCipherView,
|
||||
collectionIds = mockk(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createCipherInOrganization with ciphersService createCipher failure should return CreateCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1, hasNullUri = true),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createCipherInOrganization(
|
||||
cipherView = mockCipherView,
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createCipherInOrganization with ciphersService createCipher success should return CreateCipherResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
val mockCipher = createMockCipher(number = 1)
|
||||
coEvery {
|
||||
ciphersService.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1, hasNullUri = true),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
} returns mockCipher.asSuccess()
|
||||
coEvery { vaultDiskSource.saveCipher(userId, mockCipher) } just runs
|
||||
|
||||
val result = vaultRepository.createCipherInOrganization(
|
||||
cipherView = mockCipherView,
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Success,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateCipher with no active user should return UpdateCipherResult Error`() =
|
||||
runTest {
|
||||
|
|
|
@ -1633,6 +1633,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
@Test
|
||||
fun `clicking a Ownership option should send OwnershipChange action`() {
|
||||
updateStateWithOwners()
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(
|
||||
|
@ -1642,20 +1644,27 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "a@b.com")
|
||||
.onAllNodesWithText(text = "mockOwnerName-2")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.OwnershipChange("a@b.com"),
|
||||
VaultAddEditAction.Common.OwnershipChange(
|
||||
VaultAddEditState.Owner(
|
||||
id = "mockOwnerId-2",
|
||||
name = "mockOwnerName-2",
|
||||
collections = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the Ownership control should display the text provided by the state`() {
|
||||
updateStateWithOwners()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(
|
||||
label = "Who owns this item?, placeholder@email.com",
|
||||
|
@ -1663,11 +1672,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCommonContent(currentState) { copy(ownership = "Owner 2") }
|
||||
updateCommonContent(currentState) { copy(selectedOwnerId = "mockOwnerId-2") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, Owner 2")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, mockOwnerName-2")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
|
@ -1705,7 +1714,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
@Test
|
||||
fun `clicking a Folder Option should send FolderChange action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
updateStateWithFolders()
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
|
@ -1714,14 +1723,19 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Folder 1")
|
||||
.onAllNodesWithText(text = "mockFolderName-1")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.FolderChange("Folder 1".asText()),
|
||||
VaultAddEditAction.Common.FolderChange(
|
||||
VaultAddEditState.Folder(
|
||||
id = "mockFolderId-1",
|
||||
name = "mockFolderName-1",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1729,18 +1743,18 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `the folder control should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
updateStateWithFolders()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCommonContent(currentState) { copy(folderName = "Folder 2".asText()) }
|
||||
updateCommonContent(currentState) { copy(selectedFolderId = "mockFolderId-1") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Folder, Folder 2")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Folder, mockFolderName-1")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
|
@ -1870,7 +1884,9 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Ownership option should send OwnershipChange action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
updateStateWithOwners()
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
|
@ -1879,14 +1895,20 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "a@b.com")
|
||||
.onAllNodesWithText(text = "mockOwnerName-2")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.OwnershipChange("a@b.com"),
|
||||
VaultAddEditAction.Common.OwnershipChange(
|
||||
VaultAddEditState.Owner(
|
||||
id = "mockOwnerId-2",
|
||||
name = "mockOwnerName-2",
|
||||
collections = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1894,18 +1916,18 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes the Ownership control should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
updateStateWithOwners()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, placeholder@email.com")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCommonContent(currentState) { copy(ownership = "Owner 2") }
|
||||
updateCommonContent(currentState) { copy(selectedOwnerId = "mockOwnerId-2") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, Owner 2")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, mockOwnerName-2")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
|
@ -2459,6 +2481,28 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
return currentState.copy(viewState = updatedType)
|
||||
}
|
||||
|
||||
private fun updateStateWithOwners() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCommonContent(currentState) {
|
||||
copy(
|
||||
selectedOwnerId = null,
|
||||
availableOwners = DEFAULT_OWNERS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStateWithFolders() {
|
||||
mutableStateFlow.update {
|
||||
updateCommonContent(it) {
|
||||
copy(
|
||||
selectedFolderId = null,
|
||||
availableFolders = DEFAULT_FOLDERS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Helper functions
|
||||
|
||||
companion object {
|
||||
|
@ -2526,5 +2570,38 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
),
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_OWNERS = listOf(
|
||||
VaultAddEditState.Owner(
|
||||
id = null,
|
||||
name = "placeholder@email.com",
|
||||
collections = emptyList(),
|
||||
),
|
||||
VaultAddEditState.Owner(
|
||||
id = "mockOwnerId-1",
|
||||
name = "mockOwnerName-1",
|
||||
collections = emptyList(),
|
||||
),
|
||||
VaultAddEditState.Owner(
|
||||
id = "mockOwnerId-2",
|
||||
name = "mockOwnerName-2",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
private val DEFAULT_FOLDERS = listOf(
|
||||
VaultAddEditState.Folder(
|
||||
id = null,
|
||||
name = "No Folder",
|
||||
),
|
||||
VaultAddEditState.Folder(
|
||||
id = "mockFolderId-1",
|
||||
name = "mockFolderName-1",
|
||||
),
|
||||
VaultAddEditState.Folder(
|
||||
id = "mockFolderId-2",
|
||||
name = "mockFolderName-2",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,23 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.SendView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.util.FakeGeneratorRepository
|
||||
|
@ -22,8 +29,8 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
|
@ -60,7 +67,10 @@ import java.util.UUID
|
|||
@Suppress("LargeClass")
|
||||
class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val authRepository: AuthRepository = mockk()
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(createUserState())
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val loginInitialState = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState(),
|
||||
|
@ -72,11 +82,13 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
private val totpTestCodeFlow: MutableSharedFlow<TotpCodeResult> = bufferedMutableSharedFlow()
|
||||
|
||||
private val mutableVaultItemFlow = MutableStateFlow<DataState<CipherView?>>(DataState.Loading)
|
||||
private val resourceManager: ResourceManager = mockk()
|
||||
private val mutableVaultDataFlow = MutableStateFlow<DataState<VaultData>>(DataState.Loading)
|
||||
private val resourceManager: ResourceManager = mockk {
|
||||
every { getString(R.string.folder_none) } returns "No Folder"
|
||||
}
|
||||
private val clipboardManager: BitwardenClipboardManager = mockk()
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
every { getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID) } returns mutableVaultItemFlow
|
||||
every { vaultDataStateFlow } returns mutableVaultDataFlow
|
||||
every { totpCodeFlow } returns totpTestCodeFlow
|
||||
}
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
|
@ -107,7 +119,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
loginInitialState,
|
||||
loginInitialState.copy(viewState = VaultAddEditState.ViewState.Loading),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
|
@ -123,9 +135,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
assertEquals(initState, viewModel.stateFlow.value)
|
||||
verify(exactly = 0) {
|
||||
vaultRepository.getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID)
|
||||
assertEquals(
|
||||
initState.copy(viewState = VaultAddEditState.ViewState.Loading),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.vaultDataStateFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,9 +167,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
assertEquals(initState, viewModel.stateFlow.value)
|
||||
verify(exactly = 0) {
|
||||
vaultRepository.getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID)
|
||||
assertEquals(
|
||||
initState.copy(viewState = VaultAddEditState.ViewState.Loading),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.vaultDataStateFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +191,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID)
|
||||
vaultRepository.vaultDataStateFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +210,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID)
|
||||
vaultRepository.vaultDataStateFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,20 +284,23 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
fun `ConfirmDeleteClick with DeleteCipherResult Success should emit ShowToast and NavigateBack`() =
|
||||
runTest {
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(cipherView = cipherView),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initState,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = createMockCipherView(number = 1))
|
||||
|
||||
coEvery {
|
||||
vaultRepository.softDeleteCipher(
|
||||
cipherId = "mockId-1",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
cipherView = cipherView,
|
||||
)
|
||||
} returns DeleteCipherResult.Success
|
||||
|
||||
|
@ -300,20 +321,24 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `ConfirmDeleteClick with DeleteCipherResult Failure should show generic error`() =
|
||||
runTest {
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(cipherView = cipherView),
|
||||
)
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initState,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = createMockCipherView(number = 1))
|
||||
|
||||
coEvery {
|
||||
vaultRepository.softDeleteCipher(
|
||||
cipherId = "mockId-1",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
cipherView = cipherView,
|
||||
)
|
||||
} returns DeleteCipherResult.Error
|
||||
|
||||
|
@ -327,11 +352,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
folder = "mockId-1".asText(),
|
||||
ownership = "",
|
||||
originalCipher = createMockCipherView(number = 1),
|
||||
availableFolders = emptyList(),
|
||||
availableOwners = emptyList(),
|
||||
notes = "mockNotes-1",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
|
@ -347,7 +368,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
uri = listOf(UriItem("testId", "www.mockuri1.com", UriMatchType.HOST)),
|
||||
totpCode = "mockTotp-1",
|
||||
canViewPassword = false,
|
||||
),
|
||||
)
|
||||
.copy(totp = "mockTotp-1"),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
@ -362,17 +384,21 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
R.string.saving.asText(),
|
||||
),
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(),
|
||||
)
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
|
@ -381,7 +407,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.createCipher(any())
|
||||
vaultRepository.createCipherInOrganization(any(), any())
|
||||
} returns CreateCipherResult.Success
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
|
@ -392,7 +418,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.createCipher(any())
|
||||
vaultRepository.createCipherInOrganization(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,10 +427,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(createVaultData())
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
|
@ -413,7 +441,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.createCipher(any())
|
||||
vaultRepository.createCipherInOrganization(any(), any())
|
||||
} returns CreateCipherResult.Success
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddEditAction.Common.SaveClick)
|
||||
|
@ -423,12 +451,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `in add mode, SaveClick createCipher error should emit ShowToast`() = runTest {
|
||||
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(createVaultData())
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
|
@ -438,7 +468,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.createCipher(any())
|
||||
vaultRepository.createCipherInOrganization(any(), any())
|
||||
} returns CreateCipherResult.Error
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddEditAction.Common.SaveClick)
|
||||
|
@ -449,7 +479,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `in edit mode, SaveClick should show dialog, and remove it once an item is saved`() =
|
||||
runTest {
|
||||
val cipherView = mockk<CipherView>()
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithDialog = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
|
@ -457,14 +487,32 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
R.string.saving.asText(),
|
||||
),
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
),
|
||||
)
|
||||
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
),
|
||||
)
|
||||
every {
|
||||
|
@ -473,7 +521,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
mutableVaultItemFlow.value = DataState.Loaded(cipherView)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(cipherView = cipherView),
|
||||
)
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
|
@ -506,12 +556,21 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `in edit mode, SaveClick updateCipher error with a null message should show an error dialog with a generic message`() =
|
||||
runTest {
|
||||
val cipherView = mockk<CipherView>()
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -524,7 +583,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
coEvery {
|
||||
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
||||
} returns UpdateCipherResult.Error(errorMessage = null)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(cipherView)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(cipherView = cipherView),
|
||||
)
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
|
@ -553,26 +614,34 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `in edit mode, SaveClick updateCipher error with a non-null message should show an error dialog with that message`() =
|
||||
runTest {
|
||||
val cipherView = mockk<CipherView>()
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName",
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
),
|
||||
)
|
||||
val errorMessage = "You do not have permission to edit this."
|
||||
|
||||
every {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
cipherView.toViewState(isClone = false, resourceManager = resourceManager)
|
||||
} returns stateWithName.viewState
|
||||
coEvery {
|
||||
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
||||
} returns UpdateCipherResult.Error(errorMessage = errorMessage)
|
||||
mutableVaultItemFlow.value = DataState.Loaded(cipherView)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = cipherView),
|
||||
)
|
||||
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
|
@ -599,6 +668,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `Saving item with an empty name field will cause a dialog to show up`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val stateWithNoName = createVaultAddItemState(
|
||||
commonContentViewState = createCommonContentViewState(name = ""),
|
||||
)
|
||||
|
@ -627,6 +699,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `HandleDialogDismiss will remove the current dialog`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val errorState = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
dialogState = VaultAddEditState.DialogState.Generic(
|
||||
|
@ -652,6 +727,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `TypeOptionSelect LOGIN should switch to LoginItem`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddEditAction.Common.TypeOptionSelect(
|
||||
VaultAddEditState.ItemTypeOption.LOGIN,
|
||||
|
@ -678,6 +756,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `TypeOptionSelect CARD should switch to CardItem`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddEditAction.Common.TypeOptionSelect(
|
||||
VaultAddEditState.ItemTypeOption.CARD,
|
||||
|
@ -704,6 +785,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `TypeOptionSelect IDENTITY should switch to IdentityItem`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddEditAction.Common.TypeOptionSelect(
|
||||
VaultAddEditState.ItemTypeOption.IDENTITY,
|
||||
|
@ -730,6 +814,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `TypeOptionSelect SECURE_NOTES should switch to SecureNotesItem`() = runTest {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddEditAction.Common.TypeOptionSelect(
|
||||
VaultAddEditState.ItemTypeOption.SECURE_NOTES,
|
||||
|
@ -760,6 +847,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
|
||||
viewModel = createAddVaultItemViewModel()
|
||||
}
|
||||
|
||||
|
@ -846,7 +937,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = cipherView)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(
|
||||
cipherView = cipherView,
|
||||
),
|
||||
)
|
||||
|
||||
val breachCount = 5
|
||||
coEvery {
|
||||
|
@ -1053,6 +1148,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
vaultAddItemInitialState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Identity(),
|
||||
)
|
||||
|
@ -1344,6 +1442,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
vaultAddItemInitialState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(),
|
||||
)
|
||||
|
@ -1456,6 +1557,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(cipherView = createMockCipherView(1)),
|
||||
)
|
||||
vaultAddItemInitialState = createVaultAddItemState()
|
||||
secureNotesInitialSavedStateHandle = createSavedStateHandleWithState(
|
||||
state = vaultAddItemInitialState,
|
||||
|
@ -1493,16 +1597,18 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `FolderChange should update folder`() = runTest {
|
||||
val action = VaultAddEditAction.Common.FolderChange(
|
||||
"newFolder".asText(),
|
||||
VaultAddEditState.Folder(
|
||||
id = "mockId-1",
|
||||
name = "Folder 1",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = vaultAddItemInitialState.copy(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = createCommonContentViewState(
|
||||
folder = "newFolder".asText(),
|
||||
),
|
||||
common = createCommonContentViewState()
|
||||
.copy(selectedFolderId = "mockId-1"),
|
||||
type = createLoginTypeContentViewState(),
|
||||
),
|
||||
)
|
||||
|
@ -1570,15 +1676,20 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `OwnershipChange should update ownership`() = runTest {
|
||||
val action = VaultAddEditAction.Common.OwnershipChange(ownership = "newOwner")
|
||||
val action = VaultAddEditAction.Common.OwnershipChange(
|
||||
ownership = VaultAddEditState.Owner(
|
||||
id = "mockId-1",
|
||||
name = "a@b.com",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = vaultAddItemInitialState.copy(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = createCommonContentViewState(
|
||||
ownership = "newOwner",
|
||||
),
|
||||
common = createCommonContentViewState()
|
||||
.copy(selectedOwnerId = "mockId-1"),
|
||||
type = createLoginTypeContentViewState(),
|
||||
),
|
||||
)
|
||||
|
@ -1917,28 +2028,38 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("LongParameterList")
|
||||
private fun createCommonContentViewState(
|
||||
name: String = "",
|
||||
folder: Text = R.string.folder_none.asText(),
|
||||
favorite: Boolean = false,
|
||||
masterPasswordReprompt: Boolean = false,
|
||||
notes: String = "",
|
||||
customFieldData: List<VaultAddEditState.Custom> = listOf(),
|
||||
ownership: String = "placeholder@email.com",
|
||||
originalCipher: CipherView? = null,
|
||||
availableFolders: List<Text> = listOf(
|
||||
"Folder 1".asText(),
|
||||
"Folder 2".asText(),
|
||||
"Folder 3".asText(),
|
||||
availableFolders: List<VaultAddEditState.Folder> = listOf(
|
||||
VaultAddEditState.Folder(
|
||||
id = null,
|
||||
name = "No Folder",
|
||||
),
|
||||
),
|
||||
availableOwners: List<VaultAddEditState.Owner> = listOf(
|
||||
VaultAddEditState.Owner(
|
||||
id = null,
|
||||
name = "activeEmail",
|
||||
collections = emptyList(),
|
||||
),
|
||||
VaultAddEditState.Owner(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
collections = emptyList(),
|
||||
),
|
||||
),
|
||||
availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
|
||||
): VaultAddEditState.ViewState.Content.Common =
|
||||
VaultAddEditState.ViewState.Content.Common(
|
||||
name = name,
|
||||
folderName = folder,
|
||||
selectedFolderId = null,
|
||||
favorite = favorite,
|
||||
customFieldData = customFieldData,
|
||||
masterPasswordReprompt = masterPasswordReprompt,
|
||||
notes = notes,
|
||||
ownership = ownership,
|
||||
selectedOwnerId = null,
|
||||
originalCipher = originalCipher,
|
||||
availableFolders = availableFolders,
|
||||
availableOwners = availableOwners,
|
||||
|
@ -1992,6 +2113,45 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
authRepository = authRepository,
|
||||
)
|
||||
|
||||
private fun createVaultData(
|
||||
cipherView: CipherView? = null,
|
||||
collectionViewList: List<CollectionView> = emptyList(),
|
||||
folderViewList: List<FolderView> = emptyList(),
|
||||
sendViewList: List<SendView> = emptyList(),
|
||||
): VaultData =
|
||||
VaultData(
|
||||
cipherViewList = cipherView?.let { listOf(it) } ?: emptyList(),
|
||||
collectionViewList = collectionViewList,
|
||||
folderViewList = folderViewList,
|
||||
sendViewList = sendViewList,
|
||||
)
|
||||
|
||||
fun createUserState(): UserState =
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
avatarColorHex = "#ffecbc49",
|
||||
environment = Environment.Eu,
|
||||
isPremium = true,
|
||||
isLoggedIn = false,
|
||||
isVaultUnlocked = false,
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
),
|
||||
),
|
||||
isBiometricsEnabled = true,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* A function to test the changes in custom fields for each type.
|
||||
*/
|
||||
|
@ -2135,4 +2295,4 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
private const val TEST_ID = "testId"
|
||||
|
||||
private const val DEFAULT_EDIT_ITEM_ID: String = "edit_item_id"
|
||||
private const val DEFAULT_EDIT_ITEM_ID: String = "mockId-1"
|
||||
|
|
|
@ -13,7 +13,6 @@ import com.bitwarden.core.PasswordHistoryView
|
|||
import com.bitwarden.core.SecureNoteType
|
||||
import com.bitwarden.core.SecureNoteView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
|
@ -61,11 +60,9 @@ class CipherViewExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "cipher",
|
||||
folderName = R.string.folder_none.asText(),
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "Lots of notes",
|
||||
ownership = "",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
|
||||
VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
|
||||
|
@ -105,11 +102,9 @@ class CipherViewExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "cipher",
|
||||
folderName = R.string.folder_none.asText(),
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "Lots of notes",
|
||||
ownership = "",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
|
||||
VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
|
||||
|
@ -154,11 +149,9 @@ class CipherViewExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "cipher",
|
||||
folderName = R.string.folder_none.asText(),
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "Lots of notes",
|
||||
ownership = "",
|
||||
availableFolders = emptyList(),
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = listOf(
|
||||
|
@ -198,11 +191,9 @@ class CipherViewExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "cipher",
|
||||
folderName = R.string.folder_none.asText(),
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "Lots of notes",
|
||||
ownership = "",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
|
||||
VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
|
||||
|
@ -231,11 +222,9 @@ class CipherViewExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "cipher - Clone",
|
||||
folderName = R.string.folder_none.asText(),
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "Lots of notes",
|
||||
ownership = "",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
|
||||
VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -100,7 +101,7 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
|||
id = "mockOrganizationId-2",
|
||||
name = "mockOrganizationName-2",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
VaultCollection(
|
||||
id = "mockId-2",
|
||||
name = "mockName-2",
|
||||
isSelected = false,
|
||||
|
@ -141,7 +142,7 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
|||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
VaultCollection(
|
||||
id = "mockId-1",
|
||||
name = "mockName-1",
|
||||
isSelected = true,
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
|
@ -119,7 +120,7 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
|||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
val unselectCollection1Action = VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
VaultCollection(
|
||||
id = "mockId-1",
|
||||
name = "mockName-1",
|
||||
isSelected = true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.VaultMoveToOrganizationState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
|
||||
/**
|
||||
* Creates a list of mock [VaultMoveToOrganizationState.ViewState.Content.Organization].
|
||||
|
@ -24,7 +25,7 @@ fun createMockOrganization(
|
|||
id = "mockOrganizationId-$number",
|
||||
name = "mockOrganizationName-$number",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
VaultCollection(
|
||||
id = "mockId-$number",
|
||||
name = "mockName-$number",
|
||||
isSelected = isCollectionSelected,
|
||||
|
|
|
@ -12,7 +12,6 @@ import com.bitwarden.core.PasswordHistoryView
|
|||
import com.bitwarden.core.SecureNoteType
|
||||
import com.bitwarden.core.SecureNoteView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
|
@ -42,11 +41,11 @@ class VaultAddItemStateExtensionsTest {
|
|||
val loginItemType = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockFolderId-1",
|
||||
favorite = false,
|
||||
masterPasswordReprompt = false,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnerId-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = "mockUsername-1",
|
||||
|
@ -61,8 +60,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
assertEquals(
|
||||
CipherView(
|
||||
id = null,
|
||||
organizationId = null,
|
||||
folderId = null,
|
||||
organizationId = "mockOwnerId-1",
|
||||
folderId = "mockFolderId-1",
|
||||
collectionIds = emptyList(),
|
||||
key = null,
|
||||
name = "mockName-1",
|
||||
|
@ -109,7 +108,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockFolderId-1",
|
||||
favorite = true,
|
||||
masterPasswordReprompt = false,
|
||||
customFieldData = listOf(
|
||||
|
@ -123,7 +122,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnerId-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = "mockUsername-1",
|
||||
|
@ -141,6 +140,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
name = "mockName-1",
|
||||
notes = "mockNotes-1",
|
||||
type = CipherType.LOGIN,
|
||||
folderId = "mockFolderId-1",
|
||||
organizationId = "mockOwnerId-1",
|
||||
login = LoginView(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
|
@ -200,11 +201,11 @@ class VaultAddItemStateExtensionsTest {
|
|||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockId-1",
|
||||
favorite = false,
|
||||
masterPasswordReprompt = false,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnership-1",
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.BooleanField("testId", "TestBoolean", false),
|
||||
VaultAddEditState.Custom.TextField("testId", "TestText", "TestText"),
|
||||
|
@ -219,8 +220,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
assertEquals(
|
||||
CipherView(
|
||||
id = null,
|
||||
organizationId = null,
|
||||
folderId = null,
|
||||
organizationId = "mockOwnership-1",
|
||||
folderId = "mockId-1",
|
||||
collectionIds = emptyList(),
|
||||
key = null,
|
||||
name = "mockName-1",
|
||||
|
@ -273,11 +274,11 @@ class VaultAddItemStateExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockId-1",
|
||||
favorite = false,
|
||||
masterPasswordReprompt = true,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnerId-1",
|
||||
customFieldData = emptyList(),
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
|
@ -289,6 +290,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
cipherView.copy(
|
||||
name = "mockName-1",
|
||||
notes = "mockNotes-1",
|
||||
organizationId = "mockOwnerId-1",
|
||||
folderId = "mockId-1",
|
||||
type = CipherType.SECURE_NOTE,
|
||||
secureNote = SecureNoteView(SecureNoteType.GENERIC),
|
||||
reprompt = CipherRepromptType.PASSWORD,
|
||||
|
@ -305,11 +308,11 @@ class VaultAddItemStateExtensionsTest {
|
|||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockId-1",
|
||||
favorite = false,
|
||||
masterPasswordReprompt = false,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnerId-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = VaultIdentityTitle.MR,
|
||||
|
@ -338,8 +341,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
assertEquals(
|
||||
CipherView(
|
||||
id = null,
|
||||
organizationId = null,
|
||||
folderId = null,
|
||||
organizationId = "mockOwnerId-1",
|
||||
folderId = "mockId-1",
|
||||
collectionIds = emptyList(),
|
||||
key = null,
|
||||
name = "mockName-1",
|
||||
|
@ -392,7 +395,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = cipherView,
|
||||
name = "mockName-1",
|
||||
folderName = "mockFolder-1".asText(),
|
||||
selectedFolderId = "mockId-1",
|
||||
favorite = true,
|
||||
masterPasswordReprompt = false,
|
||||
customFieldData = listOf(
|
||||
|
@ -406,7 +409,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
selectedOwnerId = "mockOwnerId-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = VaultIdentityTitle.MR,
|
||||
|
@ -437,6 +440,8 @@ class VaultAddItemStateExtensionsTest {
|
|||
cipherView.copy(
|
||||
name = "mockName-1",
|
||||
notes = "mockNotes-1",
|
||||
organizationId = "mockOwnerId-1",
|
||||
folderId = "mockId-1",
|
||||
type = CipherType.IDENTITY,
|
||||
identity = IdentityView(
|
||||
title = "MR",
|
||||
|
|
Loading…
Reference in a new issue