From b73777ebca8e42629f6ff4f4e445497ab6731a3b Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 15 Jan 2025 22:08:40 +0100 Subject: [PATCH] fixup! add ComposerToolBarViewModelTests for canSend --- .../ComposerToolbarViewModelTests.swift | 177 +++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/UnitTests/Sources/ComposerToolbarViewModelTests.swift b/UnitTests/Sources/ComposerToolbarViewModelTests.swift index 7dad560a25..f4023c23b2 100644 --- a/UnitTests/Sources/ComposerToolbarViewModelTests.swift +++ b/UnitTests/Sources/ComposerToolbarViewModelTests.swift @@ -7,6 +7,7 @@ import Combine @testable import ElementX +import MatrixRustSDK import XCTest import WysiwygComposer @@ -93,7 +94,9 @@ class ComposerToolbarViewModelTests: XCTestCase { let suggestions: [SuggestionItem] = [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil, range: .init())), .user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: .mockMXCAvatar, range: .init()))] let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)) - viewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel, + + viewModel = ComposerToolbarViewModel(roomProxy: JoinedRoomProxyMock(.init()), + wysiwygViewModel: wysiwygViewModel, completionSuggestionService: mockCompletionSuggestionService, mediaProvider: MediaProviderMock(configuration: .init()), mentionDisplayHelper: ComposerMentionDisplayHelper.mock, @@ -632,6 +635,177 @@ class ComposerToolbarViewModelTests: XCTestCase { XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: sharedText)) } + // MARK: - Identity Violation + + func testVerificationViolationDisablesComposer() { + let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init()) + + let roomProxyMock = JoinedRoomProxyMock(.init(name: "Test")) + let roomMemberProxyMock = RoomMemberProxyMock(with: .init(userID: "@alice:localhost", membership: .join)) + + roomProxyMock.getMemberUserIDClosure = { _ in + .success(roomMemberProxyMock) + } + + let mockSubject = CurrentValueSubject<[IdentityStatusChange], Never>([IdentityStatusChange(userId: "@alice:localhost", changedTo: .verificationViolation)]) + + roomProxyMock.underlyingIdentityStatusChangesPublisher = mockSubject.asCurrentValuePublisher() + + viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, + wysiwygViewModel: wysiwygViewModel, + completionSuggestionService: mockCompletionSuggestionService, + mediaProvider: MediaProviderMock(configuration: .init()), + mentionDisplayHelper: ComposerMentionDisplayHelper.mock, + analyticsService: ServiceLocator.shared.analytics, + composerDraftService: draftServiceMock) + + let expectation1 = expectation(description: "Composer is disabled") + let cancellable = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if !canSend { + expectation1.fulfill() + } + } + + wait(for: [expectation1], timeout: 2.0) + cancellable.cancel() + + let expectation2 = expectation(description: "Composer is enabled") + let cancellable2 = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if canSend { + expectation2.fulfill() + } + } + + mockSubject.send([IdentityStatusChange(userId: "@alice:localhost", changedTo: .pinned)]) + + wait(for: [expectation2], timeout: 2.0) + cancellable2.cancel() + } + + func testMultipleViolation() { + let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init()) + + let roomProxyMock = JoinedRoomProxyMock(.init(name: "Test")) + + let aliceRoomMemberProxyMock = RoomMemberProxyMock(with: .init(userID: "@alice:localhost", membership: .join)) + let bobRoomMemberProxyMock = RoomMemberProxyMock(with: .init(userID: "@bob:localhost", membership: .join)) + + roomProxyMock.getMemberUserIDClosure = { userId in + if userId == "@alice:localhost" { + return .success(aliceRoomMemberProxyMock) + } else if userId == "@bob:localhost" { + return .success(bobRoomMemberProxyMock) + } else { + return .failure(.sdkError(ClientProxyMockError.generic)) + } + } + + // There are 2 violations, ensure that resolving the first one is not enough + let mockSubject = CurrentValueSubject<[IdentityStatusChange], Never>([ + IdentityStatusChange(userId: "@alice:localhost", changedTo: .verificationViolation), + IdentityStatusChange(userId: "@bob:localhost", changedTo: .verificationViolation) + ]) + + roomProxyMock.underlyingIdentityStatusChangesPublisher = mockSubject.asCurrentValuePublisher() + + viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, + wysiwygViewModel: wysiwygViewModel, + completionSuggestionService: mockCompletionSuggestionService, + mediaProvider: MediaProviderMock(configuration: .init()), + mentionDisplayHelper: ComposerMentionDisplayHelper.mock, + analyticsService: ServiceLocator.shared.analytics, + composerDraftService: draftServiceMock) + + let expectation1 = expectation(description: "Composer is disabled") + let cancellable = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if !canSend { + expectation1.fulfill() + } + } + + wait(for: [expectation1], timeout: 2.0) + cancellable.cancel() + + let expectation2 = expectation(description: "Composer is still disabled") + let cancellable2 = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if !canSend { + expectation2.fulfill() + } + } + + mockSubject.send([IdentityStatusChange(userId: "@alice:localhost", changedTo: .pinned)]) + + wait(for: [expectation2], timeout: 2.0) + cancellable2.cancel() + + let expectation3 = expectation(description: "Composer is now enabled") + let cancellable3 = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if canSend { + expectation3.fulfill() + } + } + + mockSubject.send([IdentityStatusChange(userId: "@bob:localhost", changedTo: .pinned)]) + + wait(for: [expectation3], timeout: 2.0) + cancellable3.cancel() + } + + func testPinViolationDoesNotDisableComposer() { + let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init()) + + let roomProxyMock = JoinedRoomProxyMock(.init(name: "Test")) + let roomMemberProxyMock = RoomMemberProxyMock(with: .init(userID: "@alice:localhost", membership: .join)) + + roomProxyMock.getMemberUserIDClosure = { _ in + .success(roomMemberProxyMock) + } + + roomProxyMock.underlyingIdentityStatusChangesPublisher = CurrentValueSubject([IdentityStatusChange(userId: "@alice:localhost", changedTo: .pinViolation)]).asCurrentValuePublisher() + + viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, + wysiwygViewModel: wysiwygViewModel, + completionSuggestionService: mockCompletionSuggestionService, + mediaProvider: MediaProviderMock(configuration: .init()), + mentionDisplayHelper: ComposerMentionDisplayHelper.mock, + analyticsService: ServiceLocator.shared.analytics, + composerDraftService: draftServiceMock) + + let expectation = expectation(description: "Composer should be enabled") + let cancellable = viewModel + .context + .$viewState + .map(\.canSend) + .sink { canSend in + if canSend { + expectation.fulfill() + } + } + + wait(for: [expectation], timeout: 2.0) + cancellable.cancel() + } + // MARK: - Helpers private func setUpViewModel(initialText: String? = nil, loadDraftClosure: (() async -> Result)? = nil) { @@ -643,6 +817,7 @@ class ComposerToolbarViewModelTests: XCTestCase { } viewModel = ComposerToolbarViewModel(initialText: initialText, + roomProxy: JoinedRoomProxyMock(.init()), wysiwygViewModel: wysiwygViewModel, completionSuggestionService: completionSuggestionServiceMock, mediaProvider: MediaProviderMock(configuration: .init()),