mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
refactor: manage member sheet in timeline
better implementation updated tests Update ElementX/Sources/Screens/Timeline/TimelineViewModel.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> fix
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@MainActor
|
||||
class ManageRoomMemberSheetViewModelTests: XCTestCase {
|
||||
private var viewModel: ManageRoomMemberSheetViewModel!
|
||||
private var context: ManageRoomMemberSheetViewModel.Context! {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
func testKick() async throws {
|
||||
let testReason = "Kick Test"
|
||||
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
|
||||
let expectation = XCTestExpectation(description: "Kick member")
|
||||
roomProxy.kickUserReasonClosure = { userID, reason in
|
||||
defer { expectation.fulfill() }
|
||||
XCTAssertEqual(userID, RoomMemberProxyMock.mockAlice.userID)
|
||||
XCTAssertEqual(reason, testReason)
|
||||
return .success(())
|
||||
}
|
||||
|
||||
viewModel = ManageRoomMemberSheetViewModel(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockAlice),
|
||||
canKick: true,
|
||||
canBan: true,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
action == .dismiss(shouldShowDetails: false)
|
||||
}
|
||||
context.send(viewAction: .kick)
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.alertInfo?.textFields?[0].text.wrappedValue = testReason
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
await fulfillment(of: [expectation])
|
||||
try await deferredAction.fulfill()
|
||||
}
|
||||
|
||||
func testBan() async throws {
|
||||
let testReason = "Ban Test"
|
||||
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
|
||||
let expectation = XCTestExpectation(description: "Ban member")
|
||||
roomProxy.banUserReasonClosure = { userID, reason in
|
||||
defer { expectation.fulfill() }
|
||||
XCTAssertEqual(userID, RoomMemberProxyMock.mockAlice.userID)
|
||||
XCTAssertEqual(reason, testReason)
|
||||
return .success(())
|
||||
}
|
||||
|
||||
viewModel = ManageRoomMemberSheetViewModel(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockAlice),
|
||||
canKick: true,
|
||||
canBan: true,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
context.send(viewAction: .ban)
|
||||
try await deferred.fulfill()
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
action == .dismiss(shouldShowDetails: false)
|
||||
}
|
||||
context.alertInfo?.textFields?[0].text.wrappedValue = testReason
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
await fulfillment(of: [expectation])
|
||||
try await deferredAction.fulfill()
|
||||
}
|
||||
|
||||
func testDisplayDetails() async throws {
|
||||
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
|
||||
viewModel = ManageRoomMemberSheetViewModel(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockAlice),
|
||||
canKick: true,
|
||||
canBan: true,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
action == .dismiss(shouldShowDetails: true)
|
||||
}
|
||||
context.send(viewAction: .displayDetails)
|
||||
try await deferredAction.fulfill()
|
||||
XCTAssertNil(context.alertInfo)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// Then the member's details should be shown.
|
||||
try await memberDetailsAction.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
}
|
||||
|
||||
func testSelectUserAsAdmin() async throws {
|
||||
@@ -151,10 +151,10 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
setup(with: .allMembersAsAdmin)
|
||||
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
||||
try await deferred.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
|
||||
// When tapping on a user in the list.
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != 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 {
|
||||
XCTFail("Expected to find a regular user.")
|
||||
return
|
||||
@@ -163,8 +163,9 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then member management should be shown for that user.
|
||||
XCTAssertEqual(context.memberToManage?.member, user)
|
||||
XCTAssertEqual(context.memberToManage?.actions, [.kick, .ban])
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.member, user)
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.canKick, true)
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.canBan, true)
|
||||
}
|
||||
|
||||
func testSelectModeratorAsAdmin() async throws {
|
||||
@@ -172,10 +173,10 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
setup(with: .allMembersAsAdmin)
|
||||
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty && $0.canKickUsers && $0.canBanUsers }
|
||||
try await deferred.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
|
||||
// When tapping on a moderator in the list.
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != nil }
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.manageMemeberViewModel != nil }
|
||||
guard let moderator = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .moderator })?.member else {
|
||||
XCTFail("Expected to find a moderator.")
|
||||
return
|
||||
@@ -184,8 +185,9 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then member management should be shown for the moderator.
|
||||
XCTAssertEqual(context.memberToManage?.member, moderator)
|
||||
XCTAssertEqual(context.memberToManage?.actions, [.kick, .ban])
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.member, moderator)
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.canKick, true)
|
||||
XCTAssertEqual(context.manageMemeberViewModel?.state.canBan, true)
|
||||
}
|
||||
|
||||
func testSelectAdminAsAdmin() async throws {
|
||||
@@ -204,7 +206,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// Then the administrator's details should be shown.
|
||||
try await memberDetailsAction.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
}
|
||||
|
||||
func testSelectOwnMemberAsAdmin() async throws {
|
||||
@@ -223,7 +225,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// Then your member's details should be shown.
|
||||
try await memberDetailsAction.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
}
|
||||
|
||||
func testSelectBannedMember() async throws {
|
||||
@@ -243,50 +245,10 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// Then an alert should be shown to unban the user.
|
||||
try await deferred.fulfill()
|
||||
XCTAssertNil(context.memberToManage)
|
||||
XCTAssertNil(context.manageMemeberViewModel)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testKickMember() async throws {
|
||||
setup(with: .allMembersAsAdmin)
|
||||
var deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
try await deferred.fulfill()
|
||||
|
||||
let expectation = XCTestExpectation(description: "Ban member")
|
||||
roomProxy.kickUserReasonClosure = { _, _ in
|
||||
defer { expectation.fulfill() }
|
||||
return .success(())
|
||||
}
|
||||
|
||||
context.send(viewAction: .kickMember(viewModel.state.visibleJoinedMembers[0].member))
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
await fulfillment(of: [expectation])
|
||||
}
|
||||
|
||||
func testBanMember() async throws {
|
||||
setup(with: .allMembersAsAdmin)
|
||||
var deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
try await deferred.fulfill()
|
||||
|
||||
let expectation = XCTestExpectation(description: "Ban member")
|
||||
roomProxy.banUserReasonClosure = { _, _ in
|
||||
defer { expectation.fulfill() }
|
||||
return .success(())
|
||||
}
|
||||
|
||||
context.send(viewAction: .banMember(viewModel.state.visibleJoinedMembers[0].member))
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
await fulfillment(of: [expectation])
|
||||
}
|
||||
|
||||
func testUnbanMember() async throws {
|
||||
setup(with: .allMembersAsAdmin)
|
||||
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
|
||||
@@ -350,6 +350,117 @@ class TimelineViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testShowManageUserAsAdmin() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockAlice],
|
||||
ownUserID: RoomMemberProxyMock.mockAdmin.userID)),
|
||||
timelineController: MockTimelineController(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: userIndicatorControllerMock,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.canCurrentUserKick && value.canCurrentUserBan
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.bindings.manageMemberViewModel != nil
|
||||
}
|
||||
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(userID: RoomMemberProxyMock.mockAlice.userID))
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.member.id, RoomMemberProxyMock.mockAlice.userID)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.canBan, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.canKick, true)
|
||||
}
|
||||
|
||||
func testShowDetailsForAnAdmin() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockAlice],
|
||||
ownUserID: RoomMemberProxyMock.mockAlice.userID)),
|
||||
timelineController: MockTimelineController(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: userIndicatorControllerMock,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
!value.canCurrentUserKick && !value.canCurrentUserBan
|
||||
}
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .displaySenderDetails(let userID):
|
||||
return userID == RoomMemberProxyMock.mockAdmin.userID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(userID: RoomMemberProxyMock.mockAdmin.userID))
|
||||
try await deferredAction.fulfill()
|
||||
|
||||
XCTAssertNil(viewModel.context.manageMemberViewModel)
|
||||
}
|
||||
|
||||
func testShowDetailsForABannedUser() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockBanned[0]],
|
||||
ownUserID: RoomMemberProxyMock.mockAdmin.userID)),
|
||||
timelineController: MockTimelineController(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: userIndicatorControllerMock,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.canCurrentUserKick && value.canCurrentUserBan
|
||||
}
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .displaySenderDetails(let userID):
|
||||
return userID == RoomMemberProxyMock.mockBanned[0].userID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(userID: RoomMemberProxyMock.mockBanned[0].userID))
|
||||
try await deferredAction.fulfill()
|
||||
|
||||
XCTAssertNil(viewModel.context.manageMemberViewModel)
|
||||
}
|
||||
|
||||
// MARK: - Pins
|
||||
|
||||
func testPinnedEvents() async throws {
|
||||
|
||||
Reference in New Issue
Block a user