From 10832668a76f996b8ed6abc66355422e78994f32 Mon Sep 17 00:00:00 2001 From: John E Date: Fri, 10 Apr 2026 12:54:12 -0400 Subject: [PATCH] Propose fix some typos Signed-off-by: John E --- .../Database/DatabaseContainer.swift | 26 +- .../Tests/Attachments_Tests.swift | 4 +- ...sageDeliveryStatus+ChannelList_Tests.swift | 24 +- .../MessageDeliveryStatus_Tests.swift | 26 +- .../Tests/QuotedReply_Tests.swift | 22 +- .../Database/DTOs/MemberModelDTO_Tests.swift | 8 +- .../StateLayer/Chat_Tests.swift | 532 +++++++++--------- 7 files changed, 321 insertions(+), 321 deletions(-) diff --git a/Sources/StreamChat/Database/DatabaseContainer.swift b/Sources/StreamChat/Database/DatabaseContainer.swift index 942f90e2f37..860c7f0b126 100644 --- a/Sources/StreamChat/Database/DatabaseContainer.swift +++ b/Sources/StreamChat/Database/DatabaseContainer.swift @@ -42,7 +42,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { context.setChatClientConfig(chatClientConfig) return context }() - + /// An immediately reacting NSManagedObjectContext for the chat state layer. /// /// Chat state layer requires that the context is refreshed when a write happens. Otherwise database observers are too slow to react. @@ -200,7 +200,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { log.debug("Context has no changes. Skipping save.", subsystems: .database) } - log.debug("Database session succesfully saved.", subsystems: .database) + log.debug("Database session successfully saved.", subsystems: .database) completion(nil) } catch { log.error("Failed to save data to DB. Error: \(error)", subsystems: .database) @@ -210,7 +210,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { } } } - + func write(_ actions: @escaping @Sendable (DatabaseSession) throws -> Void) async throws { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in write(actions) { error in @@ -222,7 +222,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { } } } - + func write(converting actions: @escaping @Sendable (DatabaseSession) throws -> T, completion: @escaping @Sendable (Result) -> Void) where T: Sendable { nonisolated(unsafe) var result: T? write { session in @@ -238,7 +238,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { } } } - + private func read( from context: NSManagedObjectContext, _ actions: @escaping @Sendable (DatabaseSession) throws -> T, @@ -257,11 +257,11 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { } } } - + func read(_ actions: @escaping @Sendable (DatabaseSession) throws -> T, completion: @escaping @Sendable (Result) -> Void) { read(from: backgroundReadOnlyContext, actions, completion: completion) } - + func read(_ actions: @escaping @Sendable (DatabaseSession) throws -> T) async throws -> T where T: Sendable { try await withCheckedThrowingContinuation { continuation in read(from: stateLayerContext, actions) { result in @@ -269,7 +269,7 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { } } } - + func readAndWait(_ actions: @Sendable (DatabaseSession) throws -> T) throws -> T where T: Sendable { let context = backgroundReadOnlyContext nonisolated(unsafe) var result: T? @@ -324,14 +324,14 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { if let writableContext = self?.writableContext, let allContext = self?.allContext { writableContext.invalidateCurrentUserCache() writableContext.reset() - + for context in allContext where context != writableContext { context.performAndWait { context.invalidateCurrentUserCache() context.reset() } } - + if FileManager.default.fileExists(atPath: URL.streamAttachmentDownloadsDirectory.path) { do { try FileManager.default.removeItem(at: .streamAttachmentDownloadsDirectory) @@ -429,12 +429,12 @@ class DatabaseContainer: NSPersistentContainer, @unchecked Sendable { let dtoClasses = managedObjectModel.entities .compactMap(\.name) .compactMap { NSClassFromString($0) } - + // Reset relationships for dtoClass in dtoClasses { dtoClass.resetEphemeralRelationshipValues?(in: writableContext) } - + // Reset properties without relationships let allRequests: [NSBatchUpdateRequest] = dtoClasses .compactMap { $0.resetEphemeralValuesBatchRequests?() } @@ -477,7 +477,7 @@ extension NSManagedObjectContext { "deleted": deletedObjects.count ] } - + func observeChanges(in otherContext: NSManagedObjectContext) -> NSObjectProtocol { assert(!automaticallyMergesChangesFromParent, "Duplicate change handling") return NotificationCenter.default diff --git a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift index 78f5d010e86..bf156dcce25 100644 --- a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift @@ -47,7 +47,7 @@ final class Attachments_Tests: StreamTestCase { userRobot.assertVideo(isPresent: true) } } - + func test_participantUploadsFile() throws { linkToScenario(withId: 33) @@ -70,7 +70,7 @@ final class Attachments_Tests: StreamTestCase { .login() .openChannel() } - WHEN("user sends an image beeing offline") { + WHEN("user sends an image being offline") { userRobot .setConnectivity(to: .off) .uploadImage() diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift index 2546c3a6115..99c94898313 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift @@ -24,7 +24,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { backendRobot.delayNewMessages(by: 10) userRobot.sendMessage(message, waitForAppearance: false) } - WHEN("user retuns to the channel list before the message is sent") { + WHEN("user returns to the channel list before the message is sent") { userRobot.tapOnBackButton() } THEN("last message delivery status in the channel preview shows clocks on the left") { @@ -45,7 +45,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { AND("user sends new message") { userRobot.sendMessage(message) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.tapOnBackButton() } THEN("last message delivery status in the channel preview shows single checkmark on the right") { @@ -69,7 +69,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { .sendMessage(failedMessage, waitForAppearance: false) .assertMessageFailedToBeSent() } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.tapOnBackButton() } THEN("error indicator is shown for the failed message") { @@ -87,10 +87,10 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } - AND("user retuns to the channel list") { + AND("user returns to the channel list") { userRobot.tapOnBackButton() } WHEN("participant reads the user's message") { @@ -116,7 +116,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { AND("user sends a new message") { userRobot.sendMessage(message) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.tapOnBackButton() } THEN("delivery status is hidden") { @@ -137,7 +137,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { WHEN("participant sends a new message") { participantRobot.sendMessage(message) } - AND("user retuns to the channel list") { + AND("user returns to the channel list") { userRobot.tapOnBackButton() } THEN("delivery status is hidden") { @@ -165,7 +165,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { AND("user replies to the message in thread") { userRobot.sendMessageInThread(threadReply) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.moveToChannelListFromThreadReplies() } THEN("delivery status is hidden") { @@ -193,7 +193,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { AND("user replies to message in thread") { userRobot.sendMessageInThread(failedThreadReply, waitForAppearance: false) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.moveToChannelListFromThreadReplies() } THEN("delivery status shows error indicator") { @@ -221,7 +221,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { AND("participant reads the user's thread reply") { participantRobot.readMessage() } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.moveToChannelListFromThreadReplies() } THEN("user spots double checkmark next to the message") { @@ -249,7 +249,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { AND("user replies to message in thread") { userRobot.sendMessageInThread(threadReply) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.moveToChannelListFromThreadReplies() } THEN("delivery status is hidden") { @@ -274,7 +274,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { AND("participant replies to message in thread") { participantRobot.sendMessageInThread(threadReply) } - WHEN("user retuns to the channel list") { + WHEN("user returns to the channel list") { userRobot.moveToChannelListFromThreadReplies() } THEN("delivery status is hidden") { diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift index 7b3c94f9f5e..c553d3e2431 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift @@ -80,7 +80,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } WHEN("participant reads the user's message") { @@ -102,7 +102,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("message is read by more than 1 participant") { @@ -130,7 +130,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("is read by participant") { @@ -156,7 +156,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("delivery status shows single checkmark") { @@ -181,7 +181,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("delivery status shows single checkmark") { @@ -211,7 +211,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends a new message") { + AND("user successfully sends a new message") { userRobot.sendMessage(message) } WHEN("user previews thread for message: \(message)") { @@ -347,7 +347,7 @@ extension MessageDeliveryStatus_Tests { func test_noDoubleCheckmarkShownInThreadReply_whenNewParticipantAdded() throws { linkToScenario(withId: 154) - + GIVEN("user opens the channel") { userRobot .login() @@ -462,7 +462,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("succesfully sends a new message") { + AND("successfully sends a new message") { userRobot.sendMessage(message) } WHEN("user sends message with invalid command") { @@ -554,7 +554,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } WHEN("participant reads the user's message") { @@ -576,7 +576,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("message is read by more than 1 participant") { @@ -601,7 +601,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("is read by participant") { @@ -628,7 +628,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("delivery status is hidden") { @@ -654,7 +654,7 @@ extension MessageDeliveryStatus_Tests { .login() .openChannel() } - AND("user succesfully sends new message") { + AND("user successfully sends new message") { userRobot.sendMessage(message) } AND("delivery status is hidden") { diff --git a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift index b45cf1a6713..b80713d7e2d 100644 --- a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift @@ -208,7 +208,7 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.assertDeletedMessage() } } - + func test_originalQuoteIsDeletedByParticipant_deletedMessageIsShown() { linkToScenario(withId: 6643) @@ -249,7 +249,7 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.assertDeletedMessage() } } - + func test_originalQuoteIsDeletedByUser_deletedMessageIsShown() { linkToScenario(withId: 6644) @@ -472,7 +472,7 @@ final class QuotedReply_Tests: StreamTestCase { .assertScrollToBottomButton(isVisible: true) } } - + // NOTE: There used to be a problem with tapping on a Send button on iOS > 16 func test_quotedReplyNotInList_whenParticipantAddsQuotedReply_Giphy_InThread() throws { linkToScenario(withId: 1936) @@ -545,7 +545,7 @@ final class QuotedReply_Tests: StreamTestCase { func test_threadRepliesCount() { linkToScenario(withId: 1938) - + let repliesCount = 5 GIVEN("user opens the channel") { @@ -560,7 +560,7 @@ final class QuotedReply_Tests: StreamTestCase { THEN("user observes the number of replies in the channel") { userRobot.assertThreadReplyCountButton(replies: repliesCount) } - WHEN("user opens the tread") { + WHEN("user opens the thread") { userRobot.openThread() } AND("user observes the number of replies in the thread") { @@ -620,7 +620,7 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.openThread().assertDeletedMessage() } } - + func test_originalQuoteIsDeletedByParticipant_deletedMessageIsShown_InThread() { linkToScenario(withId: 6645) @@ -671,7 +671,7 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.assertDeletedMessage() } } - + func test_originalQuoteIsDeletedByUser_deletedMessageIsShown_InThread() { linkToScenario(withId: 6646) @@ -725,7 +725,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1998) let pageSize = 25 - + GIVEN("user opens the channel") { backendRobot.generateChannels( channelsCount: 1, @@ -748,12 +748,12 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.assertParentMessageInThread(withText: parentText, isLoaded: true) } } - + func test_rootMessageShouldBeVisibleInThreadIfMessageCountLessThanPageSize() { linkToScenario(withId: 1999) let messageCount = 24 - + GIVEN("user opens the channel") { backendRobot.generateChannels( channelsCount: 1, @@ -770,7 +770,7 @@ final class QuotedReply_Tests: StreamTestCase { userRobot.assertParentMessageInThread(withText: parentText, isLoaded: true) } } - + func test_quoteReplyRootMessageWhenNotInTheList() { linkToScenario(withId: 2000) diff --git a/Tests/StreamChatTests/Database/DTOs/MemberModelDTO_Tests.swift b/Tests/StreamChatTests/Database/DTOs/MemberModelDTO_Tests.swift index f45e509050c..08bc515ed69 100644 --- a/Tests/StreamChatTests/Database/DTOs/MemberModelDTO_Tests.swift +++ b/Tests/StreamChatTests/Database/DTOs/MemberModelDTO_Tests.swift @@ -22,7 +22,7 @@ final class MemberModelDTO_Tests: XCTestCase { func test_memberPayload_isStoredAndLoadedFromDB() throws { let userId = UUID().uuidString - let channelId = ChannelId(type: .init(rawValue: "messsaging"), id: UUID().uuidString) + let channelId = ChannelId(type: .init(rawValue: "messaging"), id: UUID().uuidString) let userPayload: UserPayload = .init( id: userId, @@ -217,14 +217,14 @@ final class MemberModelDTO_Tests: XCTestCase { kind: .inMemory, chatClientConfig: config ) - + try database.writeSynchronously { session in try session.saveMember(payload: payload, channelId: channelId) } - + // WHEN let member = try XCTUnwrap(database.viewContext.member(userId: userId, cid: channelId)?.asModel()) - + // THEN XCTAssertEqual(member.name, "transformed member") } diff --git a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift index 6178bf77210..e10187cb8d9 100644 --- a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift +++ b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift @@ -12,7 +12,7 @@ final class Chat_Tests: XCTestCase { private var channelId: ChannelId! private var currentUserId: UserId! private var expectedTestError: TestError! - + @MainActor override func setUp() async throws { channelId = ChannelId.unique currentUserId = .unique @@ -29,9 +29,9 @@ final class Chat_Tests: XCTestCase { env = nil expectedTestError = nil } - + // MARK: - Accessing the State - + func test_get_whenLocalStoreHasState_thenGetResetsState() async throws { // Existing state let initialChannelPayload = makeChannelPayload( @@ -43,13 +43,13 @@ final class Chat_Tests: XCTestCase { env.client.mockAPIClient.test_mockResponseResult(.success(initialChannelPayload)) try await setUpChat(usesMockedUpdaters: false) try await chat.get(watch: true) - + // Recreate the chat which simulates a new session and loading the state from the store try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(10, chat.state.messages.count) await XCTAssertEqual(9, chat.state.members.count) await XCTAssertEqual(8, chat.state.watchers.count) - + let nextPayload = makeChannelPayload( messageCount: 3, memberCount: 2, @@ -58,7 +58,7 @@ final class Chat_Tests: XCTestCase { ) env.client.mockAPIClient.test_mockResponseResult(.success(nextPayload)) try await chat.get(watch: true) - + await XCTAssertEqual(3, chat.state.messages.count) await XCTAssertEqual(2, chat.state.members.count) await XCTAssertEqual(2, chat.state.channel?.reads.count ?? 0) @@ -67,13 +67,13 @@ final class Chat_Tests: XCTestCase { await XCTAssertEqual(nextPayload.members.map(\.user?.id), chat.state.members.map(\.id)) await XCTAssertEqual(nextPayload.watchers?.map(\.id), chat.state.watchers.map(\.id)) } - + func test_get_whenLocalStoreHasNoState_thenGetFetchesState() async throws { try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(0, chat.state.messages.count) await XCTAssertEqual(0, chat.state.members.count) await XCTAssertEqual(0, chat.state.watchers.count) - + let nextPayload = makeChannelPayload( messageCount: 3, memberCount: 2, @@ -82,7 +82,7 @@ final class Chat_Tests: XCTestCase { ) env.client.mockAPIClient.test_mockResponseResult(.success(nextPayload)) try await chat.get(watch: true) - + await XCTAssertEqual(3, chat.state.messages.count) await XCTAssertEqual(2, chat.state.members.count) await XCTAssertEqual(2, chat.state.channel?.reads.count ?? 0) @@ -91,13 +91,13 @@ final class Chat_Tests: XCTestCase { await XCTAssertEqual(nextPayload.members.map(\.user?.id), chat.state.members.map(\.id)) await XCTAssertEqual(nextPayload.watchers?.map(\.id), chat.state.watchers.map(\.id)) } - + func test_get_whenDefaultMemberSorting_thenMembersAndChannelReadsAreLoaded() async throws { try await setUpChat(usesMockedUpdaters: false, customMemberSorting: false) await XCTAssertEqual(0, chat.state.messages.count) await XCTAssertEqual(0, chat.state.members.count) await XCTAssertEqual(0, chat.state.watchers.count) - + let nextPayload = makeChannelPayload( messageCount: 3, memberCount: 2, @@ -106,7 +106,7 @@ final class Chat_Tests: XCTestCase { ) env.client.mockAPIClient.test_mockResponseResult(.success(nextPayload)) try await chat.get(watch: true) - + await XCTAssertEqual(3, chat.state.messages.count) await XCTAssertEqual(2, chat.state.members.count) await XCTAssertEqual(2, chat.state.channel?.reads.count ?? 0) @@ -115,33 +115,33 @@ final class Chat_Tests: XCTestCase { await XCTAssertEqual(nextPayload.members.compactMap(\.user?.id).sorted(), chat.state.members.map(\.id).sorted()) await XCTAssertEqual(nextPayload.watchers?.map(\.id), chat.state.watchers.map(\.id)) } - + func test_startWatching_whenChannelUpdaterSucceeds_thenStartWatchingActionSucceeds() async throws { env.channelUpdaterMock.startWatching_completion_result = .success(()) try await chat.watch() XCTAssertEqual(channelId, env.channelUpdaterMock.startWatching_cid) } - + func test_startWatching_whenChannelUpdaterFails_thenStartWatchingActionSucceeds() async throws { env.channelUpdaterMock.startWatching_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.watch(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.startWatching_cid) } - + func test_stopWatching_whenChannelUpdaterSucceeds_thenStopWatchingActionSucceeds() async throws { env.channelUpdaterMock.stopWatching_completion_result = .success(()) try await chat.stopWatching() XCTAssertEqual(channelId, env.channelUpdaterMock.stopWatching_cid) } - + func test_stopWatching_whenChannelUpdaterFails_thenStopWatchingActionSucceeds() async throws { env.channelUpdaterMock.stopWatching_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.stopWatching(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.stopWatching_cid) } - + // MARK: - Archiving and Unarchiving the Channel - + func test_archive_whenCurrentUserIdIsNotSet_thenArchivingFails() async throws { try await setUpChat(usesMockedUpdaters: true, loggedIn: false) await XCTAssertAsyncFailure( @@ -149,7 +149,7 @@ final class Chat_Tests: XCTestCase { expectedErrorHandler: { error in error is ClientError.CurrentUserDoesNotExist } ) } - + func test_archive_whenChannelUpdaterFails_thenExpectedErrorIsThrown() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.archive(), expectedTestError) @@ -158,7 +158,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(MemberUpdatePayload(archived: true), env.memberUpdaterMock.partialUpdate_updates) } - + func test_archive_whenChannelUpdaterSucceeds_thenArchivingSucceeds() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .success( ChatChannelMember.mock( @@ -172,7 +172,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(MemberUpdatePayload(archived: true), env.memberUpdaterMock.partialUpdate_updates) } - + func test_unarchive_whenCurrentUserIdIsNotSet_thenUnarchivingFails() async throws { try await setUpChat(usesMockedUpdaters: true, loggedIn: false) await XCTAssertAsyncFailure( @@ -189,7 +189,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(["archived"], env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_updates) } - + func test_unarchive_whenChannelUpdaterSucceeds_thenUnarchivingSucceeds() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .success( ChatChannelMember.mock( @@ -203,69 +203,69 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(["archived"], env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_updates) } - + // MARK: - Deleting the Channel - + func test_delete_whenChannelUpdaterSucceeds_thenDeleteSucceeds() async throws { env.channelUpdaterMock.deleteChannel_completion_result = .success(()) try await chat.delete() } - + func test_delete_whenChannelUpdaterFails_thenDeleteFails() async throws { env.channelUpdaterMock.deleteChannel_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.delete(), expectedTestError) } - + // MARK: - Disabling/Freezing the Channel - + func test_freeze_whenChannelUpdaterSucceeds_thenFreezeSucceeds() async throws { env.channelUpdaterMock.freezeChannel_completion_result = .success(()) try await chat.freeze() XCTAssertEqual(channelId, env.channelUpdaterMock.freezeChannel_cid) XCTAssertEqual(true, env.channelUpdaterMock.freezeChannel_freeze) } - + func test_freeze_whenChannelUpdaterFails_thenFreezeFails() async throws { env.channelUpdaterMock.freezeChannel_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.freeze(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.freezeChannel_cid) XCTAssertEqual(true, env.channelUpdaterMock.freezeChannel_freeze) } - + func test_unfreeze_whenChannelUpdaterSucceeds_thenUnfreezeSucceeds() async throws { env.channelUpdaterMock.freezeChannel_completion_result = .success(()) try await chat.unfreeze() XCTAssertEqual(channelId, env.channelUpdaterMock.freezeChannel_cid) XCTAssertEqual(false, env.channelUpdaterMock.freezeChannel_freeze) } - + func test_unfreeze_whenChannelUpdaterFails_thenUnfreezeFails() async throws { env.channelUpdaterMock.freezeChannel_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.unfreeze(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.freezeChannel_cid) XCTAssertEqual(false, env.channelUpdaterMock.freezeChannel_freeze) } - + // MARK: - Invites - + func test_acceptInvite_whenChannelUpdaterSucceeds_thenAcceptInviteSucceeds() async throws { env.channelUpdaterMock.acceptInvite_completion_result = .success(()) try await chat.acceptInvite() XCTAssertEqual(channelId, env.channelUpdaterMock.acceptInvite_cid) XCTAssertEqual(nil, env.channelUpdaterMock.acceptInvite_message) - + env.channelUpdaterMock.acceptInvite_completion_result = .success(()) try await chat.acceptInvite(with: "My system message") XCTAssertEqual(channelId, env.channelUpdaterMock.acceptInvite_cid) XCTAssertEqual("My system message", env.channelUpdaterMock.acceptInvite_message) } - + func test_acceptInvite_whenChannelUpdaterFails_thenAcceptInviteFails() async throws { env.channelUpdaterMock.acceptInvite_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.acceptInvite(), expectedTestError) await XCTAssertAsyncFailure(try await chat.acceptInvite(with: "My system message"), expectedTestError) } - + func test_inviteMembers_whenChannelUpdaterSucceeds_thenInviteMembersSucceeds() async throws { let memberIds: [UserId] = [.unique, .unique] env.channelUpdaterMock.inviteMembers_completion_result = .success(()) @@ -273,7 +273,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.channelUpdaterMock.inviteMembers_cid) XCTAssertEqual(memberIds.sorted(), env.channelUpdaterMock.inviteMembers_userIds?.sorted()) } - + func test_inviteMembers_whenChannelUpdaterFails_thenInviteMembersFails() async throws { let memberIds: [UserId] = [.unique, .unique] env.channelUpdaterMock.inviteMembers_completion_result = .failure(expectedTestError) @@ -281,21 +281,21 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.channelUpdaterMock.inviteMembers_cid) XCTAssertEqual(memberIds.sorted(), env.channelUpdaterMock.inviteMembers_userIds?.sorted()) } - + func test_rejectMembers_whenChannelUpdaterSucceeds_thenRejectMembersSucceeds() async throws { env.channelUpdaterMock.rejectInvite_completion_result = .success(()) try await chat.rejectInvite() XCTAssertEqual(channelId, env.channelUpdaterMock.rejectInvite_cid) } - + func test_rejectMembers_whenChannelUpdaterFails_thenRejectMembersFails() async throws { env.channelUpdaterMock.rejectInvite_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.rejectInvite(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.rejectInvite_cid) } - + // MARK: - Members - + func test_addMembers_whenChannelUpdaterSucceeds_thenAddMembersSucceeds() async throws { for hideHistory in [true, false] { env.channelUpdaterMock.addMembers_completion_result = .success(()) @@ -308,17 +308,17 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(currentUserId, env.channelUpdaterMock.addMembers_currentUserId) } } - + func test_addMembers_whenChannelUpdaterFails_thenAddMembersSucceeds() async throws { for hideHistory in [true, false] { env.channelUpdaterMock.addMembers_completion_result = .failure(expectedTestError) let memberIds: [UserId] = [.unique, .unique] - + await XCTAssertAsyncFailure( try await chat.addMembers(memberIds, systemMessage: "My system message", hideHistory: hideHistory), expectedTestError ) - + XCTAssertEqual(channelId, env.channelUpdaterMock.addMembers_cid) XCTAssertEqual(memberIds.sorted(), env.channelUpdaterMock.addMembers_userIds?.sorted()) XCTAssertEqual("My system message", env.channelUpdaterMock.addMembers_message) @@ -326,19 +326,19 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(currentUserId, env.channelUpdaterMock.addMembers_currentUserId) } } - + func test_addMembers_withHideHistoryBefore_whenChannelUpdaterSucceeds_thenAddMembersSucceeds() async throws { env.channelUpdaterMock.addMembers_completion_result = .success(()) let members: [MemberInfo] = [.init(userId: .unique, extraData: nil), .init(userId: .unique, extraData: nil)] let hideHistoryBefore = Date() - + try await chat.addMembers( members, systemMessage: "My system message", hideHistory: false, hideHistoryBefore: hideHistoryBefore ) - + XCTAssertEqual(channelId, env.channelUpdaterMock.addMembers_cid) XCTAssertEqual(members.map(\.userId).sorted(), env.channelUpdaterMock.addMembers_userIds?.sorted()) XCTAssertEqual("My system message", env.channelUpdaterMock.addMembers_message) @@ -346,12 +346,12 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(hideHistoryBefore, env.channelUpdaterMock.addMembers_hideHistoryBefore) XCTAssertEqual(currentUserId, env.channelUpdaterMock.addMembers_currentUserId) } - + func test_addMembers_withHideHistoryBefore_takesPrecedenceOverHideHistory() async throws { env.channelUpdaterMock.addMembers_completion_result = .success(()) let members: [MemberInfo] = [.init(userId: .unique, extraData: nil)] let hideHistoryBefore = Date() - + // Call with both hideHistory and hideHistoryBefore try await chat.addMembers( members, @@ -359,13 +359,13 @@ final class Chat_Tests: XCTestCase { hideHistory: true, hideHistoryBefore: hideHistoryBefore ) - + // Verify hideHistoryBefore is passed through XCTAssertEqual(hideHistoryBefore, env.channelUpdaterMock.addMembers_hideHistoryBefore) // Verify hideHistory is also passed (but hideHistoryBefore takes precedence in the endpoint) XCTAssertEqual(true, env.channelUpdaterMock.addMembers_hideHistory) } - + func test_removeMembers_whenChannelUpdaterSucceeds_thenRemoveMembersSucceeds() async throws { env.channelUpdaterMock.removeMembers_completion_result = .success(()) let memberIds: [UserId] = [.unique, .unique] @@ -375,59 +375,59 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual("My system message", env.channelUpdaterMock.removeMembers_message) XCTAssertEqual(currentUserId, env.channelUpdaterMock.removeMembers_currentUserId) } - + func test_removeMembers_whenChannelUpdaterFails_thenRemoveMembersSucceeds() async throws { env.channelUpdaterMock.removeMembers_completion_result = .failure(expectedTestError) let memberIds: [UserId] = [.unique, .unique] - + await XCTAssertAsyncFailure( try await chat.removeMembers(memberIds, systemMessage: "My system message"), expectedTestError ) - + XCTAssertEqual(channelId, env.channelUpdaterMock.removeMembers_cid) XCTAssertEqual(memberIds.sorted(), env.channelUpdaterMock.removeMembers_userIds?.sorted()) XCTAssertEqual("My system message", env.channelUpdaterMock.removeMembers_message) XCTAssertEqual(currentUserId, env.channelUpdaterMock.removeMembers_currentUserId) } - + func test_loadMembers_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat(usesMockedUpdaters: false) - + let apiResponse = makeChannelPayload(messageCount: 5, memberCount: 5, createdAtOffset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) let paginationMembers = try await chat.loadMembers(with: Pagination(pageSize: 5)) XCTAssertEqual(apiResponse.members.map(\.user?.id), paginationMembers.map(\.id)) await XCTAssertEqual(apiResponse.members.map(\.user?.id), chat.state.members.map(\.id)) - + let channel = try await MainActor.run { try XCTUnwrap(chat.state.channel) } XCTAssertEqual(apiResponse.channelReads.map(\.user.id).sorted(), channel.reads.map(\.user.id).sorted()) } - + func test_loadMoreMembers_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat(usesMockedUpdaters: false) - + // Initial load let initialResponse = makeChannelPayload(messageCount: 5, memberCount: 3, createdAtOffset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(initialResponse)) try await chat.loadMembers(with: Pagination(pageSize: 3)) - + // More let moreResponse = makeChannelPayload(messageCount: 0, memberCount: 5, createdAtOffset: 3) env.client.mockAPIClient.test_mockResponseResult(.success(moreResponse)) let paginationMembers = try await chat.loadMoreMembers(limit: 5) XCTAssertEqual(moreResponse.members.map(\.user?.id), paginationMembers.map(\.id)) - + let allMembers = initialResponse.members + moreResponse.members let allReads = initialResponse.channelReads + moreResponse.channelReads await XCTAssertEqual(allMembers.map(\.user?.id), chat.state.members.map(\.id)) - + let channel = try await MainActor.run { try XCTUnwrap(chat.state.channel) } XCTAssertEqual(allReads.map(\.user.id).sorted(), channel.reads.map(\.user.id).sorted()) } - + // MARK: - Member Moderation - + func test_banMember_whenMemberUpdaterSucceeds_thenBanMemberSucceeds() async throws { env.memberUpdaterMock.banMember_completion_result = .success(()) let reason = "Test reason" @@ -440,25 +440,25 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(timeout, env.memberUpdaterMock.banMember_timeoutInMinutes) XCTAssertEqual(false, env.memberUpdaterMock.banMember_shadow) } - + func test_banMember_whenMemberUpdaterFails_thenBanMemberSucceeds() async throws { env.memberUpdaterMock.banMember_completion_result = .failure(expectedTestError) let reason = "Test reason" let timeout = 5 let memberId: UserId = .unique - + await XCTAssertAsyncFailure( try await chat.banMember(memberId, reason: reason, timeout: timeout), expectedTestError ) - + XCTAssertEqual(channelId, env.memberUpdaterMock.banMember_cid) XCTAssertEqual(memberId, env.memberUpdaterMock.banMember_userId) XCTAssertEqual(reason, env.memberUpdaterMock.banMember_reason) XCTAssertEqual(timeout, env.memberUpdaterMock.banMember_timeoutInMinutes) XCTAssertEqual(false, env.memberUpdaterMock.banMember_shadow) } - + func test_shadowBanMember_whenMemberUpdaterSucceeds_thenShadowBanMemberSucceeds() async throws { env.memberUpdaterMock.banMember_completion_result = .success(()) let reason = "Test reason" @@ -471,25 +471,25 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(timeout, env.memberUpdaterMock.banMember_timeoutInMinutes) XCTAssertEqual(true, env.memberUpdaterMock.banMember_shadow) } - + func test_shadowBanMember_whenMemberUpdaterFails_thenShadowBanMemberSucceeds() async throws { env.memberUpdaterMock.banMember_completion_result = .failure(expectedTestError) let reason = "Test reason" let timeout = 5 let memberId: UserId = .unique - + await XCTAssertAsyncFailure( try await chat.shadowBanMember(memberId, reason: reason, timeout: timeout), expectedTestError ) - + XCTAssertEqual(channelId, env.memberUpdaterMock.banMember_cid) XCTAssertEqual(memberId, env.memberUpdaterMock.banMember_userId) XCTAssertEqual(reason, env.memberUpdaterMock.banMember_reason) XCTAssertEqual(timeout, env.memberUpdaterMock.banMember_timeoutInMinutes) XCTAssertEqual(true, env.memberUpdaterMock.banMember_shadow) } - + func test_unbanMember_whenMemberUpdaterSucceeds_thenUnbanMemberSucceeds() async throws { env.memberUpdaterMock.unbanMember_completion_result = .success(()) let memberId: UserId = .unique @@ -497,7 +497,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.memberUpdaterMock.unbanMember_cid) XCTAssertEqual(memberId, env.memberUpdaterMock.unbanMember_userId) } - + func test_unbanMember_whenMemberUpdaterFails_thenUnbanMemberSucceeds() async throws { env.memberUpdaterMock.unbanMember_completion_result = .failure(expectedTestError) let memberId: UserId = .unique @@ -505,9 +505,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.memberUpdaterMock.unbanMember_cid) XCTAssertEqual(memberId, env.memberUpdaterMock.unbanMember_userId) } - + // MARK: - Messages - + func test_deleteMessage_whenMessageUpdaterSucceeds_thenDeleteMessageSucceeds() async throws { for hard in [true, false] { env.messageUpdaterMock.deleteMessage_completion_result = .success(()) @@ -517,7 +517,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(hard, env.messageUpdaterMock.deleteMessage_hard) } } - + func test_deleteMessage_whenMessageUpdaterFails_thenDeleteMessageFails() async throws { for hard in [true, false] { env.messageUpdaterMock.deleteMessage_completion_result = .failure(expectedTestError) @@ -527,7 +527,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(hard, env.messageUpdaterMock.deleteMessage_hard) } } - + func test_downloadAttachment_whenMessageUpdaterSucceeds_thenSucceess() async throws { let attachmentId = AttachmentId.unique let expected = ChatMessageFileAttachment.mock(id: attachmentId) @@ -536,7 +536,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(expected, result) XCTAssertEqual(attachmentId, env.messageUpdaterMock.downloadAttachment_attachmentId) } - + func test_downloadAttachment_whenMessageUpdaterFails_thenFailure() async throws { let attachmentId = AttachmentId.unique let attachment = ChatMessageFileAttachment.mock(id: attachmentId) @@ -548,14 +548,14 @@ final class Chat_Tests: XCTestCase { ) XCTAssertEqual(attachmentId, env.messageUpdaterMock.downloadAttachment_attachmentId) } - + func test_deleteLocalAttachmentDownload_whenMessageUpdaterSucceeds_thenSucceess() async throws { let attachmentId = AttachmentId.unique env.messageUpdaterMock.deleteLocalAttachmentDownload_completion_result = .success(()) try await chat.deleteLocalAttachmentDownload(for: attachmentId) XCTAssertEqual(attachmentId, env.messageUpdaterMock.deleteLocalAttachmentDownload_attachmentId) } - + func test_deleteLocalAttachmentDownload_whenMessageUpdaterFails_thenFailure() async throws { let attachmentId = AttachmentId.unique let expected = TestError() @@ -566,10 +566,10 @@ final class Chat_Tests: XCTestCase { ) XCTAssertEqual(attachmentId, env.messageUpdaterMock.deleteLocalAttachmentDownload_attachmentId) } - + func test_resendAttachment_whenAPIRequestSucceeds_thenResendAttachmentSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) - + try await env.client.mockDatabaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) let messageId = try XCTUnwrap(dto.messages.first?.id) @@ -578,20 +578,20 @@ final class Chat_Tests: XCTestCase { let attachmentDto = try session.createNewAttachment(attachment: attachment, id: attachmentId) attachmentDto.localState = .uploadingFailed } - + let attachmentMessage = try await MainActor.run { try XCTUnwrap(chat.state.messages.first) } let attachmentId = AttachmentId(cid: channelId, messageId: attachmentMessage.id, index: 0) let uploadingState = try XCTUnwrap(attachmentMessage.attachment(with: attachmentId)?.uploadingState) XCTAssertEqual(LocalAttachmentState.uploadingFailed, uploadingState.state) - + env.client.mockAPIClient.uploadFile_completion_result = .success(.dummy()) let result = try await chat.resendAttachment(attachmentId) XCTAssertEqual(nil, result.attachment.uploadingState?.state) } - + func test_resendAttachment_whenAPIRequestFails_thenResendAttachmentFails() async throws { try await setUpChat(usesMockedUpdaters: false) - + try await env.client.mockDatabaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) let messageId = try XCTUnwrap(dto.messages.first?.id) @@ -600,22 +600,22 @@ final class Chat_Tests: XCTestCase { let attachmentDto = try session.createNewAttachment(attachment: attachment, id: attachmentId) attachmentDto.localState = .uploadingFailed } - + let attachmentMessage = try await MainActor.run { try XCTUnwrap(chat.state.messages.first) } let attachmentId = AttachmentId(cid: channelId, messageId: attachmentMessage.id, index: 0) var uploadingState = try XCTUnwrap(attachmentMessage.attachment(with: attachmentId)?.uploadingState) XCTAssertEqual(LocalAttachmentState.uploadingFailed, uploadingState.state) - + env.client.mockAPIClient.uploadFile_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure( try await chat.resendAttachment(attachmentId), expectedTestError ) - + uploadingState = try XCTUnwrap(attachmentMessage.attachment(with: attachmentId)?.uploadingState) XCTAssertEqual(LocalAttachmentState.uploadingFailed, uploadingState.state) } - + func test_resendMessage_whenAPIRequestSucceeds_thenSendMessageSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(0, chat.state.messages.count) @@ -635,7 +635,7 @@ final class Chat_Tests: XCTestCase { ) await XCTAssertEqual(1, chat.state.messages.count) await XCTAssertEqual(LocalMessageState.sendingFailed, chat.state.messages.first?.localState) - + // Resend and sending succeeds let apiResponse = MessagePayload.Boxed( message: .dummy( @@ -645,7 +645,7 @@ final class Chat_Tests: XCTestCase { ) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) let message = try await chat.resendMessage(messageId) - + XCTAssertEqual(text, message.text) await XCTAssertEqual(1, chat.state.messages.count) let messages = await chat.state.messages @@ -653,33 +653,33 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(text, stateMessage.text) XCTAssertEqual(nil, stateMessage.localState) } - + func test_sendMessageAction_whenTappingCancel_thenSendMessageActionSucceedsWithoutAPIRequest() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) dto.messages.first?.type = MessageType.ephemeral.rawValue } - + let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } let action = AttachmentAction(name: "name", value: "cancel", style: .default, type: .button, text: "text") - + try await chat.sendMessageAction(in: messageId, action: action) let message = try await MainActor.run { try XCTUnwrap(chat.localMessage(for: messageId)) } XCTAssertNotNil(message.deletedAt) XCTAssertEqual(nil, env.client.mockAPIClient.request_endpoint, "Cancel should not make any API requests") } - + func test_sendMessageAction_whenAPIRequestSucceds_thenSendMessageActionSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) dto.messages.first?.type = MessageType.ephemeral.rawValue } - + let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } let action = AttachmentAction(name: "name", value: "value", style: .default, type: .button, text: "text") - + let apiResponse = MessagePayload.Boxed(message: .dummy(type: .ephemeral, messageId: messageId, text: "TextChanged")) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) try await chat.sendMessageAction(in: messageId, action: action) @@ -687,18 +687,18 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, message.deletedAt) XCTAssertEqual("TextChanged", message.text) } - + func test_sendMessageAction_whenAPIRequestFails_thenSendMessageActionFails() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) dto.messages.first?.type = MessageType.ephemeral.rawValue } - + let initialMessage = try await MainActor.run { try XCTUnwrap(chat.state.messages.first) } let messageId = initialMessage.id let action = AttachmentAction(name: "name", value: "value", style: .default, type: .button, text: "text") - + env.client.mockAPIClient.test_mockResponseResult(Result.failure(expectedTestError)) await XCTAssertAsyncFailure( try await chat.sendMessageAction(in: messageId, action: action), @@ -708,14 +708,14 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, message.deletedAt) XCTAssertEqual(initialMessage.text, message.text) } - + func test_sendMessage_whenAPIRequestSucceeds_thenSendMessageSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(0, chat.state.messages.count) - + let typingIndicatorResponse = EmptyResponse() env.client.mockAPIClient.test_mockResponseResult(.success(typingIndicatorResponse)) - + let text = "Text" let apiResponse = MessagePayload.Boxed( message: .dummy( @@ -728,7 +728,7 @@ final class Chat_Tests: XCTestCase { with: apiResponse.message.text, messageId: apiResponse.message.id ) - + XCTAssertEqual(text, message.text) await XCTAssertEqual(1, chat.state.messages.count) let messages = await chat.state.messages @@ -736,14 +736,14 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(text, stateMessage.text) XCTAssertEqual(nil, stateMessage.localState) } - + func test_sendMessage_whenAPIRequestFails_thenSendMessageFails() async throws { try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(0, chat.state.messages.count) - + let typingIndicatorResponse = EmptyResponse() env.client.mockAPIClient.test_mockResponseResult(.success(typingIndicatorResponse)) - + let text = "Text" let apiResponse = MessagePayload.Boxed( message: .dummy( @@ -779,7 +779,7 @@ final class Chat_Tests: XCTestCase { ) ) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) - + let message = try await chat.sendSystemMessage( with: text, messageId: "0" @@ -788,7 +788,7 @@ final class Chat_Tests: XCTestCase { let body = env.client.mockAPIClient.request_endpoint?.body?.encodable as? [String: AnyEncodable] let messageRequestBody = body?["message"]?.encodable as? MessageRequestBody XCTAssertEqual(messageRequestBody?.type, MessageType.system.rawValue) - + XCTAssertEqual(text, message.text) XCTAssertEqual(.system, message.type) } @@ -797,21 +797,21 @@ final class Chat_Tests: XCTestCase { try await env.client.databaseContainer.write { session in try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) } - + try await setUpChat(usesMockedUpdaters: false) await XCTAssertEqual(1, chat.state.messages.count) let messages = await chat.state.messages let messageId = try XCTUnwrap(messages.first?.id) - + // Typing indicator and edit message env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) - + let message = try await chat.updateMessage(messageId, text: "New Text") XCTAssertEqual("New Text", message.text) XCTAssertEqual(nil, message.localState) } - + func test_updateMessage_whenTwoConsequtiveTextUpdates_thenWebSocketEventDoesNotResetTextToTheFirstEdit() async throws { try await env.client.databaseContainer.write { session in try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) @@ -820,9 +820,9 @@ final class Chat_Tests: XCTestCase { await XCTAssertEqual(1, chat.state.messages.count) let messages = await chat.state.messages let messageId = try XCTUnwrap(messages.first?.id) - + // Edit the message twice before web-socket event comes for these edits - let textUpdate1 = "Editted text 1" + let textUpdate1 = "Edited text 1" env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) // typing indicator env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) // update message try await chat.updateMessage(messageId, text: textUpdate1) @@ -839,7 +839,7 @@ final class Chat_Tests: XCTestCase { env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) // typing indicator env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) // update message - let textUpdate2 = "Editted text 2" + let textUpdate2 = "Edited text 2" try await chat.updateMessage(messageId, text: textUpdate2) let queuedWSEventPayload2 = EventPayload( eventType: .messageUpdated, @@ -851,56 +851,56 @@ final class Chat_Tests: XCTestCase { messageTextUpdatedAt: Date() ) ) - + // Web-socket events coming in with a delay try await env.client.databaseContainer.write { session in try session.saveEvent(payload: queuedWSEventPayload1) } let currentTextAfterEvent1 = try await MainActor.run { try XCTUnwrap(chat.localMessage(for: messageId)).text } XCTAssertEqual(textUpdate2, currentTextAfterEvent1, "Latest edit should persist") - + try await env.client.databaseContainer.write { session in try session.saveEvent(payload: queuedWSEventPayload2) } let currentTextAfterEvent2 = try await MainActor.run { try XCTUnwrap(chat.localMessage(for: messageId)).text } XCTAssertEqual(textUpdate2, currentTextAfterEvent2, "Latest edit should persist") } - + // MARK: - Message Pagination and State - + func test_restoreMessages_whenExistingMessages_thenStateUpdates() async throws { // DB has some older messages loaded let initialChannelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 0) try await env.client.mockDatabaseContainer.write { session in try session.saveChannel(payload: initialChannelPayload) } - + try await setUpChat(usesMockedUpdaters: false, loadState: false) - + // Accessing the state triggers loading the inital states await XCTAssertEqual(initialChannelPayload.messages.map(\.id), chat.state.messages.map(\.id)) } - + func test_restoreMessages_whenExistingMessagesWithPendingMessages_thenStateUpdates() async throws { // DB has some older messages loaded let initialChannelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 0) try await env.client.mockDatabaseContainer.write { session in try session.saveChannel(payload: initialChannelPayload) } - + try await setUpChat(usesMockedUpdaters: false, loadState: false) - + // Accessing the state triggers loading the inital states let allMessages = initialChannelPayload.messages + (initialChannelPayload.pendingMessages ?? []) await XCTAssertEqual(allMessages.map(\.id), chat.state.messages.map(\.id)) } - + func test_loadMessages_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat(usesMockedUpdaters: false) let pageSize = 2 let channelPayload = makeChannelPayload(messageCount: pageSize, createdAtOffset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(channelPayload)) - + let result = try await chat.loadMessages(with: MessagesPagination(pageSize: pageSize)) XCTAssertEqual(channelPayload.messages.map(\.id), result.map(\.id)) await MainActor.run { @@ -913,20 +913,20 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(false, chat.state.isLoadingNewerMessages) } } - + func test_loadMessagesFirstPage_whenAPIRequestSucceeds_thenStateIsReset() async throws { try await setUpChat(usesMockedUpdaters: false) - + // DB has some older messages loaded try await env.client.mockDatabaseContainer.write { session in try session.saveChannel(payload: self.makeChannelPayload(messageCount: 5, createdAtOffset: 0)) } - + // Load the first page which should reset the state let channelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 5) env.client.mockAPIClient.test_mockResponseResult(.success(channelPayload)) try await chat.loadMessages(with: MessagesPagination(pageSize: 3, parameter: nil)) - + await MainActor.run { XCTAssertEqual(channelPayload.messages.map(\.id), chat.state.messages.map(\.id)) XCTAssertEqual(false, chat.state.hasLoadedAllOldestMessages) @@ -937,21 +937,21 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(false, chat.state.isLoadingNewerMessages) } } - + func test_loadOlderMessages_whenAPIRequestSucceeds_thenStateUpdates() async throws { // DB has some messages loaded let initialChannelPayload = makeChannelPayload(messageCount: 5, createdAtOffset: 5) try await env.client.mockDatabaseContainer.write { session in try session.saveChannel(payload: initialChannelPayload) } - + try await setUpChat(usesMockedUpdaters: false) // Load older let channelPayload = makeChannelPayload(messageCount: 5, createdAtOffset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(channelPayload)) try await chat.loadOlderMessages() - + let expectedIds = (channelPayload.messages + initialChannelPayload.messages).map(\.id) await MainActor.run { XCTAssertEqual(expectedIds, chat.state.messages.map(\.id)) @@ -963,20 +963,20 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(false, chat.state.isLoadingNewerMessages) } } - + func test_loadNewerMessages_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat(usesMockedUpdaters: false) - + // Reset has loaded state since we always load newest messages let initialChannelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(initialChannelPayload)) try await chat.loadMessages(around: initialChannelPayload.messages[1].id, limit: 2) - + // Load newer let channelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 5) env.client.mockAPIClient.test_mockResponseResult(.success(channelPayload)) try await chat.loadNewerMessages() - + let expectedIds = (initialChannelPayload.messages + channelPayload.messages).map(\.id) await MainActor.run { XCTAssertEqual(expectedIds, chat.state.messages.map(\.id)) @@ -988,21 +988,21 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(false, chat.state.isLoadingNewerMessages) } } - + func test_loadMessagesAround_whenAPIRequestSucceeds_thenStateUpdates() async throws { // DB has some older messages loaded let initialChannelPayload = makeChannelPayload(messageCount: 5, createdAtOffset: 0) try await env.client.mockDatabaseContainer.write { session in try session.saveChannel(payload: initialChannelPayload) } - + try await setUpChat(usesMockedUpdaters: false) - + // Jump to a message let channelPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 10) env.client.mockAPIClient.test_mockResponseResult(.success(channelPayload)) try await chat.loadMessages(around: channelPayload.messages[1].id, limit: 2) - + XCTAssertEqual(channelPayload.messages.map(\.id), await chat.state.messages.map(\.id)) await MainActor.run { XCTAssertEqual(false, chat.state.hasLoadedAllOldestMessages) @@ -1013,9 +1013,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(false, chat.state.isLoadingNewerMessages) } } - + // MARK: - Message Local State - + func test_localMessage_whenMessageIsLocallyAvailable_thenMessageIsReturned() async throws { let initialPayload = makeChannelPayload(messageCount: 3, createdAtOffset: 0) try await env.client.databaseContainer.write { session in @@ -1027,7 +1027,7 @@ final class Chat_Tests: XCTestCase { XCTAssertNotNil(message) } } - + func test_localMessage_whenMessageIdIsForDifferentChannel_thenMessageIsNotReturned() async throws { let otherChannelPayload = makeChannelPayload( cid: .unique, @@ -1042,38 +1042,38 @@ final class Chat_Tests: XCTestCase { let message = await chat.localMessage(for: messageId) XCTAssertNil(message) } - + func test_messageState_whenMessageIsLocallyAvailable_thenAPIRequestIsSkipped() async throws { try await env.client.databaseContainer.write { session in try session.saveChannel(payload: self.makeChannelPayload(messageCount: 3, createdAtOffset: 0)) } try await setUpChat(usesMockedUpdaters: false) let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } - + // Set dummy response for failing the API call if it is mistakenly made env.client.mockAPIClient.test_mockResponseResult(Result.failure(expectedTestError)) let messageState = try await chat.messageState(for: messageId) - + XCTAssertEqual(nil, env.client.mockAPIClient.request_endpoint) await XCTAssertEqual(messageId, messageState.message.id) } - + func test_messageState_whenMessageIsNotLocallyAvailable_thenAPIRequestIsTriggeredAndMessageIsReturned() async throws { try await setUpChat(usesMockedUpdaters: false) - + let messageId = String.unique let messagePayload = try XCTUnwrap(makeChannelPayload(messageCount: 1, createdAtOffset: 0).messages.first) let apiResponse = MessagePayload.Boxed(message: messagePayload) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) let messageState = try await chat.messageState(for: messageId) - + XCTAssertNotNil(env.client.mockAPIClient.request_endpoint) await XCTAssertEqual(messagePayload.id, messageState.message.id) await XCTAssertEqual(messagePayload.createdAt, messageState.message.createdAt) } - + // MARK: - Message Flagging - + func test_flagMessage_whenMessageUpdaterSucceeds_thenFlagMessageActionSucceeds() async throws { let messageId: MessageId = .unique let reason: String = .unique @@ -1086,7 +1086,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(extraData, env.messageUpdaterMock.flagMessage_extraData) XCTAssertEqual(true, env.messageUpdaterMock.flagMessage_flag) } - + func test_flagMessage_whenMessageUpdaterFails_thenFlagMessageActionSucceeds() async throws { let messageId: MessageId = .unique env.messageUpdaterMock.flagMessage_completion_result = .failure(expectedTestError) @@ -1095,7 +1095,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.flagMessage_messageId) XCTAssertEqual(true, env.messageUpdaterMock.flagMessage_flag) } - + func test_unflagMessage_whenMessageUpdaterSucceeds_thenUnflagMessageActionSucceeds() async throws { let messageId: MessageId = .unique env.messageUpdaterMock.flagMessage_completion_result = .success(()) @@ -1104,7 +1104,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.flagMessage_messageId) XCTAssertEqual(false, env.messageUpdaterMock.flagMessage_flag) } - + func test_unflagMessage_whenMessageUpdaterFails_thenUnflagMessageActionSucceeds() async throws { let messageId: MessageId = .unique env.messageUpdaterMock.flagMessage_completion_result = .failure(expectedTestError) @@ -1113,9 +1113,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.flagMessage_messageId) XCTAssertEqual(false, env.messageUpdaterMock.flagMessage_flag) } - + // MARK: - Message Rich Content - + func test_enrichURL_whenChannelUpdaterSucceeds_thenEnrichURLActionSucceeds() async throws { let url: URL = .unique() let expectedLinkAttachmentPayload = LinkAttachmentPayload( @@ -1130,22 +1130,22 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(url, env.channelUpdaterMock.enrichUrl_url) XCTAssertEqual(actualLinkAttachmentPayload, expectedLinkAttachmentPayload) } - + func test_enrichURL_whenChannelUpdaterFails_thenEnrichURLActionSucceeds() async throws { let url: URL = .unique() env.channelUpdaterMock.enrichUrl_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(_ = try await chat.enrichURL(url), expectedTestError) XCTAssertEqual(url, env.channelUpdaterMock.enrichUrl_url) } - + // MARK: - Message Pinning - + func test_pinMessage_whenAPIRequestSucceeds_thenPinMessageSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) } - + let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) let pinnedMessage = try await chat.pinMessage(messageId, pinning: .noExpiration) @@ -1153,28 +1153,28 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(true, pinnedMessage.isPinned) XCTAssertEqual(nil, pinnedMessage.pinDetails?.expiresAt) } - + func test_unpinMessage_whenSendingFailedAndAPIRequestSucceeds_thenUnpinMessageSucceeds() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in let dto = try session.saveChannel(payload: self.makeChannelPayload(messageCount: 1, createdAtOffset: 0)) dto.messages.first?.pinned = true } - + let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) let unpinnedMessage = try await chat.unpinMessage(messageId) XCTAssertEqual(messageId, unpinnedMessage.id) XCTAssertEqual(false, unpinnedMessage.isPinned) } - + func test_loadPinnedMessages_whenAPIRequestSucceeds_thenLoadPinnedMessagesSucceeds() async throws { let responseMessages: [ChatMessage] = [ .mock(id: "0"), .mock(id: "1") ] env.channelUpdaterMock.loadPinnedMessages_completion_result = .success(responseMessages) - + let pagination = PinnedMessagesPagination.after(.unique, inclusive: true) let paginatedMessages = try await chat.loadPinnedMessages( with: pagination, @@ -1187,9 +1187,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(pagination, env.channelUpdaterMock.loadPinnedMessages_query?.pagination) XCTAssertEqual(responseMessages.map(\.id), paginatedMessages.map(\.id)) } - + // MARK: - Message Reactions - + func test_deleteReaction_whenMessageUpdaterSucceeds_thenDeleteReactionActionSucceeds() async throws { let messageId: MessageId = .unique let reactionType: MessageReactionType = .init(rawValue: "like") @@ -1198,7 +1198,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.deleteReaction_messageId) XCTAssertEqual(reactionType, env.messageUpdaterMock.deleteReaction_type) } - + func test_deleteReaction_whenMessageUpdaterFails_thenDeleteReactionActionSucceeds() async throws { let messageId: MessageId = .unique let reactionType: MessageReactionType = .init(rawValue: "like") @@ -1210,7 +1210,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.deleteReaction_messageId) XCTAssertEqual(reactionType, env.messageUpdaterMock.deleteReaction_type) } - + func test_sendReaction_whenAPIRequestSucceeds_thenMessageStateUpdates() async throws { try await setUpChat(usesMockedUpdaters: false) try await env.client.databaseContainer.write { session in @@ -1223,7 +1223,7 @@ final class Chat_Tests: XCTestCase { try await chat.sendReaction(to: messageId, with: "like") await XCTAssertEqual(1, messageState.reactions.count) } - + func test_loadReactions_whenAPIRequestSucceeds_thenMessageStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, @@ -1231,7 +1231,7 @@ final class Chat_Tests: XCTestCase { ) let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } let messageState = try await chat.messageState(for: messageId) - + let apiResponse = makeReactionsPayload(messageId: messageId, count: 5, offset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) let paginatedReactions = try await chat.loadReactions( @@ -1241,7 +1241,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(apiResponse.reactions.map(\.user.id), paginatedReactions.map(\.author.id)) await XCTAssertEqual(apiResponse.reactions.map(\.user.id), messageState.reactions.map(\.author.id)) } - + func test_loadMoreReactions_whenAPIRequestSucceeds_thenMessageStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, @@ -1249,14 +1249,14 @@ final class Chat_Tests: XCTestCase { ) let messageId = try await MainActor.run { try XCTUnwrap(chat.state.messages.first?.id) } let messageState = try await chat.messageState(for: messageId) - + let initialApiResponse = makeReactionsPayload(messageId: messageId, count: 5, offset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(initialApiResponse)) try await chat.loadReactions( for: messageId, pagination: Pagination(pageSize: initialApiResponse.reactions.count) ) - + let apiResponse = makeReactionsPayload(messageId: messageId, count: 10, offset: 5) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) let paginatedReactions = try await chat.loadMoreReactions( @@ -1268,16 +1268,16 @@ final class Chat_Tests: XCTestCase { let all = initialApiResponse.reactions + apiResponse.reactions await XCTAssertEqual(all.map(\.user.id), messageState.reactions.map(\.author.id)) } - + // MARK: - Message Reading - + func test_markRead_whenAPIRequestSucceeds_thenReadStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, messageCount: 3 ) let messages = await chat.state.messages - + // Modify the read state for allowing markRead to trigger an API request try await env.client.databaseContainer.write { session in let payload = ChannelPayload.dummy( @@ -1300,15 +1300,15 @@ final class Chat_Tests: XCTestCase { } await XCTAssertEqual(1, chat.state.channel?.reads.count) await XCTAssertEqual(2, chat.state.channel?.reads.first?.unreadMessagesCount) - + env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) try await chat.markRead() XCTAssertNotNil(env.client.mockAPIClient.request_endpoint) - + await XCTAssertEqual(1, chat.state.channel?.reads.count) await XCTAssertEqual(0, chat.state.channel?.reads.first?.unreadMessagesCount) } - + func test_markUnread_whenAPIRequestSucceeds_thenReadStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, @@ -1317,7 +1317,7 @@ final class Chat_Tests: XCTestCase { let messages = await chat.state.messages let firstMessage = try XCTUnwrap(messages.first) let lastMessage = try XCTUnwrap(messages.first) - + // Create a read state for the current user try await env.client.databaseContainer.write { session in let payload = ChannelPayload.dummy( @@ -1338,15 +1338,15 @@ final class Chat_Tests: XCTestCase { ) try session.saveChannel(payload: payload) } - + env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) try await chat.markUnread(from: firstMessage.id) XCTAssertNotNil(env.client.mockAPIClient.request_endpoint) - + await XCTAssertEqual(1, chat.state.channel?.reads.count) await XCTAssertEqual(3, chat.state.channel?.reads.first?.unreadMessagesCount) } - + func test_markUnread_whenAPIRequestSucceeds_thenReadStateUpdates_messageTimestamp() async throws { try await setUpChat( usesMockedUpdaters: false, @@ -1355,7 +1355,7 @@ final class Chat_Tests: XCTestCase { let messages = await chat.state.messages let firstMessage = try XCTUnwrap(messages.first) let lastMessage = try XCTUnwrap(messages.first) - + // Create a read state for the current user try await env.client.databaseContainer.write { session in let payload = ChannelPayload.dummy( @@ -1376,17 +1376,17 @@ final class Chat_Tests: XCTestCase { ) try session.saveChannel(payload: payload) } - + env.client.mockAPIClient.test_mockResponseResult(.success(EmptyResponse())) try await chat.markUnread(from: firstMessage.createdAt) XCTAssertNotNil(env.client.mockAPIClient.request_endpoint) - + await XCTAssertEqual(1, chat.state.channel?.reads.count) await XCTAssertEqual(2, chat.state.channel?.reads.first?.unreadMessagesCount) } - + // MARK: - Updating the Channel - + func test_update_whenChannelUpdaterSucceeds_thenUpdateSucceeds() async throws { env.channelUpdaterMock.updateChannel_completion_result = .success(()) let name = "Updated Channel Name" @@ -1396,7 +1396,7 @@ final class Chat_Tests: XCTestCase { let invites: Set = [.unique] let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] - + try await chat.update( name: name, imageURL: imageURL, @@ -1406,7 +1406,7 @@ final class Chat_Tests: XCTestCase { filterTags: filterTags, extraData: extraData ) - + let payload = try XCTUnwrap(env.channelUpdaterMock.updateChannel_payload) XCTAssertEqual(payload.name, name) XCTAssertEqual(payload.imageURL, imageURL) @@ -1416,10 +1416,10 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.filterTags, filterTags) XCTAssertEqual(payload.extraData, extraData) } - + func test_update_whenChannelUpdaterFails_thenUpdateFails() async throws { env.channelUpdaterMock.updateChannel_completion_result = .failure(expectedTestError) - + await XCTAssertAsyncFailure( try await chat.update( name: "Updated Name", @@ -1433,7 +1433,7 @@ final class Chat_Tests: XCTestCase { expectedTestError ) } - + func test_updatePartial_whenChannelUpdaterSucceeds_thenUpdatePartialSucceeds() async throws { env.channelUpdaterMock.partialChannelUpdate_completion_result = .success(()) let name = "Updated Channel Name" @@ -1444,7 +1444,7 @@ final class Chat_Tests: XCTestCase { let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] let unsetProperties = ["property1", "property2"] - + try await chat.updatePartial( name: name, imageURL: imageURL, @@ -1455,7 +1455,7 @@ final class Chat_Tests: XCTestCase { extraData: extraData, unsetProperties: unsetProperties ) - + let payload = try XCTUnwrap(env.channelUpdaterMock.partialChannelUpdate_updates) XCTAssertEqual(payload.name, name) XCTAssertEqual(payload.imageURL, imageURL) @@ -1466,10 +1466,10 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.extraData, extraData) XCTAssertEqual(env.channelUpdaterMock.partialChannelUpdate_unsetProperties, unsetProperties) } - + func test_updatePartial_whenChannelUpdaterFails_thenUpdatePartialFails() async throws { env.channelUpdaterMock.partialChannelUpdate_completion_result = .failure(expectedTestError) - + await XCTAssertAsyncFailure( try await chat.updatePartial( name: "Updated Name", @@ -1484,18 +1484,18 @@ final class Chat_Tests: XCTestCase { expectedTestError ) } - + // MARK: - Message Replies - + func test_reply_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, messageCount: 3 ) - + let messages = await chat.state.messages let lastMessageId = try XCTUnwrap(messages.last?.id) - + let typingIndicatorResponse = EmptyResponse() env.client.mockAPIClient.test_mockResponseResult(.success(typingIndicatorResponse)) let apiResponse = MessagePayload.Boxed( @@ -1505,39 +1505,39 @@ final class Chat_Tests: XCTestCase { ) ) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) - + let notificationExpectation = expectation( forNotification: .NewEventReceived, object: nil, notificationCenter: env.client.eventNotificationCenter ) - + let replyMessage = try await chat.reply( to: lastMessageId, text: "My Reply", messageId: apiResponse.message.id ) XCTAssertEqual(apiResponse.message.id, replyMessage.id) - + await fulfillment(of: [notificationExpectation], timeout: defaultTimeout) - + let messageState = try await chat.messageState(for: lastMessageId) await XCTAssertEqual(lastMessageId, messageState.message.id) await XCTAssertEqual(apiResponse.message.id, messageState.replies.last?.id) } - + func test_loadReplies_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, messageCount: 3 ) - + let messages = await chat.state.messages let lastMessageId = try XCTUnwrap(messages.last?.id) - + let apiResponse = makeRepliesPayload(parentMessageId: lastMessageId, count: 5, offset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) - + let paginatedReplies = try await chat.loadReplies( for: lastMessageId, pagination: MessagesPagination( @@ -1545,24 +1545,24 @@ final class Chat_Tests: XCTestCase { ) ) XCTAssertEqual(apiResponse.messages.map(\.id), paginatedReplies.map(\.id)) - + let messageState = try await chat.messageState(for: lastMessageId) await XCTAssertEqual(lastMessageId, messageState.message.id) await XCTAssertEqual(apiResponse.messages.map(\.id), messageState.replies.map(\.id)) - + await XCTAssertEqual(false, messageState.hasLoadedAllOldestReplies) await XCTAssertEqual(true, messageState.hasLoadedAllNewestReplies) await XCTAssertEqual(false, messageState.isLoadingOlderReplies) await XCTAssertEqual(false, messageState.isLoadingMiddleReplies) await XCTAssertEqual(false, messageState.isLoadingNewerReplies) } - + func test_loadOlderReplies_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, messageCount: 3 ) - + let messages = await chat.state.messages let lastMessageId = try XCTUnwrap(messages.last?.id) let initialApiResponse = makeRepliesPayload(parentMessageId: lastMessageId, count: 5, offset: 0) @@ -1573,32 +1573,32 @@ final class Chat_Tests: XCTestCase { pageSize: initialApiResponse.messages.count ) ) - + let apiResponse = makeRepliesPayload(parentMessageId: lastMessageId, count: 5, offset: 5) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) try await chat.loadOlderReplies( for: lastMessageId, limit: apiResponse.messages.count ) - + let messageState = try await chat.messageState(for: lastMessageId) let all = initialApiResponse.messages + apiResponse.messages await XCTAssertEqual(lastMessageId, messageState.message.id) await XCTAssertEqual(all.map(\.id), messageState.replies.map(\.id)) - + await XCTAssertEqual(false, messageState.hasLoadedAllOldestReplies) await XCTAssertEqual(true, messageState.hasLoadedAllNewestReplies) await XCTAssertEqual(false, messageState.isLoadingOlderReplies) await XCTAssertEqual(false, messageState.isLoadingMiddleReplies) await XCTAssertEqual(false, messageState.isLoadingNewerReplies) } - + func test_loadNewerReplies_whenAPIRequestSucceeds_thenStateUpdates() async throws { try await setUpChat( usesMockedUpdaters: false, messageCount: 3 ) - + let messages = await chat.state.messages let lastMessageId = try XCTUnwrap(messages.last?.id) let initialApiResponse = makeRepliesPayload(parentMessageId: lastMessageId, count: 5, offset: 5) @@ -1611,27 +1611,27 @@ final class Chat_Tests: XCTestCase { let messageState = try await chat.messageState(for: lastMessageId) await XCTAssertEqual(false, messageState.hasLoadedAllNewestReplies) await XCTAssertEqual(false, messageState.isLoadingNewerReplies) - + let apiResponse = makeRepliesPayload(parentMessageId: lastMessageId, count: 5, offset: 0) env.client.mockAPIClient.test_mockResponseResult(.success(apiResponse)) try await chat.loadNewerReplies( for: lastMessageId, limit: apiResponse.messages.count ) - + let all = apiResponse.messages + initialApiResponse.messages await XCTAssertEqual(lastMessageId, messageState.message.id) await XCTAssertEqual(all.map(\.id), messageState.replies.map(\.id)) - + await XCTAssertEqual(false, messageState.hasLoadedAllOldestReplies) await XCTAssertEqual(false, messageState.hasLoadedAllNewestReplies) await XCTAssertEqual(false, messageState.isLoadingOlderReplies) await XCTAssertEqual(false, messageState.isLoadingMiddleReplies) await XCTAssertEqual(false, messageState.isLoadingNewerReplies) } - + // MARK: - Message Translations - + func test_translateMessageState_whenMessageUpdaterSucceeds_thenTranslateMessageActionSucceeds() async throws { let messageId: MessageId = .unique let text: String = "Test message" @@ -1650,7 +1650,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.translate_messageId) XCTAssertEqual(language, env.messageUpdaterMock.translate_language) } - + func test_translateMessageState_whenMessageUpdaterFails_thenTranslateMessageActionSucceeds() async throws { let messageId: MessageId = .unique let _: String = "Test message" @@ -1664,9 +1664,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(messageId, env.messageUpdaterMock.translate_messageId) XCTAssertEqual(language, env.messageUpdaterMock.translate_language) } - + // MARK: - Muting or Hiding the Channel - + func test_mute_whenChannelUpdaterSucceeds_thenMuteActionSucceeds() async throws { for expiration in [nil, 10] { env.channelUpdaterMock.muteChannel_completion_result = .success(()) @@ -1675,7 +1675,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(expiration, env.channelUpdaterMock.muteChannel_expiration) } } - + func test_mute_whenChannelUpdaterFails_thenMuteActionSucceeds() async throws { for expiration in [nil, 10] { env.channelUpdaterMock.muteChannel_completion_result = .failure(expectedTestError) @@ -1684,7 +1684,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(expiration, env.channelUpdaterMock.muteChannel_expiration) } } - + func test_hide_whenChannelUpdaterSucceeds_thenHideActionSucceeds() async throws { for clearHistory in [true, false] { env.channelUpdaterMock.hideChannel_completion_result = .success(()) @@ -1693,7 +1693,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(clearHistory, env.channelUpdaterMock.hideChannel_clearHistory) } } - + func test_hide_whenChannelUpdaterFails_thenHideActionSucceeds() async throws { for clearHistory in [true, false] { env.channelUpdaterMock.hideChannel_completion_result = .failure(expectedTestError) @@ -1702,21 +1702,21 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(clearHistory, env.channelUpdaterMock.hideChannel_clearHistory) } } - + func test_show_whenChannelUpdaterSucceeds_thenShowActionSucceeds() async throws { env.channelUpdaterMock.showChannel_completion_result = .success(()) try await chat.show() XCTAssertEqual(channelId, env.channelUpdaterMock.showChannel_cid) } - + func test_hide_whenChannelUpdaterFails_thenShowActionSucceeds() async throws { env.channelUpdaterMock.showChannel_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.show(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.showChannel_cid) } - + // MARK: - Pinning and Unpinning the Channel - + func test_pin_whenCurrentUserIdIsNotSet_thenPinningFails() async throws { try await setUpChat(usesMockedUpdaters: true, loggedIn: false) await XCTAssertAsyncFailure( @@ -1724,7 +1724,7 @@ final class Chat_Tests: XCTestCase { expectedErrorHandler: { error in error is ClientError.CurrentUserDoesNotExist } ) } - + func test_pin_whenChannelUpdaterFails_thenExpectedErrorIsThrown() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.pin(), expectedTestError) @@ -1733,7 +1733,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(MemberUpdatePayload(pinned: true), env.memberUpdaterMock.partialUpdate_updates) } - + func test_pin_whenChannelUpdaterSucceeds_thenPinningSucceeds() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .success( ChatChannelMember.mock( @@ -1747,7 +1747,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(MemberUpdatePayload(pinned: true), env.memberUpdaterMock.partialUpdate_updates) } - + func test_unpin_whenCurrentUserIdIsNotSet_thenUnpinningFails() async throws { try await setUpChat(usesMockedUpdaters: true, loggedIn: false) await XCTAssertAsyncFailure( @@ -1764,7 +1764,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(["pinned"], env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_updates) } - + func test_unpin_whenChannelUpdaterSucceeds_thenUnpiningSucceeds() async throws { env.memberUpdaterMock.partialUpdate_completion_result = .success( ChatChannelMember.mock( @@ -1778,9 +1778,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(["pinned"], env.memberUpdaterMock.partialUpdate_unset) XCTAssertEqual(nil, env.memberUpdaterMock.partialUpdate_updates) } - + // MARK: - Throttling and Slow Mode - + func test_enableSlowMode_whenChannelUpdaterSucceeds_thenEnableSlowModeActionSucceeds() async throws { let cooldownDuration = 10 env.channelUpdaterMock.enableSlowMode_completion_result = .success(()) @@ -1788,7 +1788,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.channelUpdaterMock.enableSlowMode_cid) XCTAssertEqual(cooldownDuration, env.channelUpdaterMock.enableSlowMode_cooldownDuration) } - + func test_enableSlowMode_whenChannelUpdaterFails_thenEnableSlowModeActionSucceeds() async throws { let cooldownDuration = 10 env.channelUpdaterMock.enableSlowMode_completion_result = .failure(expectedTestError) @@ -1796,26 +1796,26 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(channelId, env.channelUpdaterMock.enableSlowMode_cid) XCTAssertEqual(cooldownDuration, env.channelUpdaterMock.enableSlowMode_cooldownDuration) } - + func test_disableSlowMode_whenChannelUpdaterSucceeds_thenDisableSlowModeActionSucceeds() async throws { env.channelUpdaterMock.enableSlowMode_completion_result = .success(()) try await chat.disableSlowMode() XCTAssertEqual(channelId, env.channelUpdaterMock.enableSlowMode_cid) XCTAssertEqual(0, env.channelUpdaterMock.enableSlowMode_cooldownDuration) } - + func test_disableSlowMode_whenChannelUpdaterFails_thenDisableSlowModeActionSucceeds() async throws { env.channelUpdaterMock.enableSlowMode_completion_result = .failure(expectedTestError) await XCTAssertAsyncFailure(try await chat.disableSlowMode(), expectedTestError) XCTAssertEqual(channelId, env.channelUpdaterMock.enableSlowMode_cid) XCTAssertEqual(0, env.channelUpdaterMock.enableSlowMode_cooldownDuration) } - + // MARK: - Truncating the Channel - + func test_truncate_whenChannelUpdaterSucceeds_thenTruncateActionSucceeds() async throws { env.channelUpdaterMock.truncateChannel_completion_result = .success(()) - + var systemMessage: String? var hardDelete = true var skipPush = true @@ -1824,7 +1824,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = "Test message" hardDelete = true skipPush = true @@ -1833,7 +1833,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = nil hardDelete = false skipPush = false @@ -1842,7 +1842,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = "Test message" hardDelete = false skipPush = false @@ -1852,10 +1852,10 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) } - + func test_truncate_whenChannelUpdaterFails_thenTruncateActionSucceeds() async throws { env.channelUpdaterMock.truncateChannel_completion_result = .failure(expectedTestError) - + var systemMessage: String? var hardDelete = true var skipPush = true @@ -1864,7 +1864,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = "Test message" hardDelete = true skipPush = true @@ -1873,7 +1873,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = nil hardDelete = false skipPush = false @@ -1882,7 +1882,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(skipPush, env.channelUpdaterMock.truncateChannel_skipPush) XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) - + systemMessage = "Test message" hardDelete = false skipPush = false @@ -1892,9 +1892,9 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(hardDelete, env.channelUpdaterMock.truncateChannel_hardDelete) XCTAssertEqual(systemMessage, env.channelUpdaterMock.truncateChannel_systemMessage) } - + // MARK: - Test Data - + /// Configures chat for testing. /// /// - Parameter usesMockedChannelUpdater: Set it for false for tests which need to update the local DB and simulate API requests. @@ -1917,7 +1917,7 @@ final class Chat_Tests: XCTestCase { if loadState { _ = chat.state } - + env.client.mockAuthenticationRepository.mockedCurrentUserId = loggedIn ? currentUserId : nil try await env.client.databaseContainer.write { session in if loggedIn { @@ -1932,7 +1932,7 @@ final class Chat_Tests: XCTestCase { try session.saveChannel(payload: self.makeChannelPayload(messageCount: messageCount, createdAtOffset: 0)) } } - + private func makeChannelPayload( cid: ChannelId? = nil, messageCount: Int, @@ -1992,7 +1992,7 @@ final class Chat_Tests: XCTestCase { channelReads: membersAndReads.map(\.read) ) } - + private func makeMemberListPayload(count: Int, offset: Int) -> ChannelMemberListPayload { let members = (0.. MessageReactionsPayload { let reactions = (0.. MessageRepliesPayload { let messages: [MessagePayload] = (0.. Chat.Environment { Chat.Environment( chatStateBuilder: { @@ -2129,7 +2129,7 @@ extension Chat_Tests { } ) } - + static func chatClientEnvironment() -> ChatClient.Environment { var environment = ChatClient.Environment.mock environment.messageRepositoryBuilder = { MessageRepository(database: $0, apiClient: $1) }