From 084e11d75e27a85ae76eb965f6c8d16c47a6e714 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 2 Dec 2025 18:11:17 +0100 Subject: [PATCH] two more edge cases tests --- .../SecurityAndPrivacyScreenModels.swift | 24 +++++-- .../SecurityAndPrivacyScreenViewModel.swift | 2 + ...curityAndPrivacyScreenViewModelTests.swift | 64 +++++++++++++++++-- 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift index f85d94f4d..1b8e75e4a 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift @@ -81,7 +81,7 @@ struct SecurityAndPrivacyScreenViewState: BindableState { L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(joinedParentSpace.name) case .singleUnknown(let id): L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(id) - case .multiple: + case .multiple, .empty: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionMultipleParentsDescription } } else { @@ -90,7 +90,9 @@ struct SecurityAndPrivacyScreenViewState: BindableState { } var accessSectionFooter: AttributedString? { - if bindings.desiredSettings.accessType.isSpaceUsers, isSpaceMembersOptionSelectable, selectableSpacesCount > 1 { + if bindings.desiredSettings.accessType.isSpaceUsers, + isSpaceMembersOptionSelectable, + case .multiple = spaceSelection { Self.accessSectionFooterAttributedString } else { nil @@ -104,13 +106,27 @@ struct SecurityAndPrivacyScreenViewState: BindableState { case singleUnknown(id: String) /// Multiple spaces are available for selection case multiple + /// Edge case where the space members access type was found but it did not contain any space + case empty } var spaceSelection: SpaceSelection { - if selectableSpacesCount > 1 { + if selectableSpacesCount == 0 { + .empty + } else if selectableSpacesCount > 1 { .multiple } else if let joinedParent = joinedParentSpaces.first { - .singleJoined(joinedParent) + if case let .spaceUsers(ids) = currentSettings.accessType { + if ids.isEmpty { + // Edge case where the access type is already space members, but it does not contain any id + // So if the user wants to add their own parent they need to do it from the selection menu + .multiple + } else { + .singleJoined(joinedParent) + } + } else { + .singleJoined(joinedParent) + } } else if let unknownSpaceID = currentSettings.accessType.spaceIDs.first { // The space is not joined by the user but is currently selected .singleUnknown(id: unknownSpaceID) diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift index 56d58cf5b..3a8f63f5d 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift @@ -245,6 +245,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, state.bindings.desiredSettings.accessType = .spaceUsers(spaceIDs: [joinedParent.id]) case .singleUnknown(let id): state.bindings.desiredSettings.accessType = .spaceUsers(spaceIDs: [id]) + case .empty: + break // Very edge case. We do nothing in this case. case .multiple: displayManageAuthorizedSpacesScreen() } diff --git a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift index 74296ebd4..3fa5ce9cb 100644 --- a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift +++ b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift @@ -39,7 +39,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { XCTAssertTrue(context.viewState.isSaveDisabled) XCTAssertTrue(context.viewState.isSpaceMembersOptionSelectable) guard case .singleJoined = context.viewState.spaceSelection else { - XCTFail("Expected spaceSelection to be .singleSpace") + XCTFail("Expected spaceSelection to be .singleJoined") return } @@ -72,7 +72,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { XCTAssertNil(context.viewState.accessSectionFooter) XCTAssertTrue(context.viewState.isSaveDisabled) guard case .singleUnknown = context.viewState.spaceSelection else { - XCTFail("Expected spaceSelection to be .singleSpace") + XCTFail("Expected spaceSelection to be .singleUnknown") return } @@ -84,7 +84,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { XCTAssertTrue(context.viewState.isSaveDisabled) XCTAssertEqual(context.desiredSettings.accessType, .spaceUsers(spaceIDs: [space.id])) guard case .singleUnknown = context.viewState.spaceSelection else { - XCTFail("Expected spaceSelection to be .singleSpace") + XCTFail("Expected spaceSelection to be .singleUnknown") return } } @@ -100,7 +100,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { XCTAssertTrue(context.viewState.isSaveDisabled) XCTAssertTrue(context.viewState.isSpaceMembersOptionSelectable) guard case .multiple = context.viewState.spaceSelection else { - XCTFail("Expected spaceSelection to be .singleSpace") + XCTFail("Expected spaceSelection to be .multiple") return } @@ -146,7 +146,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { XCTAssertTrue(context.viewState.isSaveDisabled) XCTAssertTrue(context.viewState.isSpaceMembersOptionSelectable) guard case .multiple = context.viewState.spaceSelection else { - XCTFail("Expected spaceSelection to be .singleSpace") + XCTFail("Expected spaceSelection to be .multiple") return } @@ -180,6 +180,60 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase { await fulfillment(of: [expectation]) } + func testEmptySpaceMembersSelectionEdgeCase() async throws { + // Edge case where there is no available joined parents and the room has a restricted join rule. + // With no space ids in it + setupViewModel(isSpaceSettingsEnabled: true, + joinedParentSpaces: [], + joinRule: .restricted(rules: [])) + + let deferred = deferFulfillment(context.$viewState) { $0.selectableSpacesCount == 0 } + try await deferred.fulfill() + + XCTAssertTrue(context.viewState.currentSettings.accessType.isSpaceUsers) + XCTAssertTrue(context.viewState.isSaveDisabled) + XCTAssertFalse(context.viewState.isSpaceMembersOptionSelectable) + XCTAssertNil(context.viewState.accessSectionFooter) + guard case .empty = context.viewState.spaceSelection else { + XCTFail("Expected spaceSelection to be .empty") + return + } + } + + func testEmptySpaceMembersSelectionWithJoinedParentEdgeCase() async throws { + // Edge case where there is one available joined parent but the room has a restricted join rule. + // With no space ids in it + let singleRoom = [SpaceRoomProxyProtocol].mockSingleRoom + setupViewModel(isSpaceSettingsEnabled: true, + joinedParentSpaces: singleRoom, + joinRule: .restricted(rules: [])) + + let deferred = deferFulfillment(context.$viewState) { $0.selectableSpacesCount == 1 } + try await deferred.fulfill() + + XCTAssertTrue(context.viewState.currentSettings.accessType.isSpaceUsers) + XCTAssertTrue(context.viewState.isSaveDisabled) + XCTAssertTrue(context.viewState.isSpaceMembersOptionSelectable) + XCTAssertNotNil(context.viewState.accessSectionFooter) + guard case .multiple = context.viewState.spaceSelection else { + XCTFail("Expected spaceSelection to be .multiple") + return + } + + let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in + switch action { + case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection): + return authorizedSpacesSelection.joinedParentSpaces.map(\.id) == singleRoom.map(\.id) && + authorizedSpacesSelection.unknownSpacesIDs.isEmpty && + authorizedSpacesSelection.initialSelectedIDs.isEmpty + default: + return false + } + } + context.send(viewAction: .manageSpaces) + try await deferredAction.fulfill() + } + func testSave() async throws { setupViewModel(isSpaceSettingsEnabled: false, joinedParentSpaces: [], joinRule: .public)