diff --git a/ElementX/Sources/Screens/KnockRequestsListScreen/KnockRequestsListScreenViewModel.swift b/ElementX/Sources/Screens/KnockRequestsListScreen/KnockRequestsListScreenViewModel.swift index 36b33a10e5..f949309938 100644 --- a/ElementX/Sources/Screens/KnockRequestsListScreen/KnockRequestsListScreenViewModel.swift +++ b/ElementX/Sources/Screens/KnockRequestsListScreen/KnockRequestsListScreenViewModel.swift @@ -221,7 +221,8 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn interactiveDismissDisabled: false, allowsInteraction: true), title: L10n.screenKnockRequestsListInitialLoadingTitle, - persistent: true)) + persistent: true), + delay: .milliseconds(100)) } private func showLoadingIndicator(title: String) { @@ -231,7 +232,7 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn allowsInteraction: false), title: title, persistent: true), - delay: .seconds(0.25)) + delay: .milliseconds(250)) } private func hideLoadingIndicator() { diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index 0908be9190..6adde4e293 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -21,6 +21,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { var cancellables = Set() override func setUp() { + AppSettings.resetAllSettings() cancellables.removeAll() roomProxyMock = JoinedRoomProxyMock(.init(name: "Test")) notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()) @@ -33,8 +34,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase { attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings) - - AppSettings.resetAllSettings() } func testLeaveRoomTappedWhenPublic() async throws { @@ -672,4 +671,96 @@ class RoomDetailsScreenViewModelTests: XCTestCase { XCTFail("invalid state") } } + + // MARK: - Knock Requests + + func testKnockRequestsCounter() async throws { + ServiceLocator.shared.settings.knockingEnabled = true + let mockedRequests: [JoinRequestProxyMock] = [.init(), .init()] + roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, joinRequestsState: .loaded(mockedRequests), joinRule: .knock)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), + mediaProvider: MediaProviderMock(configuration: .init()), + analyticsService: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + notificationSettingsProxy: notificationSettingsProxyMock, + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) + + let deferred = deferFulfillment(context.$viewState) { state in + state.knockRequestsCount == 2 && state.canSeeKnockingRequests + } + + try await deferred.fulfill() + } + + func testKnockRequestsCounterIsLoading() async throws { + ServiceLocator.shared.settings.knockingEnabled = true + roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, joinRequestsState: .loading, joinRule: .knock)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), + mediaProvider: MediaProviderMock(configuration: .init()), + analyticsService: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + notificationSettingsProxy: notificationSettingsProxyMock, + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) + + let deferred = deferFulfillment(context.$viewState) { state in + state.knockRequestsCount == 0 && state.canSeeKnockingRequests + } + + try await deferred.fulfill() + } + + func testKnockRequestsCounterIsNotShownIfNoPermissions() async throws { + ServiceLocator.shared.settings.knockingEnabled = true + let mockedRequests: [JoinRequestProxyMock] = [.init(), .init()] + roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, joinRequestsState: .loaded(mockedRequests), canUserInvite: false, joinRule: .knock)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), + mediaProvider: MediaProviderMock(configuration: .init()), + analyticsService: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + notificationSettingsProxy: notificationSettingsProxyMock, + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) + + let deferred = deferFulfillment(context.$viewState) { state in + state.knockRequestsCount == 2 && + state.dmRecipient == nil && + !state.canSeeKnockingRequests && + !state.canInviteUsers + } + + try await deferred.fulfill() + } + + func testKnockRequestsCounterIsNotShownIfDM() async throws { + ServiceLocator.shared.settings.knockingEnabled = true + let mockedRequests: [JoinRequestProxyMock] = [.init(), .init()] + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockAlice] + roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isPublic: false, members: mockedMembers, joinRequestsState: .loaded(mockedRequests), joinRule: .knock)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), + mediaProvider: MediaProviderMock(configuration: .init()), + analyticsService: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + notificationSettingsProxy: notificationSettingsProxyMock, + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) + + let deferred = deferFulfillment(context.$viewState) { state in + state.knockRequestsCount == 2 && + !state.canSeeKnockingRequests && + state.dmRecipient != nil && + state.canInviteUsers + } + + try await deferred.fulfill() + } } diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index 3655b6fb51..374ba86d95 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -240,6 +240,8 @@ class RoomScreenViewModelTests: XCTestCase { XCTAssertTrue(viewModel.state.shouldShowCallButton) } + // MARK: - Knock Requests + func testKnockRequestBanner() async throws { ServiceLocator.shared.settings.knockingEnabled = true let roomProxyMock = JoinedRoomProxyMock(.init(joinRequestsState: .loaded([JoinRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", reason: "Hello World!")), @@ -257,20 +259,22 @@ class RoomScreenViewModelTests: XCTestCase { userIndicatorController: ServiceLocator.shared.userIndicatorController) self.viewModel = viewModel - var deferred = deferFulfillment(viewModel.context.$viewState) { $0.shouldSeeKnockRequests } + var deferred = deferFulfillment(viewModel.context.$viewState) { state in + state.shouldSeeKnockRequests && + state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1")] + } try await deferred.fulfill() - XCTAssert(viewModel.context.viewState.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1")]) - let deferredAction = deferFulfillment(viewModel.actions) { $0 == .displayKnockRequests } viewModel.context.send(viewAction: .viewKnockRequests) try await deferredAction.fulfill() - deferred = deferFulfillment(viewModel.context.$viewState) { $0.handledEventIDs == ["1"] } + deferred = deferFulfillment(viewModel.context.$viewState) { state in + state.handledEventIDs == ["1"] && + !state.shouldSeeKnockRequests + } viewModel.context.send(viewAction: .acceptKnock(eventID: "1")) try await deferred.fulfill() - - XCTAssertFalse(viewModel.context.viewState.shouldSeeKnockRequests) } func testKnockRequestBannerMarkAsSeen() async throws { @@ -290,17 +294,19 @@ class RoomScreenViewModelTests: XCTestCase { userIndicatorController: ServiceLocator.shared.userIndicatorController) self.viewModel = viewModel - var deferred = deferFulfillment(viewModel.context.$viewState) { $0.shouldSeeKnockRequests } + var deferred = deferFulfillment(viewModel.context.$viewState) { state in + state.shouldSeeKnockRequests && + state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1"), + .init(displayName: nil, avatarURL: nil, userID: "@bob:matrix.org", reason: nil, eventID: "2")] + } try await deferred.fulfill() - XCTAssert(viewModel.context.viewState.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1"), - .init(displayName: nil, avatarURL: nil, userID: "@bob:matrix.org", reason: nil, eventID: "2")]) - - deferred = deferFulfillment(viewModel.context.$viewState) { $0.handledEventIDs == ["1", "2"] } + deferred = deferFulfillment(viewModel.context.$viewState) { state in + state.handledEventIDs == ["1", "2"] && + !state.shouldSeeKnockRequests + } viewModel.context.send(viewAction: .dismissKnockRequests) try await deferred.fulfill() - - XCTAssertFalse(viewModel.context.viewState.shouldSeeKnockRequests) } func testLoadingKnockRequests() async throws { @@ -319,7 +325,30 @@ class RoomScreenViewModelTests: XCTestCase { self.viewModel = viewModel // Loading state just does not appear at all - var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.shouldSeeKnockRequests } + let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.shouldSeeKnockRequests } + try await deferred.fulfill() + } + + func testKnockRequestsBannerDoesNotAppearIfUserHasNoPermission() async throws { + ServiceLocator.shared.settings.knockingEnabled = true + let roomProxyMock = JoinedRoomProxyMock(.init(joinRequestsState: .loaded([JoinRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", reason: "Hello World!"))]), + canUserInvite: false, + joinRule: .knock)) + let viewModel = RoomScreenViewModel(clientProxy: ClientProxyMock(), + roomProxy: roomProxyMock, + initialSelectedPinnedEventID: nil, + mediaProvider: MediaProviderMock(configuration: .init()), + ongoingCallRoomIDPublisher: .init(.init(nil)), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) + self.viewModel = viewModel + + var deferred = deferFulfillment(viewModel.context.$viewState) { state in + state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1")] && + !state.shouldSeeKnockRequests + } try await deferred.fulfill() } }