mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-01 09:42:39 +00:00
ff5dcb040d
Removes the ServiceLocator singleton and instead directly injects the dependencies it was holding for more control over instantiation of the various component subtrees and better visibility over how they're being used.
304 lines
12 KiB
Swift
304 lines
12 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2022-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 Combine
|
|
@testable import ElementX
|
|
import Testing
|
|
|
|
@MainActor
|
|
struct RoomMembersListScreenViewModelTests {
|
|
var viewModel: RoomMembersListScreenViewModel!
|
|
var roomProxy: JoinedRoomProxyMock!
|
|
var context: RoomMembersListScreenViewModel.Context {
|
|
viewModel.context
|
|
}
|
|
|
|
@Test
|
|
mutating func joinedMembers() async throws {
|
|
setup(members: [.mockAlice, .mockBob])
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleJoinedMembers.count == 2
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 2)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 2)
|
|
}
|
|
|
|
@Test
|
|
mutating func sortingMembers() async throws {
|
|
setup(members: [.mockModerator, .mockDan, .mockAlice, .mockAdmin])
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleJoinedMembers.count == 4
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
let sortedMembers: [RoomMemberListScreenEntry] = [
|
|
.init(member: .init(withProxy: RoomMemberProxyMock.mockAdmin),
|
|
verificationState: .notVerified),
|
|
.init(member: .init(withProxy: RoomMemberProxyMock.mockModerator),
|
|
verificationState: .notVerified),
|
|
.init(member: .init(withProxy: RoomMemberProxyMock.mockAlice),
|
|
verificationState: .notVerified),
|
|
.init(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
|
verificationState: .notVerified)
|
|
]
|
|
|
|
#expect(viewModel.state.visibleJoinedMembers == sortedMembers)
|
|
}
|
|
|
|
@Test
|
|
mutating func search() async throws {
|
|
setup(members: [.mockAlice, .mockBob])
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleJoinedMembers.count == 1
|
|
}
|
|
context.searchQuery = "alice"
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 2)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 1)
|
|
}
|
|
|
|
@Test
|
|
mutating func emptySearch() async throws {
|
|
setup(members: [.mockAlice, .mockBob])
|
|
|
|
context.searchQuery = "WWW"
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.joinedMembersCount == 2
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 2)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 0)
|
|
}
|
|
|
|
@Test
|
|
mutating func joinedAndInvitedMembers() async throws {
|
|
setup(members: [.mockInvited, .mockBob])
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleInvitedMembers.count == 1
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 1)
|
|
#expect(viewModel.state.visibleInvitedMembers.count == 1)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 1)
|
|
}
|
|
|
|
@Test
|
|
mutating func invitedMembers() async throws {
|
|
setup(members: [.mockInvited])
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleInvitedMembers.count == 1
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 0)
|
|
#expect(viewModel.state.visibleInvitedMembers.count == 1)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 0)
|
|
}
|
|
|
|
@Test
|
|
mutating func searchInvitedMembers() async throws {
|
|
setup(members: [.mockInvited])
|
|
|
|
context.searchQuery = "invited"
|
|
let deferred = deferFulfillment(context.$viewState) { state in
|
|
state.visibleInvitedMembers.count == 1
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.state.joinedMembersCount == 0)
|
|
#expect(viewModel.state.visibleInvitedMembers.count == 1)
|
|
#expect(viewModel.state.visibleJoinedMembers.count == 0)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectUserAsUser() async throws {
|
|
setup(members: .allMembers)
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
|
|
try await deferred.fulfill()
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
|
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .user && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
|
Issue.record("Expected to find a regular user.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(user))
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel != nil)
|
|
#expect(context.manageMemeberViewModel?.state.memberDetails.id == user.id)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canKick == false)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canBan == false)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectUserAsAdmin() async throws {
|
|
setup(members: .allMembersAsAdmin)
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel == nil)
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
|
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .user && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
|
Issue.record("Expected to find a regular user.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(user))
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel?.state.memberDetails.id == user.id)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canKick == true)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canBan == true)
|
|
#expect(context.manageMemeberViewModel?.state.isKickDisabled == false)
|
|
#expect(context.manageMemeberViewModel?.state.isBanUnbanDisabled == false)
|
|
#expect(context.manageMemeberViewModel?.state.isMemberBanned == false)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectModeratorAsAdmin() async throws {
|
|
setup(members: .allMembersAsAdmin)
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel == nil)
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
|
guard let moderator = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .moderator })?.member else {
|
|
Issue.record("Expected to find a moderator.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(moderator))
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel?.state.memberDetails.id == moderator.id)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canKick == true)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canBan == true)
|
|
#expect(context.manageMemeberViewModel?.state.isMemberBanned == false)
|
|
#expect(context.manageMemeberViewModel?.state.isKickDisabled == false)
|
|
#expect(context.manageMemeberViewModel?.state.isBanUnbanDisabled == false)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectAdminAsAdmin() async throws {
|
|
setup(members: .allMembersAsAdmin)
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
|
try await deferred.fulfill()
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
|
guard let admin = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role.isAdminOrHigher && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
|
Issue.record("Expected to find another admin.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(admin))
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel?.state.memberDetails.id == admin.id)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canKick == true)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canBan == true)
|
|
#expect(context.manageMemeberViewModel?.state.isKickDisabled == true)
|
|
#expect(context.manageMemeberViewModel?.state.isBanUnbanDisabled == true)
|
|
#expect(context.manageMemeberViewModel?.state.isMemberBanned == false)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectOwnMemberAsAdmin() async throws {
|
|
setup(members: .allMembersAsAdmin)
|
|
|
|
let deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
|
|
try await deferred.fulfill()
|
|
|
|
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
|
|
guard let ownMember = viewModel.state.visibleJoinedMembers.first(where: { $0.member.id == RoomMemberProxyMock.mockMe.userID })?.member else {
|
|
Issue.record("Expected to find own user admin.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(ownMember))
|
|
try await memberDetailsAction.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel == nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectBannedMember() async throws {
|
|
setup(members: .allMembersAsAdmin + RoomMemberProxyMock.mockBanned)
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.alertInfo == nil)
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
|
guard let bannedMember = viewModel.state.visibleBannedMembers.first?.member else {
|
|
Issue.record("Expected to find a banned user.")
|
|
return
|
|
}
|
|
|
|
context.send(viewAction: .selectMember(bannedMember))
|
|
try await deferred.fulfill()
|
|
|
|
#expect(context.manageMemeberViewModel?.state.memberDetails.id == bannedMember.id)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canKick == true)
|
|
#expect(context.manageMemeberViewModel?.state.permissions.canBan == true)
|
|
#expect(context.manageMemeberViewModel?.state.isKickDisabled == true)
|
|
#expect(context.manageMemeberViewModel?.state.isBanUnbanDisabled == false)
|
|
#expect(context.manageMemeberViewModel?.state.isMemberBanned == true)
|
|
}
|
|
|
|
@Test
|
|
mutating func switchesToMembersModeWhenThereAreNoBannedMembers() async throws {
|
|
roomProxy = JoinedRoomProxyMock(.init(name: "test"))
|
|
|
|
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([RoomMemberProxyMock].allMembersAsAdmin + RoomMemberProxyMock.mockBanned)
|
|
roomProxy.membersPublisher = subject.asCurrentValuePublisher()
|
|
|
|
viewModel = RoomMembersListScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxy,
|
|
userIndicatorController: UserIndicatorControllerMock.default,
|
|
analytics: .mock())
|
|
|
|
let context = viewModel.context
|
|
|
|
var deferred = deferFulfillment(context.$viewState) { $0.visibleBannedMembers.count == 4 && $0.bindings.mode == .banned }
|
|
context.mode = .banned
|
|
try await deferred.fulfill()
|
|
|
|
deferred = deferFulfillment(context.$viewState) { $0.visibleBannedMembers.count == 0 && $0.bindings.mode == .members }
|
|
subject.value = [RoomMemberProxyMock].allMembersAsAdmin
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private mutating func setup(members: [RoomMemberProxyMock]) {
|
|
roomProxy = JoinedRoomProxyMock(.init(name: "test", members: members))
|
|
viewModel = RoomMembersListScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxy,
|
|
userIndicatorController: UserIndicatorControllerMock.default,
|
|
analytics: .mock())
|
|
}
|
|
}
|