mirror of
https://github.com/bitwarden/android.git
synced 2024-11-29 06:18:52 +03:00
BIT-2009 Add support for nested collections (#1111)
This commit is contained in:
parent
5e1328eecb
commit
3b33360e58
13 changed files with 506 additions and 44 deletions
|
@ -39,6 +39,7 @@ import kotlinx.collections.immutable.toPersistentList
|
||||||
fun VaultItemListingContent(
|
fun VaultItemListingContent(
|
||||||
state: VaultItemListingState.ViewState.Content,
|
state: VaultItemListingState.ViewState.Content,
|
||||||
policyDisablesSend: Boolean,
|
policyDisablesSend: Boolean,
|
||||||
|
collectionClick: (id: String) -> Unit,
|
||||||
folderClick: (id: String) -> Unit,
|
folderClick: (id: String) -> Unit,
|
||||||
vaultItemClick: (id: String) -> Unit,
|
vaultItemClick: (id: String) -> Unit,
|
||||||
masterPasswordRepromptSubmit: (password: String, data: MasterPasswordRepromptData) -> Unit,
|
masterPasswordRepromptSubmit: (password: String, data: MasterPasswordRepromptData) -> Unit,
|
||||||
|
@ -111,6 +112,35 @@ fun VaultItemListingContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.displayCollectionList.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
BitwardenListHeaderTextWithSupportLabel(
|
||||||
|
label = stringResource(id = R.string.collections),
|
||||||
|
supportingLabel = state.displayCollectionList.count().toString(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
items(state.displayCollectionList) { collection ->
|
||||||
|
BitwardenGroupItem(
|
||||||
|
startIcon = painterResource(id = R.drawable.ic_collection),
|
||||||
|
label = collection.name,
|
||||||
|
supportingLabel = collection.count.toString(),
|
||||||
|
onClick = { collectionClick(collection.id) },
|
||||||
|
showDivider = false,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.displayFolderList.isNotEmpty()) {
|
if (state.displayFolderList.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
BitwardenListHeaderTextWithSupportLabel(
|
BitwardenListHeaderTextWithSupportLabel(
|
||||||
|
@ -140,7 +170,7 @@ fun VaultItemListingContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.displayItemList.isNotEmpty() && state.displayFolderList.isNotEmpty()) {
|
if (state.shouldShowDivider) {
|
||||||
item {
|
item {
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
thickness = 1.dp,
|
thickness = 1.dp,
|
||||||
|
|
|
@ -56,7 +56,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
fun VaultItemListingScreen(
|
fun VaultItemListingScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToVaultItem: (id: String) -> Unit,
|
onNavigateToVaultItem: (id: String) -> Unit,
|
||||||
|
@ -124,6 +124,10 @@ fun VaultItemListingScreen(
|
||||||
is VaultItemListingEvent.NavigateToFolderItem -> {
|
is VaultItemListingEvent.NavigateToFolderItem -> {
|
||||||
onNavigateToVaultItemListing(VaultItemListingType.Folder(event.folderId))
|
onNavigateToVaultItemListing(VaultItemListingType.Folder(event.folderId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultItemListingEvent.NavigateToCollectionItem -> {
|
||||||
|
onNavigateToVaultItemListing(VaultItemListingType.Collection(event.collectionId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +248,7 @@ private fun VaultItemListingScaffold(
|
||||||
policyDisablesSend = state.policyDisablesSend &&
|
policyDisablesSend = state.policyDisablesSend &&
|
||||||
state.itemListingType is VaultItemListingState.ItemListingType.Send,
|
state.itemListingType is VaultItemListingState.ItemListingType.Send,
|
||||||
vaultItemClick = vaultItemListingHandlers.itemClick,
|
vaultItemClick = vaultItemListingHandlers.itemClick,
|
||||||
|
collectionClick = vaultItemListingHandlers.collectionClick,
|
||||||
folderClick = vaultItemListingHandlers.folderClick,
|
folderClick = vaultItemListingHandlers.folderClick,
|
||||||
masterPasswordRepromptSubmit =
|
masterPasswordRepromptSubmit =
|
||||||
vaultItemListingHandlers.masterPasswordRepromptSubmit,
|
vaultItemListingHandlers.masterPasswordRepromptSubmit,
|
||||||
|
|
|
@ -139,6 +139,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
is VaultItemListingsAction.DismissDialogClick -> handleDismissDialogClick()
|
is VaultItemListingsAction.DismissDialogClick -> handleDismissDialogClick()
|
||||||
is VaultItemListingsAction.BackClick -> handleBackClick()
|
is VaultItemListingsAction.BackClick -> handleBackClick()
|
||||||
is VaultItemListingsAction.FolderClick -> handleFolderClick(action)
|
is VaultItemListingsAction.FolderClick -> handleFolderClick(action)
|
||||||
|
is VaultItemListingsAction.CollectionClick -> handleCollectionClick(action)
|
||||||
is VaultItemListingsAction.LockClick -> handleLockClick()
|
is VaultItemListingsAction.LockClick -> handleLockClick()
|
||||||
is VaultItemListingsAction.SyncClick -> handleSyncClick()
|
is VaultItemListingsAction.SyncClick -> handleSyncClick()
|
||||||
is VaultItemListingsAction.SearchIconClick -> handleSearchIconClick()
|
is VaultItemListingsAction.SearchIconClick -> handleSearchIconClick()
|
||||||
|
@ -168,6 +169,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
authRepository.switchAccount(userId = action.accountSummary.userId)
|
authRepository.switchAccount(userId = action.accountSummary.userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCollectionClick(action: VaultItemListingsAction.CollectionClick) {
|
||||||
|
sendEvent(VaultItemListingEvent.NavigateToCollectionItem(action.id))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleFolderClick(action: VaultItemListingsAction.FolderClick) {
|
private fun handleFolderClick(action: VaultItemListingsAction.FolderClick) {
|
||||||
sendEvent(VaultItemListingEvent.NavigateToFolderItem(action.id))
|
sendEvent(VaultItemListingEvent.NavigateToFolderItem(action.id))
|
||||||
}
|
}
|
||||||
|
@ -839,12 +844,18 @@ data class VaultItemListingState(
|
||||||
* Content state for the [VaultItemListingScreen] showing the actual content or items.
|
* Content state for the [VaultItemListingScreen] showing the actual content or items.
|
||||||
*
|
*
|
||||||
* @property displayItemList List of items to display.
|
* @property displayItemList List of items to display.
|
||||||
|
* @property displayFolderList list of folders to display.
|
||||||
|
* @property displayCollectionList list of collections to display.
|
||||||
*/
|
*/
|
||||||
data class Content(
|
data class Content(
|
||||||
val displayItemList: List<DisplayItem>,
|
val displayItemList: List<DisplayItem>,
|
||||||
val displayFolderList: List<FolderDisplayItem>,
|
val displayFolderList: List<FolderDisplayItem>,
|
||||||
|
val displayCollectionList: List<CollectionDisplayItem>,
|
||||||
) : ViewState() {
|
) : ViewState() {
|
||||||
override val isPullToRefreshEnabled: Boolean get() = true
|
override val isPullToRefreshEnabled: Boolean get() = true
|
||||||
|
val shouldShowDivider: Boolean
|
||||||
|
get() = displayItemList.isNotEmpty() &&
|
||||||
|
(displayFolderList.isNotEmpty() || displayCollectionList.isNotEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -895,6 +906,19 @@ data class VaultItemListingState(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collection that is displayed to the user on the ItemListingScreen.
|
||||||
|
*
|
||||||
|
* @property id the id of the collection.
|
||||||
|
* @property name the name of the collection.
|
||||||
|
* @property count the amount of ciphers in the collection.
|
||||||
|
*/
|
||||||
|
data class CollectionDisplayItem(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val count: Int,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents different types of item listing.
|
* Represents different types of item listing.
|
||||||
*/
|
*/
|
||||||
|
@ -1031,6 +1055,11 @@ sealed class VaultItemListingEvent {
|
||||||
*/
|
*/
|
||||||
data object NavigateToAddVaultItem : VaultItemListingEvent()
|
data object NavigateToAddVaultItem : VaultItemListingEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the collection.
|
||||||
|
*/
|
||||||
|
data class NavigateToCollectionItem(val collectionId: String) : VaultItemListingEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to the folder.
|
* Navigates to the folder.
|
||||||
*/
|
*/
|
||||||
|
@ -1165,6 +1194,13 @@ sealed class VaultItemListingsAction {
|
||||||
*/
|
*/
|
||||||
data class ItemClick(val id: String) : VaultItemListingsAction()
|
data class ItemClick(val id: String) : VaultItemListingsAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click on the collection.
|
||||||
|
*
|
||||||
|
* @property id the id of the collection that has been clicked
|
||||||
|
*/
|
||||||
|
data class CollectionClick(val id: String) : VaultItemListingsAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Click on the folder.
|
* Click on the folder.
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,6 +19,7 @@ data class VaultItemListingHandlers(
|
||||||
val addVaultItemClick: () -> Unit,
|
val addVaultItemClick: () -> Unit,
|
||||||
val itemClick: (id: String) -> Unit,
|
val itemClick: (id: String) -> Unit,
|
||||||
val folderClick: (id: String) -> Unit,
|
val folderClick: (id: String) -> Unit,
|
||||||
|
val collectionClick: (id: String) -> Unit,
|
||||||
val masterPasswordRepromptSubmit: (password: String, MasterPasswordRepromptData) -> Unit,
|
val masterPasswordRepromptSubmit: (password: String, MasterPasswordRepromptData) -> Unit,
|
||||||
val refreshClick: () -> Unit,
|
val refreshClick: () -> Unit,
|
||||||
val syncClick: () -> Unit,
|
val syncClick: () -> Unit,
|
||||||
|
@ -50,6 +51,9 @@ data class VaultItemListingHandlers(
|
||||||
addVaultItemClick = {
|
addVaultItemClick = {
|
||||||
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||||
},
|
},
|
||||||
|
collectionClick = {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.CollectionClick(it))
|
||||||
|
},
|
||||||
itemClick = { viewModel.trySendAction(VaultItemListingsAction.ItemClick(it)) },
|
itemClick = { viewModel.trySendAction(VaultItemListingsAction.ItemClick(it)) },
|
||||||
folderClick = { viewModel.trySendAction(VaultItemListingsAction.FolderClick(it)) },
|
folderClick = { viewModel.trySendAction(VaultItemListingsAction.FolderClick(it)) },
|
||||||
masterPasswordRepromptSubmit = { password, data ->
|
masterPasswordRepromptSubmit = { password, data ->
|
||||||
|
|
|
@ -19,7 +19,9 @@ import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons
|
import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toOverflowActions
|
import com.x8bit.bitwarden.ui.tools.feature.send.util.toOverflowActions
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.util.getCollections
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.getFolders
|
import com.x8bit.bitwarden.ui.vault.feature.util.getFolders
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.util.toCollectionDisplayName
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.toFolderDisplayName
|
import com.x8bit.bitwarden.ui.vault.feature.util.toFolderDisplayName
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.toLabelIcons
|
import com.x8bit.bitwarden.ui.vault.feature.util.toLabelIcons
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
|
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
|
||||||
|
@ -101,15 +103,20 @@ fun VaultData.toViewState(
|
||||||
}
|
}
|
||||||
.toFilteredList(vaultFilterType)
|
.toFilteredList(vaultFilterType)
|
||||||
|
|
||||||
val folderList = if (itemListingType is VaultItemListingState.ItemListingType.Vault.Folder &&
|
val folderList =
|
||||||
!itemListingType.folderId.isNullOrBlank()
|
(itemListingType as? VaultItemListingState.ItemListingType.Vault.Folder)
|
||||||
) {
|
?.folderId
|
||||||
folderViewList.getFolders(itemListingType.folderId)
|
?.let { folderViewList.getFolders(it) }
|
||||||
} else {
|
.orEmpty()
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (folderList.isNotEmpty() || filteredCipherViewList.isNotEmpty()) {
|
val collectionList =
|
||||||
|
(itemListingType as? VaultItemListingState.ItemListingType.Vault.Collection)
|
||||||
|
?.let { collectionViewList.getCollections(it.collectionId) }
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
|
return if (folderList.isNotEmpty() || filteredCipherViewList.isNotEmpty() ||
|
||||||
|
collectionList.isNotEmpty()
|
||||||
|
) {
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
displayItemList = filteredCipherViewList.toDisplayItemList(
|
displayItemList = filteredCipherViewList.toDisplayItemList(
|
||||||
baseIconUrl = baseIconUrl,
|
baseIconUrl = baseIconUrl,
|
||||||
|
@ -128,6 +135,18 @@ fun VaultData.toViewState(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
displayCollectionList = collectionList.map { collectionView ->
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
id = requireNotNull(collectionView.id),
|
||||||
|
name = collectionView.name,
|
||||||
|
count = this.cipherViewList
|
||||||
|
.count {
|
||||||
|
!it.id.isNullOrBlank() &&
|
||||||
|
it.deletedDate == null &&
|
||||||
|
collectionView.id in it.collectionIds
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Use the autofill empty message if necessary, otherwise use normal type-specific message
|
// Use the autofill empty message if necessary, otherwise use normal type-specific message
|
||||||
|
@ -141,6 +160,10 @@ fun VaultData.toViewState(
|
||||||
R.string.no_items_folder
|
R.string.no_items_folder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultItemListingState.ItemListingType.Vault.Collection -> {
|
||||||
|
R.string.no_items_collection
|
||||||
|
}
|
||||||
|
|
||||||
VaultItemListingState.ItemListingType.Vault.Trash -> {
|
VaultItemListingState.ItemListingType.Vault.Trash -> {
|
||||||
R.string.no_items_trash
|
R.string.no_items_trash
|
||||||
}
|
}
|
||||||
|
@ -177,6 +200,7 @@ fun List<SendView>.toViewState(
|
||||||
clock = clock,
|
clock = clock,
|
||||||
),
|
),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
VaultItemListingState.ViewState.NoItems(
|
VaultItemListingState.ViewState.NoItems(
|
||||||
|
@ -196,6 +220,7 @@ fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary(
|
||||||
collectionName = collectionList
|
collectionName = collectionList
|
||||||
.find { it.id == collectionId }
|
.find { it.id == collectionId }
|
||||||
?.name
|
?.name
|
||||||
|
?.toCollectionDisplayName(collectionList)
|
||||||
.orEmpty(),
|
.orEmpty(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.util
|
||||||
|
|
||||||
|
import com.bitwarden.core.CollectionView
|
||||||
|
|
||||||
|
private const val COLLECTION_DIVIDER: String = "/"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the nested collections of a given [collectionId] and updates their names to proper
|
||||||
|
* display names. This function is necessary if we want to show the nested collections for a
|
||||||
|
* specific collection.
|
||||||
|
*/
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
fun List<CollectionView>.getCollections(collectionId: String): List<CollectionView> {
|
||||||
|
val currentCollection = this.find { it.id == collectionId } ?: return emptyList()
|
||||||
|
|
||||||
|
// If two collections have the same name the second collection should have no nested collections
|
||||||
|
val firstCollectionWithName = this.first { it.name == currentCollection.name }
|
||||||
|
if (firstCollectionWithName.id != collectionId) return emptyList()
|
||||||
|
|
||||||
|
val collectionList = this
|
||||||
|
.getFilteredCollections(currentCollection.name)
|
||||||
|
.map {
|
||||||
|
it.copy(name = it.name.substringAfter(currentCollection.name + COLLECTION_DIVIDER))
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out the nest collections of the nest collections from the given list. If a
|
||||||
|
* [collectionName] is provided, collections that are not nested of the specified [collectionName]
|
||||||
|
* will be filtered out.
|
||||||
|
*/
|
||||||
|
fun List<CollectionView>.getFilteredCollections(
|
||||||
|
collectionName: String? = null,
|
||||||
|
): List<CollectionView> =
|
||||||
|
this.filter { collectionView ->
|
||||||
|
// If the collection name is not null we filter out collections that are not nested
|
||||||
|
// collections.
|
||||||
|
if (collectionName != null &&
|
||||||
|
!collectionView.name.startsWith(collectionName + COLLECTION_DIVIDER)
|
||||||
|
) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forEach {
|
||||||
|
val firstCollection = collectionName
|
||||||
|
?.let { name -> collectionView.name.substringAfter(name + COLLECTION_DIVIDER) }
|
||||||
|
?: collectionView.name
|
||||||
|
|
||||||
|
val secondCollection = collectionName
|
||||||
|
?.let { name -> it.name.substringAfter(name + COLLECTION_DIVIDER) }
|
||||||
|
?: it.name
|
||||||
|
|
||||||
|
// We don't want to compare the collection to itself or itself plus a slash.
|
||||||
|
if (firstCollection == secondCollection) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the first collection name is blank or the first collection is a nested collection
|
||||||
|
// of the second collection, we want to filter it out.
|
||||||
|
if (firstCollection.isEmpty() ||
|
||||||
|
firstCollection.startsWith(secondCollection + COLLECTION_DIVIDER)
|
||||||
|
) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a collection name to a user-friendly display name. This function is necessary because
|
||||||
|
* the collection name we receive is often nested, and we want to extract just the relevant name for
|
||||||
|
* display to the user.
|
||||||
|
*/
|
||||||
|
fun String.toCollectionDisplayName(list: List<CollectionView>): String {
|
||||||
|
var collectionName = this
|
||||||
|
|
||||||
|
// cycle through the list and determine the correct display name of the collection.
|
||||||
|
list.forEach { collection ->
|
||||||
|
if (this.startsWith(collection.name + COLLECTION_DIVIDER)) {
|
||||||
|
val newName = this.substringAfter(collection.name + COLLECTION_DIVIDER)
|
||||||
|
if (newName.length < collectionName.length) {
|
||||||
|
collectionName = newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionName
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.util.getFilteredCollections
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.getFilteredFolders
|
import com.x8bit.bitwarden.ui.vault.feature.util.getFilteredFolders
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.toLabelIcons
|
import com.x8bit.bitwarden.ui.vault.feature.util.toLabelIcons
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
|
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
|
||||||
|
@ -47,7 +48,10 @@ fun VaultData.toViewState(
|
||||||
|
|
||||||
val filteredFolderViewList = folderViewList.toFilteredList(vaultFilterType).getFilteredFolders()
|
val filteredFolderViewList = folderViewList.toFilteredList(vaultFilterType).getFilteredFolders()
|
||||||
|
|
||||||
val filteredCollectionViewList = collectionViewList.toFilteredList(vaultFilterType)
|
val filteredCollectionViewList = collectionViewList
|
||||||
|
.toFilteredList(vaultFilterType)
|
||||||
|
.getFilteredCollections()
|
||||||
|
|
||||||
val noFolderItems = filteredCipherViewList
|
val noFolderItems = filteredCipherViewList
|
||||||
.filter { it.folderId.isNullOrBlank() }
|
.filter { it.folderId.isNullOrBlank() }
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,12 @@ import com.bitwarden.core.CollectionView
|
||||||
/**
|
/**
|
||||||
* Create a mock [CollectionView] with a given [number].
|
* Create a mock [CollectionView] with a given [number].
|
||||||
*/
|
*/
|
||||||
fun createMockCollectionView(number: Int): CollectionView =
|
fun createMockCollectionView(number: Int, name: String? = null): CollectionView =
|
||||||
CollectionView(
|
CollectionView(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
organizationId = "mockOrganizationId-$number",
|
organizationId = "mockOrganizationId-$number",
|
||||||
hidePasswords = false,
|
hidePasswords = false,
|
||||||
name = "mockName-$number",
|
name = name ?: "mockName-$number",
|
||||||
externalId = "mockExternalId-$number",
|
externalId = "mockExternalId-$number",
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,8 +6,6 @@ import androidx.compose.ui.test.assertIsNotDisplayed
|
||||||
import androidx.compose.ui.test.assertTextEquals
|
import androidx.compose.ui.test.assertTextEquals
|
||||||
import androidx.compose.ui.test.filterToOne
|
import androidx.compose.ui.test.filterToOne
|
||||||
import androidx.compose.ui.test.hasAnyAncestor
|
import androidx.compose.ui.test.hasAnyAncestor
|
||||||
import androidx.compose.ui.test.hasScrollToNodeAction
|
|
||||||
import androidx.compose.ui.test.hasText
|
|
||||||
import androidx.compose.ui.test.isDialog
|
import androidx.compose.ui.test.isDialog
|
||||||
import androidx.compose.ui.test.isDisplayed
|
import androidx.compose.ui.test.isDisplayed
|
||||||
import androidx.compose.ui.test.isPopup
|
import androidx.compose.ui.test.isPopup
|
||||||
|
@ -15,7 +13,6 @@ import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollToNode
|
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
@ -39,6 +36,7 @@ import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
import com.x8bit.bitwarden.ui.util.assertSwitcherIsDisplayed
|
||||||
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
import com.x8bit.bitwarden.ui.util.assertSwitcherIsNotDisplayed
|
||||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||||
|
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
|
||||||
import com.x8bit.bitwarden.ui.util.performAccountClick
|
import com.x8bit.bitwarden.ui.util.performAccountClick
|
||||||
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
import com.x8bit.bitwarden.ui.util.performAccountIconClick
|
||||||
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
import com.x8bit.bitwarden.ui.util.performAccountLongClick
|
||||||
|
@ -566,14 +564,12 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
name = "test", id = "1", count = 0,
|
name = "test", id = "1", count = 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = folders)
|
||||||
.performScrollToNode(hasText(folders))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = folders)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,14 +588,12 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
displayFolderList = listOf(
|
displayFolderList = listOf(
|
||||||
VaultItemListingState.FolderDisplayItem(name = "test", id = "1", count = 0),
|
VaultItemListingState.FolderDisplayItem(name = "test", id = "1", count = 0),
|
||||||
),
|
),
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = folders)
|
||||||
.performScrollToNode(hasText(folders))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = folders)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
.assertTextEquals(folders, 1.toString())
|
.assertTextEquals(folders, 1.toString())
|
||||||
|
|
||||||
|
@ -624,15 +618,13 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
count = 0,
|
count = 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = folders)
|
||||||
.performScrollToNode(hasText(folders))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = folders)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
.assertTextEquals(folders, 3.toString())
|
.assertTextEquals(folders, 3.toString())
|
||||||
}
|
}
|
||||||
|
@ -648,6 +640,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = emptyList(),
|
displayItemList = emptyList(),
|
||||||
displayFolderList = listOf(
|
displayFolderList = listOf(
|
||||||
VaultItemListingState.FolderDisplayItem(
|
VaultItemListingState.FolderDisplayItem(
|
||||||
|
@ -665,6 +658,123 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Collections text should be displayed according to state`() {
|
||||||
|
val collectionName = "Collections"
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(text = collectionName)
|
||||||
|
.assertDoesNotExist()
|
||||||
|
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayItemList = emptyList(),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
displayCollectionList = listOf(
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = "Collection",
|
||||||
|
id = "1",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithTextAfterScroll(collectionName)
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Collection text count should be displayed according to state`() {
|
||||||
|
val collections = "Collections"
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(text = collections)
|
||||||
|
.assertDoesNotExist()
|
||||||
|
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayItemList = emptyList(),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
displayCollectionList = listOf(
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = "Collection",
|
||||||
|
id = "1",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = "Collection2",
|
||||||
|
id = "2",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = "Collection3",
|
||||||
|
id = "3",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithTextAfterScroll(text = collections)
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertTextEquals(collections, 3.toString())
|
||||||
|
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayItemList = emptyList(),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
displayCollectionList = listOf(
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = "Collection",
|
||||||
|
id = "1",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithTextAfterScroll(text = collections)
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertTextEquals(collections, 1.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `collectionDisplayItems should be displayed according to state`() {
|
||||||
|
val collectionName = "TestCollection"
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(text = collectionName)
|
||||||
|
.assertDoesNotExist()
|
||||||
|
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayItemList = emptyList(),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
displayCollectionList = listOf(
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
name = collectionName,
|
||||||
|
id = "1",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(text = collectionName)
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Items text should be displayed according to state`() {
|
fun `Items text should be displayed according to state`() {
|
||||||
val items = "Items"
|
val items = "Items"
|
||||||
|
@ -676,6 +786,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1),
|
createDisplayItem(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -684,10 +795,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = items)
|
||||||
.performScrollToNode(hasText(items))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = items)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,6 +810,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1),
|
createDisplayItem(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -710,16 +819,14 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = items)
|
||||||
.performScrollToNode(hasText(items))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = items)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
.assertTextEquals(items, 1.toString())
|
.assertTextEquals(items, 1.toString())
|
||||||
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1),
|
createDisplayItem(number = 1),
|
||||||
createDisplayItem(number = 2),
|
createDisplayItem(number = 2),
|
||||||
|
@ -732,10 +839,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNode(hasScrollToNodeAction())
|
.onNodeWithTextAfterScroll(text = items)
|
||||||
.performScrollToNode(hasText(items))
|
|
||||||
composeTestRule
|
|
||||||
.onNodeWithText(text = items)
|
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
.assertTextEquals(items, 4.toString())
|
.assertTextEquals(items, 4.toString())
|
||||||
}
|
}
|
||||||
|
@ -745,6 +849,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1),
|
createDisplayItem(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -767,6 +872,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1)
|
createDisplayItem(number = 1)
|
||||||
.copy(
|
.copy(
|
||||||
|
@ -794,6 +900,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1)
|
createDisplayItem(number = 1)
|
||||||
.copy(
|
.copy(
|
||||||
|
@ -845,6 +952,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1)
|
createDisplayItem(number = 1)
|
||||||
.copy(
|
.copy(
|
||||||
|
@ -875,6 +983,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createDisplayItem(number = 1)
|
createDisplayItem(number = 1)
|
||||||
.copy(
|
.copy(
|
||||||
|
@ -1015,6 +1124,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
it.copy(
|
it.copy(
|
||||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(createCipherDisplayItem(number = 1)),
|
displayItemList = listOf(createCipherDisplayItem(number = 1)),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
),
|
),
|
||||||
|
@ -1052,6 +1162,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
it.copy(
|
it.copy(
|
||||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createCipherDisplayItem(number = 1)
|
createCipherDisplayItem(number = 1)
|
||||||
.copy(shouldShowMasterPasswordReprompt = true),
|
.copy(shouldShowMasterPasswordReprompt = true),
|
||||||
|
@ -1083,6 +1194,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
it.copy(
|
it.copy(
|
||||||
itemListingType = VaultItemListingState.ItemListingType.Send.SendFile,
|
itemListingType = VaultItemListingState.ItemListingType.Send.SendFile,
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(createDisplayItem(number = 1)),
|
displayItemList = listOf(createDisplayItem(number = 1)),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
),
|
),
|
||||||
|
@ -1109,6 +1221,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(createDisplayItem(number = number)),
|
displayItemList = listOf(createDisplayItem(number = number)),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
),
|
),
|
||||||
|
@ -1133,6 +1246,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(createDisplayItem(number = number)),
|
displayItemList = listOf(createDisplayItem(number = number)),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
),
|
),
|
||||||
|
@ -1222,6 +1336,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(createDisplayItem(number = 1)),
|
displayItemList = listOf(createDisplayItem(number = 1)),
|
||||||
displayFolderList = emptyList(),
|
displayFolderList = emptyList(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -305,6 +305,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
} returns ValidatePasswordResult.Error
|
} returns ValidatePasswordResult.Error
|
||||||
val initialState = createVaultItemListingState(
|
val initialState = createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -354,6 +355,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
val initialState = createVaultItemListingState(
|
val initialState = createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -485,6 +487,17 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `CollectionClick for vault item should emit NavigateToCollectionItem`() = runTest {
|
||||||
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
val testId = "1"
|
||||||
|
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.actionChannel.trySend(VaultItemListingsAction.CollectionClick(testId))
|
||||||
|
assertEquals(VaultItemListingEvent.NavigateToCollectionItem(testId), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `RefreshClick should sync`() = runTest {
|
fun `RefreshClick should sync`() = runTest {
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
@ -842,6 +855,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createVaultItemListingState(
|
createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -889,6 +903,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createVaultItemListingState(
|
createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1).copy(isAutofill = true),
|
createMockDisplayItemForCipher(number = 1).copy(isAutofill = true),
|
||||||
),
|
),
|
||||||
|
@ -990,6 +1005,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createVaultItemListingState(
|
createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -1097,6 +1113,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createVaultItemListingState(
|
createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
@ -1211,6 +1228,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createVaultItemListingState(
|
createVaultItemListingState(
|
||||||
viewState = VaultItemListingState.ViewState.Content(
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(number = 1),
|
createMockDisplayItemForCipher(number = 1),
|
||||||
),
|
),
|
||||||
|
|
|
@ -397,6 +397,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(
|
createMockDisplayItemForCipher(
|
||||||
number = 1,
|
number = 1,
|
||||||
|
@ -469,6 +470,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForCipher(
|
createMockDisplayItemForCipher(
|
||||||
number = 1,
|
number = 1,
|
||||||
|
@ -582,6 +584,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(
|
displayItemList = listOf(
|
||||||
createMockDisplayItemForSend(number = 1, sendType = SendType.FILE),
|
createMockDisplayItemForSend(number = 1, sendType = SendType.FILE),
|
||||||
createMockDisplayItemForSend(number = 2, sendType = SendType.TEXT),
|
createMockDisplayItemForSend(number = 2, sendType = SendType.TEXT),
|
||||||
|
@ -683,7 +686,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
@Test
|
@Test
|
||||||
fun `toViewState should properly filter and return the correct folders`() {
|
fun `toViewState should properly filter and return the correct folders`() {
|
||||||
val vaultData = VaultData(
|
val vaultData = VaultData(
|
||||||
listOf(createMockCipherView(number = 1)),
|
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||||
collectionViewList = emptyList(),
|
collectionViewList = emptyList(),
|
||||||
folderViewList = listOf(
|
folderViewList = listOf(
|
||||||
FolderView("1", "test", clock.instant()),
|
FolderView("1", "test", clock.instant()),
|
||||||
|
@ -705,6 +708,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
displayItemList = listOf(),
|
displayItemList = listOf(),
|
||||||
displayFolderList = listOf(
|
displayFolderList = listOf(
|
||||||
VaultItemListingState.FolderDisplayItem(
|
VaultItemListingState.FolderDisplayItem(
|
||||||
|
@ -717,4 +721,48 @@ class VaultItemListingDataExtensionsTest {
|
||||||
actual,
|
actual,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toViewState should properly filter and return the correct collections`() {
|
||||||
|
val vaultData = VaultData(
|
||||||
|
cipherViewList = emptyList(),
|
||||||
|
collectionViewList = listOf(
|
||||||
|
createMockCollectionView(1, "test"),
|
||||||
|
createMockCollectionView(2, "test/test"),
|
||||||
|
createMockCollectionView(3, "Collection/test"),
|
||||||
|
createMockCollectionView(4, "test/Collection"),
|
||||||
|
createMockCollectionView(5, "Collection"),
|
||||||
|
),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = vaultData.toViewState(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
autofillSelectionData = null,
|
||||||
|
itemListingType = VaultItemListingState.ItemListingType.Vault.Collection("mockId-1"),
|
||||||
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = listOf(
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
id = "mockId-2",
|
||||||
|
name = "test",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
VaultItemListingState.CollectionDisplayItem(
|
||||||
|
id = "mockId-4",
|
||||||
|
name = "Collection",
|
||||||
|
count = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
displayItemList = emptyList(),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.util
|
||||||
|
|
||||||
|
import com.bitwarden.core.CollectionView
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class CollectionViewExtensionsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getCollections should get the collections for a collectionId with the correct names`() {
|
||||||
|
val collectionList: List<CollectionView> = listOf(
|
||||||
|
createMockCollectionView(number = 1, name = "test"),
|
||||||
|
createMockCollectionView(number = 2, name = "test/test"),
|
||||||
|
createMockCollectionView(number = 3, name = "test/Collection"),
|
||||||
|
createMockCollectionView(number = 4, name = "test/test/test"),
|
||||||
|
createMockCollectionView(number = 5, name = "Collection"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = listOf(
|
||||||
|
createMockCollectionView(number = 2, name = "test"),
|
||||||
|
createMockCollectionView(number = 3, name = "Collection"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expected,
|
||||||
|
collectionList.getCollections("mockId-1"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getFilteredCollections should properly filter out sub collections in a list`() {
|
||||||
|
val collectionList: List<CollectionView> = listOf(
|
||||||
|
createMockCollectionView(number = 1, name = "test"),
|
||||||
|
createMockCollectionView(number = 2, name = "test/test"),
|
||||||
|
createMockCollectionView(number = 3, name = "test/Collection"),
|
||||||
|
createMockCollectionView(number = 4, name = "test/test/test"),
|
||||||
|
createMockCollectionView(number = 5, name = "Collection"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = listOf(
|
||||||
|
createMockCollectionView(number = 1, name = "test"),
|
||||||
|
createMockCollectionView(number = 5, name = "Collection"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expected,
|
||||||
|
collectionList.getFilteredCollections(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toCollectionDisplayName should return the correct name`() {
|
||||||
|
val collectionName = "Collection/test/2"
|
||||||
|
|
||||||
|
val collectionList: List<CollectionView> = listOf(
|
||||||
|
createMockCollectionView(number = 1, name = "Collection/test"),
|
||||||
|
createMockCollectionView(number = 2, name = "test/test"),
|
||||||
|
createMockCollectionView(number = 3, name = "test/Collection"),
|
||||||
|
createMockCollectionView(number = 4, name = collectionName),
|
||||||
|
createMockCollectionView(number = 5, name = "Collection"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"2",
|
||||||
|
collectionName.toCollectionDisplayName(collectionList),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -494,10 +494,16 @@ class VaultDataExtensionsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `toViewState should properly filter nested folders out`() {
|
fun `toViewState should properly filter nested items out`() {
|
||||||
val vaultData = VaultData(
|
val vaultData = VaultData(
|
||||||
listOf(createMockCipherView(number = 1)),
|
listOf(createMockCipherView(number = 1)),
|
||||||
collectionViewList = emptyList(),
|
collectionViewList = listOf(
|
||||||
|
createMockCollectionView(1, "test"),
|
||||||
|
createMockCollectionView(2, "test/test"),
|
||||||
|
createMockCollectionView(3, "Collection/test"),
|
||||||
|
createMockCollectionView(4, "test/Collection"),
|
||||||
|
createMockCollectionView(5, "Collection"),
|
||||||
|
),
|
||||||
folderViewList = listOf(
|
folderViewList = listOf(
|
||||||
FolderView("1", "test", clock.instant()),
|
FolderView("1", "test", clock.instant()),
|
||||||
FolderView("2", "test/test", clock.instant()),
|
FolderView("2", "test/test", clock.instant()),
|
||||||
|
@ -522,6 +528,18 @@ class VaultDataExtensionsTest {
|
||||||
identityItemsCount = 0,
|
identityItemsCount = 0,
|
||||||
secureNoteItemsCount = 0,
|
secureNoteItemsCount = 0,
|
||||||
favoriteItems = listOf(),
|
favoriteItems = listOf(),
|
||||||
|
collectionItems = listOf(
|
||||||
|
VaultState.ViewState.CollectionItem(
|
||||||
|
id = "mockId-1",
|
||||||
|
name = "test",
|
||||||
|
itemCount = 1,
|
||||||
|
),
|
||||||
|
VaultState.ViewState.CollectionItem(
|
||||||
|
id = "mockId-5",
|
||||||
|
name = "Collection",
|
||||||
|
itemCount = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
folderItems = listOf(
|
folderItems = listOf(
|
||||||
VaultState.ViewState.FolderItem(
|
VaultState.ViewState.FolderItem(
|
||||||
id = "1",
|
id = "1",
|
||||||
|
@ -540,7 +558,6 @@ class VaultDataExtensionsTest {
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
collectionItems = listOf(),
|
|
||||||
noFolderItems = listOf(),
|
noFolderItems = listOf(),
|
||||||
trashItemsCount = 0,
|
trashItemsCount = 0,
|
||||||
totpItemsCount = 1,
|
totpItemsCount = 1,
|
||||||
|
|
Loading…
Reference in a new issue