// // Copyright 2025 Element Creations Ltd. // Copyright 2023-2025 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import Combine import Foundation import MatrixRustSDK enum RoomProxyMockError: Error { case generic } @MainActor struct JoinedRoomProxyMockConfiguration { var id = UUID().uuidString var name: String? var topic: String? var avatarURL: URL? var isDirect = false var isSpace = false var isEncrypted = true var hasOngoingCall = true var canonicalAlias: String? var alternativeAliases: [String] = [] var pinnedEventIDs: Set = [] var historyVisibility: RoomHistoryVisibility = .shared var timelineStartReached = false var members: [RoomMemberProxyMock] = .allMembers var heroes: [RoomMemberProxyMock] = [] var knockRequestsState: KnockRequestsState = .loaded([]) var ownUserID = RoomMemberProxyMock.mockMe.userID var inviter: RoomMemberProxyProtocol? var shouldUseAutoUpdatingTimeline = false var joinRule: JoinRule? = .invite var membership: Membership = .joined var isVisibleInPublicDirectory = false var predecessor: PredecessorRoom? var successor: SuccessorRoom? var powerLevelsConfiguration = RoomPowerLevelsProxyMockConfiguration() } extension JoinedRoomProxyMock { @MainActor convenience init(_ configuration: JoinedRoomProxyMockConfiguration) { self.init() id = configuration.id timeline = TimelineProxyMock(.init(isAutoUpdating: configuration.shouldUseAutoUpdatingTimeline, timelineStartReached: configuration.timelineStartReached)) pinnedEventsTimelineReturnValue = .failure(.failedCreatingPinnedTimeline) ownUserID = configuration.ownUserID membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher() knockRequestsStatePublisher = CurrentValueSubject(configuration.knockRequestsState).asCurrentValuePublisher() typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher() identityStatusChangesPublisher = CurrentValueSubject([]).asCurrentValuePublisher() makeLiveLocationServiceReturnValue = RoomLiveLocationServiceMock(.init()) updateMembersClosure = { } setNameClosure = { _ in .success(()) } setTopicClosure = { _ in .success(()) } getMemberUserIDClosure = { [weak self] userID in guard let member = self?.membersPublisher.value.first(where: { $0.userID == userID }) else { return .failure(.sdkError(RoomProxyMockError.generic)) } return .success(member) } ignoreDeviceTrustAndResendDevicesSendHandleReturnValue = .success(()) withdrawVerificationAndResendUserIDsSendHandleReturnValue = .success(()) flagAsUnreadReturnValue = .success(()) markAsReadReceiptTypeReturnValue = .success(()) flagAsFavouriteReturnValue = .success(()) applyPowerLevelChangesReturnValue = .success(()) resetPowerLevelsReturnValue = .success(()) suggestedRoleForClosure = { [weak self] userID in guard case .success(let member) = await self?.getMember(userID: userID) else { return .failure(.sdkError(RoomProxyMockError.generic)) } return .success(member.role.rustRole) } updatePowerLevelsForUsersReturnValue = .success(()) let powerLevelsProxyMock = RoomPowerLevelsProxyMock(configuration: configuration.powerLevelsConfiguration) powerLevelsProxyMock.canUserUserIDSendStateEventClosure = { [weak self] userID, _ in .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) } powerLevelsProxyMock.canOwnUserSendStateEventClosure = { [weak self] _ in self?.membersPublisher.value.first { $0.userID == configuration.ownUserID }?.role ?? .user != .user } powerLevelsProxyMock.canUserKickUserIDClosure = { [weak self] userID in .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) } powerLevelsProxyMock.canOwnUserKickClosure = { [weak self] in self?.membersPublisher.value.first { $0.userID == configuration.ownUserID }?.role ?? .user != .user } powerLevelsProxyMock.canUserBanUserIDClosure = { [weak self] userID in .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) } powerLevelsProxyMock.canOwnUserBanClosure = { [weak self] in self?.membersPublisher.value.first { $0.userID == configuration.ownUserID }?.role ?? .user != .user } powerLevelsProxyMock.canOwnUserEditRolesAndPermissionsClosure = { [weak self] in self?.membersPublisher.value.first { $0.userID == configuration.ownUserID }?.role.isAdminOrHigher ?? false } powerLevelsReturnValue = .success(powerLevelsProxyMock) inviteUserIDReturnValue = .success(()) kickUserReasonReturnValue = .success(()) banUserReasonReturnValue = .success(()) unbanUserReturnValue = .success(()) let widgetDriver = ElementCallWidgetDriverMock() widgetDriver.underlyingMessagePublisher = .init() widgetDriver.underlyingActions = PassthroughSubject().eraseToAnyPublisher() guard let url = URL(string: "https://call.element.io/\(UUID().uuidString)#?appPrompt=false") else { fatalError() } widgetDriver.startBaseURLClientIDColorSchemeVoiceOnlyRageshakeURLAnalyticsConfigurationUnderlyingReturnValue = .success(url) elementCallWidgetDriverDeviceIDReturnValue = widgetDriver matrixToPermalinkReturnValue = .success(.homeDirectory) matrixToEventPermalinkReturnValue = .success(.homeDirectory) loadDraftThreadRootEventIDReturnValue = .success(nil) clearDraftThreadRootEventIDReturnValue = .success(()) sendTypingNotificationIsTypingReturnValue = .success(()) isVisibleInRoomDirectoryReturnValue = .success(configuration.isVisibleInPublicDirectory) predecessorRoom = configuration.predecessor let roomInfoProxyMock = RoomInfoProxyMock(configuration) roomInfoProxyMock.powerLevels = powerLevelsProxyMock infoPublisher = CurrentValueSubject(roomInfoProxyMock).asCurrentValuePublisher() } } extension RoomInfoProxyMock { @MainActor convenience init(_ configuration: JoinedRoomProxyMockConfiguration) { self.init() id = configuration.id isEncrypted = configuration.isEncrypted creators = [] displayName = configuration.name rawName = configuration.name topic = configuration.topic avatarURL = configuration.avatarURL isDirect = configuration.isDirect isSpace = configuration.isSpace successor = configuration.successor isFavourite = false canonicalAlias = configuration.canonicalAlias alternativeAliases = configuration.alternativeAliases membership = configuration.membership inviter = configuration.inviter heroes = configuration.heroes.map(RoomHero.init) activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count invitedMembersCount = configuration.members.filter { $0.membership == .invite }.count joinedMembersCount = configuration.members.filter { $0.membership == .join }.count highlightCount = 0 notificationCount = 0 cachedUserDefinedNotificationMode = .allMessages hasRoomCall = configuration.hasOngoingCall activeRoomCallParticipants = [] isMarkedUnread = false unreadMessagesCount = 0 unreadNotificationsCount = 0 unreadMentionsCount = 0 pinnedEventIDs = configuration.pinnedEventIDs joinRule = configuration.joinRule historyVisibility = configuration.historyVisibility powerLevels = RoomPowerLevelsProxyMock(configuration: configuration.powerLevelsConfiguration) } } private extension RoomHero { init(from memberProxy: RoomMemberProxyMock) { self.init(userId: memberProxy.userID, displayName: memberProxy.displayName, avatarUrl: memberProxy.avatarURL?.absoluteString) } } @MainActor extension Array where Element == JoinedRoomProxyProtocol { static var mockRooms: [JoinedRoomProxyProtocol] { [ JoinedRoomProxyMock(.init(id: "1", name: "Room Name", canonicalAlias: "#room-name:example.com")), JoinedRoomProxyMock(.init(id: "2", name: "Room Name", canonicalAlias: "#room-name:example.com")), JoinedRoomProxyMock(.init(id: "3", name: "Room Name", canonicalAlias: "#room-name:example.com")) ] } }