diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5512f5bad..dc1a1d052 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### 🔄 Changed
+# [4.92.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.92.0)
+_November 07, 2025_
+
+### ✅ Added
+- Add message highlighting on jumping to a quoted message [#1032](https://github.com/GetStream/stream-chat-swiftui/pull/1032)
+- Display double grey checkmark when delivery events are enabled [#1038](https://github.com/GetStream/stream-chat-swiftui/pull/1038)
+
+### 🐞 Fixed
+- Fix composer deleting newly entered text after deleting draft text [#1030](https://github.com/GetStream/stream-chat-swiftui/pull/1030)
+- Fix mark unread action not shown for messages that are root of a thread in the channel view [#1041](https://github.com/GetStream/stream-chat-swiftui/pull/1041)
+
# [4.91.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.91.0)
_October 22, 2025_
diff --git a/Package.swift b/Package.swift
index aa0f4fefb..ddd1fb18e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -16,7 +16,7 @@ let package = Package(
)
],
dependencies: [
- .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.91.0")
+ .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.92.0")
],
targets: [
.target(
diff --git a/README.md b/README.md
index 8f3264061..070f3eecd 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
-
+
## SwiftUI StreamChat SDK
@@ -39,6 +39,17 @@ The SwiftUI SDK offers three types of components:
- Stateful components - Offer more customization options and possibility to inject custom views. Also fairly simple to integrate, if the extension points are suitable for your chat use-case. These components come with view models.
- Stateless components - These are the building blocks for the other two types of components. In order to use them, you would have to provide the state and data. Using these components only make sense if you want to implement completely custom chat experience.
+## Documentation Generation
+
+To generate the documentation for SwiftUI StreamChat SDK, run the following command:
+
+```bash
+xcodebuild docbuild -skipMacroValidation -skipPackagePluginValidation -derivedDataPath .derivedData -scheme StreamChatSwiftUI -destination generic/platform=iOS | xcpretty
+open .derivedData/Build/Products/Debug-iphoneos/StreamChatSwiftUI.doccarchive
+```
+
+This will build the documentation archive and automatically open it in Xcode.
+
## Free for Makers
Stream is free for most side and hobby projects. You can use Stream Chat for free if you have less than five team members and no more than $10,000 in monthly revenue.
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
index 47ff42773..17cb54766 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
@@ -73,6 +73,7 @@ public struct ChatChannelView: View, KeyboardReadable {
},
onJumpToMessage: viewModel.jumpToMessage(messageId:)
)
+ .environment(\.highlightedMessageId, viewModel.highlightedMessageId)
.dismissKeyboardOnTap(enabled: true) {
hideComposerCommandsAndAttachmentsPicker()
}
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
index 8ed4ee793..5b40308e5 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
@@ -56,7 +56,11 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
public var messageController: ChatMessageController?
@Published public var scrolledId: String?
+ @Published public var highlightedMessageId: String?
@Published public var listId = UUID().uuidString
+ // A boolean to skip highlighting of a message when scrolling to it.
+ // This is used for scenarios when scrolling to message Id should not highlight it.
+ var skipHighlightMessageId: String?
@Published public var showScrollToLatestButton = false
@Published var showAlertBanner = false
@@ -172,6 +176,11 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
self?.messageCachingUtils.jumpToReplyId = scrollToMessage.messageId
} else if messageController != nil, let jumpToReplyId = self?.messageCachingUtils.jumpToReplyId {
self?.scrolledId = jumpToReplyId
+ // Clear scroll ID after 2 seconds
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
+ self?.scrolledId = nil
+ }
+ self?.highlightMessage(withId: jumpToReplyId)
self?.messageCachingUtils.jumpToReplyId = nil
} else if messageController == nil {
self?.scrolledId = scrollToMessage?.messageId
@@ -232,6 +241,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
if let message = notification.userInfo?[MessageRepliesConstants.selectedMessage] as? ChatMessage {
threadMessage = message
threadMessageShown = true
+
+ // Only set jumpToReplyId if there's a specific reply message to highlight
+ // (for showReplyInChannel messages). The parent message should never be highlighted.
+ if let replyMessage = notification.userInfo?[MessageRepliesConstants.threadReplyMessage] as? ChatMessage {
+ messageCachingUtils.jumpToReplyId = replyMessage.messageId
+ }
}
}
@@ -297,9 +312,11 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
if scrolledId == nil {
scrolledId = messageId
}
+ // Clear scroll ID after 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
self?.scrolledId = nil
}
+ highlightMessage(withId: messageId)
return true
} else {
let message = channelController.dataStore.message(id: baseId)
@@ -325,16 +342,36 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
if toJumpId == baseId, let message = self?.channelController.dataStore.message(id: toJumpId) {
toJumpId = message.messageId
}
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.scrolledId = toJumpId
self?.loadingMessagesAround = false
+ self?.highlightMessage(withId: toJumpId)
}
}
return false
}
}
}
-
+
+ /// Highlights the message background.
+ ///
+ /// - Parameter messageId: The ID of the message to highlight.
+ public func highlightMessage(withId messageId: MessageId) {
+ if skipHighlightMessageId == messageId {
+ skipHighlightMessageId = nil
+ return
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+ self?.highlightedMessageId = messageId
+ }
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { [weak self] in
+ withAnimation {
+ self?.highlightedMessageId = nil
+ }
+ }
+ }
+
open func handleMessageAppear(index: Int, scrollDirection: ScrollDirection) {
if index >= channelDataSource.messages.count || loadingMessagesAround {
return
@@ -519,7 +556,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
}
// MARK: - private
-
+
private func checkForOlderMessages(index: Int) {
guard index >= channelDataSource.messages.count - 25 else { return }
guard !loadingPreviousMessages else { return }
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
index 48e1b1058..545b5a57d 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
@@ -908,14 +908,6 @@ extension MessageComposerViewModel: EventsControllerDelegate {
fillDraftMessage()
}
}
-
- if let event = event as? DraftDeletedEvent {
- let isFromSameThread = messageController?.messageId == event.threadId
- let isFromSameChannel = channelController.cid == event.cid && messageController == nil
- if isFromSameThread || isFromSameChannel {
- clearInputData()
- }
- }
}
}
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
index fefb9088f..cf9324c96 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
@@ -9,7 +9,8 @@ import SwiftUI
public struct MessageContainerView: View {
@StateObject var messageViewModel: MessageViewModel
@Environment(\.channelTranslationLanguage) var translationLanguage
-
+ @Environment(\.highlightedMessageId) var highlightedMessageId
+
@Injected(\.fonts) private var fonts
@Injected(\.colors) private var colors
@Injected(\.images) private var images
@@ -284,7 +285,17 @@ public struct MessageContainerView: View {
.padding(.horizontal, messageListConfig.messagePaddings.horizontal)
.padding(.bottom, showsAllInfo || messageViewModel.isPinned ? paddingValue : groupMessageInterItemSpacing)
.padding(.top, isLast ? paddingValue : 0)
- .background(messageViewModel.isPinned ? Color(colors.pinnedBackground) : nil)
+ .background(
+ Group {
+ if utils.messageListConfig.highlightMessageWhenJumping,
+ let highlightedMessageId = highlightedMessageId,
+ highlightedMessageId == message.messageId {
+ Color(colors.messageCellHighlightBackground)
+ } else if messageViewModel.isPinned {
+ Color(colors.pinnedBackground)
+ }
+ }
+ )
.padding(.bottom, messageViewModel.isPinned ? paddingValue / 2 : 0)
.transition(
message.isSentByCurrentUser ?
@@ -398,6 +409,18 @@ public struct MessageContainerView: View {
}
}
+// Environment plumbing colocated to avoid adding new files to the package list.
+private struct HighlightedMessageIdKey: EnvironmentKey {
+ static let defaultValue: String? = nil
+}
+
+extension EnvironmentValues {
+ var highlightedMessageId: String? {
+ get { self[HighlightedMessageIdKey.self] }
+ set { self[HighlightedMessageIdKey.self] = newValue }
+ }
+}
+
struct SendFailureIndicator: View {
@Injected(\.colors) private var colors
@Injected(\.images) private var images
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift
index b03d85500..2339728d5 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift
@@ -26,6 +26,7 @@ public struct MessageListConfig {
iPadSplitViewEnabled: Bool = true,
scrollingAnchor: UnitPoint = .center,
showNewMessagesSeparator: Bool = true,
+ highlightMessageWhenJumping: Bool = true,
handleTabBarVisibility: Bool = true,
messageListAlignment: MessageListAlignment = .standard,
uniqueReactionsEnabled: Bool = false,
@@ -57,6 +58,7 @@ public struct MessageListConfig {
self.iPadSplitViewEnabled = iPadSplitViewEnabled
self.scrollingAnchor = scrollingAnchor
self.showNewMessagesSeparator = showNewMessagesSeparator
+ self.highlightMessageWhenJumping = highlightMessageWhenJumping
self.handleTabBarVisibility = handleTabBarVisibility
self.messageListAlignment = messageListAlignment
self.uniqueReactionsEnabled = uniqueReactionsEnabled
@@ -121,6 +123,11 @@ public struct MessageListConfig {
/// A boolean value that determines if download action is shown for file attachments.
public let downloadFileAttachmentsEnabled: Bool
+
+ /// Highlights the message background when jumping to a message.
+ ///
+ /// By default it is enabled and it uses the color from `ColorPalette.messageCellHighlightBackground`.
+ public let highlightMessageWhenJumping: Bool
}
/// Contains information about the message paddings.
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift
index cdee7b8a2..f4dd7dc29 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift
@@ -100,11 +100,18 @@ public struct MessageReadIndicatorView: View {
var readUsers: [ChatUser]
var showReadCount: Bool
+ var showDelivered: Bool
var localState: LocalMessageState?
- public init(readUsers: [ChatUser], showReadCount: Bool, localState: LocalMessageState? = nil) {
+ public init(
+ readUsers: [ChatUser],
+ showReadCount: Bool,
+ showDelivered: Bool = false,
+ localState: LocalMessageState? = nil
+ ) {
self.readUsers = readUsers
self.showReadCount = showReadCount
+ self.showDelivered = showDelivered
self.localState = localState
}
@@ -135,7 +142,7 @@ public struct MessageReadIndicatorView: View {
}
private var image: UIImage {
- shouldShowReads ? images.readByAll : (isMessageSending ? images.messageReceiptSending : images.messageSent)
+ shouldShowReads || showDelivered ? images.readByAll : (isMessageSending ? images.messageReceiptSending : images.messageSent)
}
private var isMessageSending: Bool {
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift
index df3af5cf0..438fd08ee 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift
@@ -8,6 +8,7 @@ import SwiftUI
enum MessageRepliesConstants {
static let selectedMessageThread = "selectedMessageThread"
static let selectedMessage = "selectedMessage"
+ static let threadReplyMessage = "threadReplyMessage"
}
/// View shown below a message, when there are replies to it.
@@ -21,6 +22,7 @@ public struct MessageRepliesView: View {
var replyCount: Int
var isRightAligned: Bool
var showReplyCount: Bool
+ var threadReplyMessage: ChatMessage? // The actual reply message (for showReplyInChannel messages)
public init(
factory: Factory,
@@ -28,7 +30,8 @@ public struct MessageRepliesView: View {
message: ChatMessage,
replyCount: Int,
showReplyCount: Bool = true,
- isRightAligned: Bool? = nil
+ isRightAligned: Bool? = nil,
+ threadReplyMessage: ChatMessage? = nil
) {
self.factory = factory
self.channel = channel
@@ -36,6 +39,7 @@ public struct MessageRepliesView: View {
self.replyCount = replyCount
self.isRightAligned = isRightAligned ?? message.isRightAligned
self.showReplyCount = showReplyCount
+ self.threadReplyMessage = threadReplyMessage
}
public var body: some View {
@@ -44,10 +48,14 @@ public struct MessageRepliesView: View {
resignFirstResponder()
// NOTE: this is used to avoid breaking changes.
// Will be updated in a major release.
+ var userInfo: [String: Any] = [MessageRepliesConstants.selectedMessage: message]
+ if let threadReplyMessage = threadReplyMessage {
+ userInfo[MessageRepliesConstants.threadReplyMessage] = threadReplyMessage
+ }
NotificationCenter.default.post(
name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread),
object: nil,
- userInfo: [MessageRepliesConstants.selectedMessage: message]
+ userInfo: userInfo
)
} label: {
HStack {
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
index 8e29a81e9..22200fa68 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
@@ -121,16 +121,14 @@ public extension MessageAction {
messageActions.append(copyAction)
}
- if message.isRootOfThread {
- if isInsideThreadView {
- let markThreadUnreadAction = markThreadAsUnreadAction(
- messageController: messageController,
- message: message,
- onFinish: onFinish,
- onError: onError
- )
- messageActions.append(markThreadUnreadAction)
- }
+ if message.isRootOfThread && isInsideThreadView {
+ let markThreadUnreadAction = markThreadAsUnreadAction(
+ messageController: messageController,
+ message: message,
+ onFinish: onFinish,
+ onError: onError
+ )
+ messageActions.append(markThreadUnreadAction)
} else if !message.isSentByCurrentUser && channel.canReceiveReadEvents {
if !message.isPartOfThread || message.showReplyInChannel {
let markUnreadAction = markAsUnreadAction(
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/MessageActionsResolver.swift b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/MessageActionsResolver.swift
index 40f441d03..a47750571 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/MessageActionsResolver.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/MessageActionsResolver.swift
@@ -40,6 +40,7 @@ public class MessageActionsResolver: MessageActionsResolving {
} else if info.identifier == MessageActionId.markUnread {
viewModel.firstUnreadMessageId = info.message.messageId
viewModel.currentUserMarkedMessageUnread = true
+ viewModel.skipHighlightMessageId = info.message.messageId
viewModel.scrolledId = info.message.messageId
}
diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift
index e73a639b2..09da82320 100644
--- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift
@@ -85,9 +85,10 @@ public struct ChatChannelListItem: View {
MessageReadIndicatorView(
readUsers: channel.readUsers(
currentUserId: chatClient.currentUserId,
- message: channel.latestMessages.first
+ message: channel.previewMessage
),
- showReadCount: false
+ showReadCount: false,
+ showDelivered: channel.previewMessage?.deliveryStatus(for: channel) == .delivered
)
}
SubtitleText(text: injectedChannelInfo?.timestamp ?? channel.timestampText)
@@ -159,9 +160,8 @@ public struct ChatChannelListItem: View {
}
private var shouldShowReadEvents: Bool {
- if let message = channel.latestMessages.first,
- message.isSentByCurrentUser,
- !message.isDeleted {
+ if let message = channel.previewMessage,
+ message.isSentByCurrentUser {
return channel.config.readEventsEnabled
}
diff --git a/Sources/StreamChatSwiftUI/ColorPalette.swift b/Sources/StreamChatSwiftUI/ColorPalette.swift
index 5006ef98c..ec71b5ec5 100644
--- a/Sources/StreamChatSwiftUI/ColorPalette.swift
+++ b/Sources/StreamChatSwiftUI/ColorPalette.swift
@@ -59,7 +59,8 @@ public struct ColorPalette {
public var highlightedBackground: UIColor = .streamGrayGainsboro
public var highlightedAccentBackground: UIColor = .streamAccentBlue
public var highlightedAccentBackground1: UIColor = .streamBlueAlice
- public var pinnedBackground: UIColor = .streamHighlight
+ public var pinnedBackground: UIColor = .streamYellowBackground
+ public var messageCellHighlightBackground: UIColor = .streamYellowBackground
// MARK: - Borders and shadows
@@ -166,7 +167,7 @@ private extension UIColor {
static let streamAccentGreen = mode(0x20e070, 0x20e070)
static let streamGrayDisabledText = mode(0x72767e, 0x72767e)
static let streamInnerBorder = mode(0xdbdde1, 0x272a30)
- static let streamHighlight = mode(0xfbf4dd, 0x333024)
+ static let streamYellowBackground = mode(0xfbf4dd, 0x333024)
static let streamDisabled = mode(0xb4b7bb, 0x4c525c)
// Currently we are not using the correct shadow color from figma's color palette. This is to avoid
diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
index 3d881999f..348993bb2 100644
--- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
+++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
@@ -608,7 +608,8 @@ extension ViewFactory {
message: parentMessage,
replyCount: replyCount,
showReplyCount: false,
- isRightAligned: message.isRightAligned
+ isRightAligned: message.isRightAligned,
+ threadReplyMessage: message // Pass the actual reply message (shown in channel)
)
}
@@ -1026,9 +1027,11 @@ extension ViewFactory {
message: message
)
let showReadCount = channel.memberCount > 2 && !message.isLastActionFailed
+ let showDelivered = message.deliveryStatus(for: channel) == .delivered
return MessageReadIndicatorView(
readUsers: readUsers,
showReadCount: showReadCount,
+ showDelivered: showDelivered,
localState: message.localState
)
}
diff --git a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
index a5c3c79c9..7f2ac48c5 100644
--- a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
enum SystemEnvironment {
/// A Stream Chat version.
- public static let version: String = "4.91.0"
+ public static let version: String = "4.92.0"
}
diff --git a/Sources/StreamChatSwiftUI/Info.plist b/Sources/StreamChatSwiftUI/Info.plist
index 268afdcca..6bc4b9c01 100644
--- a/Sources/StreamChatSwiftUI/Info.plist
+++ b/Sources/StreamChatSwiftUI/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 4.91.0
+ 4.92.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPhotoLibraryUsageDescription
diff --git a/StreamChatSwiftUI-XCFramework.podspec b/StreamChatSwiftUI-XCFramework.podspec
index 6d88ce8d5..bef1bf4d7 100644
--- a/StreamChatSwiftUI-XCFramework.podspec
+++ b/StreamChatSwiftUI-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamChatSwiftUI-XCFramework'
- spec.version = '4.91.0'
+ spec.version = '4.92.0'
spec.summary = 'StreamChat SwiftUI Chat Components'
spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.'
@@ -19,7 +19,7 @@ Pod::Spec.new do |spec|
spec.framework = 'Foundation', 'UIKit', 'SwiftUI'
- spec.dependency 'StreamChat-XCFramework', '~> 4.91.0'
+ spec.dependency 'StreamChat-XCFramework', '~> 4.92.0'
spec.cocoapods_version = '>= 1.11.0'
end
diff --git a/StreamChatSwiftUI.podspec b/StreamChatSwiftUI.podspec
index e8cd2f445..1f38e2e6c 100644
--- a/StreamChatSwiftUI.podspec
+++ b/StreamChatSwiftUI.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamChatSwiftUI'
- spec.version = '4.91.0'
+ spec.version = '4.92.0'
spec.summary = 'StreamChat SwiftUI Chat Components'
spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.'
@@ -19,5 +19,5 @@ Pod::Spec.new do |spec|
spec.framework = 'Foundation', 'UIKit', 'SwiftUI'
- spec.dependency 'StreamChat', '~> 4.91.0'
+ spec.dependency 'StreamChat', '~> 4.92.0'
end
diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj
index 904fe0c47..cb726deb8 100644
--- a/StreamChatSwiftUI.xcodeproj/project.pbxproj
+++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj
@@ -3946,7 +3946,7 @@
repositoryURL = "https://github.com/GetStream/stream-chat-swift.git";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 4.91.0;
+ minimumVersion = 4.92.0;
};
};
E3A1C01A282BAC66002D1E26 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
diff --git a/StreamChatSwiftUIArtifacts.json b/StreamChatSwiftUIArtifacts.json
index 84875efd1..59343d470 100644
--- a/StreamChatSwiftUIArtifacts.json
+++ b/StreamChatSwiftUIArtifacts.json
@@ -1 +1 @@
-{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip","4.87.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.87.0/StreamChatSwiftUI.zip","4.88.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.88.0/StreamChatSwiftUI.zip","4.89.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.0/StreamChatSwiftUI.zip","4.89.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.1/StreamChatSwiftUI.zip","4.90.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.90.0/StreamChatSwiftUI.zip","4.91.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.91.0/StreamChatSwiftUI.zip"}
\ No newline at end of file
+{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip","4.87.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.87.0/StreamChatSwiftUI.zip","4.88.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.88.0/StreamChatSwiftUI.zip","4.89.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.0/StreamChatSwiftUI.zip","4.89.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.1/StreamChatSwiftUI.zip","4.90.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.90.0/StreamChatSwiftUI.zip","4.91.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.91.0/StreamChatSwiftUI.zip","4.92.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.92.0/StreamChatSwiftUI.zip"}
\ No newline at end of file
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/ChatChannelInfoViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/ChatChannelInfoViewModel_Tests.swift
index de6a0d03a..668ae8255 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/ChatChannelInfoViewModel_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/ChatChannelInfoViewModel_Tests.swift
@@ -389,7 +389,7 @@ class ChatChannelInfoViewModel_Tests: StreamChatTestCase {
let mutedUser = ChatUser.mock(id: .unique)
let viewModel = ChatChannelInfoViewModel(channel: channel)
let currentUserController = CurrentChatUserController_Mock(client: chatClient)
- let currentUser = CurrentChatUser.mock(id: .unique, mutedUsers: [mutedUser])
+ let currentUser = CurrentChatUser.mock(currentUserId: .unique, mutedUsers: [mutedUser])
currentUserController.currentUser_mock = currentUser
viewModel.currentUserController = currentUserController
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelExtensions_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelExtensions_Tests.swift
index 1c58eabd9..7b5e5b87a 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelExtensions_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelExtensions_Tests.swift
@@ -60,7 +60,14 @@ class ChatChannelExtensions_Tests: StreamChatTestCase {
// Given
let user = ChatUser.mock(id: .unique)
let messages = [ChatMessage.mock(id: .unique, cid: .unique, text: "Test", author: ChatUser.mock(id: .unique))]
- let read = ChatChannelRead(lastReadAt: Date(), lastReadMessageId: nil, unreadMessagesCount: 0, user: user)
+ let read = ChatChannelRead(
+ lastReadAt: Date(),
+ lastReadMessageId: nil,
+ unreadMessagesCount: 0,
+ user: user,
+ lastDeliveredAt: nil,
+ lastDeliveredMessageId: nil
+ )
let channel = ChatChannel.mockDMChannel(reads: [read], latestMessages: messages)
// When
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
index 34495e0d3..ace127d72 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
@@ -534,14 +534,126 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
let message2 = ChatMessage.mock()
let channelController = makeChannelController(messages: [message1, message2])
let viewModel = ChatChannelViewModel(channelController: channelController)
-
+
// When
let shouldJump = viewModel.jumpToMessage(messageId: .unknownMessageId)
-
+
// Then
XCTAssert(shouldJump == false)
}
-
+
+ func test_chatChannelVM_jumpToMessage_setsHighlightedMessageId() {
+ // Given
+ let message1 = ChatMessage.mock()
+ let message2 = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message1, message2])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ let testExpectation = XCTestExpectation(description: "Highlight should be set")
+ testExpectation.assertForOverFulfill = false
+
+ // When
+ let shouldJump = viewModel.jumpToMessage(messageId: message2.messageId)
+
+ // Then
+ XCTAssert(shouldJump == true)
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
+ XCTAssertEqual(viewModel.highlightedMessageId, message2.messageId)
+ testExpectation.fulfill()
+ }
+
+ wait(for: [testExpectation], timeout: 1.0)
+ }
+
+ func test_chatChannelVM_jumpToMessage_clearsHighlightedMessageId() {
+ // Given
+ let message1 = ChatMessage.mock()
+ let message2 = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message1, message2])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ let testExpectation = XCTestExpectation(description: "Highlight should be cleared")
+
+ // When
+ _ = viewModel.jumpToMessage(messageId: message2.messageId)
+
+ // Then
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
+ XCTAssertNil(viewModel.highlightedMessageId)
+ testExpectation.fulfill()
+ }
+
+ wait(for: [testExpectation], timeout: 1.5)
+ }
+
+ func test_chatChannelVM_jumpToMessage_setsScrolledId() {
+ // Given
+ let message1 = ChatMessage.mock()
+ let message2 = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message1, message2])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+
+ // When
+ _ = viewModel.jumpToMessage(messageId: message2.messageId)
+
+ // Then
+ XCTAssertEqual(viewModel.scrolledId, message2.messageId)
+ }
+
+ func test_chatChannelVM_selectedMessageThread_opensThread() {
+ // Given
+ let channelController = makeChannelController()
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: .unique,
+ text: "Test message",
+ author: .mock(id: .unique)
+ )
+
+ // When
+ NotificationCenter.default.post(
+ name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread),
+ object: nil,
+ userInfo: [MessageRepliesConstants.selectedMessage: message]
+ )
+
+ // Then
+ XCTAssertEqual(viewModel.threadMessage, message)
+ XCTAssertTrue(viewModel.threadMessageShown)
+ }
+
+ func test_chatChannelVM_selectedMessageThread_withThreadReplyMessage_opensThread() {
+ // Given
+ let channelController = makeChannelController()
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ let parentMessage = ChatMessage.mock(
+ id: .unique,
+ cid: .unique,
+ text: "Parent message",
+ author: .mock(id: .unique)
+ )
+ let replyMessage = ChatMessage.mock(
+ id: .unique,
+ cid: .unique,
+ text: "Reply message",
+ author: .mock(id: .unique),
+ parentMessageId: parentMessage.id
+ )
+
+ // When
+ NotificationCenter.default.post(
+ name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread),
+ object: nil,
+ userInfo: [
+ MessageRepliesConstants.selectedMessage: parentMessage,
+ MessageRepliesConstants.threadReplyMessage: replyMessage
+ ]
+ )
+
+ // Then
+ XCTAssertEqual(viewModel.threadMessage, parentMessage)
+ XCTAssertTrue(viewModel.threadMessageShown)
+ }
+
func test_chatChannelVM_crashWhenIndexAccess() {
// Given
let message1 = ChatMessage.mock()
@@ -660,6 +772,84 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
XCTAssertEqual(0, channelController.markReadCallCount)
}
+ // MARK: - highlightMessage Tests
+
+ func test_highlightMessage_highlightsWhenSkipHighlightMessageIdIsNotSet() {
+ // Given
+ let message = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ let testExpectation = XCTestExpectation(description: "Message should be highlighted")
+
+ // When
+ viewModel.highlightMessage(withId: message.messageId)
+
+ // Then
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+ XCTAssertEqual(viewModel.highlightedMessageId, message.messageId)
+ testExpectation.fulfill()
+ }
+
+ wait(for: [testExpectation], timeout: defaultTimeout)
+ }
+
+ func test_highlightMessage_highlightsWhenSkipHighlightMessageIdDoesNotMatch() {
+ // Given
+ let message1 = ChatMessage.mock()
+ let message2 = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message1, message2])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ viewModel.skipHighlightMessageId = message1.messageId
+ let testExpectation = XCTestExpectation(description: "Message should be highlighted")
+
+ // When
+ viewModel.highlightMessage(withId: message2.messageId)
+
+ // Then
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+ XCTAssertEqual(viewModel.highlightedMessageId, message2.messageId)
+ XCTAssertEqual(viewModel.skipHighlightMessageId, message1.messageId)
+ testExpectation.fulfill()
+ }
+
+ wait(for: [testExpectation], timeout: defaultTimeout)
+ }
+
+ func test_highlightMessage_doesNotHighlightWhenSkipHighlightMessageIdMatches() {
+ // Given
+ let message = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ viewModel.skipHighlightMessageId = message.messageId
+ let testExpectation = XCTestExpectation(description: "Message should not be highlighted")
+
+ // When
+ viewModel.highlightMessage(withId: message.messageId)
+
+ // Then
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+ XCTAssertNil(viewModel.highlightedMessageId)
+ testExpectation.fulfill()
+ }
+
+ wait(for: [testExpectation], timeout: defaultTimeout)
+ }
+
+ func test_highlightMessage_clearsSkipHighlightMessageIdAfterSkipping() {
+ // Given
+ let message = ChatMessage.mock()
+ let channelController = makeChannelController(messages: [message])
+ let viewModel = ChatChannelViewModel(channelController: channelController)
+ viewModel.skipHighlightMessageId = message.messageId
+
+ // When
+ viewModel.highlightMessage(withId: message.messageId)
+
+ // Then
+ XCTAssertNil(viewModel.skipHighlightMessageId)
+ XCTAssertNil(viewModel.highlightedMessageId)
+ }
+
// MARK: - private
private func makeChannelController(
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
index 56e084464..323130447 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
@@ -72,6 +72,105 @@ class MessageActions_Tests: StreamChatTestCase {
XCTAssert(messageActions[5].title == "Mute User")
}
+ func test_messageActions_partOfThread() {
+ // Given
+ let channel = mockDMChannel
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: channel.cid,
+ text: "Test",
+ author: .mock(id: .unique),
+ parentMessageId: .unique,
+ showReplyInChannel: false,
+ isSentByCurrentUser: false
+ )
+ let factory = DefaultViewFactory.shared
+
+ // When
+ let messageActions = MessageAction.defaultActions(
+ factory: factory,
+ for: message,
+ channel: channel,
+ chatClient: chatClient,
+ onFinish: { _ in },
+ onError: { _ in }
+ )
+
+ // Then
+ XCTAssertEqual(messageActions.count, 4)
+ XCTAssertEqual(messageActions[0].title, "Reply")
+ XCTAssertEqual(messageActions[1].title, "Pin to conversation")
+ XCTAssertEqual(messageActions[2].title, "Copy Message")
+ XCTAssertEqual(messageActions[3].title, "Mute User")
+ }
+
+ func test_messageActions_partOfThreadButAlsoInChannel() {
+ // Given
+ let channel = mockDMChannel
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: channel.cid,
+ text: "Test",
+ author: .mock(id: .unique),
+ parentMessageId: .unique,
+ showReplyInChannel: true,
+ isSentByCurrentUser: false
+ )
+ let factory = DefaultViewFactory.shared
+
+ // When
+ let messageActions = MessageAction.defaultActions(
+ factory: factory,
+ for: message,
+ channel: channel,
+ chatClient: chatClient,
+ onFinish: { _ in },
+ onError: { _ in }
+ )
+
+ // Then
+ XCTAssertEqual(messageActions.count, 5)
+ XCTAssertEqual(messageActions[0].title, "Reply")
+ XCTAssertEqual(messageActions[1].title, "Pin to conversation")
+ XCTAssertEqual(messageActions[2].title, "Copy Message")
+ XCTAssertEqual(messageActions[3].title, "Mark Unread")
+ XCTAssertEqual(messageActions[4].title, "Mute User")
+ }
+
+ func test_messageActions_rootOfThreadButAlsoInChannel() {
+ // Given
+ let channel = mockDMChannel
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: channel.cid,
+ text: "Test",
+ author: .mock(id: .unique),
+ parentMessageId: .unique,
+ showReplyInChannel: true,
+ replyCount: 3,
+ isSentByCurrentUser: false
+ )
+ let factory = DefaultViewFactory.shared
+
+ // When
+ let messageActions = MessageAction.defaultActions(
+ factory: factory,
+ for: message,
+ channel: channel,
+ chatClient: chatClient,
+ onFinish: { _ in },
+ onError: { _ in }
+ )
+
+ // Then
+ XCTAssertEqual(messageActions.count, 5)
+ XCTAssertEqual(messageActions[0].title, "Reply")
+ XCTAssertEqual(messageActions[1].title, "Pin to conversation")
+ XCTAssertEqual(messageActions[2].title, "Copy Message")
+ XCTAssertEqual(messageActions[3].title, "Mark Unread")
+ XCTAssertEqual(messageActions[4].title, "Mute User")
+ }
+
func test_messageActions_otherUserDefaultReadEventsDisabled() {
// Given
let channel = ChatChannel.mockDMChannel(ownCapabilities: [.sendMessage, .uploadFile, .pinMessage])
@@ -418,6 +517,7 @@ class MessageActions_Tests: StreamChatTestCase {
XCTAssertEqual(viewModel.firstUnreadMessageId, message.messageId)
XCTAssertTrue(viewModel.currentUserMarkedMessageUnread)
XCTAssertEqual(viewModel.scrolledId, message.messageId)
+ XCTAssertEqual(viewModel.skipHighlightMessageId, message.messageId)
XCTAssertFalse(viewModel.reactionsShown)
}
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerViewModel_Tests.swift
index 17623396f..6c9dd5201 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerViewModel_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerViewModel_Tests.swift
@@ -1109,74 +1109,6 @@ class MessageComposerViewModel_Tests: StreamChatTestCase {
XCTAssertEqual(viewModel.text, "Draft")
}
- func test_messageComposerVM_draftMessageDeletedEvent() throws {
- // Given
- let channelController = makeChannelController()
- channelController.channel_mock = .mock(cid: .unique, draftMessage: .mock(text: "Draft"))
- let viewModel = makeComposerDraftsViewModel(
- channelController: channelController,
- messageController: nil
- )
-
- // When
- channelController.channel_mock = .mock(cid: .unique, draftMessage: nil)
- let cid = try XCTUnwrap(channelController.cid)
- let event = DraftDeletedEvent(cid: cid, threadId: nil, createdAt: .unique)
- viewModel.eventsController(viewModel.eventsController, didReceiveEvent: event)
-
- // Then
- XCTAssertEqual(viewModel.text, "")
- }
-
- func test_messageComposerVM_draftReplyDeletedEvent() throws {
- // Given
- let channelController = makeChannelController()
- let messageController = ChatMessageControllerSUI_Mock.mock(
- chatClient: chatClient,
- cid: channelController.cid!,
- messageId: .unique
- )
- messageController.message_mock = .mock(draftReply: .mock(text: "Draft"))
- let viewModel = makeComposerDraftsViewModel(
- channelController: channelController,
- messageController: messageController
- )
-
- // When
- messageController.message_mock = .mock(draftReply: nil)
- let cid = try XCTUnwrap(channelController.cid)
- let event = DraftDeletedEvent(cid: cid, threadId: messageController.messageId, createdAt: .unique)
- viewModel.eventsController(viewModel.eventsController, didReceiveEvent: event)
-
- // Then
- XCTAssertEqual(viewModel.text, "")
- }
-
- func test_messageComposerVM_draftReplyDeletedEventFromOtherThread_shouldNotUpdate() throws {
- // Given
- let channelController = makeChannelController()
- let messageController = ChatMessageControllerSUI_Mock.mock(
- chatClient: chatClient,
- cid: channelController.cid!,
- messageId: .unique
- )
- messageController.message_mock = .mock(draftReply: .mock(text: "Draft"))
- let viewModel = makeComposerDraftsViewModel(
- channelController: channelController,
- messageController: messageController
- )
- viewModel.fillDraftMessage()
-
- // When
- messageController.message_mock = .mock(draftReply: nil)
- let cid = try XCTUnwrap(channelController.cid)
- let event = DraftDeletedEvent(cid: cid, threadId: .unique, createdAt: .unique)
- viewModel.eventsController(viewModel.eventsController, didReceiveEvent: event)
-
- // Then
- XCTAssertEqual(viewModel.text, "Draft")
- }
-
func test_messageComposerVM_whenLastAssetRemoved_shouldDeleteDraft() {
// Given
let channelController = makeChannelController()
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
index 9239d3ff2..74a6cb9b7 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
@@ -578,12 +578,54 @@ class MessageContainerView_Tests: StreamChatTestCase {
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}
+ func test_messageContainerHighlighted_snapshot() {
+ // Given
+ let message = ChatMessage.mock(
+ id: "test-message-id",
+ cid: .unique,
+ text: "This message is highlighted",
+ author: .mock(id: .unique, name: "Test User"),
+ isSentByCurrentUser: false
+ )
+ let messageId = message.messageId
+
+ // When
+ let view = testMessageViewContainer(
+ message: message,
+ highlightedMessageId: messageId
+ )
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
+
+ func test_messageContainerNotHighlighted_snapshot() {
+ // Given
+ let message = ChatMessage.mock(
+ id: "test-message-id",
+ cid: .unique,
+ text: "This message is not highlighted",
+ author: .mock(id: .unique, name: "Test User"),
+ isSentByCurrentUser: false
+ )
+
+ // When
+ let view = testMessageViewContainer(
+ message: message,
+ highlightedMessageId: "different-message-id"
+ )
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
+
// MARK: - private
func testMessageViewContainer(
message: ChatMessage,
channel: ChatChannel? = nil,
- messageViewModel: MessageViewModel? = nil
+ messageViewModel: MessageViewModel? = nil,
+ highlightedMessageId: String? = nil
) -> some View {
MessageContainerView(
factory: DefaultViewFactory.shared,
@@ -598,6 +640,7 @@ class MessageContainerView_Tests: StreamChatTestCase {
onLongPress: { _ in },
viewModel: messageViewModel ?? MessageViewModel(message: message, channel: channel)
)
+ .environment(\.highlightedMessageId, highlightedMessageId)
.frame(width: 375, height: 200)
}
}
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift
index e3edd1b2e..5695b7fd5 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift
@@ -122,4 +122,30 @@ class MessageReadIndicatorView_Tests: StreamChatTestCase {
// Then
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}
+
+ func test_messageReadIndicatorView_snapshotMessageDelivered() {
+ // Given
+ let view = MessageReadIndicatorView(
+ readUsers: [],
+ showReadCount: false,
+ showDelivered: true
+ )
+ .frame(width: 50, height: 16)
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
+
+ func test_messageReadIndicatorView_snapshotMessageDeliveredAndRead() {
+ // Given
+ let view = MessageReadIndicatorView(
+ readUsers: [.mock(id: .unique), .mock(id: .unique)],
+ showReadCount: true,
+ showDelivered: true
+ )
+ .frame(width: 50, height: 16)
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
}
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerHighlighted_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerHighlighted_snapshot.1.png
new file mode 100644
index 000000000..fba36a545
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerHighlighted_snapshot.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerNotHighlighted_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerNotHighlighted_snapshot.1.png
new file mode 100644
index 000000000..749171e40
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerNotHighlighted_snapshot.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDelivered.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDelivered.1.png
new file mode 100644
index 000000000..9f0030a36
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDelivered.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDeliveredAndRead.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDeliveredAndRead.1.png
new file mode 100644
index 000000000..1a3d812fd
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageDeliveredAndRead.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListItemView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListItemView_Tests.swift
index 2ecd33df6..92c637f23 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListItemView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListItemView_Tests.swift
@@ -377,6 +377,92 @@ final class ChatChannelListItemView_Tests: StreamChatTestCase {
// Then
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}
+
+ func test_channelListItem_messageDelivered() throws {
+ let date = Date(timeIntervalSince1970: 100)
+
+ // Given
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: .unique,
+ text: "Test message",
+ author: .mock(id: .unique, name: "Darth Vader"),
+ createdAt: date.addingTimeInterval(-100),
+ isSentByCurrentUser: true
+ )
+ let channel = ChatChannel.mock(
+ cid: .unique,
+ config: .mock(readEventsEnabled: true),
+ reads: [
+ .init(
+ lastReadAt: .distantPast,
+ lastReadMessageId: nil,
+ unreadMessagesCount: 0,
+ user: .unique,
+ lastDeliveredAt: date,
+ lastDeliveredMessageId: message.id
+ )
+ ],
+ latestMessages: [message],
+ previewMessage: message
+ )
+
+ // When
+ let view = ChatChannelListItem(
+ channel: channel,
+ channelName: "Test",
+ avatar: .circleImage,
+ onlineIndicatorShown: false,
+ onItemTap: { _ in }
+ )
+ .frame(width: defaultScreenSize.width)
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
+
+ func test_channelListItem_messageDeliveredAndRead() throws {
+ let date = Date(timeIntervalSince1970: 100)
+
+ // Given
+ let message = ChatMessage.mock(
+ id: .unique,
+ cid: .unique,
+ text: "Test message",
+ author: .mock(id: .unique, name: "Darth Vader"),
+ createdAt: date.addingTimeInterval(-100),
+ isSentByCurrentUser: true
+ )
+ let channel = ChatChannel.mock(
+ cid: .unique,
+ config: .mock(readEventsEnabled: true),
+ reads: [
+ .init(
+ lastReadAt: date.addingTimeInterval(10),
+ lastReadMessageId: message.id,
+ unreadMessagesCount: 0,
+ user: .unique,
+ lastDeliveredAt: date,
+ lastDeliveredMessageId: message.id
+ )
+ ],
+ latestMessages: [message],
+ previewMessage: message
+ )
+
+ // When
+ let view = ChatChannelListItem(
+ channel: channel,
+ channelName: "Test",
+ avatar: .circleImage,
+ onlineIndicatorShown: false,
+ onItemTap: { _ in }
+ )
+ .frame(width: defaultScreenSize.width)
+
+ // Then
+ assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+ }
// MARK: - private
diff --git a/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDelivered.1.png b/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDelivered.1.png
new file mode 100644
index 000000000..634640fd7
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDelivered.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDeliveredAndRead.1.png b/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDeliveredAndRead.1.png
new file mode 100644
index 000000000..1e9c06578
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_channelListItem_messageDeliveredAndRead.1.png differ
diff --git a/StreamChatSwiftUITestsAppTests/Robots/UserRobot+Asserts.swift b/StreamChatSwiftUITestsAppTests/Robots/UserRobot+Asserts.swift
index 3ab1f0b8b..cbd910dee 100644
--- a/StreamChatSwiftUITestsAppTests/Robots/UserRobot+Asserts.swift
+++ b/StreamChatSwiftUITestsAppTests/Robots/UserRobot+Asserts.swift
@@ -656,7 +656,7 @@ extension UserRobot {
@discardableResult
func assertMentionWasApplied(file: StaticString = #filePath, line: UInt = #line) -> Self {
- let expectedText = "@\(UserDetails.hanSoloName)"
+ let expectedText = "@\(UserDetails.countDookuName)"
let actualText = MessageListPage.Composer.textView.waitForText(expectedText).text
XCTAssertEqual(expectedText, actualText, file: file, line: line)
return self
diff --git a/StreamChatSwiftUITestsAppTests/Robots/UserRobot.swift b/StreamChatSwiftUITestsAppTests/Robots/UserRobot.swift
index f03af913b..f0afd185e 100644
--- a/StreamChatSwiftUITestsAppTests/Robots/UserRobot.swift
+++ b/StreamChatSwiftUITestsAppTests/Robots/UserRobot.swift
@@ -423,7 +423,7 @@ extension UserRobot {
@discardableResult
func mentionParticipant(manually: Bool = false) -> Self {
- let text = "@\(UserDetails.hanSoloId)"
+ let text = "@\(UserDetails.countDookuId)"
if manually {
typeText(text)
} else {
diff --git a/StreamChatSwiftUITestsAppTests/Tests/MessageList_Tests.swift b/StreamChatSwiftUITestsAppTests/Tests/MessageList_Tests.swift
index 8ee85d954..7cdce68c5 100644
--- a/StreamChatSwiftUITestsAppTests/Tests/MessageList_Tests.swift
+++ b/StreamChatSwiftUITestsAppTests/Tests/MessageList_Tests.swift
@@ -103,7 +103,7 @@ final class MessageList_Tests: StreamTestCase {
linkToScenario(withId: 254)
let message = "message"
- let author = "Han Solo"
+ let author = "Count Dooku"
GIVEN("user opens the channel") {
userRobot.login().openChannel()
diff --git a/StreamChatSwiftUITestsAppTests/Tests/PushNotification_Tests.swift b/StreamChatSwiftUITestsAppTests/Tests/PushNotification_Tests.swift
index 65ca6f65c..243c50266 100644
--- a/StreamChatSwiftUITestsAppTests/Tests/PushNotification_Tests.swift
+++ b/StreamChatSwiftUITestsAppTests/Tests/PushNotification_Tests.swift
@@ -6,7 +6,7 @@ import XCTest
// Requires running a standalone Sinatra server
final class PushNotification_Tests: StreamTestCase {
- let sender = "Han Solo"
+ let sender = "Count Dooku"
let message = "How are you? 🙂"
override func setUpWithError() throws {