diff --git a/ElementX/Sources/Mocks/RoomMemberProxyMock.swift b/ElementX/Sources/Mocks/RoomMemberProxyMock.swift index a38028d42..432c1cc12 100644 --- a/ElementX/Sources/Mocks/RoomMemberProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomMemberProxyMock.swift @@ -55,6 +55,18 @@ extension RoomMemberProxyMock { isAccountOwner: false, isIgnored: false)) } + + static var mockInvitedAlice: RoomMemberProxyMock { + RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org", + displayName: "Alice", + avatarURL: nil, + membership: .invite, + isNameAmbiguous: false, + powerLevel: 50, + normalizedPowerLevel: 50, + isAccountOwner: false, + isIgnored: false)) + } static var mockBob: RoomMemberProxyMock { RoomMemberProxyMock(with: .init(userID: "@bob:matrix.org", diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift index 5d2624efd..b6ee31a8a 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift @@ -39,6 +39,7 @@ struct RoomDetailsScreenViewState: BindableState { var avatarURL: URL? let permalink: URL? var members: [RoomMemberDetails] = [] + var joinedMembersCount = 0 var isProcessingIgnoreRequest = false var isLoadingMembers: Bool { diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 3237afb44..f25db99bb 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -21,26 +21,11 @@ typealias RoomDetailsScreenViewModelType = StateStoreViewModel? var callback: ((RoomDetailsScreenViewModelAction) -> Void)? @@ -57,11 +42,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr permalink: roomProxy.permalink, bindings: .init()), imageProvider: mediaProvider) - - roomProxy.membersPublisher.sink { [weak self] members in - self?.members = members - } - .store(in: &cancellables) + + setupSubscriptions() Task { await roomProxy.updateMembers() @@ -96,6 +78,49 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr // MARK: - Private + private func setupSubscriptions() { + roomProxy.membersPublisher + .sink { [weak self] members in + guard let self else { return } + + buildMembersDetailsTask = Task { + let (membersDetails, joinedMembersCount) = await self.buildMembersDetails(members: members) + + guard !Task.isCancelled else { return } + + if self.roomProxy.isDirect, self.roomProxy.isEncrypted, members.count == 2 { + self.dmRecipient = members.first(where: { !$0.isAccountOwner }) + } + + self.state.members = membersDetails + self.state.joinedMembersCount = joinedMembersCount + self.state.dmRecipient = self.dmRecipient.map(RoomMemberDetails.init(withProxy:)) + self.members = members + } + } + .store(in: &cancellables) + } + + private func buildMembersDetails(members: [RoomMemberProxyProtocol]) async -> (memberDetails: [RoomMemberDetails], joinedMembersCount: Int) { + await Task.detached { + // accessing RoomMember's properties is very slow. We need to do it in a background thread. + var roomMembersDetails: [RoomMemberDetails] = [] + var joinedMembersCount = 0 + roomMembersDetails.reserveCapacity(members.count) + + for member in members { + roomMembersDetails.append(RoomMemberDetails(withProxy: member)) + + if member.membership == .join { + joinedMembersCount += 1 + } + } + + return (roomMembersDetails, joinedMembersCount) + } + .value + } + private static let leaveRoomLoadingID = "LeaveRoomLoading" private func leaveRoom() async { diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index 7e42e2172..431f740ed 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -120,7 +120,7 @@ struct RoomDetailsScreen: View { if context.viewState.isLoadingMembers { ProgressView() } else { - Text(String(context.viewState.members.count)) + Text(String(context.viewState.joinedMembersCount)) .foregroundColor(.element.tertiaryContent) .font(.compound.bodyLG) } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenModels.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenModels.swift index 59ec1ec6d..690822edb 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenModels.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenModels.swift @@ -21,19 +21,32 @@ enum RoomMembersListScreenViewModelAction { } struct RoomMembersListScreenViewState: BindableState { - var members: [RoomMemberDetails] - + private var joinedMembers: [RoomMemberDetails] + private var invitedMembers: [RoomMemberDetails] var bindings: RoomMembersListScreenViewStateBindings + + init(joinedMembers: [RoomMemberDetails] = [], invitedMembers: [RoomMemberDetails] = [], bindings: RoomMembersListScreenViewStateBindings = .init()) { + self.joinedMembers = joinedMembers + self.invitedMembers = invitedMembers + self.bindings = bindings + } - var visibleMembers: [RoomMemberDetails] { - if bindings.searchQuery.isEmpty { - return members - } - - return members.lazy.filter { member in - member.id.localizedCaseInsensitiveContains(bindings.searchQuery) || - member.name?.localizedCaseInsensitiveContains(bindings.searchQuery) ?? false - } + var visibleJoinedMembers: [RoomMemberDetails] { + joinedMembers.lazy + .filter { member in + member.matches(searchQuery: bindings.searchQuery) + } + } + + var visibleInvitedMembers: [RoomMemberDetails] { + invitedMembers.lazy + .filter { member in + member.matches(searchQuery: bindings.searchQuery) + } + } + + var joinedMembersCount: Int { + joinedMembers.count } } @@ -47,3 +60,13 @@ struct RoomMembersListScreenViewStateBindings { enum RoomMembersListScreenViewAction { case selectMember(id: String) } + +private extension RoomMemberDetails { + func matches(searchQuery: String) -> Bool { + guard !searchQuery.isEmpty else { + return true + } + + return id.localizedCaseInsensitiveContains(searchQuery) || name?.localizedCaseInsensitiveContains(searchQuery) ?? false + } +} diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift index 286aa0511..1b80a34eb 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift @@ -24,13 +24,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe var callback: ((RoomMembersListScreenViewModelAction) -> Void)? - init(mediaProvider: MediaProviderProtocol, - members: [RoomMemberProxyProtocol]) { + init(mediaProvider: MediaProviderProtocol, members: [RoomMemberProxyProtocol]) { self.mediaProvider = mediaProvider self.members = members - super.init(initialViewState: .init(members: members.map { RoomMemberDetails(withProxy: $0) }, - bindings: .init()), - imageProvider: mediaProvider) + super.init(initialViewState: .init(), imageProvider: mediaProvider) + + buildMembersDetails(members: members) } // MARK: - Public @@ -45,4 +44,35 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe callback?(.selectMember(member)) } } + + // MARK: - Private + + func buildMembersDetails(members: [RoomMemberProxyProtocol]) { + Task { + let indicatorId = UUID().uuidString + defer { + ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(indicatorId) + } + ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: indicatorId, type: .modal, title: L10n.commonLoading, persistent: true)) + + let (invitedMembers, joinedMembers) = await split(members: members) + self.state = .init(joinedMembers: joinedMembers, invitedMembers: invitedMembers) + } + } + + func split(members: [RoomMemberProxyProtocol]) async -> (invited: [RoomMemberDetails], joined: [RoomMemberDetails]) { + await Task.detached { + // accessing RoomMember's properties is very slow. We need to do it in a background thread. + let invitedMembers = members + .filter { $0.membership == .invite } + .map(RoomMemberDetails.init(withProxy:)) + + let joinedMembers = members + .filter { $0.membership == .join } + .map(RoomMemberDetails.init(withProxy:)) + + return (invitedMembers, joinedMembers) + } + .value + } } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift index 141d3fb74..c9051760a 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift @@ -25,18 +25,8 @@ struct RoomMembersListScreen: View { var body: some View { ScrollView { LazyVStack(alignment: .leading) { - Section { - ForEach(context.viewState.visibleMembers) { member in - RoomMembersListScreenMemberCell(member: member, context: context) - .id(member.id) - } - } header: { - Text(L10n.commonMemberCount(context.viewState.members.count)) - .foregroundColor(.element.secondaryContent) - .font(.compound.bodyLG) - .padding(.vertical, 12) - } - .padding(.horizontal) + membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle) + membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(context.viewState.joinedMembersCount)) } } .searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always)) @@ -45,6 +35,24 @@ struct RoomMembersListScreen: View { .navigationTitle(L10n.commonPeople) .alert(item: $context.alertInfo) { $0.alert } } + + // MARK: - Private + + private func membersSection(data: [RoomMemberDetails], sectionTitle: String) -> some View { + Section { + ForEach(data, id: \.id) { member in + RoomMembersListScreenMemberCell(member: member, context: context) + } + } header: { + if !data.isEmpty { + Text(sectionTitle) + .foregroundColor(.element.secondaryContent) + .font(.compound.bodyLG) + .padding(.vertical, 12) + } + } + .padding(.horizontal) + } } // MARK: - Previews diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift index b3e1d3fda..b7a30df12 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift @@ -68,7 +68,7 @@ final class RoomMemberProxy: RoomMemberProxyProtocol { } func ignoreUser() async -> Result { - sendAccountDataEventBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) + sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) defer { sendAccountDataEventBackgroundTask?.stop() } @@ -84,7 +84,7 @@ final class RoomMemberProxy: RoomMemberProxyProtocol { } func unignoreUser() async -> Result { - sendAccountDataEventBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) + sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) defer { sendAccountDataEventBackgroundTask?.stop() } diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift index 1ce38bee7..511b58c17 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift @@ -22,7 +22,6 @@ enum RoomMemberProxyError: Error { case unignoreUserFailed } -@MainActor // sourcery: AutoMockable protocol RoomMemberProxyProtocol { var userID: String { get } diff --git a/ElementX/Sources/Services/RoomMember/RoomMemberDetails.swift b/ElementX/Sources/Services/RoomMember/RoomMemberDetails.swift index 4091832b9..e6b5369a5 100644 --- a/ElementX/Sources/Services/RoomMember/RoomMemberDetails.swift +++ b/ElementX/Sources/Services/RoomMember/RoomMemberDetails.swift @@ -24,7 +24,6 @@ struct RoomMemberDetails: Identifiable, Equatable { let isAccountOwner: Bool var isIgnored: Bool - @MainActor init(withProxy proxy: RoomMemberProxyProtocol) { id = proxy.userID name = proxy.displayName diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 060a614eb..dcae34353 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -307,6 +307,14 @@ class MockScreen: Identifiable { members: members)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator + case .roomMembersListScreenPendingInvites: + let navigationStackCoordinator = NavigationStackCoordinator() + let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie] + let coordinator = RoomMembersListScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator, + mediaProvider: MockMediaProvider(), + members: members)) + navigationStackCoordinator.setRootCoordinator(coordinator) + return navigationStackCoordinator case .reportContent: let navigationStackCoordinator = NavigationStackCoordinator() let coordinator = ReportContentScreenCoordinator(parameters: .init(itemID: "test", diff --git a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift index 15c92669f..58080e6fe 100644 --- a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift +++ b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift @@ -45,6 +45,7 @@ enum UITestsScreenIdentifier: String { case roomDetailsScreenWithRoomAvatar case roomDetailsScreenDmDetails case roomMembersListScreen + case roomMembersListScreenPendingInvites case roomMemberDetailsAccountOwner case roomMemberDetails case roomMemberDetailsIgnoredUser diff --git a/UITests/Sources/RoomMembersListScreenUITests.swift b/UITests/Sources/RoomMembersListScreenUITests.swift index 67fd040b2..7de29bcf8 100644 --- a/UITests/Sources/RoomMembersListScreenUITests.swift +++ b/UITests/Sources/RoomMembersListScreenUITests.swift @@ -18,9 +18,33 @@ import ElementX import XCTest class RoomMembersListScreenUITests: XCTestCase { - func testInitialStateComponents() { + func testJoinedMembers() { let app = Application.launch(.roomMembersListScreen) app.assertScreenshot(.roomMembersListScreen) } + + func testJoinedAndInvitedMembers() { + let app = Application.launch(.roomMembersListScreenPendingInvites) + + app.assertScreenshot(.roomMembersListScreenPendingInvites) + } + + func testSearchInvitedMember() { + let app = Application.launch(.roomMembersListScreenPendingInvites) + + let searchBar = app.searchFields.firstMatch + searchBar.clearAndTypeText("alice\n") + + app.assertScreenshot(.roomMembersListScreenPendingInvites, step: 1) + } + + func testSearchJoinedMember() { + let app = Application.launch(.roomMembersListScreenPendingInvites) + + let searchBar = app.searchFields.firstMatch + searchBar.clearAndTypeText("bob\n") + + app.assertScreenshot(.roomMembersListScreenPendingInvites, step: 2) + } } diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png new file mode 100644 index 000000000..9b0eb5399 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef909fdb1ebc36ed0ba44e4e1e02e0c94eb2f3a235a2a39b447da749f932e83e +size 66209 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png new file mode 100644 index 000000000..251c500fb --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8ff4e6b0f69d700570bc9ad1bb56bbeeed477b0b3c1dd79f7e1d7bdb4648dac +size 84136 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites.png new file mode 100644 index 000000000..d287caa00 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomMembersListScreenPendingInvites.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe6880e98941ff02697aa9f46c969af621c14dbce2998184daf02fb450bef04c +size 77652 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-1.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-1.png new file mode 100644 index 000000000..33854f966 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25646a7f5e3b815a5263b189a61e54a32a3ff6f1ae3be0e5c2220d0ee39e0231 +size 105655 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-2.png new file mode 100644 index 000000000..6df7b0eb4 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c946e54810512c84d6544b673fbba872bdf52f3706d6e6f1072bc080ac33548 +size 65506 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites.png new file mode 100644 index 000000000..457e7a48e --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomMembersListScreenPendingInvites.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66f3d7176bb875300dd2449a64d412c34d8160b642801c3c0820a0278ade261d +size 87000 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png new file mode 100644 index 000000000..441dea8d0 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba09fe94962b60922feddecdf6d4e073c723eb8fc936451d7de6c0bb0d981dd8 +size 66869 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png new file mode 100644 index 000000000..7fbc57afa --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e68b8b0a014a815237234312168ce643a823c94fd15abbd4945a4a2cb82a0b49 +size 68031 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites.png new file mode 100644 index 000000000..f5234616f --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomMembersListScreenPendingInvites.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2350ae4124d84ca20e2c67ecfa3fcf0d76ab3afbf5b40ffc21741f5cf5ab2e8e +size 82253 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenDmDetails.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenDmDetails.png index 3c885bb5f..79c050084 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenDmDetails.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenDmDetails.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ac11000d5f7b037570a6040e5d46a15aa7050ef7f18e6ea57ceb57b0989ae66 -size 330389 +oid sha256:cb47ad82f2952d130e17fe3c99871e83782bc573f01317fcd55cf1ffbf53f0f0 +size 154477 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-1.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-1.png new file mode 100644 index 000000000..d4cef6a21 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08a5810ff30fd54c1cc1906aa6fae2f98ddcce938bcff58cd1999934accbb0c0 +size 111364 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-2.png new file mode 100644 index 000000000..162888f84 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fc47545c87bb8fe68288300a51e6f62659b04575158c24c93a4c684e7348d04 +size 114203 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites.png new file mode 100644 index 000000000..3e0ab3e2d --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomMembersListScreenPendingInvites.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8bdab053bd2270378ec6c369d6f1432c3d74e2ad3b608274b70255f4918f9e7 +size 94195 diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index dd5fa5a53..43f10811c 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -33,6 +33,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .public) XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle) @@ -42,6 +43,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .private) XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle) @@ -85,6 +87,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) } @@ -97,6 +100,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) context.send(viewAction: .ignoreConfirmed) @@ -118,6 +122,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) context.send(viewAction: .ignoreConfirmed) @@ -140,6 +145,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) context.send(viewAction: .unignoreConfirmed) @@ -161,6 +167,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) context.send(viewAction: .unignoreConfirmed) diff --git a/UnitTests/Sources/RoomMembersListViewModelTests.swift b/UnitTests/Sources/RoomMembersListViewModelTests.swift index 64976147b..12edc1e20 100644 --- a/UnitTests/Sources/RoomMembersListViewModelTests.swift +++ b/UnitTests/Sources/RoomMembersListViewModelTests.swift @@ -19,4 +19,62 @@ import XCTest @testable import ElementX @MainActor -class RoomMembersListScreenViewModelTests: XCTestCase { } +class RoomMembersListScreenViewModelTests: XCTestCase { + var viewModel: RoomMembersListScreenViewModel! + + var context: RoomMembersListScreenViewModel.Context { + viewModel.context + } + + func testJoinedMembers() async { + setup(with: [.mockAlice, .mockBob]) + await context.nextViewState() + XCTAssertEqual(viewModel.state.joinedMembersCount, 2) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 2) + } + + func testSearch() async { + setup(with: [.mockAlice, .mockBob]) + await context.nextViewState() + context.searchQuery = "alice" + XCTAssertEqual(viewModel.state.joinedMembersCount, 2) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 1) + } + + func testEmptySearch() async { + setup(with: [.mockAlice, .mockBob]) + await context.nextViewState() + context.searchQuery = "WWW" + XCTAssertEqual(viewModel.state.joinedMembersCount, 2) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0) + } + + func testJoinedAndInvitedMembers() async { + setup(with: [.mockInvitedAlice, .mockBob]) + await context.nextViewState() + XCTAssertEqual(viewModel.state.joinedMembersCount, 1) + XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 1) + } + + func testInvitedMembers() async { + setup(with: [.mockInvitedAlice]) + await context.nextViewState() + XCTAssertEqual(viewModel.state.joinedMembersCount, 0) + XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0) + } + + func testSearchInvitedMembers() async { + setup(with: [.mockInvitedAlice]) + context.searchQuery = "alice" + await context.nextViewState() + XCTAssertEqual(viewModel.state.joinedMembersCount, 0) + XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1) + XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0) + } + + private func setup(with members: [RoomMemberProxyMock]) { + viewModel = .init(mediaProvider: MockMediaProvider(), members: members) + } +}