Skip to content

Commit 5e0c390

Browse files
authored
[PM-25993] Automatically select default collection on adding a new cipher (#1973)
1 parent 9de3747 commit 5e0c390

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessor.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,12 @@ final class AddEditItemProcessor: StateProcessor<// swiftlint:disable:this type_
301301
state.owner = ownershipOptions.first
302302
}
303303

304-
if !state.configuration.isAdding {
304+
if state.configuration.isAdding {
305+
let defaultCollection = state.collectionsForOwner.first(where: { $0.type == .defaultUserCollection })
306+
if let defaultCollectionId = defaultCollection?.id, state.collectionIds.isEmpty {
307+
state.collectionIds.append(defaultCollectionId)
308+
}
309+
} else {
305310
await services.eventService.collect(eventType: .cipherClientViewed, cipherId: state.cipher.id)
306311
}
307312
} catch {

BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemProcessorTests.swift

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,134 @@ class AddEditItemProcessorTests: BitwardenTestCase {
11281128
XCTAssertTrue(subject.state.canAssignToCollection)
11291129
}
11301130

1131+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1132+
/// and selects the default user collection when there's any on adding mode.
1133+
@MainActor
1134+
func test_perform_fetchCipherOptions_defaultUserCollection() async {
1135+
let collections: [CollectionView] = [
1136+
.fixture(id: "1", name: "Design", organizationId: "1"),
1137+
.fixture(id: "2", name: "Engineering", organizationId: "1", type: .defaultUserCollection),
1138+
.fixture(id: "3", name: "Platform", organizationId: "1"),
1139+
]
1140+
1141+
policyService.policyAppliesToUserResult[.personalOwnership] = true
1142+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1143+
vaultRepository.fetchCollectionsResult = .success(collections)
1144+
1145+
await subject.perform(.fetchCipherOptions)
1146+
1147+
XCTAssertEqual(subject.state.allUserCollections, collections)
1148+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1149+
XCTAssertEqual(subject.state.collectionIds, ["2"])
1150+
}
1151+
1152+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1153+
/// and selects the first default user collection when there are multiple on adding mode.
1154+
/// This shouldn't actually happen, but just in case check that the first one is selected.
1155+
@MainActor
1156+
func test_perform_fetchCipherOptions_defaultUserCollectionMultiple() async {
1157+
let collections: [CollectionView] = [
1158+
.fixture(id: "1", name: "Design", organizationId: "1"),
1159+
.fixture(id: "2", name: "Engineering", organizationId: "1", type: .defaultUserCollection),
1160+
.fixture(id: "3", name: "Platform", organizationId: "1", type: .defaultUserCollection),
1161+
]
1162+
1163+
policyService.policyAppliesToUserResult[.personalOwnership] = true
1164+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1165+
vaultRepository.fetchCollectionsResult = .success(collections)
1166+
1167+
await subject.perform(.fetchCipherOptions)
1168+
1169+
XCTAssertEqual(subject.state.allUserCollections, collections)
1170+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1171+
XCTAssertEqual(subject.state.collectionIds, ["2"])
1172+
}
1173+
1174+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1175+
/// and doesn't select the default user collection when there isn't any on adding mode.
1176+
@MainActor
1177+
func test_perform_fetchCipherOptions_defaultUserCollectionNotFound() async {
1178+
let collections: [CollectionView] = [
1179+
.fixture(id: "1", name: "Design", organizationId: "1", type: .sharedCollection),
1180+
.fixture(id: "2", name: "Engineering", organizationId: "1", type: .sharedCollection),
1181+
]
1182+
1183+
policyService.policyAppliesToUserResult[.personalOwnership] = true
1184+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1185+
vaultRepository.fetchCollectionsResult = .success(collections)
1186+
1187+
await subject.perform(.fetchCipherOptions)
1188+
1189+
XCTAssertEqual(subject.state.allUserCollections, collections)
1190+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1191+
XCTAssertEqual(subject.state.collectionIds, [])
1192+
}
1193+
1194+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1195+
/// and doesn't select the default user collection when personal ownership policy is disabled.
1196+
@MainActor
1197+
func test_perform_fetchCipherOptions_defaultUserCollectionPersonalOwnershipDisabled() async {
1198+
let collections: [CollectionView] = [
1199+
.fixture(id: "1", name: "Design", organizationId: "1", type: .defaultUserCollection),
1200+
.fixture(id: "2", name: "Engineering", organizationId: "1", type: .sharedCollection),
1201+
]
1202+
1203+
policyService.policyAppliesToUserResult[.personalOwnership] = false
1204+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1205+
vaultRepository.fetchCollectionsResult = .success(collections)
1206+
1207+
await subject.perform(.fetchCipherOptions)
1208+
1209+
XCTAssertEqual(subject.state.allUserCollections, collections)
1210+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1211+
XCTAssertEqual(subject.state.collectionIds, [])
1212+
}
1213+
1214+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1215+
/// and doesn't select the default user collection when it has `nil` ID.
1216+
@MainActor
1217+
func test_perform_fetchCipherOptions_defaultUserCollectionIDNil() async {
1218+
let collections: [CollectionView] = [
1219+
.fixture(id: nil, name: "Design", organizationId: "1", type: .defaultUserCollection),
1220+
.fixture(id: "2", name: "Engineering", organizationId: "1"),
1221+
]
1222+
1223+
policyService.policyAppliesToUserResult[.personalOwnership] = true
1224+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1225+
vaultRepository.fetchCollectionsResult = .success(collections)
1226+
1227+
await subject.perform(.fetchCipherOptions)
1228+
1229+
XCTAssertEqual(subject.state.allUserCollections, collections)
1230+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1231+
XCTAssertEqual(subject.state.collectionIds, [])
1232+
}
1233+
1234+
/// `perform(_:)` with `.fetchCipherOptions` fetches the ownership options for a cipher from the repository
1235+
/// and doesn't select the default user collection when it's editing a cipher instead of adding.
1236+
@MainActor
1237+
func test_perform_fetchCipherOptions_defaultUserCollectionEditing() async throws {
1238+
subject.state = try XCTUnwrap(CipherItemState(
1239+
existing: CipherView.fixture(),
1240+
hasPremium: false
1241+
))
1242+
1243+
let collections: [CollectionView] = [
1244+
.fixture(id: "1", name: "Design", organizationId: "1", type: .defaultUserCollection),
1245+
.fixture(id: "2", name: "Engineering", organizationId: "1"),
1246+
]
1247+
1248+
policyService.policyAppliesToUserResult[.personalOwnership] = true
1249+
vaultRepository.fetchCipherOwnershipOptions = [.organization(id: "1", name: "OrgTest")]
1250+
vaultRepository.fetchCollectionsResult = .success(collections)
1251+
1252+
await subject.perform(.fetchCipherOptions)
1253+
1254+
XCTAssertEqual(subject.state.allUserCollections, collections)
1255+
XCTAssertEqual(subject.state.ownershipOptions, [.organization(id: "1", name: "OrgTest")])
1256+
XCTAssertEqual(subject.state.collectionIds, [])
1257+
}
1258+
11311259
/// `perform(_:)` with `.savePressed` displays an alert if name field is invalid.
11321260
@MainActor
11331261
func test_perform_savePressed_invalidName() async throws {

0 commit comments

Comments
 (0)