From 8b80d3f8c86a2a42e6257643cf4ec3b38af4270d Mon Sep 17 00:00:00 2001
From: Toomas Vahter
Date: Mon, 25 Nov 2024 16:07:55 +0200
Subject: [PATCH 1/9] Fix batch deleting current user while it has muted users
attached to it (#3507)
---
.../StreamChatModel.xcdatamodel/contents | 6 +++---
.../StreamChatTests/Database/DatabaseContainer_Tests.swift | 3 +++
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents
index 181d663671..1b70c50a08 100644
--- a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents
+++ b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -152,7 +152,7 @@
-
+
@@ -472,7 +472,7 @@
-
+
diff --git a/Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift b/Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift
index 4d19d1e6e4..36836a666b 100644
--- a/Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift
+++ b/Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift
@@ -386,6 +386,9 @@ final class DatabaseContainer_Tests: XCTestCase {
createdAt: .unique,
updatedAt: .unique
))
+ let mutedUserId = UserId.unique
+ let mutedUserDTO = try session.saveUser(payload: .dummy(userId: mutedUserId))
+ session.currentUser?.mutedUsers = Set([mutedUserDTO])
session.saveThreadList(
payload: ThreadListPayload(
threads: [
From c0562257c008f9f7c2b7937cbbb3b4ef1ca8e8a1 Mon Sep 17 00:00:00 2001
From: Alexey Alter-Pesotskiy
Date: Mon, 25 Nov 2024 18:03:25 +0000
Subject: [PATCH 2/9] Add snapshot postfix to v4.67.0
---
Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
index 5f222e5f8d..de0d79eb7f 100644
--- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
extension SystemEnvironment {
/// A Stream Chat version.
- public static let version: String = "4.67.0"
+ public static let version: String = "4.67.0-SNAPSHOT"
}
From 82b28effc3ba7a7de80159b152d6755fec5fb314 Mon Sep 17 00:00:00 2001
From: Toomas Vahter
Date: Wed, 27 Nov 2024 09:51:58 +0200
Subject: [PATCH 3/9] Convert to models after NSFetchedResultsController
finishes reporting changes for avoiding update cycle triggered by fetch
requests in item creator (#3508)
---
CHANGELOG.md | 4 +-
.../DatabaseObserver/ListChange.swift | 40 ++++++++++++++-----
2 files changed, 33 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e2ebf6d85..f413593a58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming
-### 🔄 Changed
+## StreamChat
+### 🐞 Fixed
+- Fix a rare infinite loop triggering a crash when handling database changes [#3508](https://github.com/GetStream/stream-chat-swift/pull/3508)
# [4.67.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.67.0)
_November 25, 2024_
diff --git a/Sources/StreamChat/Controllers/DatabaseObserver/ListChange.swift b/Sources/StreamChat/Controllers/DatabaseObserver/ListChange.swift
index e9e4bbb4b0..936873b6c1 100644
--- a/Sources/StreamChat/Controllers/DatabaseObserver/ListChange.swift
+++ b/Sources/StreamChat/Controllers/DatabaseObserver/ListChange.swift
@@ -150,9 +150,8 @@ class ListChangeAggregator: NSObject, NSFetchedResul
/// Called with the aggregated changes after `FetchResultsController` calls controllerDidChangeContent` on its delegate.
var onDidChange: (([ListChange- ]) -> Void)?
- /// An array of changes in the current update. It gets reset every time `controllerWillChangeContent` is called, and
- /// published to the observer when `controllerDidChangeContent` is called.
- private var currentChanges: [ListChange
- ] = []
+ /// An array of changes in the current update.
+ private var currentChanges: [ListChange] = []
/// Creates a new `ChangeAggregator`.
///
@@ -178,8 +177,10 @@ class ListChangeAggregator: NSObject, NSFetchedResul
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
) {
- guard let dto = anObject as? DTO, let item = try? itemCreator(dto) else {
- log.debug("Skipping the update from DB because the DTO can't be converted to the model object.")
+ // Model conversions must happen in `controllerDidChangeContent`. Otherwise, it can trigger a loop where
+ // this delegate method is called again when additional fetch requests in `asModel()` are triggered.
+ guard let dto = anObject as? DTO else {
+ log.debug("Skipping the update from DB because the DTO has invalid type: \(anObject)")
return
}
@@ -189,28 +190,28 @@ class ListChangeAggregator: NSObject, NSFetchedResul
log.warning("Skipping the update from DB because `newIndexPath` is missing for `.insert` change.")
return
}
- currentChanges.append(.insert(item, index: index))
+ currentChanges.append(.insert(dto, index: index))
case .move:
guard let fromIndex = indexPath, let toIndex = newIndexPath else {
log.warning("Skipping the update from DB because `indexPath` or `newIndexPath` are missing for `.move` change.")
return
}
- currentChanges.append(.move(item, fromIndex: fromIndex, toIndex: toIndex))
+ currentChanges.append(.move(dto, fromIndex: fromIndex, toIndex: toIndex))
case .update:
guard let index = indexPath else {
log.warning("Skipping the update from DB because `indexPath` is missing for `.update` change.")
return
}
- currentChanges.append(.update(item, index: index))
+ currentChanges.append(.update(dto, index: index))
case .delete:
guard let index = indexPath else {
log.warning("Skipping the update from DB because `indexPath` is missing for `.delete` change.")
return
}
- currentChanges.append(.remove(item, index: index))
+ currentChanges.append(.remove(dto, index: index))
default:
break
@@ -218,6 +219,25 @@ class ListChangeAggregator: NSObject, NSFetchedResul
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
- onDidChange?(currentChanges)
+ // Model conversion is safe when all the changes have been processed (Core Data's _processRecentChanges can be called if conversion triggers additional fetch requests).
+ let itemChanges = currentChanges.compactMap { dtoChange in
+ do {
+ switch dtoChange {
+ case .update(let dto, index: let indexPath):
+ return try ListChange.update(itemCreator(dto), index: indexPath)
+ case .insert(let dto, index: let indexPath):
+ return try ListChange.insert(itemCreator(dto), index: indexPath)
+ case .move(let dto, fromIndex: let fromIndex, toIndex: let toIndex):
+ return try ListChange.move(itemCreator(dto), fromIndex: fromIndex, toIndex: toIndex)
+ case .remove(let dto, index: let indexPath):
+ return try ListChange.remove(itemCreator(dto), index: indexPath)
+ }
+ } catch {
+ log.debug("Skipping the update from DB because the DTO can't be converted to the model object: \(error)")
+ return nil
+ }
+ }
+ onDidChange?(itemChanges)
+ currentChanges.removeAll()
}
}
From 4b87a3e466ed1df06982e7c7992ab819c04999c1 Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Fri, 29 Nov 2024 09:56:41 +0000
Subject: [PATCH 4/9] Fix Channel List search bar disappearing (#3515)
---
CHANGELOG.md | 4 ++++
Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift | 1 +
2 files changed, 5 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f413593a58..8a107f536b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### 🐞 Fixed
- Fix a rare infinite loop triggering a crash when handling database changes [#3508](https://github.com/GetStream/stream-chat-swift/pull/3508)
+## StreamChatUI
+### 🐞 Fixed
+- Fix Channel List search bar disappearing after it loses scrollability in rare scenarios [#3515](https://github.com/GetStream/stream-chat-swift/pull/3515)
+
# [4.67.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.67.0)
_November 25, 2024_
diff --git a/Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift b/Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift
index 1657316f75..3be2f04857 100644
--- a/Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift
+++ b/Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift
@@ -138,6 +138,7 @@ open class ChatChannelListVC: _ViewController,
withReuseIdentifier: separatorReuseIdentifier
)
+ collectionView.alwaysBounceVertical = true
collectionView.dataSource = self
collectionView.delegate = self
From 11cbd2e319eee5a20c19045b2261ae29ebb64d02 Mon Sep 17 00:00:00 2001
From: Martin Mitrevski
Date: Mon, 2 Dec 2024 20:47:47 +0100
Subject: [PATCH 5/9] Added new events for AI typing indicator (#3516)
---
.../Events/AITypingEvents.swift | 105 ++++++++++++++++++
.../WebSocketClient/Events/EventPayload.swift | 18 ++-
.../WebSocketClient/Events/EventType.swift | 14 +++
StreamChat.xcodeproj/project.pbxproj | 30 +++++
.../Events/AIIndicator/AIIndicatorClear.json | 16 +++
.../Events/AIIndicator/AIIndicatorStop.json | 16 +++
.../Events/AIIndicator/AIIndicatorUpdate.json | 19 ++++
.../Events/AIIndicatorEvents_Tests.swift | 42 +++++++
8 files changed, 259 insertions(+), 1 deletion(-)
create mode 100644 Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json
create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json
create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json
create mode 100644 Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift
diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
new file mode 100644
index 0000000000..2721c682ea
--- /dev/null
+++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
@@ -0,0 +1,105 @@
+//
+// Copyright © 2024 Stream.io Inc. All rights reserved.
+//
+
+import Foundation
+
+/// An event that provides updates about the state of the AI typing indicator.
+public struct AIIndicatorUpdateEvent: Event {
+ /// The state of the AI typing indicator.
+ public let state: AITypingState
+ /// The channel ID this event is related to.
+ public let cid: ChannelId?
+ /// The message ID this event is related to.
+ public let messageId: MessageId?
+ /// Optional server message, usually when an error occurs.
+ public let aiMessage: String?
+}
+
+class AIIndicatorUpdateEventDTO: EventDTO {
+ let payload: EventPayload
+
+ init(from response: EventPayload) throws {
+ payload = response
+ }
+
+ func toDomainEvent(session: DatabaseSession) -> Event? {
+ if let typingState = payload.aiState,
+ let aiTypingState = AITypingState(rawValue: typingState) {
+ return AIIndicatorUpdateEvent(
+ state: aiTypingState,
+ cid: payload.cid,
+ messageId: payload.messageId,
+ aiMessage: payload.aiMessage
+ )
+ } else {
+ return nil
+ }
+ }
+}
+
+/// An event that clears the AI typing indicator.
+public struct AIIndicatorClearEvent: Event {
+ /// The channel ID this event is related to.
+ public let cid: ChannelId?
+}
+
+class AIIndicatorClearEventDTO: EventDTO {
+ let payload: EventPayload
+
+ init(from response: EventPayload) throws {
+ payload = response
+ }
+
+ func toDomainEvent(session: any DatabaseSession) -> (any Event)? {
+ AIIndicatorClearEvent(cid: payload.cid)
+ }
+}
+
+/// An event that indicates the AI has stopped generating the message.
+public struct AIIndicatorStopEvent: CustomEventPayload, Event {
+ public static var eventType: EventType = .aiTypingIndicatorStop
+
+ /// The channel ID this event is related to.
+ public let cid: ChannelId?
+
+ public init(cid: ChannelId?) {
+ self.cid = cid
+ }
+}
+
+class AIIndicatorStopEventDTO: EventDTO {
+ let payload: EventPayload
+
+ init(from response: EventPayload) throws {
+ payload = response
+ }
+
+ func toDomainEvent(session: any DatabaseSession) -> (any Event)? {
+ AIIndicatorStopEvent(cid: payload.cid)
+ }
+}
+
+/// The state of the AI typing indicator.
+public struct AITypingState: ExpressibleByStringLiteral, Hashable {
+ public var rawValue: String
+
+ public init?(rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ public init(stringLiteral value: String) {
+ rawValue = value
+ }
+}
+
+public extension AITypingState {
+ /// The AI is thinking.
+ static let thinking: Self = "AI_STATE_THINKING"
+ /// The AI is checking external sources.
+ static let checkingExternalSources: Self = "AI_STATE_EXTERNAL_SOURCES"
+ /// The AI is generating the message.
+ static let generating: Self = "AI_STATE_GENERATING"
+ /// There's an error with the message generation.
+ static let error: Self = "AI_STATE_ERROR"
+}
diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift
index d10d0bbf96..bcf74c6881 100644
--- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift
@@ -36,6 +36,9 @@ class EventPayload: Decodable {
case thread
case vote = "poll_vote"
case poll
+ case aiState = "ai_state"
+ case messageId = "message_id"
+ case aiMessage = "ai_message"
}
let eventType: EventType
@@ -68,6 +71,10 @@ class EventPayload: Decodable {
/// Thread Data, it is stored in Result, to be easier to debug decoding errors
let threadDetails: Result?
let threadPartial: Result?
+
+ let aiState: String?
+ let messageId: String?
+ let aiMessage: String?
init(
eventType: EventType,
@@ -96,7 +103,10 @@ class EventPayload: Decodable {
threadDetails: Result? = nil,
threadPartial: Result? = nil,
poll: PollPayload? = nil,
- vote: PollVotePayload? = nil
+ vote: PollVotePayload? = nil,
+ aiState: String? = nil,
+ messageId: String? = nil,
+ aiMessage: String? = nil
) {
self.eventType = eventType
self.connectionId = connectionId
@@ -125,6 +135,9 @@ class EventPayload: Decodable {
self.threadDetails = threadDetails
self.poll = poll
self.vote = vote
+ self.aiState = aiState
+ self.messageId = messageId
+ self.aiMessage = aiMessage
}
required init(from decoder: Decoder) throws {
@@ -158,6 +171,9 @@ class EventPayload: Decodable {
threadPartial = container.decodeAsResultIfPresent(ThreadPartialPayload.self, forKey: .thread)
vote = try container.decodeIfPresent(PollVotePayload.self, forKey: .vote)
poll = try container.decodeIfPresent(PollPayload.self, forKey: .poll)
+ aiState = try container.decodeIfPresent(String.self, forKey: .aiState)
+ messageId = try container.decodeIfPresent(String.self, forKey: .messageId)
+ aiMessage = try container.decodeIfPresent(String.self, forKey: .aiMessage)
}
func event() throws -> Event {
diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift
index 37124123ac..084e4606b6 100644
--- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift
@@ -143,6 +143,17 @@ public extension EventType {
/// When a thread has a new reply.
static let threadMessageNew: Self = "notification.thread_message_new"
+
+ // MARK: - AI
+
+ // When an AI typing indicator's state has changed.
+ static let aiTypingIndicatorChanged: Self = "ai_indicator.update"
+
+ // When an AI typing indicator has been cleared.
+ static let aiTypingIndicatorClear: Self = "ai_indicator.clear"
+
+ // When an AI typing indicator has been stopped.
+ static let aiTypingIndicatorStop: Self = "ai_indicator.stop"
}
extension EventType {
@@ -208,6 +219,9 @@ extension EventType {
case .pollVoteRemoved: return try PollVoteRemovedEventDTO(from: response)
case .threadUpdated: return try ThreadUpdatedEventDTO(from: response)
case .threadMessageNew: return try ThreadMessageNewEventDTO(from: response)
+ case .aiTypingIndicatorChanged: return try AIIndicatorUpdateEventDTO(from: response)
+ case .aiTypingIndicatorClear: return try AIIndicatorClearEventDTO(from: response)
+ case .aiTypingIndicatorStop: return try AIIndicatorStopEventDTO(from: response)
default:
if response.cid == nil {
throw ClientError.UnknownUserEvent(response.eventType)
diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj
index 813505464d..f05ba17591 100644
--- a/StreamChat.xcodeproj/project.pbxproj
+++ b/StreamChat.xcodeproj/project.pbxproj
@@ -756,6 +756,8 @@
847E946E269C687300E31D0C /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E946D269C687300E31D0C /* EventsController.swift */; };
847F3CEA2689FDEB00D240E0 /* ChatMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F3CE92689FDEB00D240E0 /* ChatMessageCell.swift */; };
8486CAF926FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486CAF826FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift */; };
+ 848849B62CEE01070010E7CA /* AITypingEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848849B52CEE01070010E7CA /* AITypingEvents.swift */; };
+ 848849B72CEE01070010E7CA /* AITypingEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848849B52CEE01070010E7CA /* AITypingEvents.swift */; };
849980F1277246DB00ABA58B /* UIScrollView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849980F0277246DB00ABA58B /* UIScrollView+Extensions.swift */; };
849AE664270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AE663270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift */; };
849AE666270CB55F00423A20 /* VideoAttachmentGalleryCell_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AE665270CB55F00423A20 /* VideoAttachmentGalleryCell_Tests.swift */; };
@@ -812,6 +814,10 @@
84DCB851269F4D31006CDF32 /* EventsController+Combine_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB850269F4D31006CDF32 /* EventsController+Combine_Tests.swift */; };
84DCB853269F569A006CDF32 /* EventsController+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB852269F569A006CDF32 /* EventsController+SwiftUI.swift */; };
84DCB855269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB854269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift */; };
+ 84E46A372CFA1B8E000CBDDE /* AIIndicatorClear.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */; };
+ 84E46A382CFA1B8E000CBDDE /* AIIndicatorStop.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */; };
+ 84E46A392CFA1B8E000CBDDE /* AIIndicatorUpdate.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */; };
+ 84E46A3B2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */; };
84EB4E76276A012900E47E73 /* ClientError_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB4E75276A012900E47E73 /* ClientError_Tests.swift */; };
84EB4E78276A03DE00E47E73 /* ErrorPayload_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB4E77276A03DE00E47E73 /* ErrorPayload_Tests.swift */; };
84EE53B12BBC32AD00FD2A13 /* Chat_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE53B02BBC32AD00FD2A13 /* Chat_Mock.swift */; };
@@ -3660,6 +3666,7 @@
847E946D269C687300E31D0C /* EventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = ""; };
847F3CE92689FDEB00D240E0 /* ChatMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageCell.swift; sourceTree = ""; };
8486CAF826FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDTOConverterMiddleware_Tests.swift; sourceTree = ""; };
+ 848849B52CEE01070010E7CA /* AITypingEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AITypingEvents.swift; sourceTree = ""; };
849980F0277246DB00ABA58B /* UIScrollView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extensions.swift"; sourceTree = ""; };
849AE661270CB00000423A20 /* VideoLoader_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoLoader_Mock.swift; sourceTree = ""; };
849AE663270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAttachmentComposerPreview_Tests.swift; sourceTree = ""; };
@@ -3711,6 +3718,10 @@
84DCB852269F569A006CDF32 /* EventsController+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventsController+SwiftUI.swift"; sourceTree = ""; };
84DCB854269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventsController+SwiftUI_Tests.swift"; sourceTree = ""; };
84E32EA4276C9AB200A27112 /* InternetConnection_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetConnection_Mock.swift; sourceTree = ""; };
+ 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorClear.json; sourceTree = ""; };
+ 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorStop.json; sourceTree = ""; };
+ 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorUpdate.json; sourceTree = ""; };
+ 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIIndicatorEvents_Tests.swift; sourceTree = ""; };
84EB4E732769F76500E47E73 /* BackgroundTaskScheduler_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskScheduler_Mock.swift; sourceTree = ""; };
84EB4E75276A012900E47E73 /* ClientError_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError_Tests.swift; sourceTree = ""; };
84EB4E77276A03DE00E47E73 /* ErrorPayload_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPayload_Tests.swift; sourceTree = ""; };
@@ -5515,6 +5526,7 @@
8A0C3BBB24C0947400CAFD19 /* UserEvents.swift */,
841BAA0F2BCEADAC000C73E4 /* PollsEvents.swift */,
AD7BE1692C209888000A5756 /* ThreadEvents.swift */,
+ 848849B52CEE01070010E7CA /* AITypingEvents.swift */,
);
path = Events;
sourceTree = "";
@@ -6247,6 +6259,16 @@
path = Cells;
sourceTree = "";
};
+ 84E46A332CFA1B73000CBDDE /* AIIndicator */ = {
+ isa = PBXGroup;
+ children = (
+ 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */,
+ 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */,
+ 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */,
+ );
+ path = AIIndicator;
+ sourceTree = "";
+ };
84EE53AF2BBC329300FD2A13 /* State */ = {
isa = PBXGroup;
children = (
@@ -6543,6 +6565,7 @@
8A62705F24BE31B20040BFD6 /* Events */ = {
isa = PBXGroup;
children = (
+ 84E46A332CFA1B73000CBDDE /* AIIndicator */,
ADE57B802C3C5C4600DD6B88 /* Thread */,
8A0C3BCA24C1C38C00CAFD19 /* Channel */,
E7DB9F2526329C0C0090D9C7 /* HealthCheck */,
@@ -6961,6 +6984,7 @@
8A62705B24BE2BC00040BFD6 /* TypingEvent_Tests.swift */,
8A0C3BC824C0BBAB00CAFD19 /* UserEvents_Tests.swift */,
ADE57B872C3C60CB00DD6B88 /* ThreadEvents_Tests.swift */,
+ 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */,
);
path = Events;
sourceTree = "";
@@ -10213,6 +10237,9 @@
A311B40327E8B9AD00CFCF6D /* NotificationInviteAccepted.json in Resources */,
A311B3D327E8B98C00CFCF6D /* Message.json in Resources */,
A311B3E427E8B98C00CFCF6D /* MessagePayloadWithCustom.json in Resources */,
+ 84E46A372CFA1B8E000CBDDE /* AIIndicatorClear.json in Resources */,
+ 84E46A382CFA1B8E000CBDDE /* AIIndicatorStop.json in Resources */,
+ 84E46A392CFA1B8E000CBDDE /* AIIndicatorUpdate.json in Resources */,
A3D9D68727EDE3B900725066 /* yoda.jpg in Resources */,
A311B42727E8B9CE00CFCF6D /* MessageReactionPayload+CustomExtraData.json in Resources */,
A311B3E527E8B98C00CFCF6D /* MessageWithBrokenAttachments.json in Resources */,
@@ -11551,6 +11578,7 @@
40789D1729F6AC500018C2BB /* AudioPlaybackContextAccessor.swift in Sources */,
79D5CDD427EA1BE300BE7D8B /* MessageTranslationsPayload.swift in Sources */,
88D85D97252F168000AE1030 /* MemberController+SwiftUI.swift in Sources */,
+ 848849B72CEE01070010E7CA /* AITypingEvents.swift in Sources */,
43D3F0FC28410A0200B74921 /* CreateCallRequestBody.swift in Sources */,
79877A0D2498E4BC00015F8B /* CurrentUser.swift in Sources */,
4042967D29FAC9DA0089126D /* AudioAnalysisContext.swift in Sources */,
@@ -11703,6 +11731,7 @@
84EB4E76276A012900E47E73 /* ClientError_Tests.swift in Sources */,
DA8407232525E871005A0F62 /* UserListPayload_Tests.swift in Sources */,
437FCA1926D906B20000223C /* ChatPushNotificationContent_Tests.swift in Sources */,
+ 84E46A3B2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift in Sources */,
F61D7C3124FF9D1F00188A0E /* MessageEndpoints_Tests.swift in Sources */,
8459C9EE2BFB673E00F0D235 /* PollVoteListController+Combine_Tests.swift in Sources */,
8836FFC325408210009FDF73 /* FlagUserPayload_Tests.swift in Sources */,
@@ -12497,6 +12526,7 @@
C121E8C2274544B100023E4C /* ChannelEventsController.swift in Sources */,
C121E8C3274544B100023E4C /* ListChange.swift in Sources */,
C15C8839286C7BF300E6A72C /* BackgroundListDatabaseObserver.swift in Sources */,
+ 848849B62CEE01070010E7CA /* AITypingEvents.swift in Sources */,
ADB951B3291C3CE900800554 /* AnyAttachmentUpdater.swift in Sources */,
40789D3829F6AC500018C2BB /* AssetPropertyLoading.swift in Sources */,
C121E8C4274544B100023E4C /* EntityChange.swift in Sources */,
diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json
new file mode 100644
index 0000000000..d1fde4915e
--- /dev/null
+++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json
@@ -0,0 +1,16 @@
+{
+ "type": "ai_indicator.clear",
+ "cid": "messaging:general-a4ea1bed-f233-4021-b9f8-f9519367cefd",
+ "channel_id": "general-a4ea1bed-f233-4021-b9f8-f9519367cefd",
+ "channel_type": "messaging",
+ "user": {
+ "id": "ai-b076753a-830e-40b0-816d-0929bb73d7ce",
+ "role": "user",
+ "created_at": "2024-11-27T15:57:45.157276Z",
+ "updated_at": "2024-11-27T15:57:45.157276Z",
+ "last_active": "2024-11-27T15:57:45.177077Z",
+ "banned": false,
+ "online": true
+ },
+ "created_at": "2024-11-27T15:57:45.32967Z"
+}
diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json
new file mode 100644
index 0000000000..d15a4ef8e0
--- /dev/null
+++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json
@@ -0,0 +1,16 @@
+{
+ "type": "ai_indicator.stop",
+ "cid": "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89",
+ "channel_id": "general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89",
+ "channel_type": "messaging",
+ "user": {
+ "id": "ai-1e9666df-9b9e-429e-b7e4-e2f54446d5ac",
+ "role": "user",
+ "created_at": "2024-11-27T15:51:55.649597Z",
+ "updated_at": "2024-11-27T15:51:55.649597Z",
+ "last_active": "2024-11-27T15:51:55.668787Z",
+ "banned": false,
+ "online": true
+ },
+ "created_at": "2024-11-27T15:51:55.803339Z"
+}
diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json
new file mode 100644
index 0000000000..3545d48cdf
--- /dev/null
+++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json
@@ -0,0 +1,19 @@
+{
+ "type": "ai_indicator.update",
+ "cid": "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89",
+ "channel_id": "general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89",
+ "channel_type": "messaging",
+ "message_id": "aba120c6-c845-4c5a-968d-31ed0429c31e",
+ "ai_state": "AI_STATE_ERROR",
+ "ai_message": "failure",
+ "user": {
+ "id": "ai-1e9666df-9b9e-429e-b7e4-e2f54446d5ac",
+ "role": "user",
+ "created_at": "2024-11-27T15:51:55.649597Z",
+ "updated_at": "2024-11-27T15:51:55.649597Z",
+ "last_active": "2024-11-27T15:51:55.668787Z",
+ "banned": false,
+ "online": true
+ },
+ "created_at": "2024-11-27T15:51:55.757904Z"
+}
diff --git a/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift
new file mode 100644
index 0000000000..5974c15f3e
--- /dev/null
+++ b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift
@@ -0,0 +1,42 @@
+//
+// Copyright © 2024 Stream.io Inc. All rights reserved.
+//
+
+@testable import StreamChat
+@testable import StreamChatTestTools
+import XCTest
+
+final class AIIndicatorEvents_Tests: XCTestCase {
+ var eventDecoder: EventDecoder!
+
+ override func setUp() {
+ super.setUp()
+ eventDecoder = EventDecoder()
+ }
+
+ override func tearDown() {
+ super.tearDown()
+ eventDecoder = nil
+ }
+
+ func test_aiIndicatorUpdate() throws {
+ let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorUpdate")
+ let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorUpdateEventDTO)
+ XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89")
+ XCTAssertEqual(event.payload.messageId, "aba120c6-c845-4c5a-968d-31ed0429c31e")
+ XCTAssertEqual(event.payload.aiState, "AI_STATE_ERROR")
+ XCTAssertEqual(event.payload.aiMessage, "failure")
+ }
+
+ func test_aiIndicatorClear() throws {
+ let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorClear")
+ let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorClearEventDTO)
+ XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-a4ea1bed-f233-4021-b9f8-f9519367cefd")
+ }
+
+ func test_aiIndicatorStop() throws {
+ let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorStop")
+ let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorStopEventDTO)
+ XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89")
+ }
+}
From 040264001c7bb457a4762fb54f80ae29b78a0d4d Mon Sep 17 00:00:00 2001
From: Toomas Vahter
Date: Tue, 3 Dec 2024 10:19:20 +0200
Subject: [PATCH 6/9] Fix unstable typing related tests in
ChatChannelController_Tests (#3520)
---
.../ChannelController/ChannelController_Tests.swift | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift b/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift
index 1088d3e0d9..32fbcc9156 100644
--- a/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift
+++ b/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift
@@ -2987,6 +2987,8 @@ final class ChannelController_Tests: XCTestCase {
controller.sendKeystrokeEvent {
XCTAssertNil($0)
}
+ wait(for: [env.eventSender!.keystroke_completion_expectation], timeout: defaultTimeout)
+ env.eventSender!.keystroke_completion_expectation = XCTestExpectation()
// Simulate `keystroke` call and catch the completion
var completionCalledError: Error?
@@ -3000,6 +3002,7 @@ final class ChannelController_Tests: XCTestCase {
controller = nil
// Check keystroke cid.
+ wait(for: [env.eventSender!.keystroke_completion_expectation], timeout: defaultTimeout)
XCTAssertEqual(env.eventSender!.keystroke_cid, channelId)
// Simulate failed update
@@ -3059,6 +3062,8 @@ final class ChannelController_Tests: XCTestCase {
controller.sendStartTypingEvent {
XCTAssertNil($0)
}
+ wait(for: [env.eventSender!.startTyping_completion_expectation], timeout: defaultTimeout)
+ env.eventSender!.startTyping_completion_expectation = XCTestExpectation()
// Simulate `startTyping` call and catch the completion
var completionCalledError: Error?
@@ -3072,6 +3077,7 @@ final class ChannelController_Tests: XCTestCase {
controller = nil
// Check `startTyping` cid.
+ wait(for: [env.eventSender!.startTyping_completion_expectation], timeout: defaultTimeout)
XCTAssertEqual(env.eventSender!.startTyping_cid, channelId)
// Simulate failed update
@@ -3131,7 +3137,9 @@ final class ChannelController_Tests: XCTestCase {
controller.sendStopTypingEvent {
XCTAssertNil($0)
}
-
+ wait(for: [env.eventSender!.stopTyping_completion_expectation], timeout: defaultTimeout)
+ env.eventSender!.stopTyping_completion_expectation = XCTestExpectation()
+
// Simulate `stopTyping` call and catch the completion
var completionCalledError: Error?
controller.sendStopTypingEvent { completionCalledError = $0 }
@@ -3144,6 +3152,7 @@ final class ChannelController_Tests: XCTestCase {
controller = nil
// Check `stopTyping` cid.
+ wait(for: [env.eventSender!.stopTyping_completion_expectation], timeout: defaultTimeout)
XCTAssertEqual(env.eventSender!.stopTyping_cid, channelId)
// Simulate failed update
From 24433631e118745fa206bdfb81aeb38077437656 Mon Sep 17 00:00:00 2001
From: Stream Bot
Date: Tue, 3 Dec 2024 08:29:36 +0000
Subject: [PATCH 7/9] Bump 4.68.0
---
CHANGELOG.md | 5 +++++
README.md | 2 +-
Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +-
Sources/StreamChat/Info.plist | 2 +-
Sources/StreamChatUI/Info.plist | 2 +-
StreamChat-XCFramework.podspec | 2 +-
StreamChat.podspec | 2 +-
StreamChatArtifacts.json | 2 +-
StreamChatUI-XCFramework.podspec | 2 +-
StreamChatUI.podspec | 2 +-
10 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a107f536b..b433fe2648 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming
+### 🔄 Changed
+
+# [4.68.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.68.0)
+_December 03, 2024_
+
## StreamChat
### 🐞 Fixed
- Fix a rare infinite loop triggering a crash when handling database changes [#3508](https://github.com/GetStream/stream-chat-swift/pull/3508)
diff --git a/README.md b/README.md
index e39d6210aa..a5834eeeb0 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
index de0d79eb7f..90768b7c3d 100644
--- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
extension SystemEnvironment {
/// A Stream Chat version.
- public static let version: String = "4.67.0-SNAPSHOT"
+ public static let version: String = "4.68.0"
}
diff --git a/Sources/StreamChat/Info.plist b/Sources/StreamChat/Info.plist
index c583202cb4..a06f6f6830 100644
--- a/Sources/StreamChat/Info.plist
+++ b/Sources/StreamChat/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 4.67.0
+ 4.68.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/Sources/StreamChatUI/Info.plist b/Sources/StreamChatUI/Info.plist
index c583202cb4..a06f6f6830 100644
--- a/Sources/StreamChatUI/Info.plist
+++ b/Sources/StreamChatUI/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 4.67.0
+ 4.68.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/StreamChat-XCFramework.podspec b/StreamChat-XCFramework.podspec
index 454fe7565d..fa944c7f77 100644
--- a/StreamChat-XCFramework.podspec
+++ b/StreamChat-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "StreamChat-XCFramework"
- spec.version = "4.67.0"
+ spec.version = "4.68.0"
spec.summary = "StreamChat iOS Client"
spec.description = "stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications."
diff --git a/StreamChat.podspec b/StreamChat.podspec
index bbdfd4e244..b1e95162a7 100644
--- a/StreamChat.podspec
+++ b/StreamChat.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "StreamChat"
- spec.version = "4.67.0"
+ spec.version = "4.68.0"
spec.summary = "StreamChat iOS Chat Client"
spec.description = "stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications."
diff --git a/StreamChatArtifacts.json b/StreamChatArtifacts.json
index 02b09389b7..f4549fcf87 100644
--- a/StreamChatArtifacts.json
+++ b/StreamChatArtifacts.json
@@ -1 +1 @@
-{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip"}
\ No newline at end of file
+{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip"}
\ No newline at end of file
diff --git a/StreamChatUI-XCFramework.podspec b/StreamChatUI-XCFramework.podspec
index ffb2103194..af6c15c82b 100644
--- a/StreamChatUI-XCFramework.podspec
+++ b/StreamChatUI-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "StreamChatUI-XCFramework"
- spec.version = "4.67.0"
+ spec.version = "4.68.0"
spec.summary = "StreamChat UI Components"
spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK."
diff --git a/StreamChatUI.podspec b/StreamChatUI.podspec
index 7725bb64f4..f6c4429198 100644
--- a/StreamChatUI.podspec
+++ b/StreamChatUI.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "StreamChatUI"
- spec.version = "4.67.0"
+ spec.version = "4.68.0"
spec.summary = "StreamChat UI Components"
spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK."
From 80755a96d2297fccd6c14746763e3f584f1034cb Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Tue, 3 Dec 2024 09:34:14 +0000
Subject: [PATCH 8/9] Fix reconnection timeout handler not working in the token
provider phase (#3513)
---
CHANGELOG.md | 1 +
.../StreamChat/ChatClient+Environment.swift | 11 +-
Sources/StreamChat/ChatClient.swift | 48 ++++++-
.../AuthenticationRepository.swift | 29 +++--
.../Repositories/ConnectionRepository.swift | 12 +-
.../WebSocketClient/ConnectionStatus.swift | 2 +-
.../WebSocketClient/WebSocketClient.swift | 23 ++--
.../ConnectionRecoveryHandler.swift | 15 +--
.../ConnectionRepository_Mock.swift | 5 +
.../AuthenticationRepository_Mock.swift | 11 +-
.../WebSocketClient_Mock.swift | 6 -
Tests/StreamChatTests/ChatClient_Tests.swift | 123 +++++++++++++++++-
.../AuthenticationRepository_Tests.swift | 8 +-
.../ConnectionRepository_Tests.swift | 13 --
.../WebSocketClient_Tests.swift | 13 ++
.../ConnectionRecoveryHandler_Tests.swift | 20 +--
16 files changed, 239 insertions(+), 101 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b433fe2648..6f3c4e7250 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ _December 03, 2024_
## StreamChat
### 🐞 Fixed
- Fix a rare infinite loop triggering a crash when handling database changes [#3508](https://github.com/GetStream/stream-chat-swift/pull/3508)
+- Fix reconnection timeout handler not working in the token provider phase [#3513](https://github.com/GetStream/stream-chat-swift/pull/3513)
## StreamChatUI
### 🐞 Fixed
diff --git a/Sources/StreamChat/ChatClient+Environment.swift b/Sources/StreamChat/ChatClient+Environment.swift
index 9d86085105..08fd6535aa 100644
--- a/Sources/StreamChat/ChatClient+Environment.swift
+++ b/Sources/StreamChat/ChatClient+Environment.swift
@@ -47,6 +47,11 @@ extension ChatClient {
)
}
+ var reconnectionHandlerBuilder: (_ chatClientConfig: ChatClientConfig) -> StreamTimer? = {
+ guard let reconnectionTimeout = $0.reconnectionTimeout else { return nil }
+ return ScheduledStreamTimer(interval: reconnectionTimeout, fireOnStart: false, repeats: false)
+ }
+
var extensionLifecycleBuilder = NotificationExtensionLifecycle.init
var requestEncoderBuilder: (_ baseURL: URL, _ apiKey: APIKey) -> RequestEncoder = DefaultRequestEncoder.init
@@ -97,8 +102,7 @@ extension ChatClient {
_ extensionLifecycle: NotificationExtensionLifecycle,
_ backgroundTaskScheduler: BackgroundTaskScheduler?,
_ internetConnection: InternetConnection,
- _ keepConnectionAliveInBackground: Bool,
- _ reconnectionTimeoutHandler: StreamTimer?
+ _ keepConnectionAliveInBackground: Bool
) -> ConnectionRecoveryHandler = {
DefaultConnectionRecoveryHandler(
webSocketClient: $0,
@@ -109,8 +113,7 @@ extension ChatClient {
internetConnection: $5,
reconnectionStrategy: DefaultRetryStrategy(),
reconnectionTimerType: DefaultTimer.self,
- keepConnectionAliveInBackground: $6,
- reconnectionTimeoutHandler: $7
+ keepConnectionAliveInBackground: $6
)
}
diff --git a/Sources/StreamChat/ChatClient.swift b/Sources/StreamChat/ChatClient.swift
index c6af54a27d..7e01e5d7c6 100644
--- a/Sources/StreamChat/ChatClient.swift
+++ b/Sources/StreamChat/ChatClient.swift
@@ -96,6 +96,9 @@ public class ChatClient {
/// Used as a bridge to communicate between the host app and the notification extension. Holds the state for the app lifecycle.
let extensionLifecycle: NotificationExtensionLifecycle
+ /// The component responsible to timeout the user connection if it takes more time than the `ChatClientConfig.reconnectionTimeout`.
+ var reconnectionTimeoutHandler: StreamTimer?
+
/// The environment object containing all dependencies of this `Client` instance.
private let environment: Environment
@@ -219,12 +222,18 @@ public class ChatClient {
setupOfflineRequestQueue()
setupConnectionRecoveryHandler(with: environment)
validateIntegrity()
+
+ reconnectionTimeoutHandler = environment.reconnectionHandlerBuilder(config)
+ reconnectionTimeoutHandler?.onChange = { [weak self] in
+ self?.timeout()
+ }
}
deinit {
Self._activeLocalStorageURLs.mutate { $0.subtract(databaseContainer.persistentStoreDescriptions.compactMap(\.url)) }
completeConnectionIdWaiters(connectionId: nil)
completeTokenWaiters(token: nil)
+ reconnectionTimeoutHandler?.stop()
}
func setupTokenRefresher() {
@@ -254,8 +263,7 @@ public class ChatClient {
extensionLifecycle,
environment.backgroundTaskSchedulerBuilder(),
environment.internetConnection(eventNotificationCenter, environment.internetMonitor),
- config.staysConnectedInBackground,
- config.reconnectionTimeout.map { ScheduledStreamTimer(interval: $0, fireOnStart: false, repeats: false) }
+ config.staysConnectedInBackground
)
}
@@ -300,7 +308,9 @@ public class ChatClient {
tokenProvider: @escaping TokenProvider,
completion: ((Error?) -> Void)? = nil
) {
+ reconnectionTimeoutHandler?.start()
connectionRecoveryHandler?.start()
+ connectionRepository.initialize()
authenticationRepository.connectUser(
userInfo: userInfo,
@@ -393,7 +403,9 @@ public class ChatClient {
userInfo: UserInfo,
completion: ((Error?) -> Void)? = nil
) {
+ connectionRepository.initialize()
connectionRecoveryHandler?.start()
+ reconnectionTimeoutHandler?.start()
authenticationRepository.connectGuestUser(userInfo: userInfo, completion: { completion?($0) })
}
@@ -417,6 +429,8 @@ public class ChatClient {
/// Connects an anonymous user
/// - Parameter completion: The completion that will be called once the **first** user session for the given token is setup.
public func connectAnonymousUser(completion: ((Error?) -> Void)? = nil) {
+ connectionRepository.initialize()
+ reconnectionTimeoutHandler?.start()
connectionRecoveryHandler?.start()
authenticationRepository.connectAnonymousUser(
completion: { completion?($0) }
@@ -458,7 +472,7 @@ public class ChatClient {
completion()
}
authenticationRepository.clearTokenProvider()
- authenticationRepository.cancelTimers()
+ authenticationRepository.reset()
}
/// Disconnects the chat client from the chat servers. No further updates from the servers
@@ -617,6 +631,15 @@ public class ChatClient {
completion?($0)
}
}
+
+ private func timeout() {
+ completeConnectionIdWaiters(connectionId: nil)
+ authenticationRepository.completeTokenCompletions(error: ClientError.ReconnectionTimeout())
+ completeTokenWaiters(token: nil)
+ authenticationRepository.reset()
+ let webSocketConnectionState = webSocketClient?.connectionState ?? .initialized
+ connectionRepository.disconnect(source: .timeout(from: webSocketConnectionState)) {}
+ }
}
extension ChatClient: AuthenticationRepositoryDelegate {
@@ -646,6 +669,17 @@ extension ChatClient: ConnectionStateDelegate {
)
connectionRecoveryHandler?.webSocketClient(client, didUpdateConnectionState: state)
try? backgroundWorker(of: MessageSender.self).didUpdateConnectionState(state)
+
+ switch state {
+ case .connecting:
+ if reconnectionTimeoutHandler?.isRunning == false {
+ reconnectionTimeoutHandler?.start()
+ }
+ case .connected:
+ reconnectionTimeoutHandler?.stop()
+ default:
+ break
+ }
}
}
@@ -692,6 +726,14 @@ extension ClientError {
}
}
+ public final class ReconnectionTimeout: ClientError {
+ override public var localizedDescription: String {
+ """
+ The reconnection process has timed out after surpassing the value from `ChatClientConfig.reconnectionTimeout`.
+ """
+ }
+ }
+
public final class MissingToken: ClientError {}
final class WaiterTimeout: ClientError {}
diff --git a/Sources/StreamChat/Repositories/AuthenticationRepository.swift b/Sources/StreamChat/Repositories/AuthenticationRepository.swift
index 7f36fca6fb..6d45afb2c2 100644
--- a/Sources/StreamChat/Repositories/AuthenticationRepository.swift
+++ b/Sources/StreamChat/Repositories/AuthenticationRepository.swift
@@ -196,9 +196,12 @@ class AuthenticationRepository {
isGettingToken = false
}
- func cancelTimers() {
+ func reset() {
connectionProviderTimer?.cancel()
tokenProviderTimer?.cancel()
+ tokenQueue.async(flags: .barrier) {
+ self._tokenExpirationRetryStrategy.resetConsecutiveFailures()
+ }
}
func logOutUser() {
@@ -280,6 +283,19 @@ class AuthenticationRepository {
updateToken(token: token, notifyTokenWaiters: true)
}
+ func completeTokenCompletions(error: Error?) {
+ let completionBlocks: [(Error?) -> Void]? = tokenQueue.sync(flags: .barrier) {
+ self._isGettingToken = false
+ let completions = self._tokenRequestCompletions
+ return completions
+ }
+ completionBlocks?.forEach { $0(error) }
+ tokenQueue.async(flags: .barrier) {
+ self._tokenRequestCompletions = []
+ self._consecutiveRefreshFailures = 0
+ }
+ }
+
private func updateToken(token: Token?, notifyTokenWaiters: Bool) {
let waiters: [String: (Result) -> Void] = tokenQueue.sync(flags: .barrier) {
_currentToken = token
@@ -331,21 +347,12 @@ class AuthenticationRepository {
isGettingToken = true
let onCompletion: (Error?) -> Void = { [weak self] error in
- guard let self = self else { return }
if let error = error {
log.error("Error when getting token: \(error)", subsystems: .authentication)
} else {
log.debug("Successfully retrieved token", subsystems: .authentication)
}
-
- let completionBlocks: [(Error?) -> Void]? = self.tokenQueue.sync(flags: .barrier) {
- self._isGettingToken = false
- let completions = self._tokenRequestCompletions
- self._tokenRequestCompletions = []
- self._consecutiveRefreshFailures = 0
- return completions
- }
- completionBlocks?.forEach { $0(error) }
+ self?.completeTokenCompletions(error: error)
}
guard consecutiveRefreshFailures < Constants.maximumTokenRefreshAttempts else {
diff --git a/Sources/StreamChat/Repositories/ConnectionRepository.swift b/Sources/StreamChat/Repositories/ConnectionRepository.swift
index d9b1ece47e..2e6489a18f 100644
--- a/Sources/StreamChat/Repositories/ConnectionRepository.swift
+++ b/Sources/StreamChat/Repositories/ConnectionRepository.swift
@@ -42,6 +42,10 @@ class ConnectionRepository {
self.timerType = timerType
}
+ func initialize() {
+ webSocketClient?.initialize()
+ }
+
/// Connects the chat client the controller represents to the chat servers.
///
/// When the connection is established, `ChatClient` starts receiving chat updates, and `currentUser` variable is available.
@@ -95,14 +99,6 @@ class ConnectionRepository {
return
}
- if connectionId == nil {
- if source == .userInitiated {
- log.warning("The client is already disconnected. Skipping the `disconnect` call.")
- }
- completion()
- return
- }
-
// Disconnect the web socket
webSocketClient?.disconnect(source: source) { [weak self] in
// Reset `connectionId`. This would happen asynchronously by the callback from WebSocketClient anyway, but it's
diff --git a/Sources/StreamChat/WebSocketClient/ConnectionStatus.swift b/Sources/StreamChat/WebSocketClient/ConnectionStatus.swift
index 50a7607cd8..94fb00e06b 100644
--- a/Sources/StreamChat/WebSocketClient/ConnectionStatus.swift
+++ b/Sources/StreamChat/WebSocketClient/ConnectionStatus.swift
@@ -77,7 +77,7 @@ enum WebSocketConnectionState: Equatable {
}
}
- /// The initial state meaning that there was no atempt to connect yet.
+ /// The initial state meaning that the web socket engine is not yet connected or connecting.
case initialized
/// The web socket is not connected. Contains the source/reason why the disconnection has happened.
diff --git a/Sources/StreamChat/WebSocketClient/WebSocketClient.swift b/Sources/StreamChat/WebSocketClient/WebSocketClient.swift
index bb03570918..3bcdf9f092 100644
--- a/Sources/StreamChat/WebSocketClient/WebSocketClient.swift
+++ b/Sources/StreamChat/WebSocketClient/WebSocketClient.swift
@@ -100,6 +100,10 @@ class WebSocketClient {
self.eventNotificationCenter = eventNotificationCenter
}
+ func initialize() {
+ connectionState = .initialized
+ }
+
/// Connects the web connect.
///
/// Calling this method has no effect is the web socket is already connected, or is in the connecting phase.
@@ -137,23 +141,18 @@ class WebSocketClient {
source: WebSocketConnectionState.DisconnectionSource = .userInitiated,
completion: @escaping () -> Void
) {
- connectionState = .disconnecting(source: source)
- engineQueue.async { [engine, eventsBatcher] in
- engine?.disconnect()
-
- eventsBatcher.processImmediately(completion: completion)
+ switch connectionState {
+ case .initialized, .disconnected, .disconnecting:
+ connectionState = .disconnected(source: source)
+ case .connecting, .waitingForConnectionId, .connected:
+ connectionState = .disconnecting(source: source)
}
- }
-
- func timeout() {
- let previousState = connectionState
- connectionState = .disconnected(source: .timeout(from: previousState))
+
engineQueue.async { [engine, eventsBatcher] in
engine?.disconnect()
- eventsBatcher.processImmediately {}
+ eventsBatcher.processImmediately(completion: completion)
}
- log.error("Connection timed out. `\(connectionState)", subsystems: .webSocket)
}
}
diff --git a/Sources/StreamChat/Workers/Background/ConnectionRecoveryHandler.swift b/Sources/StreamChat/Workers/Background/ConnectionRecoveryHandler.swift
index 14d99290a0..41e4a90344 100644
--- a/Sources/StreamChat/Workers/Background/ConnectionRecoveryHandler.swift
+++ b/Sources/StreamChat/Workers/Background/ConnectionRecoveryHandler.swift
@@ -36,7 +36,6 @@ final class DefaultConnectionRecoveryHandler: ConnectionRecoveryHandler {
private var reconnectionStrategy: RetryStrategy
private var reconnectionTimer: TimerControl?
private let keepConnectionAliveInBackground: Bool
- private var reconnectionTimeoutHandler: StreamTimer?
// MARK: - Init
@@ -49,8 +48,7 @@ final class DefaultConnectionRecoveryHandler: ConnectionRecoveryHandler {
internetConnection: InternetConnection,
reconnectionStrategy: RetryStrategy,
reconnectionTimerType: Timer.Type,
- keepConnectionAliveInBackground: Bool,
- reconnectionTimeoutHandler: StreamTimer?
+ keepConnectionAliveInBackground: Bool
) {
self.webSocketClient = webSocketClient
self.eventNotificationCenter = eventNotificationCenter
@@ -61,7 +59,6 @@ final class DefaultConnectionRecoveryHandler: ConnectionRecoveryHandler {
self.reconnectionStrategy = reconnectionStrategy
self.reconnectionTimerType = reconnectionTimerType
self.keepConnectionAliveInBackground = keepConnectionAliveInBackground
- self.reconnectionTimeoutHandler = reconnectionTimeoutHandler
}
func start() {
@@ -71,7 +68,6 @@ final class DefaultConnectionRecoveryHandler: ConnectionRecoveryHandler {
func stop() {
unsubscribeFromNotifications()
cancelReconnectionTimer()
- reconnectionTimeoutHandler?.stop()
}
deinit {
@@ -94,11 +90,6 @@ private extension DefaultConnectionRecoveryHandler {
name: .internetConnectionAvailabilityDidChange,
object: nil
)
-
- reconnectionTimeoutHandler?.onChange = { [weak self] in
- self?.webSocketClient.timeout()
- self?.cancelReconnectionTimer()
- }
}
func unsubscribeFromNotifications() {
@@ -177,9 +168,6 @@ extension DefaultConnectionRecoveryHandler {
switch state {
case .connecting:
cancelReconnectionTimer()
- if reconnectionTimeoutHandler?.isRunning == false {
- reconnectionTimeoutHandler?.start()
- }
case .connected:
extensionLifecycle.setAppState(isReceivingEvents: true)
@@ -187,7 +175,6 @@ extension DefaultConnectionRecoveryHandler {
syncRepository.syncLocalState {
log.info("Local state sync completed", subsystems: .offlineSupport)
}
- reconnectionTimeoutHandler?.stop()
case .disconnected:
extensionLifecycle.setAppState(isReceivingEvents: false)
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift
index efd3cf5a4e..bd95468abd 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift
@@ -8,6 +8,7 @@ import Foundation
/// Mock implementation of `ChatClientUpdater`
final class ConnectionRepository_Mock: ConnectionRepository, Spy {
enum Signature {
+ static let initialize = "initialize()"
static let connect = "connect(completion:)"
static let disconnect = "disconnect(source:completion:)"
static let forceConnectionInactiveMode = "forceConnectionStatusForInactiveModeIfNeeded()"
@@ -58,6 +59,10 @@ final class ConnectionRepository_Mock: ConnectionRepository, Spy {
// MARK: - Overrides
+ override func initialize() {
+ record()
+ }
+
override func connect(completion: ((Error?) -> Void)? = nil) {
record()
if let result = connectResult {
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift
index 5daf097ba9..e8e6577e71 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift
@@ -14,6 +14,7 @@ class AuthenticationRepository_Mock: AuthenticationRepository, Spy {
static let clearTokenProvider = "clearTokenProvider()"
static let logOut = "logOutUser()"
static let completeTokenWaiters = "completeTokenWaiters(token:)"
+ static let completeTokenCompletions = "completeTokenCompletions(error:)"
static let setToken = "setToken(token:completeTokenWaiters:)"
static let provideToken = "provideToken(timeout:completion:)"
}
@@ -94,9 +95,9 @@ class AuthenticationRepository_Mock: AuthenticationRepository, Spy {
record()
}
- var cancelTimersCallCount: Int = 0
- override func cancelTimers() {
- cancelTimersCallCount += 1
+ var resetCallCount: Int = 0
+ override func reset() {
+ resetCallCount += 1
}
override func completeTokenWaiters(token: Token?) {
@@ -104,6 +105,10 @@ class AuthenticationRepository_Mock: AuthenticationRepository, Spy {
completeWaitersToken = token
}
+ override func completeTokenCompletions(error: (any Error)?) {
+ record()
+ }
+
override func provideToken(timeout: TimeInterval = 10, completion: @escaping (Result) -> Void) {
record()
}
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift
index 169931a561..95180c1fe1 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift
@@ -21,8 +21,6 @@ final class WebSocketClient_Mock: WebSocketClient {
var disconnect_called: Bool { disconnect_calledCounter > 0 }
var disconnect_completion: (() -> Void)?
- var timeout_callCount = 0
-
var mockedConnectionState: WebSocketConnectionState?
@@ -78,10 +76,6 @@ final class WebSocketClient_Mock: WebSocketClient {
disconnect_completion = completion
}
- override func timeout() {
- timeout_callCount += 1
- }
-
var mockEventsBatcher: EventBatcher_Mock {
eventsBatcher as! EventBatcher_Mock
}
diff --git a/Tests/StreamChatTests/ChatClient_Tests.swift b/Tests/StreamChatTests/ChatClient_Tests.swift
index dc2a0b86dd..bd887776ca 100644
--- a/Tests/StreamChatTests/ChatClient_Tests.swift
+++ b/Tests/StreamChatTests/ChatClient_Tests.swift
@@ -462,6 +462,9 @@ final class ChatClient_Tests: XCTestCase {
let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
let userInfo = UserInfo(id: "id")
let authenticationRepository = try XCTUnwrap(client.authenticationRepository as? AuthenticationRepository_Mock)
+ let reconnectionTimeoutHandler = try XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ let connectionRecoveryHandler = try XCTUnwrap(client.connectionRecoveryHandler as? ConnectionRecoveryHandler_Mock)
+ let connectionRepository = try XCTUnwrap(client.connectionRepository as? ConnectionRepository_Mock)
let expectation = self.expectation(description: "Connect completes")
authenticationRepository.connectUserResult = .success(())
@@ -472,8 +475,11 @@ final class ChatClient_Tests: XCTestCase {
}
waitForExpectations(timeout: defaultTimeout)
- XCTAssertCall(AuthenticationRepository_Mock.Signature.connectTokenProvider, on: authenticationRepository)
XCTAssertNil(receivedError)
+ XCTAssertCall(AuthenticationRepository_Mock.Signature.connectTokenProvider, on: authenticationRepository)
+ XCTAssertCall(ConnectionRepository_Mock.Signature.initialize, on: connectionRepository)
+ XCTAssertEqual(reconnectionTimeoutHandler.startCallCount, 1)
+ XCTAssertEqual(connectionRecoveryHandler.startCallCount, 1)
}
// MARK: - Connect Static Token
@@ -598,6 +604,9 @@ final class ChatClient_Tests: XCTestCase {
let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
let userInfo = UserInfo(id: "id")
let authenticationRepository = try XCTUnwrap(client.authenticationRepository as? AuthenticationRepository_Mock)
+ let reconnectionTimeoutHandler = try XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ let connectionRecoveryHandler = try XCTUnwrap(client.connectionRecoveryHandler as? ConnectionRecoveryHandler_Mock)
+ let connectionRepository = try XCTUnwrap(client.connectionRepository as? ConnectionRepository_Mock)
let expectation = self.expectation(description: "Connect completes")
authenticationRepository.connectGuestResult = .success(())
@@ -608,8 +617,11 @@ final class ChatClient_Tests: XCTestCase {
}
waitForExpectations(timeout: defaultTimeout)
- XCTAssertCall(AuthenticationRepository_Mock.Signature.connectGuest, on: authenticationRepository)
XCTAssertNil(receivedError)
+ XCTAssertCall(AuthenticationRepository_Mock.Signature.connectGuest, on: authenticationRepository)
+ XCTAssertCall(ConnectionRepository_Mock.Signature.initialize, on: connectionRepository)
+ XCTAssertEqual(reconnectionTimeoutHandler.startCallCount, 1)
+ XCTAssertEqual(connectionRecoveryHandler.startCallCount, 1)
}
// MARK: - Connect Anonymous
@@ -635,6 +647,9 @@ final class ChatClient_Tests: XCTestCase {
func test_connectAnonymous_tokenProvider_callsAuthenticationRepository_success() throws {
let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
let authenticationRepository = try XCTUnwrap(client.authenticationRepository as? AuthenticationRepository_Mock)
+ let reconnectionTimeoutHandler = try XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ let connectionRecoveryHandler = try XCTUnwrap(client.connectionRecoveryHandler as? ConnectionRecoveryHandler_Mock)
+ let connectionRepository = try XCTUnwrap(client.connectionRepository as? ConnectionRepository_Mock)
let expectation = self.expectation(description: "Connect completes")
authenticationRepository.connectAnonResult = .success(())
@@ -645,8 +660,11 @@ final class ChatClient_Tests: XCTestCase {
}
waitForExpectations(timeout: defaultTimeout)
- XCTAssertCall(AuthenticationRepository_Mock.Signature.connectAnon, on: authenticationRepository)
XCTAssertNil(receivedError)
+ XCTAssertCall(AuthenticationRepository_Mock.Signature.connectAnon, on: authenticationRepository)
+ XCTAssertCall(ConnectionRepository_Mock.Signature.initialize, on: connectionRepository)
+ XCTAssertEqual(reconnectionTimeoutHandler.startCallCount, 1)
+ XCTAssertEqual(connectionRecoveryHandler.startCallCount, 1)
}
// MARK: - Disconnect
@@ -665,7 +683,7 @@ final class ChatClient_Tests: XCTestCase {
XCTAssertCall(ConnectionRepository_Mock.Signature.disconnect, on: connectionRepository)
XCTAssertCall(AuthenticationRepository_Mock.Signature.clearTokenProvider, on: authenticationRepository)
- XCTAssertEqual(client.mockAuthenticationRepository.cancelTimersCallCount, 1)
+ XCTAssertEqual(client.mockAuthenticationRepository.resetCallCount, 1)
}
func test_logout_shouldDisconnect_logOut_andRemoveAllData() throws {
@@ -835,6 +853,97 @@ final class ChatClient_Tests: XCTestCase {
XCTAssertEqual(streamHeader, SystemEnvironment.xStreamClientHeader)
}
+
+ // MARK: - Reconnection Timeout Tests
+
+ func test_reconnectionTimeoutHandler_isInitializedWithConfig() {
+ // Given
+ var config = inMemoryStorageConfig
+ config.reconnectionTimeout = 20
+ let client = ChatClient(config: config)
+
+ // Then
+ XCTAssertNotNil(client.reconnectionTimeoutHandler)
+ }
+
+ func test_reconnectionTimeoutHandler_notInitialisedIfTimeoutNotProvided() {
+ // Given
+ var config = inMemoryStorageConfig
+ config.reconnectionTimeout = nil
+ let client = ChatClient(config: config)
+
+ // Then
+ XCTAssertNil(client.reconnectionTimeoutHandler)
+ }
+
+ func test_reconnectionTimeoutHandler_startsOnConnect() {
+ // Given
+ let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
+ let timerMock = try! XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+
+ // When
+ client.connectAnonymousUser()
+
+ // Then
+ XCTAssertEqual(timerMock.startCallCount, 1)
+ }
+
+ func test_reconnectionTimeoutHandler_stopsOnConnected() {
+ // Given
+ let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
+ let timerMock = try! XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+
+ // When
+ client.webSocketClient(client.webSocketClient!, didUpdateConnectionState: .connected(connectionId: .unique))
+
+ // Then
+ XCTAssertEqual(timerMock.stopCallCount, 1)
+ }
+
+ func test_reconnectionTimeoutHandler_startsOnConnecting() {
+ // Given
+ let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
+ let timerMock = try! XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ timerMock.isRunning = false
+
+ // When
+ client.webSocketClient(client.webSocketClient!, didUpdateConnectionState: .connecting)
+
+ // Then
+ XCTAssertEqual(timerMock.startCallCount, 1)
+ }
+
+ func test_reconnectionTimeoutHandler_whenRunning_doesNotStart() {
+ let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
+ let timerMock = try! XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ timerMock.isRunning = true
+
+ // When
+ client.webSocketClient(client.webSocketClient!, didUpdateConnectionState: .connecting)
+
+ // Then
+ XCTAssertEqual(timerMock.startCallCount, 0)
+ }
+
+ func test_reconnectionTimeout_onChange() throws {
+ // Given
+ let client = ChatClient(config: inMemoryStorageConfig, environment: testEnv.environment)
+ let timerMock = try XCTUnwrap(client.reconnectionTimeoutHandler as? ScheduledStreamTimer_Mock)
+ let authenticationRepository = try XCTUnwrap(client.authenticationRepository as? AuthenticationRepository_Mock)
+ let connectionRepository = try XCTUnwrap(client.connectionRepository as? ConnectionRepository_Mock)
+ connectionRepository.disconnectResult = .success(())
+
+ // When
+ timerMock.onChange?()
+
+ // Then
+ XCTAssertCall(ConnectionRepository_Mock.Signature.disconnect, on: connectionRepository)
+ XCTAssertCall(ConnectionRepository_Mock.Signature.completeConnectionIdWaiters, on: connectionRepository)
+ XCTAssertCall(AuthenticationRepository_Mock.Signature.completeTokenWaiters, on: authenticationRepository)
+ XCTAssertCall(AuthenticationRepository_Mock.Signature.completeTokenCompletions, on: authenticationRepository)
+ XCTAssertEqual(connectionRepository.disconnectSource, .timeout(from: .initialized))
+ XCTAssertEqual(authenticationRepository.resetCallCount, 1)
+ }
}
final class TestWorker: Worker {
@@ -904,6 +1013,9 @@ private class TestEnvironment {
)
return self.databaseContainer!
},
+ reconnectionHandlerBuilder: { _ in
+ ScheduledStreamTimer_Mock()
+ },
requestEncoderBuilder: {
if let encoder = self.requestEncoder {
return encoder
@@ -940,6 +1052,9 @@ private class TestEnvironment {
return self.backgroundTaskScheduler!
},
timerType: VirtualTimeTimer.self,
+ connectionRecoveryHandlerBuilder: { _, _, _, _, _, _, _ in
+ ConnectionRecoveryHandler_Mock()
+ },
authenticationRepositoryBuilder: {
self.authenticationRepository = AuthenticationRepository_Mock(
apiClient: $0,
diff --git a/Tests/StreamChatTests/Repositories/AuthenticationRepository_Tests.swift b/Tests/StreamChatTests/Repositories/AuthenticationRepository_Tests.swift
index 3cdbd7e658..63c54ddb90 100644
--- a/Tests/StreamChatTests/Repositories/AuthenticationRepository_Tests.swift
+++ b/Tests/StreamChatTests/Repositories/AuthenticationRepository_Tests.swift
@@ -1039,11 +1039,12 @@ final class AuthenticationRepository_Tests: XCTestCase {
XCTAssertEqual(state, .newToken)
}
- // MARK: Cancel Timers
+ // MARK: Reset
- func test_cancelTimers() {
+ func test_reset() {
let mockTimer = MockTimer()
FakeTimer.mockTimer = mockTimer
+ retryStrategy.consecutiveFailuresCount = 5
let repository = AuthenticationRepository(
apiClient: apiClient,
databaseContainer: database,
@@ -1059,10 +1060,11 @@ final class AuthenticationRepository_Tests: XCTestCase {
completion: { _ in }
)
- repository.cancelTimers()
+ repository.reset()
// should cancel the connection provider timer and the
// the token provider timer
XCTAssertEqual(mockTimer.cancelCallCount, 2)
+ XCTAssertEqual(retryStrategy.mock_resetConsecutiveFailures.count, 1)
}
// MARK: Helpers
diff --git a/Tests/StreamChatTests/Repositories/ConnectionRepository_Tests.swift b/Tests/StreamChatTests/Repositories/ConnectionRepository_Tests.swift
index 58a1e70192..55a4087d05 100644
--- a/Tests/StreamChatTests/Repositories/ConnectionRepository_Tests.swift
+++ b/Tests/StreamChatTests/Repositories/ConnectionRepository_Tests.swift
@@ -150,19 +150,6 @@ final class ConnectionRepository_Tests: XCTestCase {
// MARK: Disconnect
- func test_disconnect_noConnectionId_shouldReturnWithoutTryingToConnect() {
- XCTAssertNil(repository.connectionId)
-
- let expectation = self.expectation(description: "connect completes")
- repository.disconnect(source: .userInitiated) { expectation.fulfill() }
-
- waitForExpectations(timeout: defaultTimeout)
-
- XCTAssertFalse(webSocketClient.disconnect_called)
- XCTAssertCall(APIClient_Spy.Signature.flushRequestsQueue, on: apiClient)
- XCTAssertCall(SyncRepository_Mock.Signature.cancelRecoveryFlow, on: syncRepository)
- }
-
func test_disconnect_withConnectionId_notInActiveMode_shouldReturnError() {
repository.completeConnectionIdWaiters(connectionId: "123")
XCTAssertNotNil(repository.connectionId)
diff --git a/Tests/StreamChatTests/WebSocketClient/WebSocketClient_Tests.swift b/Tests/StreamChatTests/WebSocketClient/WebSocketClient_Tests.swift
index e992b436fa..6ddf97c582 100644
--- a/Tests/StreamChatTests/WebSocketClient/WebSocketClient_Tests.swift
+++ b/Tests/StreamChatTests/WebSocketClient/WebSocketClient_Tests.swift
@@ -238,7 +238,9 @@ final class WebSocketClient_Tests: XCTestCase {
]
for source in testCases {
+ // reset state
engine?.disconnect_calledCount = 0
+ webSocketClient.connect()
// Call `disconnect` with the given source
webSocketClient.disconnect(source: source) {}
@@ -257,6 +259,17 @@ final class WebSocketClient_Tests: XCTestCase {
}
}
+ func test_disconnect_whenInitialized_shouldDisconnect() {
+ // When in initialized state
+ XCTAssertEqual(webSocketClient.connectionState, .initialized)
+
+ // Call disconnect when not connected
+ webSocketClient.disconnect {}
+
+ // Assert connection state is updated
+ XCTAssertEqual(webSocketClient.connectionState, .disconnected(source: .userInitiated))
+ }
+
func test_connectionState_afterDecodingError() {
// Simulate connection
test_connectionFlow()
diff --git a/Tests/StreamChatTests/Workers/Background/ConnectionRecoveryHandler_Tests.swift b/Tests/StreamChatTests/Workers/Background/ConnectionRecoveryHandler_Tests.swift
index 7584c85b4f..b6b4a2b463 100644
--- a/Tests/StreamChatTests/Workers/Background/ConnectionRecoveryHandler_Tests.swift
+++ b/Tests/StreamChatTests/Workers/Background/ConnectionRecoveryHandler_Tests.swift
@@ -14,7 +14,6 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
var mockBackgroundTaskScheduler: BackgroundTaskScheduler_Mock!
var mockRetryStrategy: RetryStrategy_Spy!
var mockTime: VirtualTime { VirtualTimeTimer.time }
- var mockReconnectionTimeoutHandler: ScheduledStreamTimer_Mock!
override func setUp() {
super.setUp()
@@ -26,7 +25,6 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
mockRetryStrategy = RetryStrategy_Spy()
mockRetryStrategy.mock_nextRetryDelay.returns(5)
mockInternetConnection = .init(notificationCenter: mockChatClient.eventNotificationCenter)
- mockReconnectionTimeoutHandler = ScheduledStreamTimer_Mock()
}
override func tearDown() {
@@ -46,14 +44,6 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
super.tearDown()
}
- func test_reconnectionTimeoutHandler_onChange_shouldTimeout() {
- handler = makeConnectionRecoveryHandler(keepConnectionAliveInBackground: false, withReconnectionTimeout: true)
- mockReconnectionTimeoutHandler.onChange?()
-
- XCTAssertEqual(mockChatClient.mockWebSocketClient.timeout_callCount, 1)
- XCTAssertTrue(mockTime.scheduledTimers.isEmpty)
- }
-
/// keepConnectionAliveInBackground == false
///
/// 1. internet -> OFF (no disconnect, no bg task, no timer)
@@ -511,31 +501,26 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
XCTAssertNotCall("syncLocalState(completion:)", on: mockChatClient.mockSyncRepository)
XCTAssertNil(mockChatClient.mockExtensionLifecycle.receivedIsReceivingEvents)
- XCTAssertEqual(mockReconnectionTimeoutHandler.startCallCount, 0)
}
func test_webSocketStateUpdate_connecting_whenTimeout_whenNotRunning_shouldStartTimeout() {
handler = makeConnectionRecoveryHandler(keepConnectionAliveInBackground: false, withReconnectionTimeout: true)
- mockReconnectionTimeoutHandler.isRunning = false
// Simulate connection update
handler.webSocketClient(mockChatClient.mockWebSocketClient, didUpdateConnectionState: .connecting)
XCTAssertNotCall("syncLocalState(completion:)", on: mockChatClient.mockSyncRepository)
XCTAssertNil(mockChatClient.mockExtensionLifecycle.receivedIsReceivingEvents)
- XCTAssertEqual(mockReconnectionTimeoutHandler.startCallCount, 1)
}
func test_webSocketStateUpdate_connecting_whenTimeout_whenRunning_shouldNotStartTimeout() {
handler = makeConnectionRecoveryHandler(keepConnectionAliveInBackground: false, withReconnectionTimeout: true)
- mockReconnectionTimeoutHandler.isRunning = true
// Simulate connection update
handler.webSocketClient(mockChatClient.mockWebSocketClient, didUpdateConnectionState: .connecting)
XCTAssertNotCall("syncLocalState(completion:)", on: mockChatClient.mockSyncRepository)
XCTAssertNil(mockChatClient.mockExtensionLifecycle.receivedIsReceivingEvents)
- XCTAssertEqual(mockReconnectionTimeoutHandler.startCallCount, 0)
}
func test_webSocketStateUpdate_connected() {
@@ -547,7 +532,6 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
XCTAssertCall(RetryStrategy_Spy.Signature.resetConsecutiveFailures, on: mockRetryStrategy, times: 1)
XCTAssertCall("syncLocalState(completion:)", on: mockChatClient.mockSyncRepository, times: 1)
XCTAssert(mockChatClient.mockExtensionLifecycle.receivedIsReceivingEvents == true)
- XCTAssertEqual(mockReconnectionTimeoutHandler.stopCallCount, 0)
}
func test_webSocketStateUpdate_connected_whenTimeout_shouldStopTimeout() {
@@ -559,7 +543,6 @@ final class ConnectionRecoveryHandler_Tests: XCTestCase {
XCTAssertCall(RetryStrategy_Spy.Signature.resetConsecutiveFailures, on: mockRetryStrategy, times: 1)
XCTAssertCall("syncLocalState(completion:)", on: mockChatClient.mockSyncRepository, times: 1)
XCTAssert(mockChatClient.mockExtensionLifecycle.receivedIsReceivingEvents == true)
- XCTAssertEqual(mockReconnectionTimeoutHandler.stopCallCount, 1)
}
func test_webSocketStateUpdate_disconnected_userInitiated() {
@@ -648,8 +631,7 @@ private extension ConnectionRecoveryHandler_Tests {
internetConnection: mockInternetConnection,
reconnectionStrategy: mockRetryStrategy,
reconnectionTimerType: VirtualTimeTimer.self,
- keepConnectionAliveInBackground: keepConnectionAliveInBackground,
- reconnectionTimeoutHandler: withReconnectionTimeout ? mockReconnectionTimeoutHandler : nil
+ keepConnectionAliveInBackground: keepConnectionAliveInBackground
)
handler.start()
From 88c724bd767fb367a8431bc26fec4738fe221194 Mon Sep 17 00:00:00 2001
From: Alexey Alter-Pesotskiy
Date: Tue, 3 Dec 2024 11:15:34 +0000
Subject: [PATCH 9/9] [CI] Bump Xcode version (#3509)
---
.github/workflows/cron-checks.yml | 28 ++++----
.github/workflows/release-publish.yml | 2 +-
.github/workflows/smoke-checks.yml | 26 ++++----
.github/workflows/update-copyright.yml | 2 +-
CHANGELOG.md | 2 +
.../Robots/UserRobot.swift | 3 +
.../ChatChannelController_Mock.swift | 62 +++++++++---------
.../ChatChannelListController_Mock.swift | 30 ++++-----
.../ChatMessageController_Mock.swift | 26 ++++----
.../ChatMessageSearchController_Mock.swift | 18 ++---
.../ChatThreadListController_Mock.swift | 18 ++---
.../ChatUserSearchController_Mock.swift | 12 ++--
.../CurrentChatUserController_Mock.swift | 8 +--
..._chatChannel_isPopulated.default-light.png | Bin 22778 -> 23014 bytes
...nViewValues_arePopulated.default-light.png | Bin 24331 -> 24551 bytes
...rePopulated.extraExtraExtraLarge-light.png | Bin 27057 -> 27300 bytes
...arePopulated.rightToLeftLayout-default.png | Bin 24537 -> 24765 bytes
...tionViewValues_arePopulated.small-dark.png | Bin 23596 -> 24086 bytes
.../test_defaultAppearance.default-light.png | Bin 26167 -> 26383 bytes
...ibilityWhenAlwaysVisible.default-light.png | Bin 21306 -> 21471 bytes
...lityWhenOnlyVisibleToYou.default-light.png | Bin 17236 -> 17334 bytes
...ithAttachmentsAppearance.default-light.png | Bin 27366 -> 27518 bytes
.../test_emptyAppearance.default-light.png | Bin 15190 -> 15257 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 16715 -> 16193 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 15089 -> 15186 bytes
.../test_emptyAppearance.small-dark.png | Bin 14890 -> 14977 bytes
...ator_AndIsMarkedAsUnread.default-light.png | Bin 23845 -> 24008 bytes
...ator_AndIsMarkedAsUnread.default-light.png | Bin 26190 -> 26419 bytes
...lyEmojiMessageAppearance.default-light.png | Bin 42665 -> 42768 bytes
...eAppearance.extraExtraExtraLarge-light.png | Bin 46217 -> 45872 bytes
...geAppearance.rightToLeftLayout-default.png | Bin 42578 -> 42707 bytes
..._onlyEmojiMessageAppearance.small-dark.png | Bin 42002 -> 41983 bytes
...rance_whenQuotingMessage.default-light.png | Bin 43551 -> 44087 bytes
...tingMessage.extraExtraExtraLarge-light.png | Bin 47011 -> 47184 bytes
...otingMessage.rightToLeftLayout-default.png | Bin 43506 -> 44033 bytes
...pearance_whenQuotingMessage.small-dark.png | Bin 42923 -> 43675 bytes
...DateSeparatorsAppearance.default-light.png | Bin 32681 -> 33001 bytes
...ditedMessageIsNotGrouped.default-light.png | Bin 25054 -> 25159 bytes
...ge_messagesAreNotGrouped.default-light.png | Bin 31165 -> 31544 bytes
...ge_messagesAreNotGrouped.default-light.png | Bin 24758 -> 24878 bytes
...ge_messagesAreNotGrouped.default-light.png | Bin 25030 -> 25164 bytes
...ault-light-after-current-user-reaction.png | Bin 19920 -> 20068 bytes
...ult-light-before-current-user-reaction.png | Bin 19886 -> 20035 bytes
...MessagesStartAtTheTopIsTrue.small-dark.png | Bin 19101 -> 19658 bytes
...enJumpToUnreadIsDisabled.default-light.png | Bin 32697 -> 33271 bytes
...henJumpToUnreadIsEnabled.default-light.png | Bin 32697 -> 33271 bytes
...ouldShowABannerOnTopOfIt.default-light.png | Bin 25755 -> 25977 bytes
...ouldShowABannerOnTopOfIt.default-light.png | Bin 28160 -> 28360 bytes
...rLess_messagesAreGrouped.default-light.png | Bin 23183 -> 23456 bytes
...in_messagesAreNotGrouped.default-light.png | Bin 22656 -> 22833 bytes
...rLess_messagesAreGrouped.default-light.png | Bin 21845 -> 21943 bytes
...in_messagesAreNotGrouped.default-light.png | Bin 20318 -> 20422 bytes
.../Search/ChatMessageSearchVC_Tests.swift | 4 +-
.../test_defaultAppearance.default-light.png | Bin 27904 -> 27857 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 33166 -> 33143 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 27834 -> 27760 bytes
.../test_defaultAppearance.small-dark.png | Bin 27004 -> 26985 bytes
.../test_emptyAppearance.default-light.png | Bin 19623 -> 19613 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 20156 -> 20217 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 19635 -> 19644 bytes
.../test_emptyAppearance.small-dark.png | Bin 20284 -> 20138 bytes
.../test_loadingAppearance.default-light.png | Bin 13340 -> 13304 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 13885 -> 13956 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 13344 -> 13332 bytes
.../test_loadingAppearance.small-dark.png | Bin 13461 -> 13416 bytes
.../test_defaultAppearance.default-light.png | Bin 32311 -> 33829 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 39369 -> 41173 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 32364 -> 33812 bytes
.../test_defaultAppearance.small-dark.png | Bin 31164 -> 32499 bytes
.../test_emptyAppearance.default-light.png | Bin 19623 -> 19613 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 20156 -> 20217 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 19635 -> 19644 bytes
.../test_emptyAppearance.small-dark.png | Bin 20284 -> 20138 bytes
.../test_loadingAppearance.default-light.png | Bin 13340 -> 13304 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 13885 -> 13956 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 13344 -> 13332 bytes
.../test_loadingAppearance.small-dark.png | Bin 13461 -> 13416 bytes
...mization_usingAppearance.default-light.png | Bin 23044 -> 23029 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 23044 -> 23029 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 23044 -> 23029 bytes
...stomization_usingAppearance.small-dark.png | Bin 25688 -> 25678 bytes
...ization_usingSubclassing.default-light.png | Bin 19511 -> 19477 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 19511 -> 19477 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 19511 -> 19477 bytes
...tomization_usingSubclassing.small-dark.png | Bin 19574 -> 19536 bytes
.../test_defaultAppearance.default-light.png | Bin 21789 -> 21777 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 21789 -> 21777 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 21789 -> 21777 bytes
.../test_defaultAppearance.small-dark.png | Bin 23890 -> 23466 bytes
...mization_usingAppearance.default-light.png | Bin 4651 -> 4654 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 4651 -> 4654 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 4666 -> 4662 bytes
...stomization_usingAppearance.small-dark.png | Bin 4651 -> 4654 bytes
...ization_usingSubclassing.default-light.png | Bin 4451 -> 4462 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 4451 -> 4462 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 4489 -> 4494 bytes
...tomization_usingSubclassing.small-dark.png | Bin 4451 -> 4462 bytes
.../test_defaultAppearance.default-light.png | Bin 4651 -> 4654 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 4651 -> 4654 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 4666 -> 4662 bytes
.../test_defaultAppearance.small-dark.png | Bin 4651 -> 4654 bytes
...test_injectedSwiftUIView.default-light.png | Bin 9727 -> 9729 bytes
...SwiftUIView.extraExtraExtraLarge-light.png | Bin 10202 -> 10204 bytes
...dSwiftUIView.rightToLeftLayout-default.png | Bin 9820 -> 9817 bytes
.../test_injectedSwiftUIView.small-dark.png | Bin 9384 -> 9386 bytes
...mization_usingAppearance.default-light.png | Bin 10816 -> 10853 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 11670 -> 11698 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 10844 -> 10902 bytes
...stomization_usingAppearance.small-dark.png | Bin 10737 -> 10729 bytes
...ization_usingSubclassing.default-light.png | Bin 13610 -> 13640 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 16112 -> 16144 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 13633 -> 13667 bytes
...tomization_usingSubclassing.small-dark.png | Bin 13105 -> 13090 bytes
...ance_audioPreviewMessage.default-light.png | Bin 12123 -> 12118 bytes
...sage_whenMultipleMembers.default-light.png | Bin 12457 -> 12460 bytes
...ge_whenSentByCurrentUser.default-light.png | Bin 12520 -> 12530 bytes
...wMessage_whenTextIsEmpty.default-light.png | Bin 11133 -> 11142 bytes
...CurrentUser_readsDisabled.default-dark.png | Bin 12985 -> 12973 bytes
...urrentUser_readsDisabled.default-light.png | Bin 13141 -> 13160 bytes
...mCurrentUser_readsEnabled.default-dark.png | Bin 12985 -> 12973 bytes
...CurrentUser_readsEnabled.default-light.png | Bin 13141 -> 13160 bytes
...rance_filePreviewMessage.default-light.png | Bin 11291 -> 11302 bytes
...sage_whenMultipleMembers.default-light.png | Bin 11684 -> 11697 bytes
...ge_whenSentByCurrentUser.default-light.png | Bin 11728 -> 11748 bytes
...ewMessage_whenTitleIsNil.default-light.png | Bin 12121 -> 12119 bytes
...ance_giphyPreviewMessage.default-light.png | Bin 10791 -> 10799 bytes
...sage_whenMultipleMembers.default-light.png | Bin 11198 -> 11203 bytes
...ge_whenSentByCurrentUser.default-light.png | Bin 11248 -> 11241 bytes
...ance_imagePreviewMessage.default-light.png | Bin 12345 -> 12338 bytes
...sage_whenMultipleMembers.default-light.png | Bin 12755 -> 12745 bytes
...ge_whenSentByCurrentUser.default-light.png | Bin 12809 -> 12810 bytes
...wMessage_whenTextIsEmpty.default-light.png | Bin 11367 -> 11378 bytes
...urrentUser_readsDisabled.default-light.png | Bin 12543 -> 12550 bytes
...CurrentUser_readsEnabled.default-light.png | Bin 12543 -> 12550 bytes
...LatestVoterIsAnotherUser.default-light.png | Bin 12742 -> 12728 bytes
...LatestVoterIsCurrentUser.default-light.png | Bin 12293 -> 12306 bytes
...CurrentUser.extraExtraExtraLarge-light.png | Bin 14415 -> 14476 bytes
...sCurrentUser.rightToLeftLayout-default.png | Bin 12345 -> 12363 bytes
...henLatestVoterIsCurrentUser.small-dark.png | Bin 11646 -> 11653 bytes
...PollCreatedByAnotherUser.default-light.png | Bin 12979 -> 12981 bytes
...PollCreatedByCurrentUser.default-light.png | Bin 12481 -> 12467 bytes
...wMessageIsVoiceRecording.default-light.png | Bin 6458 -> 6445 bytes
...ceRecording.extraExtraExtraLarge-light.png | Bin 7666 -> 7641 bytes
...iceRecording.rightToLeftLayout-default.png | Bin 6587 -> 6548 bytes
...viewMessageIsVoiceRecording.small-dark.png | Bin 6028 -> 6051 bytes
...omAnotherUser_readEnabled.default-dark.png | Bin 12850 -> 12842 bytes
...mAnotherUser_readEnabled.default-light.png | Bin 13091 -> 13115 bytes
...CurrentUser_readsDisabled.default-dark.png | Bin 12610 -> 12593 bytes
...urrentUser_readsDisabled.default-light.png | Bin 12769 -> 12804 bytes
...mCurrentUser_readsEnabled.default-dark.png | Bin 12858 -> 12847 bytes
...CurrentUser_readsEnabled.default-light.png | Bin 13002 -> 13046 bytes
...ppearance_searchedMessage.default-dark.png | Bin 13070 -> 13057 bytes
...age.default-light-without-channel-name.png | Bin 11549 -> 11576 bytes
...pearance_searchedMessage.default-light.png | Bin 13223 -> 13306 bytes
...CurrentUser_readsDisabled.default-dark.png | Bin 12497 -> 12516 bytes
...urrentUser_readsDisabled.default-light.png | Bin 12720 -> 12745 bytes
...mCurrentUser_readsEnabled.default-dark.png | Bin 12611 -> 12628 bytes
...CurrentUser_readsEnabled.default-light.png | Bin 12846 -> 12868 bytes
...ance_systemPreviewMessage.default-dark.png | Bin 11351 -> 11355 bytes
...nce_systemPreviewMessage.default-light.png | Bin 11627 -> 11627 bytes
...earance_translatedMessage.default-dark.png | Bin 10836 -> 10831 bytes
...arance_translatedMessage.default-light.png | Bin 11070 -> 11067 bytes
...Message_whenHasAttachment.default-dark.png | Bin 11552 -> 11570 bytes
...essage_whenHasAttachment.default-light.png | Bin 11787 -> 11795 bytes
...ance_videoPreviewMessage.default-light.png | Bin 12396 -> 12408 bytes
...sage_whenMultipleMembers.default-light.png | Bin 12766 -> 12764 bytes
...ge_whenSentByCurrentUser.default-light.png | Bin 12807 -> 12820 bytes
...wMessage_whenTextIsEmpty.default-light.png | Bin 11421 -> 11428 bytes
.../test_emptyState.default-light.png | Bin 4962 -> 4966 bytes
..._emptyState.extraExtraExtraLarge-light.png | Bin 4962 -> 4966 bytes
...t_emptyState.rightToLeftLayout-default.png | Bin 5115 -> 5086 bytes
.../test_emptyState.small-dark.png | Bin 4847 -> 4856 bytes
...tChannelList_isPopulated.default-light.png | Bin 49006 -> 49155 bytes
...isPopulated.extraExtraExtraLarge-light.png | Bin 54755 -> 54861 bytes
..._isPopulated.rightToLeftLayout-default.png | Bin 49180 -> 49260 bytes
...chatChannelList_isPopulated.small-dark.png | Bin 44929 -> 44790 bytes
...nViewValues_arePopulated.default-light.png | Bin 51043 -> 51174 bytes
...rePopulated.extraExtraExtraLarge-light.png | Bin 56808 -> 56905 bytes
...arePopulated.rightToLeftLayout-default.png | Bin 51042 -> 51173 bytes
...tionViewValues_arePopulated.small-dark.png | Bin 50160 -> 49981 bytes
...omization_usingComponents.default-dark.png | Bin 51313 -> 51113 bytes
...mization_usingComponents.default-light.png | Bin 50892 -> 51032 bytes
...mization_usingSubclassing.default-dark.png | Bin 49558 -> 49417 bytes
...ization_usingSubclassing.default-light.png | Bin 49030 -> 49202 bytes
...appearance_withSearchBar.default-light.png | Bin 52652 -> 52800 bytes
...thSearchBar.extraExtraExtraLarge-light.png | Bin 58332 -> 58410 bytes
...ithSearchBar.rightToLeftLayout-default.png | Bin 52871 -> 52946 bytes
...st_appearance_withSearchBar.small-dark.png | Bin 51584 -> 51387 bytes
.../test_defaultAppearance.default-light.png | Bin 50881 -> 51026 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 56428 -> 56510 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 51086 -> 51164 bytes
.../test_defaultAppearance.small-dark.png | Bin 49819 -> 49632 bytes
.../test_emptyAppearance.default-light.png | Bin 27949 -> 27908 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 28507 -> 28528 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 27959 -> 27952 bytes
.../test_emptyAppearance.small-dark.png | Bin 29698 -> 28853 bytes
...test_injectedSwiftUIView.default-light.png | Bin 1789 -> 1791 bytes
...SwiftUIView.extraExtraExtraLarge-light.png | Bin 2073 -> 2075 bytes
...dSwiftUIView.rightToLeftLayout-default.png | Bin 1799 -> 1794 bytes
.../test_injectedSwiftUIView.small-dark.png | Bin 1588 -> 1588 bytes
...mization_usingComponents.default-light.png | Bin 799 -> 803 bytes
...gComponents.extraExtraExtraLarge-light.png | Bin 1071 -> 1060 bytes
...ngComponents.rightToLeftLayout-default.png | Bin 799 -> 803 bytes
...stomization_usingComponents.small-dark.png | Bin 776 -> 775 bytes
...ization_usingSubclassing.default-light.png | Bin 1657 -> 1679 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 1585 -> 1605 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 1657 -> 1679 bytes
...tomization_usingSubclassing.small-dark.png | Bin 1571 -> 1597 bytes
...efaultAppearance.default-light-2digits.png | Bin 895 -> 900 bytes
...efaultAppearance.default-light-3digits.png | Bin 1094 -> 1138 bytes
...nce.extraExtraExtraLarge-light-2digits.png | Bin 1179 -> 1176 bytes
...nce.extraExtraExtraLarge-light-3digits.png | Bin 1495 -> 1512 bytes
...ance.rightToLeftLayout-default-2digits.png | Bin 895 -> 900 bytes
...ance.rightToLeftLayout-default-3digits.png | Bin 1094 -> 1138 bytes
...t_defaultAppearance.small-dark-2digits.png | Bin 875 -> 867 bytes
...t_defaultAppearance.small-dark-3digits.png | Bin 1061 -> 1081 bytes
...mization_usingAppearance.default-light.png | Bin 5194 -> 5365 bytes
...ization_usingSubclassing.default-light.png | Bin 5630 -> 5822 bytes
.../test_appearance_pdf.default-light.png | Bin 5155 -> 5306 bytes
...nDownloadedThenShareIcon.default-light.png | Bin 5561 -> 5730 bytes
...rance_pdf_whenSizeIsZero.default-light.png | Bin 4929 -> 5099 bytes
..._whenUploadingStateIsNil.default-light.png | Bin 5155 -> 5306 bytes
...t_appearance_whenUnknown.default-light.png | Bin 6155 -> 6315 bytes
...ization_usingSubclassing.default-light.png | Bin 17668 -> 17823 bytes
...earance_five_attachments.default-light.png | Bin 21937 -> 22532 bytes
...ppearance_one_attachment.default-light.png | Bin 5252 -> 5415 bytes
...pearance_two_attachments.default-light.png | Bin 9436 -> 9741 bytes
...mization_usingAppearance.default-light.png | Bin 69921 -> 69920 bytes
...ce_whenMoreThanFourImages.default-dark.png | Bin 68979 -> 68973 bytes
...e_whenMoreThanFourImages.default-light.png | Bin 68979 -> 68973 bytes
.../test_appearance.default-light.png | Bin 48623 -> 48761 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 45875 -> 46075 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 47292 -> 47422 bytes
.../test_appearance.small-dark.png | Bin 49665 -> 49785 bytes
...mization_usingAppearance.default-light.png | Bin 48676 -> 48820 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 45937 -> 46133 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 47352 -> 47481 bytes
...stomization_usingAppearance.small-dark.png | Bin 49714 -> 49833 bytes
...ization_usingSubclassing.default-light.png | Bin 48736 -> 48887 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 45843 -> 46037 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 47426 -> 47555 bytes
...tomization_usingSubclassing.small-dark.png | Bin 49825 -> 49947 bytes
...appearance_whenLongTexts.default-light.png | Bin 51273 -> 51426 bytes
...enLongTexts.extraExtraExtraLarge-light.png | Bin 50183 -> 50378 bytes
...henLongTexts.rightToLeftLayout-default.png | Bin 50370 -> 50505 bytes
...st_appearance_whenLongTexts.small-dark.png | Bin 51976 -> 52097 bytes
..._appearance_whenNoAuthor.default-light.png | Bin 53584 -> 53623 bytes
...henNoAuthor.extraExtraExtraLarge-light.png | Bin 54063 -> 54099 bytes
...whenNoAuthor.rightToLeftLayout-default.png | Bin 53585 -> 53623 bytes
...est_appearance_whenNoAuthor.small-dark.png | Bin 53551 -> 53586 bytes
...rance_whenNoImagePreview.default-light.png | Bin 3401 -> 3450 bytes
...magePreview.extraExtraExtraLarge-light.png | Bin 4532 -> 4583 bytes
...ImagePreview.rightToLeftLayout-default.png | Bin 3416 -> 3455 bytes
...pearance_whenNoImagePreview.small-dark.png | Bin 3238 -> 3269 bytes
...t_appearance_whenNoTitle.default-light.png | Bin 48013 -> 48178 bytes
...whenNoTitle.extraExtraExtraLarge-light.png | Bin 44950 -> 45149 bytes
..._whenNoTitle.rightToLeftLayout-default.png | Bin 46711 -> 46853 bytes
...test_appearance_whenNoTitle.small-dark.png | Bin 49138 -> 49263 bytes
...rance_whenNoTitleAndText.default-light.png | Bin 47658 -> 47815 bytes
...itleAndText.extraExtraExtraLarge-light.png | Bin 44314 -> 44510 bytes
...TitleAndText.rightToLeftLayout-default.png | Bin 46266 -> 46405 bytes
...pearance_whenNoTitleAndText.small-dark.png | Bin 48696 -> 48803 bytes
...ization_usingSubclassing.default-light.png | Bin 18206 -> 18288 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 22318 -> 22374 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 17749 -> 17781 bytes
...tomization_usingSubclassing.small-dark.png | Bin 16570 -> 16759 bytes
...earance_five_attachments.default-light.png | Bin 35641 -> 35968 bytes
...attachments.extraExtraExtraLarge-light.png | Bin 40462 -> 40669 bytes
..._attachments.rightToLeftLayout-default.png | Bin 35203 -> 35457 bytes
...appearance_five_attachments.small-dark.png | Bin 33740 -> 34013 bytes
...ppearance_one_attachment.default-light.png | Bin 7141 -> 7181 bytes
..._attachment.extraExtraExtraLarge-light.png | Bin 7758 -> 7835 bytes
...e_attachment.rightToLeftLayout-default.png | Bin 7015 -> 7112 bytes
...t_appearance_one_attachment.small-dark.png | Bin 6654 -> 6776 bytes
...pearance_two_attachments.default-light.png | Bin 14280 -> 14515 bytes
...attachments.extraExtraExtraLarge-light.png | Bin 16026 -> 16128 bytes
..._attachments.rightToLeftLayout-default.png | Bin 14046 -> 14248 bytes
..._appearance_two_attachments.small-dark.png | Bin 13471 -> 13729 bytes
...omization_usingAppearance.default-dark.png | Bin 2431 -> 2789 bytes
...mization_usingAppearance.default-light.png | Bin 2413 -> 2826 bytes
...mization_usingSubclassing.default-dark.png | Bin 2162 -> 2301 bytes
...ization_usingSubclassing.default-light.png | Bin 2167 -> 2246 bytes
...pearance_whenContentIsSet.default-dark.png | Bin 2636 -> 2845 bytes
...earance_whenContentIsSet.default-light.png | Bin 2576 -> 2825 bytes
...pearance_whenNoContentSet.default-dark.png | Bin 2431 -> 2789 bytes
...earance_whenNoContentSet.default-light.png | Bin 2413 -> 2826 bytes
...ge-reactions-text-threadInfo-timestamp.png | Bin 18127 -> 18528 bytes
...quotedMessage-reactions-text-timestamp.png | Bin 15101 -> 15478 bytes
...ar-bubble-quotedMessage-text-timestamp.png | Bin 13707 -> 14008 bytes
...avatar-bubble-reactions-text-timestamp.png | Bin 9783 -> 9917 bytes
...ult-light-avatar-bubble-text-timestamp.png | Bin 8281 -> 8431 bytes
...uotedMessage-reactions-text-threadInfo.png | Bin 15229 -> 15609 bytes
...ousBubble-quotedMessage-reactions-text.png | Bin 12192 -> 12564 bytes
...le-continuousBubble-quotedMessage-text.png | Bin 10625 -> 10927 bytes
...bubble-continuousBubble-reactions-text.png | Bin 6962 -> 7099 bytes
...zePadding-bubble-continuousBubble-text.png | Bin 5348 -> 5497 bytes
...ge-reactions-text-threadInfo-timestamp.png | Bin 15744 -> 15905 bytes
...uotedMessage-reactions-text-threadInfo.png | Bin 14588 -> 14743 bytes
...ge-reactions-text-threadInfo-timestamp.png | Bin 15397 -> 15566 bytes
...uotedMessage-reactions-text-threadInfo.png | Bin 14236 -> 14407 bytes
...e-flipped-quotedMessage-reactions-text.png | Bin 11202 -> 11347 bytes
...nuousBubble-flipped-quotedMessage-text.png | Bin 9607 -> 9783 bytes
...ontinuousBubble-flipped-reactions-text.png | Bin 6095 -> 6079 bytes
...t-bubble-continuousBubble-flipped-text.png | Bin 5017 -> 5026 bytes
...quotedMessage-reactions-text-timestamp.png | Bin 12275 -> 12450 bytes
...e-flipped-quotedMessage-text-timestamp.png | Bin 10864 -> 11051 bytes
...ubble-flipped-reactions-text-timestamp.png | Bin 7747 -> 7736 bytes
...lt-light-bubble-flipped-text-timestamp.png | Bin 5802 -> 5829 bytes
...ization_usingSubclassing.default-light.png | Bin 5873 -> 5901 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 8830 -> 8887 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 5897 -> 5919 bytes
...tomization_usingSubclassing.small-dark.png | Bin 5432 -> 5540 bytes
...tomization_usingUIConfig.default-light.png | Bin 5873 -> 5901 bytes
...ingUIConfig.extraExtraExtraLarge-light.png | Bin 8830 -> 8887 bytes
...singUIConfig.rightToLeftLayout-default.png | Bin 5897 -> 5919 bytes
...Customization_usingUIConfig.small-dark.png | Bin 5432 -> 5540 bytes
...ppearanceForErrorMessage.default-light.png | Bin 3315 -> 3312 bytes
...rrorMessage.extraExtraExtraLarge-light.png | Bin 5680 -> 5676 bytes
...ErrorMessage.rightToLeftLayout-default.png | Bin 3315 -> 3312 bytes
...t_appearanceForErrorMessage.small-dark.png | Bin 3156 -> 3144 bytes
...pearanceForSystemMessage.default-light.png | Bin 3315 -> 3312 bytes
...stemMessage.extraExtraExtraLarge-light.png | Bin 5680 -> 5676 bytes
...ystemMessage.rightToLeftLayout-default.png | Bin 3315 -> 3312 bytes
..._appearanceForSystemMessage.small-dark.png | Bin 3156 -> 3144 bytes
...eCurrentUserIsPendingSend.default-dark.png | Bin 5319 -> 5360 bytes
...CurrentUserIsPendingSend.default-light.png | Bin 5307 -> 5314 bytes
..._inDirectMesssagesChannel.default-dark.png | Bin 8760 -> 8852 bytes
...inDirectMesssagesChannel.default-light.png | Bin 8643 -> 8765 bytes
...UserIsRead_inGroupChannel.default-dark.png | Bin 7625 -> 7724 bytes
...serIsRead_inGroupChannel.default-light.png | Bin 7534 -> 7636 bytes
...eFromTheCurrentUserIsSent.default-dark.png | Bin 4814 -> 4851 bytes
...FromTheCurrentUserIsSent.default-light.png | Bin 4819 -> 4827 bytes
...arance_whenMessageHasLink.default-dark.png | Bin 99871 -> 100009 bytes
...rance_whenMessageHasLink.default-light.png | Bin 99051 -> 99282 bytes
...essageHasLinkAndMarkdown.default-light.png | Bin 102215 -> 102778 bytes
...MessageHasLinkAndMention.default-light.png | Bin 102318 -> 102712 bytes
...ssageHasLinkWithoutAuthor.default-dark.png | Bin 103165 -> 103298 bytes
...sageHasLinkWithoutAuthor.default-light.png | Bin 102939 -> 103140 bytes
...essageHasLinkWithoutImage.default-dark.png | Bin 21528 -> 21446 bytes
...ssageHasLinkWithoutImage.default-light.png | Bin 21044 -> 21019 bytes
...LinkWithoutImageAndAuthor.default-dark.png | Bin 19830 -> 19771 bytes
...inkWithoutImageAndAuthor.default-light.png | Bin 19352 -> 19443 bytes
...lation_whenHasAttachment.default-light.png | Bin 103494 -> 103789 bytes
...n_whenIsSentByCurrentUser.default-dark.png | Bin 5655 -> 5693 bytes
..._whenIsSentByCurrentUser.default-light.png | Bin 5625 -> 5657 bytes
...lation_whenNotLastInGroup.default-dark.png | Bin 4629 -> 4658 bytes
...ation_whenNotLastInGroup.default-light.png | Bin 4602 -> 4615 bytes
..._whenNotSentByCurrentUser.default-dark.png | Bin 6918 -> 7045 bytes
...whenNotSentByCurrentUser.default-light.png | Bin 6925 -> 7024 bytes
...nce_whenMessageIsBounced.default-light.png | Bin 3713 -> 3750 bytes
...ance_whenMessageIsEdited.default-light.png | Bin 5203 -> 5208 bytes
...ageIsEdited.extraExtraExtraLarge-light.png | Bin 6990 -> 7050 bytes
...sageIsEdited.rightToLeftLayout-default.png | Bin 5221 -> 5234 bytes
...earance_whenMessageIsEdited.small-dark.png | Bin 4792 -> 4829 bytes
...shouldNotShowEditedLabel.default-light.png | Bin 5148 -> 5189 bytes
...eWithAMentionedUserIsSent.default-dark.png | Bin 8093 -> 8174 bytes
...WithAMentionedUserIsSent.default-light.png | Bin 7919 -> 8023 bytes
...nt_whenDuplicatedMentions.default-dark.png | Bin 8256 -> 8389 bytes
...t_whenDuplicatedMentions.default-light.png | Bin 8105 -> 8252 bytes
...onedUserIsSent_whenNoName.default-dark.png | Bin 7571 -> 7652 bytes
...nedUserIsSent_whenNoName.default-light.png | Bin 7460 -> 7553 bytes
...nFromTheCurrentUserIsSent.default-dark.png | Bin 14598 -> 14635 bytes
...FromTheCurrentUserIsSent.default-light.png | Bin 14443 -> 14522 bytes
...eFromTheCurrentUserIsSent.default-dark.png | Bin 45428 -> 45537 bytes
...FromTheCurrentUserIsSent.default-light.png | Bin 43888 -> 43983 bytes
...tFromTheCurrentUserIsSent.default-dark.png | Bin 16696 -> 16637 bytes
...FromTheCurrentUserIsSent.default-light.png | Bin 16661 -> 16696 bytes
...tFromTheCurrentUserIsSent.default-dark.png | Bin 11654 -> 11731 bytes
...FromTheCurrentUserIsSent.default-light.png | Bin 11576 -> 11674 bytes
...oteMessageHasTranslation.default-light.png | Bin 8290 -> 8602 bytes
...ssageIsReadInGroupChannel.default-dark.png | Bin 817 -> 818 bytes
.../test_appearance.default-light.png | Bin 29095 -> 29155 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 27491 -> 27580 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 29111 -> 29121 bytes
.../test_appearance.small-dark.png | Bin 28053 -> 28131 bytes
..._appearance_whenIsClosed.default-light.png | Bin 26084 -> 26072 bytes
...henIsClosed.extraExtraExtraLarge-light.png | Bin 24782 -> 24785 bytes
...whenIsClosed.rightToLeftLayout-default.png | Bin 26200 -> 26169 bytes
...est_appearance_whenIsClosed.small-dark.png | Bin 25712 -> 25705 bytes
.../test_appearance.default-light.png | Bin 38066 -> 38341 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 47469 -> 47593 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 38254 -> 38330 bytes
.../test_appearance.small-dark.png | Bin 35693 -> 36091 bytes
...appearance_whenAnonymous.default-light.png | Bin 38946 -> 38944 bytes
...est_appearance_whenClosed.default-dark.png | Bin 33898 -> 34149 bytes
...st_appearance_whenClosed.default-light.png | Bin 33135 -> 33297 bytes
...oreThanMaxVisibleOptions.default-light.png | Bin 36114 -> 36341 bytes
...e_whenSuggestionsEnabled.default-light.png | Bin 46406 -> 46594 bytes
...t_appearance_withComments.default-dark.png | Bin 45331 -> 45391 bytes
..._appearance_withComments.default-light.png | Bin 43252 -> 43579 bytes
...urrentUserAlreadyComment.default-light.png | Bin 40940 -> 41136 bytes
...earance_withZeroComments.default-light.png | Bin 40560 -> 40808 bytes
.../test_appearance.default-light.png | Bin 27292 -> 27542 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 29836 -> 29955 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 27292 -> 27542 bytes
.../test_appearance.small-dark.png | Bin 27131 -> 27419 bytes
...nce_whenAlreadyCommented.default-light.png | Bin 36530 -> 36777 bytes
...dyCommented.extraExtraExtraLarge-light.png | Bin 41155 -> 41300 bytes
...adyCommented.rightToLeftLayout-default.png | Bin 36530 -> 36777 bytes
...arance_whenAlreadyCommented.small-dark.png | Bin 36039 -> 36492 bytes
.../test_appearance.default-light.png | Bin 33811 -> 34250 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 34623 -> 35061 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 33769 -> 34236 bytes
.../test_appearance.small-dark.png | Bin 34937 -> 35650 bytes
...earance_whenCanCreatePoll.default-dark.png | Bin 35708 -> 36566 bytes
...arance_whenCanCreatePoll.default-light.png | Bin 34625 -> 35062 bytes
...nFeaturesEnabledByDefault.default-dark.png | Bin 40177 -> 40975 bytes
...FeaturesEnabledByDefault.default-light.png | Bin 38936 -> 39679 bytes
..._whenFeaturesNotSupported.default-dark.png | Bin 19357 -> 19397 bytes
...whenFeaturesNotSupported.default-light.png | Bin 18705 -> 18795 bytes
..._whenMaxVotesOnlyDisabled.default-dark.png | Bin 36069 -> 36752 bytes
...whenMaxVotesOnlyDisabled.default-light.png | Bin 34969 -> 35648 bytes
...st_appearance_withErrors.default-light.png | Bin 37506 -> 38006 bytes
..._withErrors.extraExtraExtraLarge-light.png | Bin 42833 -> 43328 bytes
...e_withErrors.rightToLeftLayout-default.png | Bin 37419 -> 37986 bytes
.../test_appearance_withErrors.small-dark.png | Bin 37527 -> 38238 bytes
.../test_appearance.default-light.png | Bin 59012 -> 59301 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 67923 -> 68172 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 59012 -> 59301 bytes
.../test_appearance.small-dark.png | Bin 63706 -> 64964 bytes
...appearance_whenAnonymous.default-light.png | Bin 33979 -> 34352 bytes
...est_appearance_whenClosed.default-dark.png | Bin 68383 -> 69653 bytes
...st_appearance_whenClosed.default-light.png | Bin 68040 -> 68256 bytes
...ce_whenVotesMoreThanLimit.default-dark.png | Bin 69881 -> 71196 bytes
...e_whenVotesMoreThanLimit.default-light.png | Bin 69315 -> 69567 bytes
.../test_appearance.default-light.png | Bin 29188 -> 29240 bytes
..._appearance.extraExtraExtraLarge-light.png | Bin 33083 -> 33150 bytes
...t_appearance.rightToLeftLayout-default.png | Bin 29188 -> 29240 bytes
.../test_appearance.small-dark.png | Bin 27995 -> 28116 bytes
.../test_customAppearance.default-dark.png | Bin 86491 -> 85628 bytes
.../test_customAppearance.default-light.png | Bin 86365 -> 85656 bytes
.../test_defaultAppearance.default-light.png | Bin 55004 -> 54641 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 58413 -> 58036 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 55004 -> 54641 bytes
.../test_defaultAppearance.small-dark.png | Bin 53874 -> 53223 bytes
...nderUnavailableReactions.default-light.png | Bin 17808 -> 17869 bytes
...dUseSingularLocalization.default-light.png | Bin 17808 -> 17869 bytes
...dMessages_hundredMessages.default-dark.png | Bin 2729 -> 2785 bytes
...Messages_hundredMessages.default-light.png | Bin 2729 -> 2785 bytes
...readMessages_zeroMessages.default-dark.png | Bin 2471 -> 2519 bytes
...eadMessages_zeroMessages.default-light.png | Bin 2471 -> 2519 bytes
...mization_usingAppearance.default-light.png | Bin 4678 -> 4681 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 4678 -> 4681 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 4698 -> 4700 bytes
...stomization_usingAppearance.small-dark.png | Bin 4676 -> 4681 bytes
...ization_usingSubclassing.default-light.png | Bin 5249 -> 5246 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 5249 -> 5246 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 5251 -> 5254 bytes
...tomization_usingSubclassing.small-dark.png | Bin 5514 -> 5511 bytes
.../test_defaultAppearance.default-light.png | Bin 3764 -> 3760 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 3764 -> 3760 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 3750 -> 3743 bytes
.../test_defaultAppearance.small-dark.png | Bin 4059 -> 4066 bytes
...chatThreadVC_isPopulated.default-light.png | Bin 34788 -> 35131 bytes
...nViewValues_arePopulated.default-light.png | Bin 39355 -> 39627 bytes
...rePopulated.extraExtraExtraLarge-light.png | Bin 43641 -> 43947 bytes
...arePopulated.rightToLeftLayout-default.png | Bin 39622 -> 39985 bytes
...tionViewValues_arePopulated.small-dark.png | Bin 38215 -> 38937 bytes
.../test_defaultAppearance.default-light.png | Bin 37029 -> 37327 bytes
.../test_emptyAppearance.default-light.png | Bin 21947 -> 22187 bytes
...MessagesStartAtTheTopIsTrue.small-dark.png | Bin 29110 -> 29848 bytes
...entMessageEnabledIsFalse.default-light.png | Bin 22824 -> 23030 bytes
.../test_defaultAppearance.default-light.png | Bin 14196 -> 14231 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 17676 -> 17691 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 14242 -> 14278 bytes
.../test_defaultAppearance.small-dark.png | Bin 13466 -> 13508 bytes
...pearance_whenAttachments.default-light.png | Bin 12886 -> 12923 bytes
...nce_withLastReplyDeleted.default-light.png | Bin 15014 -> 15029 bytes
...withParentMessageDeleted.default-light.png | Bin 14852 -> 14874 bytes
...pearance_withThreadTitle.default-light.png | Bin 13903 -> 13935 bytes
...ltAppearance_withUnreads.default-light.png | Bin 14637 -> 14683 bytes
...withUnreads.extraExtraExtraLarge-light.png | Bin 18421 -> 18410 bytes
..._withUnreads.rightToLeftLayout-default.png | Bin 14679 -> 14702 bytes
...faultAppearance_withUnreads.small-dark.png | Bin 13867 -> 13885 bytes
.../test_defaultAppearance.default-light.png | Bin 26945 -> 27008 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 33674 -> 33693 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 26953 -> 27006 bytes
.../test_defaultAppearance.small-dark.png | Bin 25887 -> 25918 bytes
...owLoadingHeaderBannerView.default-dark.png | Bin 28264 -> 28326 bytes
...wLoadingHeaderBannerView.default-light.png | Bin 27480 -> 27538 bytes
...owLoadingHeaderBannerView.default-dark.png | Bin 28264 -> 28326 bytes
...wLoadingHeaderBannerView.default-light.png | Bin 27480 -> 27538 bytes
.../test_emptyAppearance.default-light.png | Bin 12436 -> 12459 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 12436 -> 12459 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 12442 -> 12454 bytes
.../test_emptyAppearance.small-dark.png | Bin 12907 -> 12897 bytes
.../test_errorAppearance.default-light.png | Bin 10553 -> 10558 bytes
...rAppearance.extraExtraExtraLarge-light.png | Bin 10553 -> 10558 bytes
...orAppearance.rightToLeftLayout-default.png | Bin 10556 -> 10564 bytes
.../test_errorAppearance.small-dark.png | Bin 10563 -> 10569 bytes
...est_newThreadsAppearance.default-light.png | Bin 29194 -> 29245 bytes
...sAppearance.extraExtraExtraLarge-light.png | Bin 35914 -> 35967 bytes
...dsAppearance.rightToLeftLayout-default.png | Bin 29205 -> 29261 bytes
.../test_newThreadsAppearance.small-dark.png | Bin 28181 -> 28199 bytes
..._wasConfiguredAsExpected.default-light.png | Bin 6395 -> 6485 bytes
...dAsExpected.extraExtraExtraLarge-light.png | Bin 7054 -> 7122 bytes
...edAsExpected.rightToLeftLayout-default.png | Bin 6412 -> 6494 bytes
...nce_wasConfiguredAsExpected.small-dark.png | Bin 5926 -> 6087 bytes
...alItems_rendersCorrectly.default-light.png | Bin 15304 -> 15570 bytes
...rsCorrectly.extraExtraExtraLarge-light.png | Bin 16453 -> 16681 bytes
...ersCorrectly.rightToLeftLayout-default.png | Bin 15266 -> 15488 bytes
...ticalItems_rendersCorrectly.small-dark.png | Bin 14054 -> 14346 bytes
...alItems_rendersCorrectly.default-light.png | Bin 23775 -> 24009 bytes
...rsCorrectly.extraExtraExtraLarge-light.png | Bin 23874 -> 24221 bytes
...ersCorrectly.rightToLeftLayout-default.png | Bin 23660 -> 23943 bytes
...ticalItems_rendersCorrectly.small-dark.png | Bin 21883 -> 22058 bytes
...ngAppearanceAndComponents.default-dark.png | Bin 6509 -> 6584 bytes
...gAppearanceAndComponents.default-light.png | Bin 6511 -> 6583 bytes
...mization_usingSubclassing.default-dark.png | Bin 5675 -> 5729 bytes
...ization_usingSubclassing.default-light.png | Bin 5675 -> 5729 bytes
...FourMembersInNonDMChannel.default-dark.png | Bin 6450 -> 6509 bytes
...ourMembersInNonDMChannel.default-light.png | Bin 6450 -> 6509 bytes
...nceWithNoMembersInChannel.default-dark.png | Bin 2980 -> 2981 bytes
...ceWithNoMembersInChannel.default-light.png | Bin 2980 -> 2981 bytes
...ingleMemberInNonDMChannel.default-dark.png | Bin 6654 -> 6691 bytes
...ngleMemberInNonDMChannel.default-light.png | Bin 6654 -> 6691 bytes
...hreeMembersInNonDMChannel.default-dark.png | Bin 6355 -> 6397 bytes
...reeMembersInNonDMChannel.default-light.png | Bin 6355 -> 6397 bytes
...hTwoMembersInNonDMChannel.default-dark.png | Bin 6206 -> 6247 bytes
...TwoMembersInNonDMChannel.default-light.png | Bin 6206 -> 6247 bytes
...nel.default-dark-with-online-indicator.png | Bin 6622 -> 6683 bytes
..._withDirectMessageChannel.default-dark.png | Bin 6666 -> 6716 bytes
...el.default-light-with-online-indicator.png | Bin 6622 -> 6683 bytes
...withDirectMessageChannel.default-light.png | Bin 6666 -> 6716 bytes
...annel_whenMultipleMembers.default-dark.png | Bin 6993 -> 7058 bytes
...nnel_whenMultipleMembers.default-light.png | Bin 6993 -> 7058 bytes
.../test_emptyAppearance.default-dark.png | Bin 3867 -> 3870 bytes
.../test_emptyAppearance.default-light.png | Bin 3867 -> 3870 bytes
...hannelAvatarViewInSwiftUI.default-dark.png | Bin 12024 -> 11982 bytes
...annelAvatarViewInSwiftUI.default-light.png | Bin 11087 -> 11068 bytes
...ngAppearanceAndComponents.default-dark.png | Bin 6509 -> 6584 bytes
...gAppearanceAndComponents.default-light.png | Bin 6511 -> 6583 bytes
...mization_usingSubclassing.default-dark.png | Bin 5675 -> 5729 bytes
...ization_usingSubclassing.default-light.png | Bin 5675 -> 5729 bytes
...nOnlineIndicatorDisabled.default-light.png | Bin 6666 -> 6716 bytes
...nce.default-dark-with-online-indicator.png | Bin 6622 -> 6683 bytes
....default-dark-without-online-indicator.png | Bin 6666 -> 6716 bytes
...ce.default-light-with-online-indicator.png | Bin 6622 -> 6683 bytes
...default-light-without-online-indicator.png | Bin 6666 -> 6716 bytes
.../test_emptyAppearance.default-dark.png | Bin 3326 -> 3319 bytes
.../test_emptyAppearance.default-light.png | Bin 3326 -> 3319 bytes
...ionUsingSubclassing.default-dark-empty.png | Bin 3278 -> 3277 bytes
...omizationUsingSubclassing.default-dark.png | Bin 6491 -> 6517 bytes
...onUsingSubclassing.default-light-empty.png | Bin 3278 -> 3277 bytes
...mizationUsingSubclassing.default-light.png | Bin 6491 -> 6517 bytes
.../test_defaultAppearance.default-dark.png | Bin 3152 -> 3149 bytes
.../test_defaultAppearance.default-light.png | Bin 3184 -> 3179 bytes
.../test_emptyAppearance.default-dark.png | Bin 1987 -> 1964 bytes
.../test_emptyAppearance.default-light.png | Bin 1987 -> 1964 bytes
.../test_channelNameSet.default-light.png | Bin 3446 -> 3439 bytes
...nnelNameSet.extraExtraExtraLarge-light.png | Bin 5050 -> 5056 bytes
...annelNameSet.rightToLeftLayout-default.png | Bin 3446 -> 3439 bytes
.../test_channelNameSet.small-dark.png | Bin 3369 -> 3362 bytes
.../test_defaultAppearance.default-light.png | Bin 1781 -> 1858 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 2282 -> 2300 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 1786 -> 1837 bytes
.../test_defaultAppearance.small-dark.png | Bin 1729 -> 1747 bytes
...efaultAppearance_SwiftUI.default-light.png | Bin 9485 -> 9588 bytes
...nce_SwiftUI.extraExtraExtraLarge-light.png | Bin 10348 -> 10217 bytes
...ance_SwiftUI.rightToLeftLayout-default.png | Bin 9443 -> 9548 bytes
...t_defaultAppearance_SwiftUI.small-dark.png | Bin 10603 -> 10526 bytes
.../test_emptyAppearance.default-light.png | Bin 873 -> 895 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 873 -> 895 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 870 -> 905 bytes
.../test_emptyAppearance.small-dark.png | Bin 873 -> 895 bytes
...nfiguredCorrectly.default-light-bigger.png | Bin 1806 -> 1874 bytes
...dCorrectly.default-light-isHighlighted.png | Bin 1300 -> 1240 bytes
...e_wasConfiguredCorrectly.default-light.png | Bin 1322 -> 1270 bytes
...ctly.extraExtraExtraLarge-light-bigger.png | Bin 1806 -> 1874 bytes
...traExtraExtraLarge-light-isHighlighted.png | Bin 1300 -> 1240 bytes
...edCorrectly.extraExtraExtraLarge-light.png | Bin 1322 -> 1270 bytes
...ectly.rightToLeftLayout-default-bigger.png | Bin 1806 -> 1874 bytes
...ightToLeftLayout-default-isHighlighted.png | Bin 1300 -> 1240 bytes
...redCorrectly.rightToLeftLayout-default.png | Bin 1322 -> 1270 bytes
...sConfiguredCorrectly.small-dark-bigger.png | Bin 1806 -> 1874 bytes
...uredCorrectly.small-dark-isHighlighted.png | Bin 1243 -> 1182 bytes
...ance_wasConfiguredCorrectly.small-dark.png | Bin 1322 -> 1270 bytes
...test_injectedSwiftUIView.default-light.png | Bin 3288 -> 3291 bytes
...mization_usingComponents.default-light.png | Bin 5536 -> 5620 bytes
...ization_usingSubclassing.default-light.png | Bin 12515 -> 12834 bytes
.../test_defaultAppearance.default-light.png | Bin 4300 -> 4406 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 5018 -> 5161 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 4268 -> 4376 bytes
.../test_defaultAppearance.small-dark.png | Bin 4444 -> 4647 bytes
.../test_emptyAppearance.default-dark.png | Bin 2561 -> 2744 bytes
.../test_emptyAppearance.default-light.png | Bin 2546 -> 2718 bytes
...rAlignmentLeftAppearance.default-light.png | Bin 4300 -> 4406 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 5018 -> 5161 bytes
...ftAppearance.rightToLeftLayout-default.png | Bin 4268 -> 4376 bytes
...atarAlignmentLeftAppearance.small-dark.png | Bin 4444 -> 4647 bytes
...AlignmentRightAppearance.default-light.png | Bin 4268 -> 4376 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 4972 -> 5113 bytes
...htAppearance.rightToLeftLayout-default.png | Bin 4300 -> 4406 bytes
...tarAlignmentRightAppearance.small-dark.png | Bin 4416 -> 4615 bytes
...mentAppearance.default-light-emptyText.png | Bin 4814 -> 4969 bytes
...FileAttachmentAppearance.default-light.png | Bin 5175 -> 5337 bytes
...e.extraExtraExtraLarge-light-emptyText.png | Bin 5396 -> 5556 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 6000 -> 6158 bytes
...ce.rightToLeftLayout-default-emptyText.png | Bin 4773 -> 4988 bytes
...ntAppearance.rightToLeftLayout-default.png | Bin 5135 -> 5341 bytes
...achmentAppearance.small-dark-emptyText.png | Bin 5050 -> 5361 bytes
...ithFileAttachmentAppearance.small-dark.png | Bin 5414 -> 5711 bytes
...ce_currentUser.default-light-emptyText.png | Bin 4774 -> 4996 bytes
...ntAppearance_currentUser.default-light.png | Bin 5135 -> 5361 bytes
...r.extraExtraExtraLarge-light-emptyText.png | Bin 5356 -> 5583 bytes
...currentUser.extraExtraExtraLarge-light.png | Bin 5962 -> 6180 bytes
...er.rightToLeftLayout-default-emptyText.png | Bin 4815 -> 5010 bytes
..._currentUser.rightToLeftLayout-default.png | Bin 5179 -> 5367 bytes
...rance_currentUser.small-dark-emptyText.png | Bin 5010 -> 5337 bytes
...hmentAppearance_currentUser.small-dark.png | Bin 5373 -> 5700 bytes
...mentAppearance.default-light-emptyText.png | Bin 7337 -> 7578 bytes
...iphyAttachmentAppearance.default-light.png | Bin 7942 -> 8200 bytes
...e.extraExtraExtraLarge-light-emptyText.png | Bin 7866 -> 8041 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 8807 -> 9001 bytes
...ce.rightToLeftLayout-default-emptyText.png | Bin 7294 -> 7506 bytes
...ntAppearance.rightToLeftLayout-default.png | Bin 7899 -> 8118 bytes
...achmentAppearance.small-dark-emptyText.png | Bin 7401 -> 7663 bytes
...thGiphyAttachmentAppearance.small-dark.png | Bin 8056 -> 8286 bytes
...mentAppearance.default-light-emptyText.png | Bin 7270 -> 7515 bytes
...mageAttachmentAppearance.default-light.png | Bin 7942 -> 8200 bytes
...e.extraExtraExtraLarge-light-emptyText.png | Bin 7708 -> 7907 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 8807 -> 9001 bytes
...ce.rightToLeftLayout-default-emptyText.png | Bin 7218 -> 7439 bytes
...ntAppearance.rightToLeftLayout-default.png | Bin 7899 -> 8118 bytes
...achmentAppearance.small-dark-emptyText.png | Bin 7362 -> 7590 bytes
...thImageAttachmentAppearance.small-dark.png | Bin 8056 -> 8286 bytes
...ce_currentUser.default-light-emptyText.png | Bin 7225 -> 7463 bytes
...ntAppearance_currentUser.default-light.png | Bin 7897 -> 8147 bytes
...r.extraExtraExtraLarge-light-emptyText.png | Bin 7667 -> 7930 bytes
...currentUser.extraExtraExtraLarge-light.png | Bin 8772 -> 9011 bytes
...er.rightToLeftLayout-default-emptyText.png | Bin 7267 -> 7491 bytes
..._currentUser.rightToLeftLayout-default.png | Bin 7945 -> 8170 bytes
...rance_currentUser.small-dark-emptyText.png | Bin 7324 -> 7566 bytes
...hmentAppearance_currentUser.small-dark.png | Bin 8017 -> 8264 bytes
...LinkAttachmentAppearance.default-light.png | Bin 9252 -> 9547 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 10822 -> 11104 bytes
...ntAppearance.rightToLeftLayout-default.png | Bin 9199 -> 9494 bytes
...ithLinkAttachmentAppearance.small-dark.png | Bin 9213 -> 9486 bytes
...ntAppearance_currentUser.default-light.png | Bin 9209 -> 9532 bytes
...currentUser.extraExtraExtraLarge-light.png | Bin 10775 -> 11088 bytes
..._currentUser.rightToLeftLayout-default.png | Bin 9222 -> 9495 bytes
...hmentAppearance_currentUser.small-dark.png | Bin 9174 -> 9458 bytes
...t_withLongTextAppearance.default-light.png | Bin 11318 -> 11572 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 13646 -> 13859 bytes
...xtAppearance.rightToLeftLayout-default.png | Bin 11304 -> 11504 bytes
...test_withLongTextAppearance.small-dark.png | Bin 11566 -> 11894 bytes
.../test_withPoll.default-light.png | Bin 5397 -> 5529 bytes
.../test_withTranslatedText.default-light.png | Bin 3214 -> 3369 bytes
...dText_whenHasAttachments.default-light.png | Bin 6980 -> 7228 bytes
...ithUnsupportedAttachment.default-light.png | Bin 4209 -> 4408 bytes
...dAttachment.extraExtraExtraLarge-light.png | Bin 4637 -> 4826 bytes
...edAttachment.rightToLeftLayout-default.png | Bin 4167 -> 4414 bytes
...t_withUnsupportedAttachment.small-dark.png | Bin 4566 -> 4873 bytes
...Attachment_whenEmptyText.default-light.png | Bin 6181 -> 6393 bytes
...dingAttachmentAppearance.default-light.png | Bin 7594 -> 7915 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 8144 -> 8459 bytes
...ntAppearance.rightToLeftLayout-default.png | Bin 7525 -> 7858 bytes
...cordingAttachmentAppearance.small-dark.png | Bin 7480 -> 7938 bytes
.../test_wrappedInSwiftUI.default-light.png | Bin 11842 -> 11973 bytes
...ngAppearance.default-dark-new-disabled.png | Bin 1559 -> 1558 bytes
...ltAppearance.default-dark-new-disabled.png | Bin 822 -> 846 bytes
...mization_usingAppearance.default-light.png | Bin 5804 -> 5808 bytes
...ization_usingSubclassing.default-light.png | Bin 4521 -> 4524 bytes
.../test_defaultAppearance.default-light.png | Bin 4187 -> 4193 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 4710 -> 4704 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 4181 -> 4186 bytes
.../test_defaultAppearance.small-dark.png | Bin 3769 -> 3771 bytes
...default-light-online-indicator-visible.png | Bin 7266 -> 7279 bytes
...onents.default-light-user-name-not-set.png | Bin 7137 -> 7178 bytes
...mization_usingComponents.default-light.png | Bin 7316 -> 7326 bytes
...raLarge-light-online-indicator-visible.png | Bin 8627 -> 8640 bytes
...xtraExtraLarge-light-user-name-not-set.png | Bin 7771 -> 7801 bytes
...gComponents.extraExtraExtraLarge-light.png | Bin 8705 -> 8720 bytes
...ayout-default-online-indicator-visible.png | Bin 7383 -> 7387 bytes
...ToLeftLayout-default-user-name-not-set.png | Bin 7168 -> 7189 bytes
...ngComponents.rightToLeftLayout-default.png | Bin 7422 -> 7423 bytes
...ts.small-dark-online-indicator-visible.png | Bin 7162 -> 7176 bytes
...omponents.small-dark-user-name-not-set.png | Bin 7061 -> 7081 bytes
...stomization_usingComponents.small-dark.png | Bin 7234 -> 7250 bytes
...assing.default-light-user-name-not-set.png | Bin 5916 -> 5924 bytes
...ng.default-light-with-online-indicator.png | Bin 7271 -> 7271 bytes
...ization_usingSubclassing.default-light.png | Bin 7242 -> 7235 bytes
...xtraExtraLarge-light-user-name-not-set.png | Bin 6619 -> 6644 bytes
...ExtraLarge-light-with-online-indicator.png | Bin 9035 -> 9057 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 8997 -> 9014 bytes
...ToLeftLayout-default-user-name-not-set.png | Bin 5952 -> 5980 bytes
...ftLayout-default-with-online-indicator.png | Bin 7282 -> 7279 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 7249 -> 7255 bytes
...bclassing.small-dark-user-name-not-set.png | Bin 5663 -> 5691 bytes
...ssing.small-dark-with-online-indicator.png | Bin 6724 -> 6724 bytes
...tomization_usingSubclassing.small-dark.png | Bin 6695 -> 6686 bytes
...default-light-online-indicator-visible.png | Bin 7202 -> 7227 bytes
...arance.default-light-user-name-not-set.png | Bin 7193 -> 7222 bytes
.../test_defaultAppearance.default-light.png | Bin 7153 -> 7174 bytes
...raLarge-light-online-indicator-visible.png | Bin 8576 -> 8587 bytes
...xtraExtraLarge-light-user-name-not-set.png | Bin 7825 -> 7842 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 8536 -> 8538 bytes
...ayout-default-online-indicator-visible.png | Bin 7235 -> 7236 bytes
...ToLeftLayout-default-user-name-not-set.png | Bin 7149 -> 7192 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 7189 -> 7187 bytes
...ce.small-dark-online-indicator-visible.png | Bin 7122 -> 7135 bytes
...ppearance.small-dark-user-name-not-set.png | Bin 6993 -> 7029 bytes
.../test_defaultAppearance.small-dark.png | Bin 7081 -> 7086 bytes
.../test_emptyAppearance.default-light.png | Bin 3824 -> 3811 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 3824 -> 3811 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 3845 -> 3843 bytes
.../test_emptyAppearance.small-dark.png | Bin 3944 -> 3921 bytes
...LabelGetsShown.default-light-with-name.png | Bin 7202 -> 7227 bytes
...elGetsShown.default-light-without-name.png | Bin 7193 -> 7222 bytes
...mization_usingAppearance.default-light.png | Bin 1801 -> 1801 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 2012 -> 2012 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 1801 -> 1801 bytes
...stomization_usingAppearance.small-dark.png | Bin 1812 -> 1815 bytes
...ization_usingSubclassing.default-light.png | Bin 1257 -> 1257 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 1257 -> 1257 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 1257 -> 1257 bytes
...tomization_usingSubclassing.small-dark.png | Bin 1257 -> 1257 bytes
...t_defaultAppearance.default-light-full.png | Bin 2063 -> 2065 bytes
...Appearance.default-light-only-subtitle.png | Bin 1573 -> 1576 bytes
...arance.extraExtraExtraLarge-light-full.png | Bin 2743 -> 2746 bytes
...traExtraExtraLarge-light-only-subtitle.png | Bin 2198 -> 2197 bytes
....extraExtraExtraLarge-light-only-title.png | Bin 1440 -> 1444 bytes
...earance.rightToLeftLayout-default-full.png | Bin 2063 -> 2065 bytes
...ightToLeftLayout-default-only-subtitle.png | Bin 1573 -> 1576 bytes
...test_defaultAppearance.small-dark-full.png | Bin 1982 -> 1978 bytes
...ultAppearance.small-dark-only-subtitle.png | Bin 1507 -> 1507 bytes
...efaultAppearance.small-dark-only-title.png | Bin 1222 -> 1223 bytes
.../test_swiftUIWrapper.default-light.png | Bin 13077 -> 13336 bytes
...ftUIWrapper.extraExtraExtraLarge-light.png | Bin 15316 -> 15316 bytes
...iftUIWrapper.rightToLeftLayout-default.png | Bin 13077 -> 13336 bytes
.../test_swiftUIWrapper.small-dark.png | Bin 13043 -> 13043 bytes
...omization_usingComponents.default-dark.png | Bin 17493 -> 17530 bytes
...mization_usingComponents.default-light.png | Bin 16460 -> 16518 bytes
...ization_usingSubclassing.default-light.png | Bin 18074 -> 18421 bytes
...mmands_defaultAppearance.default-light.png | Bin 16748 -> 16811 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 19727 -> 19777 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 16778 -> 16856 bytes
..._commands_defaultAppearance.small-dark.png | Bin 16225 -> 16286 bytes
..._commands_emptyAppearance.default-dark.png | Bin 6247 -> 6322 bytes
...commands_emptyAppearance.default-light.png | Bin 6114 -> 6160 bytes
...omization_usingComponents.default-dark.png | Bin 16654 -> 16683 bytes
...mization_usingComponents.default-light.png | Bin 16259 -> 16390 bytes
...ization_usingSubclassing.default-light.png | Bin 16173 -> 16211 bytes
...ntions_defaultAppearance.default-light.png | Bin 16013 -> 16149 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 18163 -> 18258 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 16067 -> 16185 bytes
..._mentions_defaultAppearance.small-dark.png | Bin 15842 -> 15895 bytes
..._mentions_emptyAppearance.default-dark.png | Bin 3488 -> 3662 bytes
...mentions_emptyAppearance.default-light.png | Bin 3636 -> 3697 bytes
...light-horizontal-axisLeading-equal-0-0.png | Bin 3277 -> 3273 bytes
...ight-horizontal-axisLeading-equal-32-0.png | Bin 3344 -> 3343 bytes
...light-horizontal-axisLeading-equal-inf.png | Bin 3337 -> 3334 bytes
...ght-horizontal-axisLeading-natural-0-0.png | Bin 3126 -> 3130 bytes
...ht-horizontal-axisLeading-natural-32-0.png | Bin 3230 -> 3233 bytes
...ght-horizontal-axisLeading-natural-inf.png | Bin 3187 -> 3188 bytes
...ight-horizontal-axisTrailing-equal-0-0.png | Bin 3277 -> 3273 bytes
...ght-horizontal-axisTrailing-equal-32-0.png | Bin 3344 -> 3343 bytes
...ight-horizontal-axisTrailing-equal-inf.png | Bin 3337 -> 3334 bytes
...ht-horizontal-axisTrailing-natural-0-0.png | Bin 3126 -> 3130 bytes
...t-horizontal-axisTrailing-natural-32-0.png | Bin 3230 -> 3233 bytes
...ht-horizontal-axisTrailing-natural-inf.png | Bin 3187 -> 3188 bytes
...ault-light-horizontal-center-equal-0-0.png | Bin 3277 -> 3273 bytes
...ult-light-horizontal-center-equal-32-0.png | Bin 3344 -> 3343 bytes
...ault-light-horizontal-center-equal-inf.png | Bin 3337 -> 3334 bytes
...lt-light-horizontal-center-natural-0-0.png | Bin 3126 -> 3130 bytes
...t-light-horizontal-center-natural-32-0.png | Bin 3230 -> 3233 bytes
...lt-light-horizontal-center-natural-inf.png | Bin 3187 -> 3188 bytes
...efault-light-horizontal-fill-equal-0-0.png | Bin 3277 -> 3273 bytes
...fault-light-horizontal-fill-equal-32-0.png | Bin 3344 -> 3343 bytes
...efault-light-horizontal-fill-equal-inf.png | Bin 3337 -> 3334 bytes
...ault-light-horizontal-fill-natural-0-0.png | Bin 3126 -> 3130 bytes
...ult-light-horizontal-fill-natural-32-0.png | Bin 3230 -> 3233 bytes
...ault-light-horizontal-fill-natural-inf.png | Bin 3187 -> 3188 bytes
...t-light-vertical-axisLeading-equal-0-0.png | Bin 3123 -> 3125 bytes
...-light-vertical-axisLeading-equal-32-0.png | Bin 3303 -> 3307 bytes
...t-light-vertical-axisLeading-equal-inf.png | Bin 3183 -> 3185 bytes
...light-vertical-axisLeading-natural-0-0.png | Bin 3123 -> 3125 bytes
...ight-vertical-axisLeading-natural-32-0.png | Bin 3303 -> 3307 bytes
...light-vertical-axisLeading-natural-inf.png | Bin 3183 -> 3185 bytes
...-light-vertical-axisTrailing-equal-0-0.png | Bin 3111 -> 3112 bytes
...light-vertical-axisTrailing-equal-32-0.png | Bin 3291 -> 3298 bytes
...-light-vertical-axisTrailing-equal-inf.png | Bin 3174 -> 3176 bytes
...ight-vertical-axisTrailing-natural-0-0.png | Bin 3111 -> 3112 bytes
...ght-vertical-axisTrailing-natural-32-0.png | Bin 3291 -> 3298 bytes
...ight-vertical-axisTrailing-natural-inf.png | Bin 3174 -> 3176 bytes
...efault-light-vertical-center-equal-0-0.png | Bin 3142 -> 3145 bytes
...fault-light-vertical-center-equal-32-0.png | Bin 3310 -> 3313 bytes
...efault-light-vertical-center-equal-inf.png | Bin 3190 -> 3193 bytes
...ault-light-vertical-center-natural-0-0.png | Bin 3142 -> 3145 bytes
...ult-light-vertical-center-natural-32-0.png | Bin 3310 -> 3313 bytes
...ault-light-vertical-center-natural-inf.png | Bin 3190 -> 3193 bytes
....default-light-vertical-fill-equal-0-0.png | Bin 3119 -> 3121 bytes
...default-light-vertical-fill-equal-32-0.png | Bin 3304 -> 3302 bytes
....default-light-vertical-fill-equal-inf.png | Bin 3180 -> 3180 bytes
...efault-light-vertical-fill-natural-0-0.png | Bin 3119 -> 3121 bytes
...fault-light-vertical-fill-natural-32-0.png | Bin 3304 -> 3302 bytes
...efault-light-vertical-fill-natural-inf.png | Bin 3180 -> 3180 bytes
...thOneViewOnly.default-light-horizontal.png | Bin 1678 -> 1689 bytes
...withOneViewOnly.default-light-vertical.png | Bin 1678 -> 1689 bytes
...dCorrectly.default-light-isHighlighted.png | Bin 5175 -> 5243 bytes
...e_wasConfiguredCorrectly.default-light.png | Bin 5238 -> 5315 bytes
...traExtraExtraLarge-light-isHighlighted.png | Bin 5175 -> 5243 bytes
...edCorrectly.extraExtraExtraLarge-light.png | Bin 5238 -> 5315 bytes
...ightToLeftLayout-default-isHighlighted.png | Bin 5175 -> 5243 bytes
...redCorrectly.rightToLeftLayout-default.png | Bin 5238 -> 5315 bytes
...uredCorrectly.small-dark-isHighlighted.png | Bin 4180 -> 4257 bytes
...ance_wasConfiguredCorrectly.small-dark.png | Bin 5238 -> 5315 bytes
...ments_addedOneAfterThree.default-light.png | Bin 25127 -> 25700 bytes
...eAfterThree.extraExtraExtraLarge-light.png | Bin 26784 -> 27328 bytes
...neAfterThree.rightToLeftLayout-default.png | Bin 25091 -> 25576 bytes
...achments_addedOneAfterThree.small-dark.png | Bin 24893 -> 25604 bytes
...ttachments_addedSameTime.default-light.png | Bin 25127 -> 25700 bytes
...dedSameTime.extraExtraExtraLarge-light.png | Bin 26784 -> 27328 bytes
...ddedSameTime.rightToLeftLayout-default.png | Bin 25091 -> 25576 bytes
...urAttachments_addedSameTime.small-dark.png | Bin 24893 -> 25604 bytes
...ments_addedThreeAfterOne.default-light.png | Bin 25127 -> 25700 bytes
...reeAfterOne.extraExtraExtraLarge-light.png | Bin 26784 -> 27328 bytes
...hreeAfterOne.rightToLeftLayout-default.png | Bin 25091 -> 25576 bytes
...achments_addedThreeAfterOne.small-dark.png | Bin 24893 -> 25604 bytes
...chments_addedTwoAfterTwo.default-light.png | Bin 25127 -> 25700 bytes
...TwoAfterTwo.extraExtraExtraLarge-light.png | Bin 26784 -> 27328 bytes
...dTwoAfterTwo.rightToLeftLayout-default.png | Bin 25091 -> 25576 bytes
...ttachments_addedTwoAfterTwo.small-dark.png | Bin 24893 -> 25604 bytes
...review_withLongFileNames.default-light.png | Bin 17753 -> 18103 bytes
...hMultipleAttachmentTypes.default-light.png | Bin 51585 -> 52103 bytes
...chmentTypes.extraExtraExtraLarge-light.png | Bin 53323 -> 53834 bytes
...achmentTypes.rightToLeftLayout-default.png | Bin 51682 -> 52134 bytes
...withMultipleAttachmentTypes.small-dark.png | Bin 50151 -> 50849 bytes
.../test_canNotSendMessage.default-light.png | Bin 12635 -> 12722 bytes
...SendMessage.extraExtraExtraLarge-light.png | Bin 12971 -> 13096 bytes
...tSendMessage.rightToLeftLayout-default.png | Bin 12708 -> 12817 bytes
.../test_canNotSendMessage.small-dark.png | Bin 12248 -> 12487 bytes
...deIsOnWithCountdownShown.default-light.png | Bin 10803 -> 10930 bytes
...deIsOnWithCountdownShown.default-light.png | Bin 10760 -> 10858 bytes
...ntdownShown.extraExtraExtraLarge-light.png | Bin 11656 -> 11837 bytes
...untdownShown.rightToLeftLayout-default.png | Bin 10756 -> 10892 bytes
...wModeIsOnWithCountdownShown.small-dark.png | Bin 10725 -> 11015 bytes
...rgs_hasSendButtonEnabled.default-light.png | Bin 13920 -> 14149 bytes
...ttonEnabled.extraExtraExtraLarge-light.png | Bin 14209 -> 14414 bytes
...uttonEnabled.rightToLeftLayout-default.png | Bin 13978 -> 14221 bytes
...tyArgs_hasSendButtonEnabled.small-dark.png | Bin 13682 -> 13956 bytes
...gs_hasSendButtonDisabled.default-light.png | Bin 13163 -> 13388 bytes
...tonDisabled.extraExtraExtraLarge-light.png | Bin 13694 -> 13887 bytes
...ttonDisabled.rightToLeftLayout-default.png | Bin 13152 -> 13338 bytes
...yArgs_hasSendButtonDisabled.small-dark.png | Bin 12628 -> 12950 bytes
.../test_dismissLinkPreview.default-light.png | Bin 22586 -> 22758 bytes
...abled_thenNoHighlighting.default-light.png | Bin 20447 -> 20599 bytes
...bled_thenHighlightsLinks.default-light.png | Bin 22586 -> 22758 bytes
..._quotedTranslatedMessage.default-light.png | Bin 17158 -> 17448 bytes
...atedMessage.extraExtraExtraLarge-light.png | Bin 18903 -> 19248 bytes
...latedMessage.rightToLeftLayout-default.png | Bin 17075 -> 17362 bytes
...est_quotedTranslatedMessage.small-dark.png | Bin 16727 -> 17238 bytes
...owCommandSuggestionsView.default-light.png | Bin 12257 -> 12468 bytes
.../test_showLinkPreview.default-light.png | Bin 27227 -> 27377 bytes
...wLinkPreview.rightToLeftLayout-default.png | Bin 27214 -> 27378 bytes
.../test_showLinkPreview.small-dark.png | Bin 26038 -> 26368 bytes
...review_whenNoDescription.default-light.png | Bin 22234 -> 22399 bytes
...wLinkPreview_whenNoImage.default-light.png | Bin 23941 -> 24072 bytes
...nkPreview_whenNoMetadata.default-light.png | Bin 22885 -> 23043 bytes
...wLinkPreview_whenNoTitle.default-light.png | Bin 24605 -> 24745 bytes
...owMentionSuggestionsView.default-light.png | Bin 14084 -> 14213 bytes
...ization_usingSubclassing.default-light.png | Bin 243858 -> 243853 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 244867 -> 244883 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 312585 -> 312605 bytes
...tomization_usingSubclassing.small-dark.png | Bin 244892 -> 244891 bytes
...tomization_usingUIConfig.default-light.png | Bin 243722 -> 243695 bytes
...ingUIConfig.extraExtraExtraLarge-light.png | Bin 244856 -> 244844 bytes
...singUIConfig.rightToLeftLayout-default.png | Bin 312438 -> 312399 bytes
...Customization_usingUIConfig.small-dark.png | Bin 243065 -> 243043 bytes
.../test_defaultAppearance.default-light.png | Bin 243858 -> 243853 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 244867 -> 244883 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 312585 -> 312605 bytes
.../test_defaultAppearance.small-dark.png | Bin 243820 -> 243809 bytes
...mization_usingAppearance.default-light.png | Bin 2070 -> 2070 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 2561 -> 2577 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 2082 -> 2080 bytes
...stomization_usingAppearance.small-dark.png | Bin 1925 -> 1934 bytes
...ization_usingSubclassing.default-light.png | Bin 2067 -> 2064 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 2597 -> 2599 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 2095 -> 2089 bytes
...tomization_usingSubclassing.small-dark.png | Bin 1874 -> 1877 bytes
.../test_defaultAppearance.default-light.png | Bin 2056 -> 2055 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 2598 -> 2595 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 2088 -> 2087 bytes
.../test_defaultAppearance.small-dark.png | Bin 2041 -> 2046 bytes
...pearance_whenDestructive.default-light.png | Bin 2079 -> 2079 bytes
...Destructive.extraExtraExtraLarge-light.png | Bin 2603 -> 2599 bytes
...nDestructive.rightToLeftLayout-default.png | Bin 2111 -> 2096 bytes
...tAppearance_whenDestructive.small-dark.png | Bin 1945 -> 1948 bytes
...pearance_whenHighlighted.default-light.png | Bin 1834 -> 1833 bytes
...Highlighted.extraExtraExtraLarge-light.png | Bin 2341 -> 2340 bytes
...nHighlighted.rightToLeftLayout-default.png | Bin 1883 -> 1884 bytes
...tAppearance_whenHighlighted.small-dark.png | Bin 1787 -> 1786 bytes
...ltAppearance_whenPrimary.default-light.png | Bin 2151 -> 2153 bytes
...whenPrimary.extraExtraExtraLarge-light.png | Bin 2697 -> 2699 bytes
..._whenPrimary.rightToLeftLayout-default.png | Bin 2176 -> 2176 bytes
...faultAppearance_whenPrimary.small-dark.png | Bin 2059 -> 2063 bytes
...henPrimaryAndDestructive.default-light.png | Bin 2079 -> 2079 bytes
...Destructive.extraExtraExtraLarge-light.png | Bin 2603 -> 2599 bytes
...dDestructive.rightToLeftLayout-default.png | Bin 2111 -> 2096 bytes
...e_whenPrimaryAndDestructive.small-dark.png | Bin 1945 -> 1948 bytes
...faultMultilineAppearance.default-light.png | Bin 5267 -> 5267 bytes
...eAppearance.extraExtraExtraLarge-light.png | Bin 6952 -> 6957 bytes
...neAppearance.rightToLeftLayout-default.png | Bin 5293 -> 5292 bytes
..._defaultMultilineAppearance.small-dark.png | Bin 4878 -> 4875 bytes
...mization_usingAppearance.default-light.png | Bin 20387 -> 20538 bytes
...gAppearance.extraExtraExtraLarge-light.png | Bin 24454 -> 24629 bytes
...ngAppearance.rightToLeftLayout-default.png | Bin 20434 -> 20589 bytes
...stomization_usingAppearance.small-dark.png | Bin 19394 -> 19500 bytes
...ization_usingSubclassing.default-light.png | Bin 18331 -> 18432 bytes
...Subclassing.extraExtraExtraLarge-light.png | Bin 21862 -> 22020 bytes
...gSubclassing.rightToLeftLayout-default.png | Bin 18363 -> 18473 bytes
...tomization_usingSubclassing.small-dark.png | Bin 17423 -> 17552 bytes
.../test_defaultAppearance.default-light.png | Bin 20397 -> 20487 bytes
...tAppearance.extraExtraExtraLarge-light.png | Bin 24484 -> 24645 bytes
...ltAppearance.rightToLeftLayout-default.png | Bin 20444 -> 20567 bytes
.../test_defaultAppearance.small-dark.png | Bin 19350 -> 19455 bytes
.../test_emptyAppearance.default-light.png | Bin 8080 -> 7658 bytes
...yAppearance.extraExtraExtraLarge-light.png | Bin 8080 -> 7658 bytes
...tyAppearance.rightToLeftLayout-default.png | Bin 8080 -> 7658 bytes
.../test_emptyAppearance.small-dark.png | Bin 8080 -> 7639 bytes
...tions_hasCorrectOrdering.default-light.png | Bin 26376 -> 26450 bytes
...omization_usingAppearance.default-dark.png | Bin 27443 -> 27840 bytes
...mization_usingAppearance.default-light.png | Bin 26483 -> 26743 bytes
...mization_usingSubclassing.default-dark.png | Bin 26338 -> 26464 bytes
...ization_usingSubclassing.default-light.png | Bin 25222 -> 25460 bytes
...nce_when_largeLongMessage.default-dark.png | Bin 45655 -> 45957 bytes
...ce_when_largeLongMessage.default-light.png | Bin 44107 -> 44492 bytes
...ouldOnlyHaveSmallerHeight.default-dark.png | Bin 21636 -> 21436 bytes
...uldOnlyHaveSmallerHeight.default-light.png | Bin 21421 -> 21143 bytes
...n4_shouldHaveSmallerWidth.default-dark.png | Bin 20451 -> 20386 bytes
...4_shouldHaveSmallerWidth.default-light.png | Bin 20289 -> 20298 bytes
...an4_shouldHaveBiggerWidth.default-dark.png | Bin 25809 -> 25964 bytes
...n4_shouldHaveBiggerWidth.default-light.png | Bin 25933 -> 26116 bytes
...d_snapshotsAreAsExpected.default-light.png | Bin 6454 -> 6424 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 6454 -> 6424 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 6481 -> 6441 bytes
...used_snapshotsAreAsExpected.small-dark.png | Bin 6615 -> 6547 bytes
...g_snapshotsAreAsExpected.default-light.png | Bin 6250 -> 6259 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 6250 -> 6259 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 6231 -> 6235 bytes
...ying_snapshotsAreAsExpected.small-dark.png | Bin 6392 -> 6400 bytes
...g_snapshotsAreAsExpected.default-light.png | Bin 3352 -> 3350 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 3352 -> 3350 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 3377 -> 3379 bytes
...ding_snapshotsAreAsExpected.small-dark.png | Bin 3423 -> 3424 bytes
...hotsAreAsExpected.default-light-Locked.png | Bin 1161 -> 1190 bytes
...e_snapshotsAreAsExpected.default-light.png | Bin 1503 -> 1544 bytes
...cted.extraExtraExtraLarge-light-Locked.png | Bin 1161 -> 1190 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 1503 -> 1544 bytes
...ected.rightToLeftLayout-default-Locked.png | Bin 1161 -> 1190 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 1503 -> 1544 bytes
...apshotsAreAsExpected.small-dark-Locked.png | Bin 1130 -> 1175 bytes
...ance_snapshotsAreAsExpected.small-dark.png | Bin 1509 -> 1625 bytes
...pected.default-light-IncreasedDuration.png | Bin 1886 -> 1885 bytes
...e_snapshotsAreAsExpected.default-light.png | Bin 1955 -> 1956 bytes
...xtraExtraLarge-light-IncreasedDuration.png | Bin 1886 -> 1885 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 1955 -> 1956 bytes
...ToLeftLayout-default-IncreasedDuration.png | Bin 1879 -> 1880 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 1949 -> 1949 bytes
...sExpected.small-dark-IncreasedDuration.png | Bin 1886 -> 1885 bytes
...ance_snapshotsAreAsExpected.small-dark.png | Bin 1955 -> 1956 bytes
...e_snapshotsAreAsExpected.default-light.png | Bin 3473 -> 3470 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 3473 -> 3470 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 3473 -> 3470 bytes
...ance_snapshotsAreAsExpected.small-dark.png | Bin 3529 -> 3525 bytes
...eAsExpected.default-light-AlmostHidden.png | Bin 2527 -> 2521 bytes
...e_snapshotsAreAsExpected.default-light.png | Bin 3259 -> 3265 bytes
...xtraExtraExtraLarge-light-AlmostHidden.png | Bin 2527 -> 2521 bytes
...eAsExpected.extraExtraExtraLarge-light.png | Bin 3259 -> 3265 bytes
...rightToLeftLayout-default-AlmostHidden.png | Bin 2533 -> 2529 bytes
...reAsExpected.rightToLeftLayout-default.png | Bin 3273 -> 3277 bytes
...sAreAsExpected.small-dark-AlmostHidden.png | Bin 2527 -> 2521 bytes
...ance_snapshotsAreAsExpected.small-dark.png | Bin 3259 -> 3265 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 13941 -> 14544 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 13927 -> 14332 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 14505 -> 14914 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 14402 -> 14704 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 13592 -> 13913 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 13404 -> 13731 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 14505 -> 14914 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 14402 -> 14704 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 15344 -> 15837 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 15395 -> 15864 bytes
...iewIsConfiguredAsExpected.default-dark.png | Bin 17000 -> 17588 bytes
...ewIsConfiguredAsExpected.default-light.png | Bin 16882 -> 17288 bytes
fastlane/Fastfile | 3 +-
987 files changed, 123 insertions(+), 121 deletions(-)
diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml
index 8e112dec9c..eb3ac1e9c9 100644
--- a/.github/workflows/cron-checks.yml
+++ b/.github/workflows/cron-checks.yml
@@ -41,6 +41,11 @@ jobs:
strategy:
matrix:
include:
+ - ios: 18.1
+ xcode: 16.1
+ os: macos-15
+ device: "iPhone 16 Pro"
+ setup_runtime: false
- ios: 17.4
xcode: 15.4
os: macos-14
@@ -123,6 +128,11 @@ jobs:
strategy:
matrix:
include:
+ - ios: 18.1
+ xcode: 16.1
+ os: macos-15
+ device: "iPhone 16 Pro"
+ setup_runtime: false
- ios: 17.4
xcode: 15.4
os: macos-14
@@ -138,16 +148,6 @@ jobs:
os: macos-14
device: "iPhone 13 Pro"
setup_runtime: true
- - ios: 14.5
- xcode: 14.2
- os: macos-12
- device: "iPhone 12 Pro"
- setup_runtime: true
- - ios: 13.7
- xcode: 14.2
- os: macos-12
- device: "iPhone 11 Pro"
- setup_runtime: true
fail-fast: false
runs-on: ${{ matrix.os }}
env:
@@ -189,11 +189,11 @@ jobs:
fastlane/test_output/logs/*/Diagnostics/**/*.txt
fastlane/test_output/logs/*/Diagnostics/simctl_diagnostics/DiagnosticReports/*
- build-xcode14:
- name: Build LLC + UI (Xcode 14)
- runs-on: macos-12
+ build-old-xcode:
+ name: Build LLC + UI (Xcode 15)
+ runs-on: macos-14
env:
- XCODE_VERSION: "14.0.1"
+ XCODE_VERSION: "15.0.1"
steps:
- name: Connect Bot
uses: webfactory/ssh-agent@v0.7.0
diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml
index 48bfffd20b..e664c5a7e1 100644
--- a/.github/workflows/release-publish.yml
+++ b/.github/workflows/release-publish.yml
@@ -6,7 +6,7 @@ on:
jobs:
release:
name: Publish new release
- runs-on: macos-12
+ runs-on: macos-15
steps:
- name: Connect Bot
uses: webfactory/ssh-agent@v0.7.0
diff --git a/.github/workflows/smoke-checks.yml b/.github/workflows/smoke-checks.yml
index 0dc31813bc..babfcf2cea 100644
--- a/.github/workflows/smoke-checks.yml
+++ b/.github/workflows/smoke-checks.yml
@@ -19,7 +19,7 @@ concurrency:
env:
HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI
- IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.4)"
+ IOS_SIMULATOR_DEVICE: "iPhone 16 Pro (18.1)"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
@@ -48,9 +48,9 @@ jobs:
automated-code-review:
name: Automated Code Review
- runs-on: macos-12
+ runs-on: macos-14
env:
- XCODE_VERSION: "14.0.1"
+ XCODE_VERSION: "15.0.1"
if: ${{ github.event_name != 'push' && github.event.inputs.snapshots != 'true' }}
steps:
- uses: actions/checkout@v4.1.1
@@ -67,12 +67,12 @@ jobs:
if: startsWith(github.event.pull_request.head.ref, 'release/')
run: bundle exec fastlane pod_lint
- build-xcode14:
- name: Build LLC + UI (Xcode 14)
- runs-on: macos-12
+ build-old-xcode:
+ name: Build LLC + UI (Xcode 15)
+ runs-on: macos-14
if: ${{ github.event_name != 'push' && github.event.inputs.snapshots != 'true' }}
env:
- XCODE_VERSION: "14.0.1"
+ XCODE_VERSION: "15.0.1"
steps:
- uses: actions/checkout@v4.1.1
- uses: ./.github/actions/ruby-cache
@@ -87,7 +87,7 @@ jobs:
test-llc-debug:
name: Test LLC (Debug)
- runs-on: macos-14
+ runs-on: macos-15
if: ${{ github.event.inputs.snapshots != 'true' }}
needs: build-test-app-and-frameworks
steps:
@@ -136,7 +136,7 @@ jobs:
test-ui-debug:
name: Test UI (Debug)
- runs-on: macos-14
+ runs-on: macos-15
needs: build-test-app-and-frameworks
if: ${{ github.event_name != 'push' }}
steps:
@@ -152,7 +152,7 @@ jobs:
SKIP_BREW_BOOTSTRAP: true
- name: Run UI Tests (Debug)
run: bundle exec fastlane test_ui device:"${{ env.IOS_SIMULATOR_DEVICE }}" skip_build:true record:${{ github.event.inputs.snapshots }}
- timeout-minutes: 60
+ timeout-minutes: 120
env:
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} # to open a PR
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # to use github cli
@@ -170,7 +170,7 @@ jobs:
allure_testops_launch:
name: Launch Allure TestOps
- runs-on: macos-13
+ runs-on: macos-14
if: ${{ github.event_name != 'push' && github.event.inputs.snapshots != 'true' }}
needs: build-test-app-and-frameworks
outputs:
@@ -189,7 +189,7 @@ jobs:
test-e2e-debug:
name: Test E2E UI (Debug)
- runs-on: macos-14
+ runs-on: macos-15
if: ${{ github.event_name != 'push' && github.event.inputs.snapshots != 'true' }}
needs:
- allure_testops_launch
@@ -216,8 +216,6 @@ jobs:
run: bundle exec fastlane test_e2e_mock device:"${{ env.IOS_SIMULATOR_DEVICE }}" batch:'${{ matrix.batch }}' test_without_building:true
timeout-minutes: 100
env:
- XCODE_VERSION: "15.0.1" # the most stable pair of Xcode
- IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.0)" # and iOS
MATRIX_SIZE: ${{ strategy.job-total }}
STREAM_DEMO_APP_SECRET: ${{ secrets.STREAM_DEMO_APP_SECRET }}
- name: Allure TestOps Upload
diff --git a/.github/workflows/update-copyright.yml b/.github/workflows/update-copyright.yml
index 1cd27ed3b3..1649699d35 100644
--- a/.github/workflows/update-copyright.yml
+++ b/.github/workflows/update-copyright.yml
@@ -13,7 +13,7 @@ env:
jobs:
copyright:
name: Copyright
- runs-on: macos-13
+ runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.1
- uses: ./.github/actions/ruby-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f3c4e7250..b125dffee3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ _December 03, 2024_
### 🐞 Fixed
- Fix a rare infinite loop triggering a crash when handling database changes [#3508](https://github.com/GetStream/stream-chat-swift/pull/3508)
- Fix reconnection timeout handler not working in the token provider phase [#3513](https://github.com/GetStream/stream-chat-swift/pull/3513)
+### 🔄 Changed
+- Minor breaking change in the test tools. Some mock classes were made internal and now require a `@testable` annotation [#3509](https://github.com/GetStream/stream-chat-swift/pull/3509)
## StreamChatUI
### 🐞 Fixed
diff --git a/StreamChatUITestsAppUITests/Robots/UserRobot.swift b/StreamChatUITestsAppUITests/Robots/UserRobot.swift
index 4f78ab68c0..c08e7764a2 100644
--- a/StreamChatUITestsAppUITests/Robots/UserRobot.swift
+++ b/StreamChatUITestsAppUITests/Robots/UserRobot.swift
@@ -142,6 +142,9 @@ extension UserRobot {
func clearComposer() -> Self {
if !composer.textView.text.isEmpty {
composer.inputField.tap()
+ if !composer.selectAllButton.waitForExistence(timeout: 1) {
+ composer.inputField.tap()
+ }
composer.selectAllButton.wait().safeTap()
composer.inputField.typeText(XCUIKeyboardKey.delete.rawValue)
}
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift
index 71cbaf7434..85937401b4 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift
@@ -5,20 +5,20 @@
import Foundation
@testable import StreamChat
-public class ChatChannelController_Mock: ChatChannelController {
+class ChatChannelController_Mock: ChatChannelController {
var mockCid: ChannelId?
- public override var cid: ChannelId? {
+ override var cid: ChannelId? {
mockCid ?? super.cid
}
var mockFirstUnreadMessageId: MessageId?
- public override var firstUnreadMessageId: MessageId? {
+ override var firstUnreadMessageId: MessageId? {
mockFirstUnreadMessageId ?? super.firstUnreadMessageId
}
/// Creates a new mock instance of `ChatChannelController`.
- public static func mock(chatClientConfig: ChatClientConfig? = nil) -> ChatChannelController_Mock {
+ static func mock(chatClientConfig: ChatClientConfig? = nil) -> ChatChannelController_Mock {
.init(
channelQuery: .init(cid: try! .init(cid: "mock:channel")),
channelListQuery: nil,
@@ -35,7 +35,7 @@ public class ChatChannelController_Mock: ChatChannelController {
)
}
- public static func mock(
+ static func mock(
channelQuery: ChannelQuery,
channelListQuery: ChannelListQuery?,
client: ChatClient
@@ -47,7 +47,7 @@ public class ChatChannelController_Mock: ChatChannelController {
)
}
- public static func mock(client: ChatClient) -> ChatChannelController_Mock {
+ static func mock(client: ChatClient) -> ChatChannelController_Mock {
.init(
channelQuery: .init(cid: try! .init(cid: "mock:channel")),
channelListQuery: nil,
@@ -56,7 +56,7 @@ public class ChatChannelController_Mock: ChatChannelController {
}
var createNewMessageCallCount = 0
- public override func createNewMessage(
+ override func createNewMessage(
messageId: MessageId? = nil,
text: String, pinning: MessagePinning? = nil,
isSilent: Bool = false,
@@ -71,61 +71,61 @@ public class ChatChannelController_Mock: ChatChannelController {
createNewMessageCallCount += 1
}
- public var hasLoadedAllNextMessages_mock: Bool? = true
- public override var hasLoadedAllNextMessages: Bool {
+ var hasLoadedAllNextMessages_mock: Bool? = true
+ override var hasLoadedAllNextMessages: Bool {
hasLoadedAllNextMessages_mock ?? super.hasLoadedAllNextMessages
}
- public var hasLoadedAllPreviousMessages_mock: Bool? = true
- public override var hasLoadedAllPreviousMessages: Bool {
+ var hasLoadedAllPreviousMessages_mock: Bool? = true
+ override var hasLoadedAllPreviousMessages: Bool {
hasLoadedAllPreviousMessages_mock ?? super.hasLoadedAllPreviousMessages
}
- public var markedAsUnread_mock: Bool? = true
- public override var isMarkedAsUnread: Bool {
+ var markedAsUnread_mock: Bool? = true
+ override var isMarkedAsUnread: Bool {
markedAsUnread_mock ?? super.isMarkedAsUnread
}
- public var channel_mock: ChatChannel?
- override public var channel: ChatChannel? {
+ var channel_mock: ChatChannel?
+ override var channel: ChatChannel? {
channel_mock ?? super.channel
}
- public var channelQuery_mock: ChannelQuery?
- public override var channelQuery: ChannelQuery {
+ var channelQuery_mock: ChannelQuery?
+ override var channelQuery: ChannelQuery {
channelQuery_mock ?? super.channelQuery
}
- public var messages_mock: [ChatMessage]?
- override public var messages: LazyCachedMapCollection {
+ var messages_mock: [ChatMessage]?
+ override var messages: LazyCachedMapCollection {
messages_mock.map { $0.lazyCachedMap { $0 } } ?? super.messages
}
- public var markReadCallCount = 0
- public override func markRead(completion: ((Error?) -> Void)?) {
+ var markReadCallCount = 0
+ override func markRead(completion: ((Error?) -> Void)?) {
markReadCallCount += 1
}
- public var state_mock: State?
- override public var state: DataController.State {
+ var state_mock: State?
+ override var state: DataController.State {
get { state_mock ?? super.state }
set { super.state = newValue }
}
- public private(set) var synchronize_completion: ((Error?) -> Void)?
- override public func synchronize(_ completion: ((Error?) -> Void)? = nil) {
+ private(set) var synchronize_completion: ((Error?) -> Void)?
+ override func synchronize(_ completion: ((Error?) -> Void)? = nil) {
synchronize_completion = completion
}
- public var loadFirstPageCallCount = 0
- public var loadFirstPage_result: Error?
- public override func loadFirstPage(_ completion: ((Error?) -> Void)? = nil) {
+ var loadFirstPageCallCount = 0
+ var loadFirstPage_result: Error?
+ override func loadFirstPage(_ completion: ((Error?) -> Void)? = nil) {
loadFirstPageCallCount += 1
completion?(loadFirstPage_result)
}
- public var loadPageAroundMessageIdCallCount = 0
- public override func loadPageAroundMessageId(
+ var loadPageAroundMessageIdCallCount = 0
+ override func loadPageAroundMessageId(
_ messageId: MessageId,
limit: Int? = nil,
completion: ((Error?) -> Void)? = nil
@@ -134,7 +134,7 @@ public class ChatChannelController_Mock: ChatChannelController {
}
}
-public extension ChatChannelController_Mock {
+extension ChatChannelController_Mock {
/// Simulates the initial conditions. Setting these values doesn't trigger any observer callback.
func simulateInitial(channel: ChatChannel, messages: [ChatMessage], state: DataController.State) {
channel_mock = channel
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelListController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelListController_Mock.swift
index c2f374a990..c20c0704a0 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelListController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelListController_Mock.swift
@@ -5,40 +5,40 @@
import Foundation
@testable import StreamChat
-public class ChatChannelListController_Mock: ChatChannelListController, Spy {
- public let spyState = SpyState()
- public var loadNextChannelsIsCalled = false
- public var loadNextChannelsCallCount = 0
- public var resetChannelsQueryResult: Result<(synchedAndWatched: [ChatChannel], unwanted: Set), Error>?
- public var refreshLoadedChannelsResult: Result, any Error>?
+class ChatChannelListController_Mock: ChatChannelListController, Spy {
+ let spyState = SpyState()
+ var loadNextChannelsIsCalled = false
+ var loadNextChannelsCallCount = 0
+ var resetChannelsQueryResult: Result<(synchedAndWatched: [ChatChannel], unwanted: Set), Error>?
+ var refreshLoadedChannelsResult: Result, any Error>?
/// Creates a new mock instance of `ChatChannelListController`.
- public static func mock(client: ChatClient? = nil) -> ChatChannelListController_Mock {
+ static func mock(client: ChatClient? = nil) -> ChatChannelListController_Mock {
.init(query: .init(filter: .equal(.memberCount, to: 0)), client: client ?? .mock())
}
- public var channels_mock: [ChatChannel]?
- override public var channels: LazyCachedMapCollection {
+ var channels_mock: [ChatChannel]?
+ override var channels: LazyCachedMapCollection {
channels_mock.map { $0.lazyCachedMap { $0 } } ?? super.channels
}
- public var state_mock: State?
- override public var state: DataController.State {
+ var state_mock: State?
+ override var state: DataController.State {
get { state_mock ?? super.state }
set { super.state = newValue }
}
- override public func loadNextChannels(limit: Int?, completion: ((Error?) -> Void)?) {
+ override func loadNextChannels(limit: Int?, completion: ((Error?) -> Void)?) {
loadNextChannelsCallCount += 1
loadNextChannelsIsCalled = true
}
- override public func refreshLoadedChannels(completion: @escaping (Result, any Error>) -> Void) {
+ override func refreshLoadedChannels(completion: @escaping (Result, any Error>) -> Void) {
record()
refreshLoadedChannelsResult.map(completion)
}
- override public func resetQuery(
+ override func resetQuery(
watchedAndSynchedChannelIds: Set,
synchedChannelIds: Set,
completion: @escaping (Result<(synchedAndWatched: [ChatChannel], unwanted: Set), Error>) -> Void
@@ -48,7 +48,7 @@ public class ChatChannelListController_Mock: ChatChannelListController, Spy {
}
}
-public extension ChatChannelListController_Mock {
+extension ChatChannelListController_Mock {
/// Simulates the initial conditions. Setting these values doesn't trigger any observer callback.
func simulateInitial(channels: [ChatChannel], state: DataController.State) {
channels_mock = channels
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift
index 3d07927091..88c27fc7fb 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift
@@ -5,9 +5,9 @@
import Foundation
@testable import StreamChat
-public class ChatMessageController_Mock: ChatMessageController {
+class ChatMessageController_Mock: ChatMessageController {
/// Creates a new mock instance of `ChatMessageController`.
- public static func mock(
+ static func mock(
currentUserId: UserId = "ID",
cid: ChannelId? = nil,
messageId: String = "MockMessage"
@@ -23,24 +23,24 @@ public class ChatMessageController_Mock: ChatMessageController {
return .init(client: chatClient, cid: channelId!, messageId: messageId, replyPaginationHandler: MessagesPaginationStateHandler_Mock())
}
- public var message_mock: ChatMessage?
- override public var message: ChatMessage? {
+ var message_mock: ChatMessage?
+ override var message: ChatMessage? {
message_mock ?? super.message
}
- public var replies_mock: [ChatMessage]?
- override public var replies: LazyCachedMapCollection {
+ var replies_mock: [ChatMessage]?
+ override var replies: LazyCachedMapCollection {
replies_mock.map { $0.lazyCachedMap { $0 } } ?? super.replies
}
- public var state_mock: State?
- override public var state: DataController.State {
+ var state_mock: State?
+ override var state: DataController.State {
get { state_mock ?? super.state }
set { super.state = newValue }
}
- public var startObserversIfNeeded_mock: (() -> Void)?
- override public func startObserversIfNeeded() {
+ var startObserversIfNeeded_mock: (() -> Void)?
+ override func startObserversIfNeeded() {
if let mock = startObserversIfNeeded_mock {
mock()
return
@@ -51,7 +51,7 @@ public class ChatMessageController_Mock: ChatMessageController {
var synchronize_callCount = 0
var synchronize_completion: ((Error?) -> Void)?
- override public func synchronize(_ completion: ((Error?) -> Void)? = nil) {
+ override func synchronize(_ completion: ((Error?) -> Void)? = nil) {
synchronize_callCount += 1
synchronize_completion = completion
}
@@ -59,7 +59,7 @@ public class ChatMessageController_Mock: ChatMessageController {
var loadPageAroundReplyId_callCount = 0
var loadPageAroundReplyId_completion: ((Error?) -> Void)?
- override public func loadPageAroundReplyId(
+ override func loadPageAroundReplyId(
_ replyId: MessageId,
limit: Int? = nil,
completion: ((Error?) -> Void)? = nil
@@ -69,7 +69,7 @@ public class ChatMessageController_Mock: ChatMessageController {
}
}
-public extension ChatMessageController_Mock {
+extension ChatMessageController_Mock {
/// Simulates the initial conditions. Setting these values doesn't trigger any observer callback.
func simulateInitial(message: ChatMessage, replies: [ChatMessage], state: DataController.State) {
message_mock = message
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageSearchController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageSearchController_Mock.swift
index 5f66fcfb47..eb90beec21 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageSearchController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageSearchController_Mock.swift
@@ -5,18 +5,18 @@
import Foundation
@testable import StreamChat
-public class ChatMessageSearchController_Mock: ChatMessageSearchController {
- public static func mock(client: ChatClient? = nil) -> ChatMessageSearchController_Mock {
+class ChatMessageSearchController_Mock: ChatMessageSearchController {
+ static func mock(client: ChatClient? = nil) -> ChatMessageSearchController_Mock {
.init(client: client ?? .mock())
}
- public var messages_mock: LazyCachedMapCollection?
- override public var messages: LazyCachedMapCollection {
+ var messages_mock: LazyCachedMapCollection?
+ override var messages: LazyCachedMapCollection {
messages_mock ?? super.messages
}
- public var state_mock: DataController.State?
- public override var state: DataController.State {
+ var state_mock: DataController.State?
+ override var state: DataController.State {
get {
state_mock ?? super.state
}
@@ -26,18 +26,18 @@ public class ChatMessageSearchController_Mock: ChatMessageSearchController {
}
var loadNextMessagesCallCount = 0
- override public func loadNextMessages(limit: Int = 25, completion: ((Error?) -> Void)? = nil) {
+ override func loadNextMessages(limit: Int = 25, completion: ((Error?) -> Void)? = nil) {
loadNextMessagesCallCount += 1
completion?(nil)
}
var searchCallCount = 0
- override public func search(query: MessageSearchQuery, completion: ((Error?) -> Void)? = nil) {
+ override func search(query: MessageSearchQuery, completion: ((Error?) -> Void)? = nil) {
searchCallCount += 1
completion?(nil)
}
- public override func search(text: String, completion: ((Error?) -> Void)? = nil) {
+ override func search(text: String, completion: ((Error?) -> Void)? = nil) {
searchCallCount += 1
}
}
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatThreadListController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatThreadListController_Mock.swift
index 3bd9eb0502..0ff904f337 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatThreadListController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatThreadListController_Mock.swift
@@ -5,25 +5,25 @@
import Foundation
@testable import StreamChat
-public class ChatThreadListController_Mock: ChatThreadListController {
- public static func mock(query: ThreadListQuery, client: ChatClient? = nil) -> ChatThreadListController_Mock {
+class ChatThreadListController_Mock: ChatThreadListController {
+ static func mock(query: ThreadListQuery, client: ChatClient? = nil) -> ChatThreadListController_Mock {
.init(query: query, client: client ?? .mock())
}
- public var threads_mock: [ChatThread]?
- public override var threads: LazyCachedMapCollection {
+ var threads_mock: [ChatThread]?
+ override var threads: LazyCachedMapCollection {
threads_mock.map { $0.lazyCachedMap { $0 } } ?? super.threads
}
- public var state_mock: State?
- override public var state: DataController.State {
+ var state_mock: State?
+ override var state: DataController.State {
get { state_mock ?? super.state }
set { super.state = newValue }
}
- public var synchronize_completion: (((any Error)?) -> Void)?
- public var synchronize_callCount = 0
- public override func synchronize(_ completion: (((any Error)?) -> Void)? = nil) {
+ var synchronize_completion: (((any Error)?) -> Void)?
+ var synchronize_callCount = 0
+ override func synchronize(_ completion: (((any Error)?) -> Void)? = nil) {
synchronize_callCount += 1
synchronize_completion = completion
}
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift
index 004f2dbc50..dd17b86c96 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift
@@ -5,25 +5,25 @@
import Foundation
@testable import StreamChat
-public class ChatUserSearchController_Mock: ChatUserSearchController {
+class ChatUserSearchController_Mock: ChatUserSearchController {
var searchCallCount = 0
- public static func mock(client: ChatClient? = nil) -> ChatUserSearchController_Mock {
+ static func mock(client: ChatClient? = nil) -> ChatUserSearchController_Mock {
.init(client: client ?? .mock())
}
- public var users_mock: [ChatUser]?
- override public var userArray: [ChatUser] {
+ var users_mock: [ChatUser]?
+ override var userArray: [ChatUser] {
users_mock ?? super.userArray
}
- override public func search(query: UserListQuery, completion: ((Error?) -> Void)? = nil) {
+ override func search(query: UserListQuery, completion: ((Error?) -> Void)? = nil) {
searchCallCount += 1
completion?(nil)
}
- override public func search(term: String?, completion: ((Error?) -> Void)? = nil) {
+ override func search(term: String?, completion: ((Error?) -> Void)? = nil) {
searchCallCount += 1
users_mock = users_mock?.filter { user in
user.name?.contains(term ?? "") ?? true
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/CurrentChatUserController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/CurrentChatUserController_Mock.swift
index ece0a7da07..88563e88f6 100644
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/CurrentChatUserController_Mock.swift
+++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/CurrentChatUserController_Mock.swift
@@ -5,13 +5,13 @@
import Foundation
@testable import StreamChat
-public class CurrentChatUserController_Mock: CurrentChatUserController {
- public static func mock(client: ChatClient? = nil) -> CurrentChatUserController_Mock {
+class CurrentChatUserController_Mock: CurrentChatUserController {
+ static func mock(client: ChatClient? = nil) -> CurrentChatUserController_Mock {
.init(client: client ?? .mock())
}
- public var currentUser_mock: CurrentChatUser?
- override public var currentUser: CurrentChatUser? {
+ var currentUser_mock: CurrentChatUser?
+ override var currentUser: CurrentChatUser? {
currentUser_mock
}
}
diff --git a/Tests/StreamChatUITests/SnapshotTests/ChatChannel/__Snapshots__/ChatChannelVC+SwiftUI_Tests/test_chatChannel_isPopulated.default-light.png b/Tests/StreamChatUITests/SnapshotTests/ChatChannel/__Snapshots__/ChatChannelVC+SwiftUI_Tests/test_chatChannel_isPopulated.default-light.png
index 225be7654df09ac83a817ff95f0656ccfb9b0d62..0078c5b7c477cd47dcb3e0750dfd4e4b4df4188b 100644
GIT binary patch
literal 23014
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
literal 22778
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 S
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
HoD!M<0temj
diff --git a/Tests/StreamChatUITests/SnapshotTests/ChatChannel/__Snapshots__/ChatChannelVC+SwiftUI_Tests/test_customNavigationViewValues_arePopulated.default-light.png b/Tests/StreamChatUITests/SnapshotTests/ChatChannel/__Snapshots__/ChatChannelVC+SwiftUI_Tests/test_customNavigationViewValues_arePopulated.default-light.png
index fd46829283037e00d0590e4371069ca0dd008289..d5fb462384534cd3781128b264282bda28266871 100644
GIT binary patch
literal 24551
zcmeIacU)7=);0`+AQC{4-W8ChbOGs#f{21hRXPYr@4ZVGDN+Q404g0JAT@O99i&4-
zO(3*{p3r%B?sLw2p7Xr_e&zYT-wnU8CzF}U?8WR^YhCMF6a7kEh2lEXbpiqc3e{&%
zUK0=ydJzy1-MdBt)IjK#5x|Ae?X}8dg0dl&b>K~yrM{}w%a;WFK=~TMRYGb4B776z
zmw=Fo;Obvx0)pp+%>TRmnvnOOZLSayMA{G#|Fg|o;EMlw2E2gY|GX0A5dM3_oGbro
zP3VV#loCTJ*7m7r*P1<59*
zt(F)}%%zB706yTlkwyR|rJP9Q;vghTkRicei0>+5LX@hx@WUYE2=SK?vIM`ySNBzc
zF$ka*e-HKlA6bp3Kyw!^u(fC&g=5vzPNBdG(V#7ptzl{Gv;}M|3tNR<*9ba!%Z?Zh
z#8!H)eT%Ho2;56T19fB7^OGsmM{d+>UrsH9?9u6w)!3c@1HW4VMdG5KIZe62rwnVjGdk#m*OU`3%#(O_$*y|HO3E@b|;QD0l{pVDA
zRoHo0o&rq;ZM~{-4Kg!Ja>#!LMXI6fvum$>@Y`zBUHuP}6wsafv
zQ)1p5!QE?nL3-T&QX<3=Dx{9FPOb9XXEl&ntLmH@2r$whp*NhJ-Hz;PIpY1Sex+2w
z#D8Yz(Eax^^RT~BZE3vpMxfB~IXv6?(r?#!W~F`D_|I;}H*KRa+yt6ktnqpgm5N<8
zh1O#M5r?M3Np$1>5VOTGg8g!uQsHdJvs-4~r4jRk{d~>)I4{+BRkn&cv?e;t@Ci5=acm!?Mz
zuU!|m1<2WZok)E!M+>E;E!K8+%>~sSUQX!J%LhHn#d^iusFw3rc7B=Tvwy4y99DiF
z&O?XUk5}7H-=0lSvOA%-;~mf=kZNpelR*Q@`EvNOXNODlY))aseu6^`z_4*
z{h~@{^G-v{;r?Wni?0)(FPEKqmGfe3`D6p*A<`@TYa`6uN%14B<;9wHWLVD>P*Ty$laCT
zo}-Y2m&+}YX)xgW)OrrnOwKp-guDmF=p$KNp9vgKIbeLJ8fx?!muVd!O;{#K+5MvW
zGvHy+INi`|k)Ah^o6&KDWWR|o{d!5?a?}YtQn+=B3CZT&ScK)Ft!eF?Cv$e=MzmS%
zgGmM)t{{86lDJIciojFML|;5>Sm11%-YlB7yUMdAh|Rc<(i{SlHGO~R6`*H_GOjxN
zmU_Uh5Orv_?NnOhUba5Hx?s=%YcPH>e`}Jsr_IC9|g`
z3X`)4o1FjJ*CQFr)T=3sN_A5rmtB!65
z;s}20sGyx7)st@#Xbt&e0{d5=m&sz~6fzfJGO~z5!jcpD4G}O4QxY
zcHE;9rvcKNTlt`&wO=Or#5!KGaft$fQ`fMnKG)Jvp4bUG_`yE7;O=RVFZff^a8pOA
zDX|b;ygu!>-PC^((Ui1>H5!=
z*iS~z&e1KG7Y^A0SesLHt%)D7eMlQ$6spMChHHqA1)SUqnIYEw3NN{TIl(bDJ5^TG
zywpSx@H1b;9nFf(%4z8IggQ@cwPc
zJQeTKC5!_nK;pK53n-oQd5=5%XcoHUGOlv&bKdk8<~zNSZp_zc%IjMlxy>*X=sT97
zJd-S?=dPVm7B77lI$X0+-#Bzt=%Kn(DYR9FOOxa)1v@}yrB00TTIvri8-Lc=>VARS85FRo$PrFh
zWa762zfTXxOH%p;8|;0f |