mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Room details: improve members count UX (#1050)
* Inject user id into room’s details * Refactor member fetch logic in RoomDetailsScreenViewModel * Refactor DI * Fetch members in RoomMembersListScreenViewModel * Fetch members in InviteUsersScreen * Use joinedMembersCount in room members list * Cleanup * Limit members refresh * Cleanup binding * Fix UT build errors * Fix RoomDetailsScreenViewModelTests * Fix RoomMembersListScreenViewModelTests * Fix UTs * Fix RoomDetailsScreenUITests * Refine RoomDetailsScreenViewModelTests * Add roomProxy extension * PR comments * Refactor room’s counter to Int * Improve updateMembers() performances * Fix memory leaks
This commit is contained in:
@@ -61,7 +61,7 @@ class InviteUsersScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testInviteButton() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob]
|
||||
setupWithRoomType(roomType: .room(members: mockedMembers, userIndicatorController: UserIndicatorControllerMock.default))
|
||||
setupWithRoomType(roomType: .room(roomProxy: RoomProxyMock(with: .init(displayName: "test", members: mockedMembers))))
|
||||
_ = await viewModel.context.$viewState.values.first(where: { $0.membershipState.isEmpty == false })
|
||||
context.send(viewAction: .toggleUser(.mockAlice))
|
||||
|
||||
@@ -86,7 +86,10 @@ class InviteUsersScreenViewModelTests: XCTestCase {
|
||||
userDiscoveryService.fetchSuggestionsReturnValue = .success([])
|
||||
userDiscoveryService.searchProfilesWithReturnValue = .success([])
|
||||
usersSubject.send([])
|
||||
let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(), roomType: roomType, mediaProvider: MockMediaProvider(), userDiscoveryService: userDiscoveryService)
|
||||
let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(),
|
||||
roomType: roomType, mediaProvider: MockMediaProvider(),
|
||||
userDiscoveryService: userDiscoveryService,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie])
|
||||
self.viewModel = viewModel
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
var viewModel: RoomDetailsScreenViewModelProtocol!
|
||||
var viewModel: RoomDetailsScreenViewModel!
|
||||
var roomProxyMock: RoomProxyMock!
|
||||
var context: RoomDetailsScreenViewModelType.Context { viewModel.context }
|
||||
|
||||
override func setUp() {
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test"))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", joinedMembersCount: 0))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
AppSettings.reset()
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
func testLeaveRoomTappedWhenPublic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
context.send(viewAction: .processTapLeave)
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
||||
@@ -45,7 +45,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
func testLeaveRoomTappedWhenRoomNotPublic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
context.send(viewAction: .processTapLeave)
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
||||
@@ -88,8 +88,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
func testInitialDMDetailsState() async {
|
||||
let recipient = RoomMemberProxyMock.mockDan
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
}
|
||||
@@ -101,8 +101,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
return .success(())
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
@@ -123,8 +123,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
return .failure(.ignoreUserFailed)
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
@@ -146,8 +146,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
return .success(())
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
@@ -168,8 +168,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
return .failure(.unignoreUserFailed)
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
@@ -186,28 +186,32 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testCannotInvitePeople() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test",
|
||||
isPublic: true,
|
||||
members: mockedMembers,
|
||||
memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false),
|
||||
joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
await context.nextViewState()
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertFalse(context.viewState.canInviteUsers)
|
||||
}
|
||||
|
||||
func testInvitePeople() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers, joinedMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
await context.nextViewState()
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertTrue(context.viewState.canInviteUsers)
|
||||
|
||||
var callbackCorrectlyCalled = false
|
||||
viewModel.callback = { action in
|
||||
switch action {
|
||||
case .requestInvitePeoplePresentation(let members):
|
||||
callbackCorrectlyCalled = members.map(\.userID) == mockedMembers.map(\.userID)
|
||||
case .requestInvitePeoplePresentation:
|
||||
callbackCorrectlyCalled = true
|
||||
default:
|
||||
callbackCorrectlyCalled = false
|
||||
}
|
||||
@@ -219,16 +223,16 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRoomSubscription() async {
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(roomProxyMock.registerTimelineListenerIfNeededCallsCount, 1)
|
||||
}
|
||||
|
||||
func testCanEditAvatar() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomAvatar])
|
||||
let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertTrue(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
@@ -237,11 +241,12 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCanEditName() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomName]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomName])
|
||||
let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertTrue(context.viewState.canEditRoomName)
|
||||
@@ -250,11 +255,12 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCanEditTopic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomTopic])
|
||||
let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
@@ -263,11 +269,12 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCannotEditRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: []), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [])
|
||||
let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
@@ -278,9 +285,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
func testCannotEditDirectRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
_ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first()
|
||||
|
||||
XCTAssertFalse(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
private func setup(with members: [RoomMemberProxyMock]) {
|
||||
viewModel = .init(mediaProvider: MockMediaProvider(), members: members)
|
||||
viewModel = .init(roomProxy: RoomProxyMock(with: .init(displayName: "test", members: members, joinedMembersCount: members.filter { $0.membership == .join }.count)),
|
||||
mediaProvider: MockMediaProvider())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user