mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Contextual menu for rooms (#842)
* Add room contextual menu * Add room context menu view actions * Add navigation to room’s detail * Show leave alert in HomeScreen * Implement leave logic * Refine leave navigation for iPad * Optimize leave from homescreen * Refactor roomSettings -> roomDetails * Refactor room for id mock * Add UTs * Cleanup * Fix alert presentation in RoomDetailsScreenViewModel * Fix animation flag
This commit is contained in:
@@ -32,6 +32,7 @@ struct RoomProxyMockConfiguration {
|
|||||||
var alternativeAliases: [String] = []
|
var alternativeAliases: [String] = []
|
||||||
var hasUnreadNotifications = Bool.random()
|
var hasUnreadNotifications = Bool.random()
|
||||||
var members: [RoomMemberProxyProtocol]?
|
var members: [RoomMemberProxyProtocol]?
|
||||||
|
var inviter: RoomMemberProxyMock?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RoomProxyMock {
|
extension RoomProxyMock {
|
||||||
@@ -57,7 +58,12 @@ extension RoomProxyMock {
|
|||||||
} else {
|
} else {
|
||||||
membersPublisher = Just([]).eraseToAnyPublisher()
|
membersPublisher = Just([]).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let inviter = configuration.inviter {
|
||||||
|
inviterClosure = { inviter }
|
||||||
|
}
|
||||||
|
|
||||||
updateMembersClosure = { }
|
updateMembersClosure = { }
|
||||||
|
acceptInvitationClosure = { .success(()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ struct HomeScreenCoordinatorParameters {
|
|||||||
|
|
||||||
enum HomeScreenCoordinatorAction {
|
enum HomeScreenCoordinatorAction {
|
||||||
case presentRoom(roomIdentifier: String)
|
case presentRoom(roomIdentifier: String)
|
||||||
|
case presentRoomDetails(roomIdentifier: String)
|
||||||
|
case roomLeft(roomIdentifier: String)
|
||||||
case presentSettingsScreen
|
case presentSettingsScreen
|
||||||
case presentFeedbackScreen
|
case presentFeedbackScreen
|
||||||
case presentSessionVerificationScreen
|
case presentSessionVerificationScreen
|
||||||
@@ -54,6 +56,10 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .presentRoom(let roomIdentifier):
|
case .presentRoom(let roomIdentifier):
|
||||||
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||||
|
case .presentRoomDetails(roomIdentifier: let roomIdentifier):
|
||||||
|
self.callback?(.presentRoomDetails(roomIdentifier: roomIdentifier))
|
||||||
|
case .roomLeft(roomIdentifier: let roomIdentifier):
|
||||||
|
self.callback?(.roomLeft(roomIdentifier: roomIdentifier))
|
||||||
case .presentFeedbackScreen:
|
case .presentFeedbackScreen:
|
||||||
self.callback?(.presentFeedbackScreen)
|
self.callback?(.presentFeedbackScreen)
|
||||||
case .presentSettingsScreen:
|
case .presentSettingsScreen:
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import UIKit
|
|||||||
|
|
||||||
enum HomeScreenViewModelAction {
|
enum HomeScreenViewModelAction {
|
||||||
case presentRoom(roomIdentifier: String)
|
case presentRoom(roomIdentifier: String)
|
||||||
|
case presentRoomDetails(roomIdentifier: String)
|
||||||
|
case roomLeft(roomIdentifier: String)
|
||||||
case presentSessionVerificationScreen
|
case presentSessionVerificationScreen
|
||||||
case presentSettingsScreen
|
case presentSettingsScreen
|
||||||
case presentFeedbackScreen
|
case presentFeedbackScreen
|
||||||
@@ -36,6 +38,9 @@ enum HomeScreenViewUserMenuAction {
|
|||||||
|
|
||||||
enum HomeScreenViewAction {
|
enum HomeScreenViewAction {
|
||||||
case selectRoom(roomIdentifier: String)
|
case selectRoom(roomIdentifier: String)
|
||||||
|
case showRoomDetails(roomIdentifier: String)
|
||||||
|
case leaveRoom(roomIdentifier: String)
|
||||||
|
case confirmLeaveRoom(roomIdentifier: String)
|
||||||
case userMenu(action: HomeScreenViewUserMenuAction)
|
case userMenu(action: HomeScreenViewUserMenuAction)
|
||||||
case startChat
|
case startChat
|
||||||
case verifySession
|
case verifySession
|
||||||
@@ -101,6 +106,7 @@ struct HomeScreenViewStateBindings {
|
|||||||
var searchQuery = ""
|
var searchQuery = ""
|
||||||
|
|
||||||
var alertInfo: AlertInfo<UUID>?
|
var alertInfo: AlertInfo<UUID>?
|
||||||
|
var leaveRoomAlertItem: LeaveRoomAlertItem?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HomeScreenRoom: Identifiable, Equatable {
|
struct HomeScreenRoom: Identifiable, Equatable {
|
||||||
|
|||||||
@@ -153,10 +153,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
override func process(viewAction: HomeScreenViewAction) {
|
override func process(viewAction: HomeScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||||
|
case .showRoomDetails(roomIdentifier: let roomIdentifier):
|
||||||
|
callback?(.presentRoomDetails(roomIdentifier: roomIdentifier))
|
||||||
|
case .leaveRoom(roomIdentifier: let roomIdentifier):
|
||||||
|
startLeaveRoomProcess(roomId: roomIdentifier)
|
||||||
|
case .confirmLeaveRoom(roomIdentifier: let roomIdentifier):
|
||||||
|
leaveRoom(roomId: roomIdentifier)
|
||||||
case .userMenu(let action):
|
case .userMenu(let action):
|
||||||
switch action {
|
switch action {
|
||||||
case .feedback:
|
case .feedback:
|
||||||
@@ -301,4 +308,56 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
visibleRoomsSummaryProvider.updateVisibleRange(range, timelineLimit: timelineLimit)
|
visibleRoomsSummaryProvider.updateVisibleRange(range, timelineLimit: timelineLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||||
|
|
||||||
|
private func startLeaveRoomProcess(roomId: String) {
|
||||||
|
Task {
|
||||||
|
defer {
|
||||||
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID)
|
||||||
|
}
|
||||||
|
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||||
|
|
||||||
|
let room = await userSession.clientProxy.roomForIdentifier(roomId)
|
||||||
|
|
||||||
|
guard let room else {
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: UUID(), title: L10n.errorUnknown)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !room.isPublic else {
|
||||||
|
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomId: roomId, state: .public)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await room.updateMembers()
|
||||||
|
let joinedMembers = await room.membersPublisher.values.first()?.filter { $0.membership == .join }
|
||||||
|
|
||||||
|
guard let joinedMembers else {
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: UUID(), title: L10n.errorUnknown)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.bindings.leaveRoomAlertItem = joinedMembers.count > 1 ? LeaveRoomAlertItem(roomId: roomId, state: .private) : LeaveRoomAlertItem(roomId: roomId, state: .empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func leaveRoom(roomId: String) {
|
||||||
|
Task {
|
||||||
|
defer {
|
||||||
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID)
|
||||||
|
}
|
||||||
|
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||||
|
|
||||||
|
let room = await userSession.clientProxy.roomForIdentifier(roomId)
|
||||||
|
let result = await room?.leaveRoom()
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .none, .some(.failure):
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: UUID(), title: L10n.errorUnknown)
|
||||||
|
case .some(.success):
|
||||||
|
callback?(.roomLeft(roomIdentifier: roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,19 @@ struct HomeScreen: View {
|
|||||||
.redacted(reason: .placeholder)
|
.redacted(reason: .placeholder)
|
||||||
} else {
|
} else {
|
||||||
HomeScreenRoomCell(room: room, context: context)
|
HomeScreenRoomCell(room: room, context: context)
|
||||||
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .showRoomDetails(roomIdentifier: room.id))
|
||||||
|
} label: {
|
||||||
|
Label(L10n.commonSettings, systemImage: "gearshape")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(role: .destructive) {
|
||||||
|
context.send(viewAction: .leaveRoom(roomIdentifier: room.id))
|
||||||
|
} label: {
|
||||||
|
Label(L10n.actionLeaveRoom, systemImage: "rectangle.portrait.and.arrow.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +110,9 @@ struct HomeScreen: View {
|
|||||||
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
|
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
|
||||||
.animation(.elementDefault, value: context.viewState.roomListMode)
|
.animation(.elementDefault, value: context.viewState.roomListMode)
|
||||||
.alert(item: $context.alertInfo) { $0.alert }
|
.alert(item: $context.alertInfo) { $0.alert }
|
||||||
|
.alert(item: $context.leaveRoomAlertItem,
|
||||||
|
actions: leaveRoomAlertActions,
|
||||||
|
message: leaveRoomAlertMessage)
|
||||||
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
|
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
@@ -238,6 +254,18 @@ struct HomeScreen: View {
|
|||||||
// This will be deduped and throttled on the view model layer
|
// This will be deduped and throttled on the view model layer
|
||||||
context.send(viewAction: .updateVisibleItemRange(range: lowerBound..<upperBound, isScrolling: scrollViewAdapter.isScrolling.value))
|
context.send(viewAction: .updateVisibleItemRange(range: lowerBound..<upperBound, isScrolling: scrollViewAdapter.isScrolling.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func leaveRoomAlertActions(_ item: LeaveRoomAlertItem) -> some View {
|
||||||
|
Button(item.cancelTitle, role: .cancel) { }
|
||||||
|
Button(item.confirmationTitle, role: .destructive) {
|
||||||
|
context.send(viewAction: .confirmLeaveRoom(roomIdentifier: item.roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func leaveRoomAlertMessage(_ item: LeaveRoomAlertItem) -> some View {
|
||||||
|
Text(item.subtitle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ struct LeaveRoomAlertItem: AlertItem {
|
|||||||
case `private`
|
case `private`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roomId: String
|
||||||
let state: RoomState
|
let state: RoomState
|
||||||
let title = L10n.actionLeaveRoom
|
let title = L10n.actionLeaveRoom
|
||||||
let confirmationTitle = L10n.actionLeave
|
let confirmationTitle = L10n.actionLeave
|
||||||
|
|||||||
@@ -75,11 +75,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
case .processTapPeople:
|
case .processTapPeople:
|
||||||
callback?(.requestMemberDetailsPresentation(members))
|
callback?(.requestMemberDetailsPresentation(members))
|
||||||
case .processTapLeave:
|
case .processTapLeave:
|
||||||
guard members.count > 1 else {
|
let joinedMembers = members.filter { $0.membership == .join }
|
||||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: .empty)
|
guard joinedMembers.count > 1 else {
|
||||||
|
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomId: roomProxy.id, state: .empty)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomId: roomProxy.id, state: roomProxy.isPublic ? .public : .private)
|
||||||
case .confirmLeave:
|
case .confirmLeave:
|
||||||
Task { await leaveRoom() }
|
Task { await leaveRoom() }
|
||||||
case .processTapIgnore:
|
case .processTapIgnore:
|
||||||
|
|||||||
@@ -53,9 +53,13 @@ class MockClientProxy: ClientProxyProtocol {
|
|||||||
.failure(.failedCreatingRoom)
|
.failure(.failedCreatingRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomInviter: RoomMemberProxyMock?
|
var roomForIdentifierMocks: [String: RoomProxyMock] = .init()
|
||||||
@MainActor
|
@MainActor
|
||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
|
||||||
|
guard roomForIdentifierMocks[identifier] == nil else {
|
||||||
|
return roomForIdentifierMocks[identifier]
|
||||||
|
}
|
||||||
|
|
||||||
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,10 +68,7 @@ class MockClientProxy: ClientProxyProtocol {
|
|||||||
case .empty:
|
case .empty:
|
||||||
return RoomProxyMock(with: .init(displayName: "Empty room"))
|
return RoomProxyMock(with: .init(displayName: "Empty room"))
|
||||||
case .filled(let details), .invalidated(let details):
|
case .filled(let details), .invalidated(let details):
|
||||||
let room = RoomProxyMock(with: .init(displayName: details.name))
|
return RoomProxyMock(with: .init(displayName: details.name))
|
||||||
room.acceptInvitationReturnValue = .success(())
|
|
||||||
room.inviterReturnValue = roomInviter
|
|
||||||
return room
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,9 +123,14 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
case (.invitesScreen, .closedInvitesScreen, .roomList):
|
case (.invitesScreen, .closedInvitesScreen, .roomList):
|
||||||
break
|
break
|
||||||
case (.invitesScreen, .selectRoom(let roomId), .invitesScreen(let selectedRoomId)) where roomId == selectedRoomId:
|
case (.invitesScreen, .selectRoom(let roomId), .invitesScreen(let selectedRoomId)) where roomId == selectedRoomId:
|
||||||
self.presentRoomWithIdentifier(roomId)
|
self.presentRoomWithIdentifier(roomId, animated: animated)
|
||||||
case (.invitesScreen, .deselectRoom, .invitesScreen):
|
case (.invitesScreen, .deselectRoom, .invitesScreen):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case (.roomList(let currentRoomId), .selectRoomDetails(let roomId), .roomList) where currentRoomId == roomId:
|
||||||
|
break
|
||||||
|
case (.roomList, .selectRoomDetails(let roomId), .roomList(let selectedRoomId)) where roomId == selectedRoomId:
|
||||||
|
self.presentRoomDetails(roomIdentifier: roomId, animated: animated)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fatalError("Unknown transition: \(context)")
|
fatalError("Unknown transition: \(context)")
|
||||||
@@ -152,6 +157,10 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .presentRoom(let roomIdentifier):
|
case .presentRoom(let roomIdentifier):
|
||||||
self.stateMachine.processEvent(.selectRoom(roomId: roomIdentifier))
|
self.stateMachine.processEvent(.selectRoom(roomId: roomIdentifier))
|
||||||
|
case .presentRoomDetails(let roomIdentifier):
|
||||||
|
self.stateMachine.processEvent(.selectRoomDetails(roomId: roomIdentifier))
|
||||||
|
case .roomLeft(let roomIdentifier):
|
||||||
|
self.deselectRoomIfNeeded(roomIdentifier: roomIdentifier)
|
||||||
case .presentSettingsScreen:
|
case .presentSettingsScreen:
|
||||||
self.stateMachine.processEvent(.showSettingsScreen)
|
self.stateMachine.processEvent(.showSettingsScreen)
|
||||||
case .presentFeedbackScreen:
|
case .presentFeedbackScreen:
|
||||||
@@ -226,11 +235,54 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deselectRoomIfNeeded(roomIdentifier: String) {
|
||||||
|
guard
|
||||||
|
case .roomList(selectedRoomId: let selectedRoomId) = stateMachine.state,
|
||||||
|
selectedRoomId == roomIdentifier
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMachine.processEvent(.deselectRoom)
|
||||||
|
navigationSplitCoordinator.setDetailCoordinator(nil)
|
||||||
|
}
|
||||||
|
|
||||||
private func dismissRoom() {
|
private func dismissRoom() {
|
||||||
detailNavigationStackCoordinator.popToRoot(animated: true)
|
detailNavigationStackCoordinator.popToRoot(animated: true)
|
||||||
navigationSplitCoordinator.setDetailCoordinator(nil)
|
navigationSplitCoordinator.setDetailCoordinator(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentRoomDetails(roomIdentifier: String, animated: Bool = true) {
|
||||||
|
Task {
|
||||||
|
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomIdentifier) else {
|
||||||
|
MXLog.error("Invalid room identifier: \(roomIdentifier)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = RoomDetailsScreenCoordinatorParameters(navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
|
let coordinator = RoomDetailsScreenCoordinator(parameters: params)
|
||||||
|
|
||||||
|
coordinator.callback = { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .cancel, .leftRoom:
|
||||||
|
self?.stateMachine.processEvent(.deselectRoom)
|
||||||
|
self?.detailNavigationStackCoordinator.setRootCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detailNavigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self, roomIdentifier] in
|
||||||
|
self?.deselectRoomIfNeeded(roomIdentifier: roomIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if navigationSplitCoordinator.detailCoordinator == nil {
|
||||||
|
navigationSplitCoordinator.setDetailCoordinator(detailNavigationStackCoordinator, animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
case showInvitesScreen
|
case showInvitesScreen
|
||||||
/// The invites screen has been dismissed
|
/// The invites screen has been dismissed
|
||||||
case closedInvitesScreen
|
case closedInvitesScreen
|
||||||
|
|
||||||
|
/// Request presentation of the settings of a specific room
|
||||||
|
case selectRoomDetails(roomId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let stateMachine: StateMachine<State, Event>
|
private let stateMachine: StateMachine<State, Event>
|
||||||
@@ -129,6 +132,9 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
return .invitesScreen(selectedRoomId: roomId)
|
return .invitesScreen(selectedRoomId: roomId)
|
||||||
case (.deselectRoom, .invitesScreen):
|
case (.deselectRoom, .invitesScreen):
|
||||||
return .invitesScreen(selectedRoomId: nil)
|
return .invitesScreen(selectedRoomId: nil)
|
||||||
|
|
||||||
|
case (.selectRoomDetails(let roomId), .roomList):
|
||||||
|
return .roomList(selectedRoomId: roomId)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -368,9 +368,9 @@ class MockScreen: Identifiable {
|
|||||||
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.dropFirst(1).compactMap(\.id))
|
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.dropFirst(1).compactMap(\.id))
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
clientProxy.roomInviter = RoomMemberProxyMock.mockCharlie
|
clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie))
|
||||||
|
clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie))
|
||||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||||
clientProxy.visibleRoomsSummaryProvider = summaryProvider
|
|
||||||
clientProxy.invitesSummaryProvider = summaryProvider
|
clientProxy.invitesSummaryProvider = summaryProvider
|
||||||
|
|
||||||
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||||
@@ -380,9 +380,9 @@ class MockScreen: Identifiable {
|
|||||||
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.compactMap(\.id))
|
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.compactMap(\.id))
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
clientProxy.roomInviter = RoomMemberProxyMock.mockCharlie
|
clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie))
|
||||||
|
clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie))
|
||||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||||
clientProxy.visibleRoomsSummaryProvider = summaryProvider
|
|
||||||
clientProxy.invitesSummaryProvider = summaryProvider
|
clientProxy.invitesSummaryProvider = summaryProvider
|
||||||
|
|
||||||
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||||
|
|||||||
@@ -21,13 +21,17 @@ import XCTest
|
|||||||
@MainActor
|
@MainActor
|
||||||
class HomeScreenViewModelTests: XCTestCase {
|
class HomeScreenViewModelTests: XCTestCase {
|
||||||
var viewModel: HomeScreenViewModelProtocol!
|
var viewModel: HomeScreenViewModelProtocol!
|
||||||
var context: HomeScreenViewModelType.Context!
|
var clientProxy: MockClientProxy!
|
||||||
|
|
||||||
|
var context: HomeScreenViewModelType.Context! {
|
||||||
|
viewModel.context
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
|
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: clientProxy,
|
||||||
mediaProvider: MockMediaProvider()),
|
mediaProvider: MockMediaProvider()),
|
||||||
attributedStringBuilder: AttributedStringBuilder())
|
attributedStringBuilder: AttributedStringBuilder())
|
||||||
context = viewModel.context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSelectRoom() async throws {
|
func testSelectRoom() async throws {
|
||||||
@@ -65,4 +69,59 @@ class HomeScreenViewModelTests: XCTestCase {
|
|||||||
await Task.yield()
|
await Task.yield()
|
||||||
XCTAssert(correctResult)
|
XCTAssert(correctResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testLeaveRoomAlert() async throws {
|
||||||
|
let mockRoomId = "1"
|
||||||
|
clientProxy.roomForIdentifierMocks[mockRoomId] = .init(with: .init(id: mockRoomId, displayName: "Some room"))
|
||||||
|
context.send(viewAction: .leaveRoom(roomIdentifier: mockRoomId))
|
||||||
|
await context.nextViewState()
|
||||||
|
XCTAssertEqual(context.leaveRoomAlertItem?.roomId, mockRoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLeaveRoomError() async throws {
|
||||||
|
let mockRoomId = "1"
|
||||||
|
let room: RoomProxyMock = .init(with: .init(id: mockRoomId, displayName: "Some room"))
|
||||||
|
room.leaveRoomClosure = { .failure(.failedLeavingRoom) }
|
||||||
|
clientProxy.roomForIdentifierMocks[mockRoomId] = room
|
||||||
|
context.send(viewAction: .confirmLeaveRoom(roomIdentifier: mockRoomId))
|
||||||
|
let newState = await context.nextViewState()
|
||||||
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLeaveRoomSuccess() async throws {
|
||||||
|
let mockRoomId = "1"
|
||||||
|
var correctResult = false
|
||||||
|
viewModel.callback = { result in
|
||||||
|
switch result {
|
||||||
|
case .roomLeft(let roomIdentifier):
|
||||||
|
correctResult = roomIdentifier == mockRoomId
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let room: RoomProxyMock = .init(with: .init(id: mockRoomId, displayName: "Some room"))
|
||||||
|
room.leaveRoomClosure = { .success(()) }
|
||||||
|
clientProxy.roomForIdentifierMocks[mockRoomId] = room
|
||||||
|
context.send(viewAction: .confirmLeaveRoom(roomIdentifier: mockRoomId))
|
||||||
|
await Task.yield()
|
||||||
|
XCTAssertNil(context.alertInfo)
|
||||||
|
XCTAssertTrue(correctResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShowRoomDetails() async throws {
|
||||||
|
let mockRoomId = "1"
|
||||||
|
var correctResult = false
|
||||||
|
viewModel.callback = { result in
|
||||||
|
switch result {
|
||||||
|
case .presentRoomDetails(let roomIdentifier):
|
||||||
|
correctResult = roomIdentifier == mockRoomId
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.send(viewAction: .showRoomDetails(roomIdentifier: mockRoomId))
|
||||||
|
await Task.yield()
|
||||||
|
XCTAssertNil(context.alertInfo)
|
||||||
|
XCTAssertTrue(correctResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class InvitesViewModelTests: XCTestCase {
|
class InvitesScreenViewModelTests: XCTestCase {
|
||||||
var viewModel: InvitesScreenViewModelProtocol!
|
var viewModel: InvitesScreenViewModelProtocol!
|
||||||
var clientProxy: MockClientProxy!
|
var clientProxy: MockClientProxy!
|
||||||
var userSession: MockUserSession!
|
var userSession: MockUserSession!
|
||||||
Reference in New Issue
Block a user