mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Invited members in room's members list (#880)
* Add invited people in RoomMembersListScreen * Optimize main thread perfomance in RoomDetailsScreenViewModel * Fix member count in RoomDetailsScreenViewModel * Add warning * Add performance warnings * Build members in RoomMembersListScreenViewModel in global queue * Refactor RoomDetailsScreenViewModel build members process * Localize strings * Cleanup * Fix RoomDetailsScreenViewModelTests UTs * Fix UI tests * Add RoomMembersListScreenViewModel UTs * Add UI tests * Amend UI tests * Use clearAndTypeText
This commit is contained in:
@@ -55,6 +55,18 @@ extension RoomMemberProxyMock {
|
|||||||
isAccountOwner: false,
|
isAccountOwner: false,
|
||||||
isIgnored: 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 {
|
static var mockBob: RoomMemberProxyMock {
|
||||||
RoomMemberProxyMock(with: .init(userID: "@bob:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@bob:matrix.org",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||||||
var avatarURL: URL?
|
var avatarURL: URL?
|
||||||
let permalink: URL?
|
let permalink: URL?
|
||||||
var members: [RoomMemberDetails] = []
|
var members: [RoomMemberDetails] = []
|
||||||
|
var joinedMembersCount = 0
|
||||||
var isProcessingIgnoreRequest = false
|
var isProcessingIgnoreRequest = false
|
||||||
|
|
||||||
var isLoadingMembers: Bool {
|
var isLoadingMembers: Bool {
|
||||||
|
|||||||
@@ -21,26 +21,11 @@ typealias RoomDetailsScreenViewModelType = StateStoreViewModel<RoomDetailsScreen
|
|||||||
|
|
||||||
class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol {
|
class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol {
|
||||||
private let roomProxy: RoomProxyProtocol
|
private let roomProxy: RoomProxyProtocol
|
||||||
private var members: [RoomMemberProxyProtocol] = [] {
|
private var members: [RoomMemberProxyProtocol] = []
|
||||||
didSet {
|
private var dmRecipient: RoomMemberProxyProtocol?
|
||||||
state.members = members.map { RoomMemberDetails(withProxy: $0) }
|
|
||||||
if roomProxy.isDirect, roomProxy.isEncrypted, members.count == 2 {
|
@CancellableTask
|
||||||
dmRecipient = members.first(where: { !$0.isAccountOwner })
|
private var buildMembersDetailsTask: Task<Void, Never>?
|
||||||
} else {
|
|
||||||
dmRecipient = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var dmRecipient: RoomMemberProxyProtocol? {
|
|
||||||
didSet {
|
|
||||||
if let dmRecipient {
|
|
||||||
state.dmRecipient = RoomMemberDetails(withProxy: dmRecipient)
|
|
||||||
} else {
|
|
||||||
state.dmRecipient = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var callback: ((RoomDetailsScreenViewModelAction) -> Void)?
|
var callback: ((RoomDetailsScreenViewModelAction) -> Void)?
|
||||||
|
|
||||||
@@ -57,11 +42,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
permalink: roomProxy.permalink,
|
permalink: roomProxy.permalink,
|
||||||
bindings: .init()),
|
bindings: .init()),
|
||||||
imageProvider: mediaProvider)
|
imageProvider: mediaProvider)
|
||||||
|
|
||||||
roomProxy.membersPublisher.sink { [weak self] members in
|
setupSubscriptions()
|
||||||
self?.members = members
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await roomProxy.updateMembers()
|
await roomProxy.updateMembers()
|
||||||
@@ -96,6 +78,49 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
|
|
||||||
// MARK: - Private
|
// 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 static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||||
|
|
||||||
private func leaveRoom() async {
|
private func leaveRoom() async {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ struct RoomDetailsScreen: View {
|
|||||||
if context.viewState.isLoadingMembers {
|
if context.viewState.isLoadingMembers {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else {
|
} else {
|
||||||
Text(String(context.viewState.members.count))
|
Text(String(context.viewState.joinedMembersCount))
|
||||||
.foregroundColor(.element.tertiaryContent)
|
.foregroundColor(.element.tertiaryContent)
|
||||||
.font(.compound.bodyLG)
|
.font(.compound.bodyLG)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,32 @@ enum RoomMembersListScreenViewModelAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RoomMembersListScreenViewState: BindableState {
|
struct RoomMembersListScreenViewState: BindableState {
|
||||||
var members: [RoomMemberDetails]
|
private var joinedMembers: [RoomMemberDetails]
|
||||||
|
private var invitedMembers: [RoomMemberDetails]
|
||||||
var bindings: RoomMembersListScreenViewStateBindings
|
var bindings: RoomMembersListScreenViewStateBindings
|
||||||
|
|
||||||
|
init(joinedMembers: [RoomMemberDetails] = [], invitedMembers: [RoomMemberDetails] = [], bindings: RoomMembersListScreenViewStateBindings = .init()) {
|
||||||
|
self.joinedMembers = joinedMembers
|
||||||
|
self.invitedMembers = invitedMembers
|
||||||
|
self.bindings = bindings
|
||||||
|
}
|
||||||
|
|
||||||
var visibleMembers: [RoomMemberDetails] {
|
var visibleJoinedMembers: [RoomMemberDetails] {
|
||||||
if bindings.searchQuery.isEmpty {
|
joinedMembers.lazy
|
||||||
return members
|
.filter { member in
|
||||||
}
|
member.matches(searchQuery: bindings.searchQuery)
|
||||||
|
}
|
||||||
return members.lazy.filter { member in
|
}
|
||||||
member.id.localizedCaseInsensitiveContains(bindings.searchQuery) ||
|
|
||||||
member.name?.localizedCaseInsensitiveContains(bindings.searchQuery) ?? false
|
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 {
|
enum RoomMembersListScreenViewAction {
|
||||||
case selectMember(id: String)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
|||||||
|
|
||||||
var callback: ((RoomMembersListScreenViewModelAction) -> Void)?
|
var callback: ((RoomMembersListScreenViewModelAction) -> Void)?
|
||||||
|
|
||||||
init(mediaProvider: MediaProviderProtocol,
|
init(mediaProvider: MediaProviderProtocol, members: [RoomMemberProxyProtocol]) {
|
||||||
members: [RoomMemberProxyProtocol]) {
|
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.members = members
|
self.members = members
|
||||||
super.init(initialViewState: .init(members: members.map { RoomMemberDetails(withProxy: $0) },
|
super.init(initialViewState: .init(), imageProvider: mediaProvider)
|
||||||
bindings: .init()),
|
|
||||||
imageProvider: mediaProvider)
|
buildMembersDetails(members: members)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -45,4 +44,35 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
|||||||
callback?(.selectMember(member))
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,18 +25,8 @@ struct RoomMembersListScreen: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
Section {
|
membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle)
|
||||||
ForEach(context.viewState.visibleMembers) { member in
|
membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(context.viewState.joinedMembersCount))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
|
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
|
||||||
@@ -45,6 +35,24 @@ struct RoomMembersListScreen: View {
|
|||||||
.navigationTitle(L10n.commonPeople)
|
.navigationTitle(L10n.commonPeople)
|
||||||
.alert(item: $context.alertInfo) { $0.alert }
|
.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
|
// MARK: - Previews
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ final class RoomMemberProxy: RoomMemberProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ignoreUser() async -> Result<Void, RoomMemberProxyError> {
|
func ignoreUser() async -> Result<Void, RoomMemberProxyError> {
|
||||||
sendAccountDataEventBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true)
|
sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true)
|
||||||
defer {
|
defer {
|
||||||
sendAccountDataEventBackgroundTask?.stop()
|
sendAccountDataEventBackgroundTask?.stop()
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ final class RoomMemberProxy: RoomMemberProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func unignoreUser() async -> Result<Void, RoomMemberProxyError> {
|
func unignoreUser() async -> Result<Void, RoomMemberProxyError> {
|
||||||
sendAccountDataEventBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true)
|
sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true)
|
||||||
defer {
|
defer {
|
||||||
sendAccountDataEventBackgroundTask?.stop()
|
sendAccountDataEventBackgroundTask?.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ enum RoomMemberProxyError: Error {
|
|||||||
case unignoreUserFailed
|
case unignoreUserFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol RoomMemberProxyProtocol {
|
protocol RoomMemberProxyProtocol {
|
||||||
var userID: String { get }
|
var userID: String { get }
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ struct RoomMemberDetails: Identifiable, Equatable {
|
|||||||
let isAccountOwner: Bool
|
let isAccountOwner: Bool
|
||||||
var isIgnored: Bool
|
var isIgnored: Bool
|
||||||
|
|
||||||
@MainActor
|
|
||||||
init(withProxy proxy: RoomMemberProxyProtocol) {
|
init(withProxy proxy: RoomMemberProxyProtocol) {
|
||||||
id = proxy.userID
|
id = proxy.userID
|
||||||
name = proxy.displayName
|
name = proxy.displayName
|
||||||
|
|||||||
@@ -307,6 +307,14 @@ class MockScreen: Identifiable {
|
|||||||
members: members))
|
members: members))
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
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:
|
case .reportContent:
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let coordinator = ReportContentScreenCoordinator(parameters: .init(itemID: "test",
|
let coordinator = ReportContentScreenCoordinator(parameters: .init(itemID: "test",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ enum UITestsScreenIdentifier: String {
|
|||||||
case roomDetailsScreenWithRoomAvatar
|
case roomDetailsScreenWithRoomAvatar
|
||||||
case roomDetailsScreenDmDetails
|
case roomDetailsScreenDmDetails
|
||||||
case roomMembersListScreen
|
case roomMembersListScreen
|
||||||
|
case roomMembersListScreenPendingInvites
|
||||||
case roomMemberDetailsAccountOwner
|
case roomMemberDetailsAccountOwner
|
||||||
case roomMemberDetails
|
case roomMemberDetails
|
||||||
case roomMemberDetailsIgnoredUser
|
case roomMemberDetailsIgnoredUser
|
||||||
|
|||||||
@@ -18,9 +18,33 @@ import ElementX
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class RoomMembersListScreenUITests: XCTestCase {
|
class RoomMembersListScreenUITests: XCTestCase {
|
||||||
func testInitialStateComponents() {
|
func testJoinedMembers() {
|
||||||
let app = Application.launch(.roomMembersListScreen)
|
let app = Application.launch(.roomMembersListScreen)
|
||||||
|
|
||||||
app.assertScreenshot(.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ef909fdb1ebc36ed0ba44e4e1e02e0c94eb2f3a235a2a39b447da749f932e83e
|
||||||
|
size 66209
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a8ff4e6b0f69d700570bc9ad1bb56bbeeed477b0b3c1dd79f7e1d7bdb4648dac
|
||||||
|
size 84136
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:fe6880e98941ff02697aa9f46c969af621c14dbce2998184daf02fb450bef04c
|
||||||
|
size 77652
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:25646a7f5e3b815a5263b189a61e54a32a3ff6f1ae3be0e5c2220d0ee39e0231
|
||||||
|
size 105655
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4c946e54810512c84d6544b673fbba872bdf52f3706d6e6f1072bc080ac33548
|
||||||
|
size 65506
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:66f3d7176bb875300dd2449a64d412c34d8160b642801c3c0820a0278ade261d
|
||||||
|
size 87000
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ba09fe94962b60922feddecdf6d4e073c723eb8fc936451d7de6c0bb0d981dd8
|
||||||
|
size 66869
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e68b8b0a014a815237234312168ce643a823c94fd15abbd4945a4a2cb82a0b49
|
||||||
|
size 68031
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2350ae4124d84ca20e2c67ecfa3fcf0d76ab3afbf5b40ffc21741f5cf5ab2e8e
|
||||||
|
size 82253
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6ac11000d5f7b037570a6040e5d46a15aa7050ef7f18e6ea57ceb57b0989ae66
|
oid sha256:cb47ad82f2952d130e17fe3c99871e83782bc573f01317fcd55cf1ffbf53f0f0
|
||||||
size 330389
|
size 154477
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:08a5810ff30fd54c1cc1906aa6fae2f98ddcce938bcff58cd1999934accbb0c0
|
||||||
|
size 111364
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2fc47545c87bb8fe68288300a51e6f62659b04575158c24c93a4c684e7348d04
|
||||||
|
size 114203
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d8bdab053bd2270378ec6c369d6f1432c3d74e2ad3b608274b70255f4918f9e7
|
||||||
|
size 94195
|
||||||
@@ -33,6 +33,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
context.send(viewAction: .processTapLeave)
|
context.send(viewAction: .processTapLeave)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
||||||
@@ -42,6 +43,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
context.send(viewAction: .processTapLeave)
|
context.send(viewAction: .processTapLeave)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
||||||
@@ -85,6 +87,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +100,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
@@ -118,6 +122,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
@@ -140,6 +145,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
@@ -161,6 +167,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
await context.nextViewState()
|
||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
|
|||||||
@@ -19,4 +19,62 @@ import XCTest
|
|||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
|
||||||
@MainActor
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user