// // Copyright 2025 Element Creations Ltd. // Copyright 2022-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. // @testable import ElementX import Testing @MainActor struct KnockRequestsListScreenViewModelTests { init() { AppSettings.resetAllSettings() } @Test func loadingState() async throws { let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loading, joinRule: .knock)) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context let deferred = deferFulfillment(context.$viewState) { state in !state.shouldDisplayRequests && state.isKnockableRoom && state.canAccept && !state.canBan && !state.canDecline && state.isLoading && !state.shouldDisplayEmptyView } try await deferred.fulfill() } @Test func emptyState() async throws { let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([]), joinRule: .knock)) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context let deferred = deferFulfillment(context.$viewState) { state in !state.shouldDisplayRequests && state.isKnockableRoom && state.canAccept && !state.canBan && !state.canDecline && !state.isLoading && state.shouldDisplayEmptyView } try await deferred.fulfill() } @Test func loadedState() async throws { let roomProxyMock = JoinedRoomProxyMock(.init(members: [.mockAdmin], knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")), KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")), KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")), KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]), ownUserID: RoomMemberProxyMock.mockAdmin.userID, joinRule: .knock)) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context var deferred = deferFulfillment(context.$viewState) { state in state.shouldDisplayRequests && state.isKnockableRoom && state.canAccept && state.canBan && state.canDecline && !state.isLoading && !state.shouldDisplayEmptyView && state.displayedRequests.count == 4 && state.handledEventIDs.isEmpty && state.shouldDisplayAcceptAllButton } try await deferred.fulfill() deferred = deferFulfillment(context.$viewState) { state in state.shouldDisplayRequests && state.handledEventIDs == ["1"] && !state.shouldDisplayEmptyView && state.displayedRequests.count == 3 && state.shouldDisplayAcceptAllButton } context.send(viewAction: .acceptRequest(eventID: "1")) try await deferred.fulfill() deferred = deferFulfillment(context.$viewState) { state in state.bindings.alertInfo?.id == .declineRequest } context.send(viewAction: .declineRequest(eventID: "2")) try await deferred.fulfill() let declineAlertInfo = try #require(context.alertInfo) deferred = deferFulfillment(context.$viewState) { state in state.shouldDisplayRequests && state.handledEventIDs == ["1", "2"] && !state.shouldDisplayEmptyView && state.displayedRequests.count == 2 && state.shouldDisplayAcceptAllButton } declineAlertInfo.primaryButton.action?() try await deferred.fulfill() deferred = deferFulfillment(context.$viewState) { state in state.bindings.alertInfo?.id == .declineAndBan } context.send(viewAction: .ban(eventID: "3")) try await deferred.fulfill() let banAlertInfo = try #require(context.alertInfo) deferred = deferFulfillment(context.$viewState) { state in state.shouldDisplayRequests && state.handledEventIDs == ["1", "2", "3"] && !state.shouldDisplayEmptyView && state.displayedRequests.count == 1 && !state.shouldDisplayAcceptAllButton } banAlertInfo.primaryButton.action?() try await deferred.fulfill() } @Test func acceptAll() async throws { let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")), KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")), KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")), KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]), joinRule: .knock)) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context var deferred = deferFulfillment(context.$viewState) { state in state.shouldDisplayRequests && state.isKnockableRoom && state.canAccept && !state.canBan && !state.canDecline && !state.isLoading && !state.shouldDisplayEmptyView && state.displayedRequests.count == 4 && state.handledEventIDs.isEmpty && state.shouldDisplayAcceptAllButton } try await deferred.fulfill() deferred = deferFulfillment(context.$viewState) { state in state.bindings.alertInfo?.id == .acceptAllRequests } context.send(viewAction: .acceptAllRequests) try await deferred.fulfill() let alertInfo = try #require(context.alertInfo) deferred = deferFulfillment(context.$viewState) { state in !state.shouldDisplayRequests && state.handledEventIDs == ["1", "2", "3", "4"] && !state.isLoading && state.shouldDisplayEmptyView } alertInfo.primaryButton.action?() try await deferred.fulfill() } @Test func loadedStateBecomesEmptyIfTheJoinRuleIsNotKnocking() async throws { // If there is a sudden change in the rule, but the requests are still published, we want to hide all of them and show the empty view let roomProxyMock = JoinedRoomProxyMock(.init(members: [.mockAdmin], knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")), KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")), KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")), KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]), ownUserID: RoomMemberProxyMock.mockAdmin.userID, joinRule: .invite)) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context let deferred = deferFulfillment(context.$viewState) { state in !state.shouldDisplayRequests && state.shouldDisplayEmptyView && !state.isLoading && !state.isKnockableRoom } try await deferred.fulfill() } @Test func loadedStateBecomesEmptyIfPermissionsAreRemoved() async throws { // If there is a sudden change in permissions, and the user can't do any other action, we hide all the requests and shoe the empty view let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org")), KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org")), KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org")), KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org"))]), joinRule: .knock, powerLevelsConfiguration: .init(canUserInvite: false))) let viewModel = KnockRequestsListScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MediaProviderMock(), userIndicatorController: UserIndicatorControllerMock()) let context = viewModel.context let deferred = deferFulfillment(context.$viewState) { state in !state.shouldDisplayRequests && state.shouldDisplayEmptyView && !state.canAccept && !state.canBan && !state.canDecline && !state.isLoading } try await deferred.fulfill() } }