From c91c69b86b66c2b03fd4ffbf02dab1320d3dac62 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:58:38 +0100 Subject: [PATCH] Joining a space from the room flow. (#4563) * Add the More menu to SpaceScreen. And a simple way to leave a space for testing. * Start a space flow when accepting a space invite from the room flow. * Minimise the tab bar on iOS 26 when scrolling down. * Enable spaces by default! --- ElementX.xcodeproj/project.pbxproj | 4 +++ .../Navigation/NavigationTabCoordinator.swift | 1 + .../Application/Settings/AppSettings.swift | 2 +- .../ChatsFlowCoordinator.swift | 2 ++ .../RoomFlowCoordinator.swift | 29 ++++++++++++--- .../RoomFlowCoordinatorStateMachine.swift | 4 +++ .../SpaceFlowCoordinator.swift | 21 +++++++++-- .../Sources/Other/SwiftUI/Backports.swift | 19 ++++++++++ .../SpaceScreen/SpaceScreenCoordinator.swift | 3 ++ .../SpaceScreen/SpaceScreenModels.swift | 4 +++ .../SpaceScreen/SpaceScreenViewModel.swift | 16 +++++++++ .../Spaces/SpaceScreen/View/SpaceScreen.swift | 17 +++++++++ .../Sources/RoomSummaryProviderTests.swift | 35 ++++++++----------- 13 files changed, 128 insertions(+), 29 deletions(-) create mode 100644 ElementX/Sources/Other/SwiftUI/Backports.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 7e2a8248f..d5e6a0e0e 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -367,6 +367,7 @@ 41DFDD212D1BE57CA50D783B /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; 41F553349AF44567184822D8 /* APNSPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D670124FC3E84F23A62CCF /* APNSPayload.swift */; }; 4219391CD2351E410554B3E8 /* AggregratedReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */; }; + 42239FD59394A086E046C9BF /* Backports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A46ABD27628CB5FC402541 /* Backports.swift */; }; 422E8D182CA688D4565CD1E1 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; }; 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EC7285D3CFEF0D3011BCF /* GeoURI.swift */; }; 42995EA68E194B19DAD6AEEF /* test_rotated_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */; }; @@ -2098,6 +2099,7 @@ 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSendInfoLabel.swift; sourceTree = ""; }; 75821CD31A4BD02B99C327A4 /* DataProtectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionManager.swift; sourceTree = ""; }; 76310030C831D4610A705603 /* URLComponentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsTests.swift; sourceTree = ""; }; + 76A46ABD27628CB5FC402541 /* Backports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backports.swift; sourceTree = ""; }; 7720ACAC6155AB7F9C70B546 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nb; path = nb.lproj/Localizable.stringsdict; sourceTree = ""; }; 7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = ""; }; 780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModel.swift; sourceTree = ""; }; @@ -2995,6 +2997,7 @@ 052CC920F473C10B509F9FC1 /* SwiftUI */ = { isa = PBXGroup; children = ( + 76A46ABD27628CB5FC402541 /* Backports.swift */, 693E16574C6F7F9FA1015A8C /* Search.swift */, 832397B5C3D00A4BF52C5F0B /* ShouldScrollOnKeyboardDidShow.swift */, D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */, @@ -7579,6 +7582,7 @@ 0B05A35FF5D1ED9E8A0B41A7 /* AuthenticationStartScreenViewModelProtocol.swift in Sources */, 4AAA8606FBA290E23D15422E /* AvatarHeaderView.swift in Sources */, 1621BF6316FFFEF5AE067C77 /* Avatars.swift in Sources */, + 42239FD59394A086E046C9BF /* Backports.swift in Sources */, 7A25D6926A2C01DB8D0D67A5 /* BadgeLabel.swift in Sources */, A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */, 2B52B2A5A6496C5C0F952776 /* BannedRoomProxy.swift in Sources */, diff --git a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift index 399fbe228..b1ea7af4a 100644 --- a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift @@ -307,6 +307,7 @@ private struct NavigationTabCoordinatorView: View { .toolbar(module.details.barVisibility(in: horizontalSizeClass), for: .tabBar) } } + .backportTabBarMinimizeBehaviorOnScrollDown() .introspect(.tabView, on: .supportedVersions, customize: configureAppearance) .sheet(item: $navigationTabCoordinator.sheetModule) { module in module.coordinator?.toPresentable() diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index 1415cc2eb..a27522f96 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -381,7 +381,7 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.threadsEnabled, defaultValue: false, storageType: .userDefaults(store)) var threadsEnabled - @UserPreference(key: UserDefaultsKeys.spacesEnabled, defaultValue: false, storageType: .userDefaults(store)) + @UserPreference(key: UserDefaultsKeys.spacesEnabled, defaultValue: true, storageType: .userDefaults(store)) var spacesEnabled @UserPreference(key: UserDefaultsKeys.nextGenHTMLParserEnabled, defaultValue: true, storageType: .userDefaults(store)) diff --git a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift index 674fce74e..8f30be891 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift @@ -469,6 +469,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { actionsSubject.send(.showCallScreen(roomProxy: roomProxy)) case .verifyUser(let userID): actionsSubject.send(.sessionVerification(.userInitiator(userID: userID))) + case .continueWithSpaceFlow(let spaceRoomListProxy): + stateMachine.processEvent(.startSpaceFlow, userInfo: .init(animated: false, spaceRoomListProxy: spaceRoomListProxy)) case .finished: stateMachine.processEvent(.deselectRoom) } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 36eee2d44..8ad72c972 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -13,6 +13,9 @@ import UserNotifications enum RoomFlowCoordinatorAction: Equatable { case presentCallScreen(roomProxy: JoinedRoomProxyProtocol) case verifyUser(userID: String) + /// The requested room was actually a space. The room flow has been dismissed + /// and a space flow should be started to continue. + case continueWithSpaceFlow(SpaceRoomListProxyProtocol) case finished static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool { @@ -385,6 +388,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { presentJoinRoomScreen(via: via, animated: true) case (_, .dismissJoinRoomScreen, .complete): dismissFlow(animated: animated) + case (_, .joinedSpace, .complete): + guard let spaceRoomListProxy = (context.userInfo as? EventUserInfo)?.spaceRoomListProxy else { + fatalError("The space room list proxy is required to present a space.") + } + dismissFlow(animated: animated, continuingWith: spaceRoomListProxy) case (.joinRoomScreen, .presentDeclineAndBlockScreen(let userID), .declineAndBlockScreen): presentDeclineAndBlockScreen(userID: userID) @@ -720,8 +728,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.dismissFlow, userInfo: EventUserInfo(animated: animated)) } } - case .joined(.space): - #warning("The space flow should be shown here.") + case .joined(.space(let spaceRoomListProxy)): + stateMachine.tryEvent(.joinedSpace, userInfo: EventUserInfo(animated: true, spaceRoomListProxy: spaceRoomListProxy)) case .cancelled: stateMachine.tryEvent(.dismissJoinRoomScreen) case .presentDeclineAndBlock(let userID): @@ -745,7 +753,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } - private func dismissFlow(animated: Bool) { + private func dismissFlow(animated: Bool, continuingWith spaceRoomListProxy: SpaceRoomListProxyProtocol? = nil) { childRoomFlowCoordinator?.clearRoute(animated: animated) if isChildFlow { @@ -757,12 +765,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } else { navigationStackCoordinator.popToRoot(animated: false) - navigationStackCoordinator.setRootCoordinator(nil, animated: false) + + // Leave the root alone when it is about to be replaced by the space flow, otherwise when running on + // iPhone the compact module diffs call the dismissal callback and we present a blank space flow 🙈 + if spaceRoomListProxy == nil { + navigationStackCoordinator.setRootCoordinator(nil, animated: false) + } } timelineController = nil - actionsSubject.send(.finished) + if let spaceRoomListProxy { + actionsSubject.send(.continueWithSpaceFlow(spaceRoomListProxy)) + } else { + actionsSubject.send(.finished) + } flowParameters.analytics.signpost.endRoomFlow() } @@ -1457,6 +1474,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { actionsSubject.send(.presentCallScreen(roomProxy: roomProxy)) case .verifyUser(let userID): actionsSubject.send(.verifyUser(userID: userID)) + case .continueWithSpaceFlow(let spaceRoomListProxy): + #warning("Present the space as a child.") case .finished: stateMachine.tryEvent(.dismissChildFlow) } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift index 8baff73e2..d101b5432 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift @@ -84,11 +84,13 @@ extension RoomFlowCoordinator { struct EventUserInfo { let animated: Bool var timelineController: TimelineControllerProtocol? + var spaceRoomListProxy: SpaceRoomListProxyProtocol? } enum Event: EventType { case presentJoinRoomScreen(via: [String]) case dismissJoinRoomScreen + case joinedSpace case presentRoom(presentationAction: PresentationAction?) case dismissFlow @@ -321,6 +323,8 @@ extension RoomFlowCoordinator { return .joinRoomScreen case (_, .dismissJoinRoomScreen): return .complete + case (_, .joinedSpace): + return .complete case (.joinRoomScreen, .presentDeclineAndBlockScreen): return .declineAndBlockScreen diff --git a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift index 849f780d6..e28cb9cc1 100644 --- a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift @@ -52,6 +52,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { case presentingChild(childSpaceID: String, previousState: State) /// A room flow is in progress case roomFlow(previousState: State) + + case leftSpace } enum Event: EventType { @@ -62,6 +64,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { /// The join space screen joined the space. case joinedSpace + /// The space screen left the space. + case leftSpace /// Request the presentation of a child space flow. /// @@ -116,7 +120,7 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { switch stateMachine.state { case .initial: break - case .joinSpace, .space: + case .joinSpace, .space, .leftSpace: if isChildFlow { navigationStackCoordinator.pop(animated: animated) } else { @@ -145,11 +149,18 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { stateMachine.addRoutes(event: .joinedSpace, transitions: [.joinSpace => .space]) { [weak self] _ in self?.presentSpaceAfterJoining() } + stateMachine.addRoutes(event: .leftSpace, transitions: [.space => .leftSpace]) { [weak self] _ in + self?.clearRoute(animated: true) + } stateMachine.addRouteMapping { event, fromState, userInfo in - guard event == .startChildFlow, case .space = fromState else { return nil } + guard event == .startChildFlow else { return nil } guard let childEntryPoint = userInfo as? SpaceFlowCoordinatorEntryPoint else { fatalError("An entry point must be provided.") } - return .presentingChild(childSpaceID: childEntryPoint.spaceID, previousState: fromState) + return switch fromState { + case .space: .presentingChild(childSpaceID: childEntryPoint.spaceID, previousState: fromState) + case .roomFlow(let previousState): .presentingChild(childSpaceID: childEntryPoint.spaceID, previousState: previousState) + default: nil + } } handler: { [weak self] context in guard let self, let entryPoint = context.userInfo as? SpaceFlowCoordinatorEntryPoint else { return } startChildFlow(with: entryPoint) @@ -205,6 +216,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.startChildFlow, userInfo: SpaceFlowCoordinatorEntryPoint.joinSpace(spaceRoomProxy)) case .selectRoom(let roomID): stateMachine.tryEvent(.startRoomFlow(roomID: roomID)) + case .leftSpace: + stateMachine.tryEvent(.leftSpace) } } .store(in: &cancellables) @@ -314,6 +327,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { actionsSubject.send(.presentCallScreen(roomProxy: roomProxy)) case .verifyUser(let userID): actionsSubject.send(.verifyUser(userID: userID)) + case .continueWithSpaceFlow(let spaceRoomListProxy): + stateMachine.tryEvent(.startChildFlow, userInfo: SpaceFlowCoordinatorEntryPoint.space(spaceRoomListProxy)) case .finished: stateMachine.tryEvent(.stopRoomFlow) } diff --git a/ElementX/Sources/Other/SwiftUI/Backports.swift b/ElementX/Sources/Other/SwiftUI/Backports.swift new file mode 100644 index 000000000..4afcd0ef2 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Backports.swift @@ -0,0 +1,19 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import SwiftUI + +extension View { + @ViewBuilder + func backportTabBarMinimizeBehaviorOnScrollDown() -> some View { + if #available(iOS 26.0, *) { + tabBarMinimizeBehavior(.onScrollDown) + } else { + self + } + } +} diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift index 655a182df..5bab50664 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift @@ -22,6 +22,7 @@ enum SpaceScreenCoordinatorAction { case selectSpace(SpaceRoomListProxyProtocol) case selectUnjoinedSpace(SpaceRoomProxyProtocol) case selectRoom(roomID: String) + case leftSpace } final class SpaceScreenCoordinator: CoordinatorProtocol { @@ -57,6 +58,8 @@ final class SpaceScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.selectUnjoinedSpace(spaceRoomProxy)) case .selectRoom(let roomID): actionsSubject.send(.selectRoom(roomID: roomID)) + case .leftSpace: + actionsSubject.send(.leftSpace) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenModels.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenModels.swift index 690bead9f..0248b9b03 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenModels.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenModels.swift @@ -11,11 +11,14 @@ enum SpaceScreenViewModelAction { case selectSpace(SpaceRoomListProxyProtocol) case selectUnjoinedSpace(SpaceRoomProxyProtocol) case selectRoom(roomID: String) + case leftSpace } struct SpaceScreenViewState: BindableState { let space: SpaceRoomProxyProtocol + var permalink: URL? + var isPaginating = false var rooms: [SpaceRoomProxyProtocol] var selectedSpaceRoomID: String? @@ -30,4 +33,5 @@ struct SpaceScreenViewStateBindings { } enum SpaceScreenViewAction { case spaceAction(SpaceRoomCell.Action) + case leaveSpace } diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift index fb9c53796..e07b3c91f 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift @@ -60,6 +60,13 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc selectedSpaceRoomPublisher .weakAssign(to: \.state.selectedSpaceRoomID, on: self) .store(in: &cancellables) + + Task { + if case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(spaceRoomListProxy.spaceRoomProxy.id), + case let .success(permalinkURL) = await roomProxy.matrixToPermalink() { + state.permalink = permalinkURL + } + } } // MARK: - Public @@ -81,6 +88,15 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc } case .spaceAction(.join(let spaceRoomProxy)): Task { await join(spaceRoomProxy) } + case .leaveSpace: + #if DEBUG + Task { // Temporary implementation to make joining a space easier to test. + if case let .joined(roomProxy) = await clientProxy.roomForIdentifier(spaceRoomListProxy.spaceRoomProxy.id), + case .success = await roomProxy.leaveRoom() { + actionsSubject.send(.leftSpace) + } + } + #endif } } diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift index 40b3879ff..f8867fc4d 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift @@ -42,6 +42,7 @@ struct SpaceScreen: View { } } + @ToolbarContentBuilder var toolbar: some ToolbarContent { // Use the same trick as the RoomScreen for a leading title view that // also hides the navigation title. @@ -50,6 +51,22 @@ struct SpaceScreen: View { roomAvatar: context.viewState.space.avatar, mediaProvider: context.mediaProvider) } + + ToolbarItemGroup(placement: .secondaryAction) { + if let permalink = context.viewState.permalink { + Section { + ShareLink(item: permalink) { + Label(L10n.actionShare, icon: \.shareIos) + } + } + } + + Section { + Button(role: .destructive) { context.send(viewAction: .leaveSpace) } label: { + Label(L10n.actionLeaveSpace, icon: \.leave) + } + } + } } } diff --git a/UnitTests/Sources/RoomSummaryProviderTests.swift b/UnitTests/Sources/RoomSummaryProviderTests.swift index dbb46c9de..b96ce9916 100644 --- a/UnitTests/Sources/RoomSummaryProviderTests.swift +++ b/UnitTests/Sources/RoomSummaryProviderTests.swift @@ -5,6 +5,7 @@ // Please see LICENSE files in the repository root for full details. // +import MatrixRustSDK import XCTest @testable import ElementX @@ -14,6 +15,10 @@ final class RoomSummaryProviderTests: XCTestCase { var roomList: RoomListSDKMock! var dynamicEntriesController: RoomListDynamicEntriesControllerSDKMock! + let baseFilters: [RoomListEntriesDynamicFilterKind] = [.any(filters: [.all(filters: [.nonSpace, .nonLeft]), + .all(filters: [.space, .invite])]), + .deduplicateVersions] + var roomSummaryProvider: RoomSummaryProvider! override func setUp() { @@ -32,9 +37,8 @@ final class RoomSummaryProviderTests: XCTestCase { // Then it should have the default Rust filters enabled. XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 1) - XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.nonLeft, - .nonSpace, - .deduplicateVersions])) + XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, + .all(filters: baseFilters)) // When setting one our user filters. roomSummaryProvider.setFilter(.all(filters: [.favourites])) @@ -42,10 +46,8 @@ final class RoomSummaryProviderTests: XCTestCase { // Then that filter should be added to the default Rust filters. XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 2) - XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.all(filters: [.favourite, .joined]), - .nonLeft, - .nonSpace, - .deduplicateVersions])) + XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, + .all(filters: [.all(filters: [.favourite, .joined])] + baseFilters)) } func testLowPriorityRustFilters() async { @@ -56,10 +58,8 @@ final class RoomSummaryProviderTests: XCTestCase { // Then the default Rust filters should include the non-low priority filter, // so that low priority rooms are hidden from the top of the room list. XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 1) - XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.nonLeft, - .nonSpace, - .deduplicateVersions, - .nonLowPriority])) + XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, + .all(filters: baseFilters + [.nonLowPriority])) // When setting the low priority filter. roomSummaryProvider.setFilter(.all(filters: [.lowPriority])) @@ -67,10 +67,8 @@ final class RoomSummaryProviderTests: XCTestCase { // Then the non-low priority filter should be replaced with the low priority filter. XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 2) - XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.all(filters: [.lowPriority, .joined]), - .nonLeft, - .nonSpace, - .deduplicateVersions])) + XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, + .all(filters: [.all(filters: [.lowPriority, .joined])] + baseFilters)) // When setting another one of our filters. roomSummaryProvider.setFilter(.all(filters: [.rooms])) @@ -78,11 +76,8 @@ final class RoomSummaryProviderTests: XCTestCase { // Then the filter should be combined with the non-low priority filter. XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 3) - XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.all(filters: [.category(expect: .group), .joined]), - .nonLeft, - .nonSpace, - .deduplicateVersions, - .nonLowPriority])) + XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, + .all(filters: [.all(filters: [.category(expect: .group), .joined])] + baseFilters + [.nonLowPriority])) } // MARK: - Helpers