Skip to content

Commit

Permalink
Fix jump to thread reply when opening channel (#2901)
Browse files Browse the repository at this point in the history
* Fix flaky test

* Fix not jumping to reply inside thread when opening channel

* Add test coverage to avoid regressions
  • Loading branch information
nuno-vieira committed Nov 17, 2023
1 parent 251b53f commit 7f261a9
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 15 deletions.
20 changes: 11 additions & 9 deletions Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ open class ChatChannelVC: _ViewController,
private var firstUnreadMessageId: MessageId?

/// In case the given around message id is from a thread, we need to jump to the parent message and then the reply.
private var initialReplyId: MessageId?
internal var initialReplyId: MessageId?

override open func setUp() {
super.setUp()
Expand Down Expand Up @@ -246,14 +246,16 @@ open class ChatChannelVC: _ViewController,
if let messageId = channelController.channelQuery.pagination?.parameter?.aroundMessageId {
// Jump to a message when opening the channel.
jumpToMessage(id: messageId, animated: components.shouldAnimateJumpToMessageWhenOpeningChannel)
} else if let replyId = initialReplyId {
// Jump to a parent message when opening the channel, and then to the reply.
// The delay is necessary so that the animation does not happen to quickly.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.jumpToMessage(
id: replyId,
animated: self.components.shouldAnimateJumpToMessageWhenOpeningChannel
)

if let replyId = initialReplyId {
// Jump to a parent message when opening the channel, and then to the reply.
// The delay is necessary so that the animation does not happen to quickly.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.jumpToMessage(
id: replyId,
animated: self.components.shouldAnimateJumpToMessageWhenOpeningChannel
)
}
}
} else if components.shouldJumpToUnreadWhenOpeningChannel {
// Jump to the unread message.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ final class EventNotificationCenter_Mock: EventNotificationCenter {
var newMessageIdsMock: Set<MessageId>?

lazy var mock_process = MockFunc<([Event], Bool, (() -> Void)?), Void>.mock(for: process)
var mock_processCalledWithEvents: [Event] = []

override func process(
_ events: [Event],
postNotifications: Bool = true,
completion: (() -> Void)? = nil
) {
super.process(events, postNotifications: postNotifications, completion: completion)


mock_processCalledWithEvents = events
mock_process.call(with: (events, postNotifications, completion))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@ final class MessageSender_Tests: XCTestCase {
func test_sender_sendsMessage_whenError_sendsEvent() throws {
var messageId: MessageId!

struct MockError: Error {}
messageRepository.sendMessageResult = .failure(.failedToSendMessage(MockError()))

try database.writeSynchronously { session in
let message = try session.createNewMessage(
in: self.cid,
Expand All @@ -397,12 +400,8 @@ final class MessageSender_Tests: XCTestCase {
messageId = message.id
}

struct MockError: Error {}

messageRepository.sendMessageResult = .failure(.failedToSendMessage(MockError()))

AssertAsync.willBeTrue(messageRepository.sendMessageIds.contains(where: { $0 == messageId }))
AssertAsync.willBeTrue(eventsNotificationCenter.mock_process.input.0.first is NewMessageErrorEvent)
AssertAsync.willBeTrue(eventsNotificationCenter.mock_processCalledWithEvents.first is NewMessageErrorEvent)
XCTAssertCall("sendMessage(with:completion:)", on: messageRepository, times: 1)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ class ChatMessageListVC_Mock: ChatMessageListVC {
jumpToMessageCallCount += 1
jumpToMessageCalledWith = (id: id, animated: animated, onHighlight: onHighlight)
}

var jumpToUnreadMessageCallCount = 0
override func jumpToUnreadMessage(animated: Bool = true, onHighlight: ((IndexPath) -> Void)? = nil) {
jumpToUnreadMessageCallCount += 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,95 @@ final class ChatChannelVC_Tests: XCTestCase {
XCTAssertEqual(messageListVCMock?.jumpToMessageCalledWith?.animated, false)
}

// MARK: - didFinishSynchronizing()

func test_didFinishSynchronizing_whenPaginationParameterIsAroundMessageId_shouldJumpToMessage() {
var components = Components.mock
components.messageListVC = ChatMessageListVC_Mock.self
vc.components = components
let messageListVCMock = vc.messageListVC as? ChatMessageListVC_Mock

channelControllerMock.channelQuery_mock = .init(
cid: .unique,
paginationParameter: .around(.newUniqueId)
)

vc.didFinishSynchronizing(with: nil)

XCTAssertEqual(messageListVCMock?.jumpToMessageCallCount, 1)
}

func test_didFinishSynchronizing_whenPaginationParameterIsAroundMessageId_whenInitialReplyId_shouldJumpToParentAndReply() {
var components = Components.mock
components.messageListVC = ChatMessageListVC_Mock.self
vc.components = components
let messageListVCMock = vc.messageListVC as? ChatMessageListVC_Mock

channelControllerMock.channelQuery_mock = .init(
cid: .unique,
paginationParameter: .around(.newUniqueId)
)

vc.initialReplyId = .newUniqueId

vc.didFinishSynchronizing(with: nil)

AssertAsync.willBeEqual(messageListVCMock?.jumpToMessageCallCount, 2)
}

func test_didFinishSynchronizing_whenPaginationParameterNotAroundMessage_whenShouldJumpToUnreadWhenOpeningChannel_shouldJumpToUnreadMessage() {
var components = Components.mock
components.shouldJumpToUnreadWhenOpeningChannel = true
components.messageListVC = ChatMessageListVC_Mock.self
vc.components = components
let messageListVCMock = vc.messageListVC as? ChatMessageListVC_Mock

channelControllerMock.channelQuery_mock = .init(
cid: .unique
)

vc.didFinishSynchronizing(with: nil)

AssertAsync.willBeEqual(messageListVCMock?.jumpToMessageCallCount, 0)
XCTAssertEqual(messageListVCMock?.jumpToUnreadMessageCallCount, 1)
}

func test_didFinishSynchronizing_whenPaginationParameterNotAroundMessage_whenNotJumpToUnreadWhenOpeningChannel_shouldNotJumpToUnreadMessage() {
var components = Components.mock
components.shouldJumpToUnreadWhenOpeningChannel = false
components.messageListVC = ChatMessageListVC_Mock.self
vc.components = components
let messageListVCMock = vc.messageListVC as? ChatMessageListVC_Mock

channelControllerMock.channelQuery_mock = .init(
cid: .unique
)

vc.didFinishSynchronizing(with: nil)

AssertAsync.willBeEqual(messageListVCMock?.jumpToMessageCallCount, 0)
XCTAssertEqual(messageListVCMock?.jumpToUnreadMessageCallCount, 0)
}

func test_didFinishSynchronizing_whenPaginationParameterIsAroundMessage_whenShouldJumpToUnreadWhenOpeningChannel_shouldNotJumpToUnreadMessage() {
var components = Components.mock
components.shouldJumpToUnreadWhenOpeningChannel = false
components.messageListVC = ChatMessageListVC_Mock.self
vc.components = components
let messageListVCMock = vc.messageListVC as? ChatMessageListVC_Mock

channelControllerMock.channelQuery_mock = .init(
cid: .unique,
paginationParameter: .around(.unique)
)

vc.didFinishSynchronizing(with: nil)

/// If there is a message id to jump, ignore the jump to unread messages.
AssertAsync.willBeEqual(messageListVCMock?.jumpToMessageCallCount, 1)
XCTAssertEqual(messageListVCMock?.jumpToUnreadMessageCallCount, 0)
}

// MARK: - audioQueuePlayerNextAssetURL

func test_audioQueuePlayerNextAssetURL_callsNextAvailableVoiceRecordingProvideWithExpectedInputAndReturnsValue() throws {
Expand Down

0 comments on commit 7f261a9

Please sign in to comment.