Add a Low Priority room filter behind a feature flag. (#4394)

* Add Low Priority room filter with incompatibility rules

Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com>

* Add a feature flag and use the SDK filters.

* Fix filter tests and add RoomSummaryProvider tests for the default filters.

* Remove unnecessary comment.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com>
Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
Copilot
2025-08-20 11:56:44 +01:00
committed by GitHub
parent 6408432c76
commit 9fab9f9c9a
21 changed files with 234 additions and 35 deletions

View File

@@ -574,6 +574,7 @@
6A54F52443EC52AC5CD772C0 /* JoinRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869A8A4632E511351BFE2EC4 /* JoinRoomScreen.swift */; }; 6A54F52443EC52AC5CD772C0 /* JoinRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869A8A4632E511351BFE2EC4 /* JoinRoomScreen.swift */; };
6A64546ABE648ED9E6DBB459 /* RemoteSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D186A6DB8FAC5C9D0E4D61 /* RemoteSettingsHook.swift */; }; 6A64546ABE648ED9E6DBB459 /* RemoteSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D186A6DB8FAC5C9D0E4D61 /* RemoteSettingsHook.swift */; };
6A916606A8B92FE8A990A219 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A1941B874A3BE9CDDF43EF /* XCTestCase.swift */; }; 6A916606A8B92FE8A990A219 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A1941B874A3BE9CDDF43EF /* XCTestCase.swift */; };
6AB306367E56A6F6DFA0E2FF /* RoomSummaryProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46E441BA50705E6CEC89FE0 /* RoomSummaryProviderTests.swift */; };
6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */; }; 6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */; };
6AEB650311F694A5702255C9 /* UserProfileScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B4932E4EFBC8FAC10972CD /* UserProfileScreenCoordinator.swift */; }; 6AEB650311F694A5702255C9 /* UserProfileScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B4932E4EFBC8FAC10972CD /* UserProfileScreenCoordinator.swift */; };
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; }; 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; };
@@ -2707,6 +2708,7 @@
F409D44C2E6BE50462E82F8A /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = "<group>"; }; F409D44C2E6BE50462E82F8A /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = "<group>"; };
F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionMock.swift; sourceTree = "<group>"; }; F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionMock.swift; sourceTree = "<group>"; };
F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = "<group>"; }; F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = "<group>"; };
F46E441BA50705E6CEC89FE0 /* RoomSummaryProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderTests.swift; sourceTree = "<group>"; };
F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = "<group>"; }; F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = "<group>"; };
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; }; F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -4530,6 +4532,7 @@
48FEFF746DB341CDB18D7AAA /* RoomRolesAndPermissionsScreenViewModelTests.swift */, 48FEFF746DB341CDB18D7AAA /* RoomRolesAndPermissionsScreenViewModelTests.swift */,
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */, 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */, AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */,
F46E441BA50705E6CEC89FE0 /* RoomSummaryProviderTests.swift */,
046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */, 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */,
2E88534A39781D76487D59DF /* SecureBackupKeyBackupScreenViewModelTests.swift */, 2E88534A39781D76487D59DF /* SecureBackupKeyBackupScreenViewModelTests.swift */,
848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */, 848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */,
@@ -7242,6 +7245,7 @@
84C631E734FD2555B39B681C /* RoomRolesAndPermissionsScreenViewModelTests.swift in Sources */, 84C631E734FD2555B39B681C /* RoomRolesAndPermissionsScreenViewModelTests.swift in Sources */,
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */, 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */, CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */,
6AB306367E56A6F6DFA0E2FF /* RoomSummaryProviderTests.swift in Sources */,
15913A5B07118C1268A840E4 /* RoomSummaryTests.swift in Sources */, 15913A5B07118C1268A840E4 /* RoomSummaryTests.swift in Sources */,
7691233E3572A9173FD96CB3 /* SecureBackupKeyBackupScreenViewModelTests.swift in Sources */, 7691233E3572A9173FD96CB3 /* SecureBackupKeyBackupScreenViewModelTests.swift in Sources */,
EB87DF90CF6F8D5D12404C6E /* SecureBackupLogoutConfirmationScreenViewModelTests.swift in Sources */, EB87DF90CF6F8D5D12404C6E /* SecureBackupLogoutConfirmationScreenViewModelTests.swift in Sources */,

View File

@@ -55,6 +55,7 @@ final class AppSettings {
// Feature flags // Feature flags
case publicSearchEnabled case publicSearchEnabled
case fuzzyRoomListSearchEnabled case fuzzyRoomListSearchEnabled
case lowPriorityFilterEnabled
case enableOnlySignedDeviceIsolationMode case enableOnlySignedDeviceIsolationMode
case enableKeyShareOnInvite case enableKeyShareOnInvite
case knockingEnabled case knockingEnabled
@@ -345,6 +346,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.fuzzyRoomListSearchEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.fuzzyRoomListSearchEnabled, defaultValue: false, storageType: .userDefaults(store))
var fuzzyRoomListSearchEnabled var fuzzyRoomListSearchEnabled
@UserPreference(key: UserDefaultsKeys.lowPriorityFilterEnabled, defaultValue: false, storageType: .userDefaults(store))
var lowPriorityFilterEnabled
@UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store))
var knockingEnabled var knockingEnabled

View File

@@ -113,7 +113,7 @@ struct HomeScreenViewState: BindableState {
return rooms return rooms
} }
var bindings = HomeScreenViewStateBindings() var bindings: HomeScreenViewStateBindings
var placeholderRooms: [HomeScreenRoom] { var placeholderRooms: [HomeScreenRoom] {
(1...10).map { _ in (1...10).map { _ in
@@ -136,7 +136,7 @@ struct HomeScreenViewState: BindableState {
} }
struct HomeScreenViewStateBindings { struct HomeScreenViewStateBindings {
var filtersState = RoomListFiltersState() var filtersState: RoomListFiltersState
var searchQuery = "" var searchQuery = ""
var isSearchFieldFocused = false var isSearchFieldFocused = false

View File

@@ -40,7 +40,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
super.init(initialViewState: .init(userID: userSession.clientProxy.userID), super.init(initialViewState: .init(userID: userSession.clientProxy.userID,
bindings: .init(filtersState: .init(appSettings: appSettings))),
mediaProvider: userSession.mediaProvider) mediaProvider: userSession.mediaProvider)
userSession.clientProxy.userAvatarURLPublisher userSession.clientProxy.userAvatarURLPublisher

View File

@@ -21,6 +21,7 @@ enum RoomListFilter: Int, CaseIterable, Identifiable {
case rooms case rooms
case favourites case favourites
case invites case invites
case lowPriority
static var availableFilters: [RoomListFilter] { static var availableFilters: [RoomListFilter] {
RoomListFilter.allCases RoomListFilter.allCases
@@ -38,6 +39,8 @@ enum RoomListFilter: Int, CaseIterable, Identifiable {
return L10n.screenRoomlistFilterFavourites return L10n.screenRoomlistFilterFavourites
case .invites: case .invites:
return L10n.screenRoomlistFilterInvites return L10n.screenRoomlistFilterInvites
case .lowPriority:
return L10n.screenRoomlistFilterLowPriority
} }
} }
@@ -50,10 +53,11 @@ enum RoomListFilter: Int, CaseIterable, Identifiable {
case .unreads: case .unreads:
return [.invites] return [.invites]
case .favourites: case .favourites:
// When we will have Low Priority we may need to return it here return [.invites, .lowPriority]
return [.invites]
case .invites: case .invites:
return [.rooms, .people, .unreads, .favourites] return [.rooms, .people, .unreads, .favourites, .lowPriority]
case .lowPriority:
return [.favourites, .invites]
} }
} }
@@ -69,20 +73,29 @@ enum RoomListFilter: Int, CaseIterable, Identifiable {
return .all(filters: [.favourite, .joined]) return .all(filters: [.favourite, .joined])
case .invites: case .invites:
return .invite return .invite
case .lowPriority:
// Note: When not activated, the setFilter method automatically applies the .nonLowPriority filter.
return .all(filters: [.lowPriority, .joined])
} }
} }
} }
struct RoomListFiltersState { struct RoomListFiltersState {
private(set) var activeFilters: OrderedSet<RoomListFilter> private(set) var activeFilters: OrderedSet<RoomListFilter>
private let appSettings: AppSettings
init(activeFilters: OrderedSet<RoomListFilter> = []) { init(activeFilters: OrderedSet<RoomListFilter> = [], appSettings: AppSettings) {
self.activeFilters = .init(activeFilters) self.activeFilters = .init(activeFilters)
self.appSettings = appSettings
} }
var availableFilters: [RoomListFilter] { var availableFilters: [RoomListFilter] {
var availableFilters = OrderedSet(RoomListFilter.availableFilters) var availableFilters = OrderedSet(RoomListFilter.availableFilters)
if !appSettings.lowPriorityFilterEnabled {
availableFilters.remove(.lowPriority)
}
for filter in activeFilters { for filter in activeFilters {
availableFilters.remove(filter) availableFilters.remove(filter)
filter.incompatibleFilters.forEach { availableFilters.remove($0) } filter.incompatibleFilters.forEach { availableFilters.remove($0) }

View File

@@ -19,13 +19,6 @@ struct RoomListFilterView: View {
} }
} }
struct RoomListFilterView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
RoomListFilterView(filter: .people, isActive: .constant(false))
RoomListFilterView(filter: .people, isActive: .constant(true))
}
}
private struct FilterToggleStyle: ToggleStyle { private struct FilterToggleStyle: ToggleStyle {
private func strokeColor(isOn: Bool) -> Color { private func strokeColor(isOn: Bool) -> Color {
isOn ? .compound.bgActionPrimaryRest : .compound.borderInteractiveSecondary isOn ? .compound.bgActionPrimaryRest : .compound.borderInteractiveSecondary
@@ -59,3 +52,12 @@ private struct FilterToggleStyle: ToggleStyle {
} }
} }
} }
// MARK: - Previews
struct RoomListFilterView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
RoomListFilterView(filter: .people, isActive: .constant(false))
RoomListFilterView(filter: .people, isActive: .constant(true))
}
}

View File

@@ -23,6 +23,8 @@ struct RoomListFiltersEmptyStateView: View {
return L10n.screenRoomlistFilterFavouritesEmptyStateTitle return L10n.screenRoomlistFilterFavouritesEmptyStateTitle
case .invites: case .invites:
return L10n.screenRoomlistFilterInvitesEmptyStateTitle return L10n.screenRoomlistFilterInvitesEmptyStateTitle
case .lowPriority:
return L10n.screenRoomlistFilterLowPriorityEmptyStateTitle
} }
} }
return L10n.screenRoomlistFilterMixedEmptyStateTitle return L10n.screenRoomlistFilterMixedEmptyStateTitle
@@ -55,9 +57,11 @@ struct RoomListFiltersEmptyStateView_Previews: PreviewProvider, TestablePreview
static var previews: some View { static var previews: some View {
VStack(spacing: 24) { VStack(spacing: 24) {
ForEach(RoomListFilter.allCases) { filter in ForEach(RoomListFilter.allCases) { filter in
RoomListFiltersEmptyStateView(state: .init(activeFilters: [filter])) RoomListFiltersEmptyStateView(state: .init(activeFilters: [filter],
appSettings: ServiceLocator.shared.settings))
} }
RoomListFiltersEmptyStateView(state: .init(activeFilters: [.people, .favourites])) RoomListFiltersEmptyStateView(state: .init(activeFilters: [.people, .favourites],
appSettings: ServiceLocator.shared.settings))
} }
.padding(.bottom) .padding(.bottom)
.previewLayout(.sizeThatFits) .previewLayout(.sizeThatFits)

View File

@@ -87,7 +87,10 @@ struct RoomListFiltersView: View {
struct RoomListFiltersView_Previews: PreviewProvider, TestablePreview { struct RoomListFiltersView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
RoomListFiltersView(state: .constant(.init())) RoomListFiltersView(state: .constant(.init(appSettings: ServiceLocator.shared.settings)))
RoomListFiltersView(state: .constant(.init(activeFilters: [.rooms, .favourites]))) RoomListFiltersView(state: .constant(.init(activeFilters: [.rooms, .favourites],
appSettings: ServiceLocator.shared.settings)))
RoomListFiltersView(state: .constant(.init(activeFilters: [.lowPriority],
appSettings: ServiceLocator.shared.settings)))
} }
} }

View File

@@ -50,6 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var publicSearchEnabled: Bool { get set } var publicSearchEnabled: Bool { get set }
var fuzzyRoomListSearchEnabled: Bool { get set } var fuzzyRoomListSearchEnabled: Bool { get set }
var lowPriorityFilterEnabled: Bool { get set }
var knockingEnabled: Bool { get set } var knockingEnabled: Bool { get set }
} }

View File

@@ -50,6 +50,10 @@ struct DeveloperOptionsScreen: View {
Toggle(isOn: $context.fuzzyRoomListSearchEnabled) { Toggle(isOn: $context.fuzzyRoomListSearchEnabled) {
Text("Fuzzy searching") Text("Fuzzy searching")
} }
Toggle(isOn: $context.lowPriorityFilterEnabled) {
Text("Low priority filter")
}
} }
Section("Timeline") { Section("Timeline") {

View File

@@ -127,9 +127,14 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
} }
_ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .all(filters: filters)) _ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .all(filters: filters))
case let .all(filters): case let .all(filters):
var filters = filters.map(\.rustFilter) var rustFilters = filters.map(\.rustFilter)
filters.append(contentsOf: [.nonLeft, .nonSpace, .deduplicateVersions]) rustFilters.append(contentsOf: [.nonLeft, .nonSpace, .deduplicateVersions])
_ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .all(filters: filters))
if !filters.contains(.lowPriority), appSettings.lowPriorityFilterEnabled {
rustFilters.append(.all(filters: [.nonLowPriority, .joined]))
}
_ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .all(filters: rustFilters))
} }
} }

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:298a6d99b6e2a010ca3db350524498210f988cd6e302474cd7c3aa902de0a5cb
size 81295

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d17daf1432f41dbb4ff28d010500f85e48da50e605a6671a8c28e828497f8116
size 82478

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:92f896aab9210b8aec55600da87c0032fdcbcd87c3a1aa8b5fc3dba5a570a67e
size 38601

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d19d7e28fadb77f1cbb3a0a0b6ccae511910c6b1924b28507b5f105712f801a
size 37696

View File

@@ -10,16 +10,25 @@ import XCTest
@testable import ElementX @testable import ElementX
final class RoomListFiltersStateTests: XCTestCase { final class RoomListFiltersStateTests: XCTestCase {
var appSettings: AppSettings!
var state: RoomListFiltersState! var state: RoomListFiltersState!
var allCasesWithoutLowPriority = RoomListFilter.allCases.filter { $0 != .lowPriority }
override func setUp() { override func setUp() {
state = RoomListFiltersState() AppSettings.resetAllSettings()
appSettings = AppSettings()
state = RoomListFiltersState(appSettings: appSettings)
}
override func tearDown() {
AppSettings.resetAllSettings()
} }
func testInitialState() { func testInitialState() {
XCTAssertFalse(state.isFiltering) XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, []) XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases) XCTAssertEqual(state.availableFilters, allCasesWithoutLowPriority)
} }
func testSetAndUnsetFilters() { func testSetAndUnsetFilters() {
@@ -30,7 +39,7 @@ final class RoomListFiltersStateTests: XCTestCase {
state.deactivateFilter(.unreads) state.deactivateFilter(.unreads)
XCTAssertFalse(state.isFiltering) XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, []) XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases) XCTAssertEqual(state.availableFilters, allCasesWithoutLowPriority)
} }
func testMutuallyExclusiveFilters() { func testMutuallyExclusiveFilters() {
@@ -42,7 +51,7 @@ final class RoomListFiltersStateTests: XCTestCase {
state.deactivateFilter(.people) state.deactivateFilter(.people)
XCTAssertFalse(state.isFiltering) XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, []) XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases) XCTAssertEqual(state.availableFilters, allCasesWithoutLowPriority)
state.activateFilter(.rooms) state.activateFilter(.rooms)
XCTAssertTrue(state.isFiltering) XCTAssertTrue(state.isFiltering)
@@ -71,7 +80,7 @@ final class RoomListFiltersStateTests: XCTestCase {
state.clearFilters() state.clearFilters()
XCTAssertFalse(state.isFiltering) XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, []) XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases) XCTAssertEqual(state.availableFilters, allCasesWithoutLowPriority)
} }
func testOrder() { func testOrder() {
@@ -81,7 +90,7 @@ final class RoomListFiltersStateTests: XCTestCase {
state.deactivateFilter(.favourites) state.deactivateFilter(.favourites)
XCTAssertEqual(state.activeFilters, []) XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases) XCTAssertEqual(state.availableFilters, allCasesWithoutLowPriority)
state.activateFilter(.rooms) state.activateFilter(.rooms)
XCTAssertEqual(state.activeFilters, [.rooms]) XCTAssertEqual(state.activeFilters, [.rooms])
@@ -95,4 +104,25 @@ final class RoomListFiltersStateTests: XCTestCase {
XCTAssertEqual(state.activeFilters, [.rooms]) XCTAssertEqual(state.activeFilters, [.rooms])
XCTAssertEqual(state.availableFilters, [.unreads, .favourites]) XCTAssertEqual(state.availableFilters, [.unreads, .favourites])
} }
// MARK: Low Priority feature flag
// Don't forget to add .lowPriority into the mix above when enabling the feature.
func testWithLowPriorityFeature() {
enableLowPriorityFeature()
XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases)
state.activateFilter(.lowPriority)
XCTAssertEqual(state.activeFilters, [.lowPriority])
XCTAssertEqual(state.availableFilters, [.unreads, .people, .rooms])
}
// MARK: - Helpers
private func enableLowPriorityFeature() {
appSettings.lowPriorityFilterEnabled = true
state = RoomListFiltersState(appSettings: appSettings)
}
} }

View File

@@ -0,0 +1,116 @@
//
// 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 XCTest
@testable import ElementX
final class RoomSummaryProviderTests: XCTestCase {
var appSettings: AppSettings!
var roomList: RoomListSDKMock!
var dynamicEntriesController: RoomListDynamicEntriesControllerSDKMock!
var roomSummaryProvider: RoomSummaryProvider!
override func setUp() {
AppSettings.resetAllSettings()
appSettings = AppSettings()
}
override func tearDown() {
AppSettings.resetAllSettings()
}
func testDefaultRustFilters() async {
// Given a new room provider.
setupProvider()
await Task.yield()
// Then it should have the default Rust filters enabled.
XCTAssertEqual(dynamicEntriesController.setFilterKindCallsCount, 1)
XCTAssertEqual(dynamicEntriesController.setFilterKindReceivedInvocations.last, .all(filters: [.nonLeft,
.nonSpace,
.deduplicateVersions]))
// When setting one our user filters.
roomSummaryProvider.setFilter(.all(filters: [.favourites]))
await Task.yield()
// 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]))
}
func testLowPriorityRustFilters() async {
// Given a new room provider with the low priority filter enabled.
setupProvider(isLowPriorityFilterEnabled: true)
await Task.yield()
// 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,
.all(filters: [.nonLowPriority, .joined])]))
// When setting the low priority filter.
roomSummaryProvider.setFilter(.all(filters: [.lowPriority]))
await Task.yield()
// 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]))
// When setting another one of our filters.
roomSummaryProvider.setFilter(.all(filters: [.rooms]))
await Task.yield()
// 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,
.all(filters: [.nonLowPriority, .joined])]))
}
// MARK: - Helpers
private func setupProvider(isLowPriorityFilterEnabled: Bool = false) {
appSettings.lowPriorityFilterEnabled = isLowPriorityFilterEnabled
let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org")
let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder())
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: stateEventStringBuilder,
messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: attributedStringBuilder,
destination: .roomList),
shouldDisambiguateDisplayNames: true,
shouldPrefixSenderName: true)
roomSummaryProvider = RoomSummaryProvider(roomListService: RoomListServiceSDKMock(),
eventStringBuilder: eventStringBuilder,
name: "Test",
notificationSettings: NotificationSettingsProxyMock(with: .init()),
appSettings: appSettings)
dynamicEntriesController = RoomListDynamicEntriesControllerSDKMock()
dynamicEntriesController.setFilterKindReturnValue = true
let dynamicAdaptersResult = RoomListEntriesWithDynamicAdaptersResultSDKMock()
dynamicAdaptersResult.controllerReturnValue = dynamicEntriesController
roomList = RoomListSDKMock()
roomList.entriesWithDynamicAdaptersPageSizeListenerReturnValue = dynamicAdaptersResult
roomList.loadingStateListenerReturnValue = .some(.init(state: .notLoaded, stateStream: .init(noPointer: .init())))
roomSummaryProvider.setRoomList(roomList)
}
}