Convert the old MockTimelineController to the generated TimelineControllerMock

This commit is contained in:
Stefan Ceriu
2026-06-02 14:28:57 +03:00
parent d66a968afd
commit 6ad1654d97
29 changed files with 299 additions and 380 deletions
@@ -21,7 +21,7 @@ extension TimelineControllerFactoryMock {
if let timelineController = configuration.threadTimelineController { if let timelineController = configuration.threadTimelineController {
return timelineController return timelineController
} else { } else {
return MockTimelineController(timelineItems: RoomTimelineItemFixtures.largeChunk) return TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.largeChunk))
} }
} }
@@ -29,7 +29,7 @@ extension TimelineControllerFactoryMock {
if let timelineController = configuration.threadTimelineController { if let timelineController = configuration.threadTimelineController {
return .success(timelineController) return .success(timelineController)
} else { } else {
let timelineController = MockTimelineController(timelineKind: .thread(rootEventID: threadRootEventID), timelineItems: RoomTimelineItemFixtures.largeChunk) let timelineController = TimelineControllerMock(.init(timelineKind: .thread(rootEventID: threadRootEventID), timelineItems: RoomTimelineItemFixtures.largeChunk))
return .success(timelineController) return .success(timelineController)
} }
} }
@@ -226,7 +226,7 @@ struct PollFormScreen_Previews: PreviewProvider, TestablePreview {
static func makeViewModel(mode: PollFormMode) -> PollFormScreenViewModel { static func makeViewModel(mode: PollFormMode) -> PollFormScreenViewModel {
PollFormScreenViewModel(mode: mode, PollFormScreenViewModel(mode: mode,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
} }
@@ -104,7 +104,7 @@ struct EmojiPickerScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent, static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent,
selectedEmojis: ["😀", "😄"], selectedEmojis: ["😀", "😄"],
emojiProvider: EmojiProvider(appSettings: .volatile()), emojiProvider: EmojiProvider(appSettings: .volatile()),
timelineController: MockTimelineController()) timelineController: TimelineControllerMock(.init()))
static var previews: some View { static var previews: some View {
EmojiPickerScreen(context: viewModel.context) EmojiPickerScreen(context: viewModel.context)
@@ -117,7 +117,7 @@ struct EmojiPickerScreenSheet_Previews: PreviewProvider {
static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent, static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent,
selectedEmojis: ["😀", "😄"], selectedEmojis: ["😀", "😄"],
emojiProvider: EmojiProvider(appSettings: .volatile()), emojiProvider: EmojiProvider(appSettings: .volatile()),
timelineController: MockTimelineController()) timelineController: TimelineControllerMock(.init()))
static var previews: some View { static var previews: some View {
Text("Timeline view") Text("Timeline view")
@@ -163,7 +163,7 @@ struct TimelineMediaPreviewModifier_Previews: PreviewProvider {
thumbnailSource: nil, thumbnailSource: nil,
contentType: .pdf)) contentType: .pdf))
let timelineController = MockTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: [item]) let timelineController = TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: [item]))
let mediaProvider = MediaProviderMock(.init()) let mediaProvider = MediaProviderMock(.init())
@@ -220,7 +220,7 @@ struct TimelineMediaPreviewDetailsView_Previews: PreviewProvider, TestablePrevie
contentType: contentType)) contentType: contentType))
let timelineKind = TimelineKind.media(isPresentedOnRoomScreen ? .roomScreenLive : .mediaFilesScreen) let timelineKind = TimelineKind.media(isPresentedOnRoomScreen ? .roomScreenLive : .mediaFilesScreen)
let timelineController = MockTimelineController(timelineKind: timelineKind, timelineItems: [item]) let timelineController = TimelineControllerMock(.init(timelineKind: timelineKind, timelineItems: [item]))
let viewModel = TimelineMediaPreviewViewModel(initialItem: item, let viewModel = TimelineMediaPreviewViewModel(initialItem: item,
timelineViewModel: TimelineViewModel.mock(timelineKind: timelineKind, timelineViewModel: TimelineViewModel.mock(timelineKind: timelineKind,
@@ -142,7 +142,7 @@ struct TimelineMediaPreviewRedactConfirmationView_Previews: PreviewProvider, Tes
thumbnailInfo: .mockThumbnail, thumbnailInfo: .mockThumbnail,
contentType: contentType)) contentType: contentType))
let timelineController = MockTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: [item]) let timelineController = TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: [item]))
return TimelineMediaPreviewViewModel(initialItem: item, return TimelineMediaPreviewViewModel(initialItem: item,
timelineViewModel: TimelineViewModel.mock(timelineKind: timelineController.timelineKind, timelineViewModel: TimelineViewModel.mock(timelineKind: timelineController.timelineKind,
timelineController: timelineController), timelineController: timelineController),
@@ -392,7 +392,7 @@ extension LocationSharingScreenViewModel {
return LocationSharingScreenViewModel(interactionMode: interactionMode, return LocationSharingScreenViewModel(interactionMode: interactionMode,
mapURLBuilder: AppSettings.volatile().mapTilerSettings.publisher.value, mapURLBuilder: AppSettings.volatile().mapTilerSettings.publisher.value,
roomProxy: roomProxy, roomProxy: roomProxy,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
liveLocationManager: LiveLocationManagerMock(), liveLocationManager: LiveLocationManagerMock(),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -305,9 +305,9 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
private static func makeTimelineViewModel(empty: Bool) -> TimelineViewModel { private static func makeTimelineViewModel(empty: Bool) -> TimelineViewModel {
let timelineController = if empty { let timelineController = if empty {
MockTimelineController.emptyMediaGallery TimelineControllerMock.emptyMediaGallery
} else { } else {
MockTimelineController.mediaGallery TimelineControllerMock.mediaGallery
} }
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
@@ -372,7 +372,7 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
isRoomEncrypted: true, isRoomEncrypted: true,
shouldShowCaptionWarning: true, shouldShowCaptionWarning: true,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: .volatile()), mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: .volatile()),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
clientProxy: ClientProxyMock(.init()), clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
static var previews: some View { static var previews: some View {
@@ -72,7 +72,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
analyticsService: AnalyticsServiceMock(.init())) analyticsService: AnalyticsServiceMock(.init()))
static let emptyTimelineViewModel: TimelineViewModel = { static let emptyTimelineViewModel: TimelineViewModel = {
let timelineController = MockTimelineController(timelineKind: .pinned, timelineItems: []) let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: []))
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
@@ -115,7 +115,7 @@ private extension DateFormatter {
struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview { struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview {
static let viewModelEmpty: RoomPollsHistoryScreenViewModel = { static let viewModelEmpty: RoomPollsHistoryScreenViewModel = {
let timelineController = MockTimelineController(timelineItems: []) let timelineController = TimelineControllerMock(.init(timelineItems: []))
let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls") let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls")
return RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(), return RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(),
timelineController: timelineController, timelineController: timelineController,
@@ -134,8 +134,8 @@ struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview {
timelineItemsTimestamps[item.id] = date timelineItemsTimestamps[item.id] = date
} }
let timelineController = MockTimelineController(timelineItems: polls, let timelineController = TimelineControllerMock(.init(timelineItems: polls,
timelineItemsTimestamps: timelineItemsTimestamps) timelineItemsTimestamps: timelineItemsTimestamps))
let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls", timelineStartReached: true) let roomProxyMockConfiguration = JoinedRoomProxyMockConfiguration(name: "Polls", timelineStartReached: true)
return RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(), return RoomPollsHistoryScreenViewModel(pollInteractionHandler: PollInteractionHandlerMock(),
@@ -364,7 +364,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock, let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -1115,7 +1115,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
extension TimelineViewModel { extension TimelineViewModel {
static let mock = mock(timelineKind: .live) static let mock = mock(timelineKind: .live)
static func mock(timelineKind: TimelineKind = .live, timelineController: MockTimelineController? = nil, hasPredecessor: Bool = false) -> TimelineViewModel { static func mock(timelineKind: TimelineKind = .live, timelineController: TimelineControllerMock? = nil, hasPredecessor: Bool = false) -> TimelineViewModel {
let clientProxyMock = ClientProxyMock(.init()) let clientProxyMock = ClientProxyMock(.init())
clientProxyMock.roomSummaryForAliasReturnValue = .mock(id: "!room:matrix.org", name: "Room") clientProxyMock.roomSummaryForAliasReturnValue = .mock(id: "!room:matrix.org", name: "Room")
clientProxyMock.roomSummaryForIdentifierReturnValue = .mock(id: "!room:matrix.org", name: "Room", canonicalAlias: "#room:matrix.org") clientProxyMock.roomSummaryForIdentifierReturnValue = .mock(id: "!room:matrix.org", name: "Room", canonicalAlias: "#room:matrix.org")
@@ -1125,7 +1125,7 @@ extension TimelineViewModel {
return TimelineViewModel(roomProxy: roomProxy, return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil, focussedEventID: nil,
timelineController: timelineController ?? MockTimelineController(timelineKind: timelineKind), timelineController: timelineController ?? TimelineControllerMock(.init(timelineKind: timelineKind)),
userSession: UserSessionMock(.init(clientProxy: clientProxyMock)), userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -48,7 +48,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxyMock, return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -340,7 +340,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
let roomProxy = JoinedRoomProxyMock(.init()) let roomProxy = JoinedRoomProxyMock(.init())
return TimelineViewModel(roomProxy: roomProxy, return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil, focussedEventID: nil,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -359,7 +359,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"])) let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"]))
return TimelineViewModel(roomProxy: roomProxy, return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil, focussedEventID: nil,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -83,7 +83,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)), return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -94,7 +94,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
return TimelineViewModel(roomProxy: roomProxyMock, return TimelineViewModel(roomProxy: roomProxyMock,
focussedEventID: focussedEventID, focussedEventID: focussedEventID,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -156,7 +156,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxyMock, return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -12,126 +12,77 @@ import Combine
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
class MockTimelineController: TimelineControllerProtocol { struct TimelineControllerMockConfiguration {
private let lastInsertedMessageID: TimelineItemIdentifier = .event(uniqueID: .init("last_message"), var roomProxy: JoinedRoomProxyProtocol?
eventOrTransactionID: .eventID("last_message")) var timelineKind: TimelineKind = .live
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
private var roomProxy: JoinedRoomProxyProtocol? var timelineProxy: TimelineProxyProtocol?
private var timelineProxy: TimelineProxyProtocol? var timelineItemsTimestamps: [TimelineItemIdentifier: Date] = [:]
var paginationState: TimelinePaginationState = .initial
var roomID: String {
roomProxy?.id ?? "MockRoomIdentifier"
} }
private(set) var timelineKind: TimelineKind extension TimelineControllerMock {
static var mediaGallery: TimelineControllerMock {
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>() TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in
private(set) var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default {
didSet {
callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false))
}
}
private let timelineItemsTimestamps: [TimelineItemIdentifier: Date]
/// An array of timeline item arrays that will be inserted in order for each back pagination request.
var backPaginationResponses: [[RoomTimelineItemProtocol]] = []
var paginationState: TimelinePaginationState = .initial {
didSet {
callbacks.send(.paginationState(paginationState))
}
}
static var mediaGallery: MockTimelineController {
MockTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in
partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk
}) }))
} }
static var emptyMediaGallery: MockTimelineController { static var emptyMediaGallery: TimelineControllerMock {
let mock = MockTimelineController(timelineKind: .media(.mediaFilesScreen)) TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen),
mock.paginationState = TimelinePaginationState(backward: .endReached, forward: .endReached) paginationState: TimelinePaginationState(backward: .endReached, forward: .endReached)))
return mock
} }
init(roomProxy: JoinedRoomProxyProtocol? = nil, // swiftlint:disable:next function_body_length cyclomatic_complexity
timelineKind: TimelineKind = .live, convenience init(_ configuration: TimelineControllerMockConfiguration) {
timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default, self.init()
timelineProxy: TimelineProxyProtocol? = nil,
timelineItemsTimestamps: [TimelineItemIdentifier: Date] = [TimelineItemIdentifier: Date]()) {
self.roomProxy = roomProxy
self.timelineKind = timelineKind
self.timelineItems = timelineItems
self.timelineProxy = timelineProxy
self.timelineItemsTimestamps = timelineItemsTimestamps
callbacks.send(.paginationState(paginationState)) let lastInsertedMessageID: TimelineItemIdentifier = .event(uniqueID: .init("last_message"),
eventOrTransactionID: .eventID("last_message"))
let roomProxy = configuration.roomProxy
let timelineProxy = configuration.timelineProxy
let timelineItemsTimestamps = configuration.timelineItemsTimestamps
underlyingCallbacks = PassthroughSubject()
underlyingRoomID = roomProxy?.id ?? "MockRoomIdentifier"
underlyingTimelineKind = configuration.timelineKind
underlyingPaginationState = configuration.paginationState
timelineItems = configuration.timelineItems
callbacks.send(.paginationState(configuration.paginationState))
callbacks.send(.isLive(true)) callbacks.send(.isLive(true))
}
private(set) var focusOnEventCallCount = 0 focusOnEventTimelineSizeClosure = { [weak self] _, _ in
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, TimelineControllerError> { self?.callbacks.send(.isLive(false))
focusOnEventCallCount += 1
callbacks.send(.isLive(false))
return .success(()) return .success(())
} }
private(set) var focusLiveCallCount = 0 focusLiveClosure = { [weak self] in
func focusLive() { self?.callbacks.send(.isLive(true))
focusLiveCallCount += 1
callbacks.send(.isLive(true))
} }
private(set) var paginateBackwardsCallCount = 0 paginateBackwardsRequestSizeReturnValue = .success(())
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> { paginateForwardsRequestSizeReturnValue = .success(())
paginateBackwardsCallCount += 1
paginationState = TimelinePaginationState(backward: .paginating, forward: .endReached)
simulateBackPagination()
return .success(())
}
private func simulateBackPagination() { sendReadReceiptForClosure = { [roomProxy] itemID in
defer {
paginationState = TimelinePaginationState(backward: backPaginationResponses.isEmpty ? .endReached : .idle,
forward: .endReached)
}
guard !backPaginationResponses.isEmpty else { return }
let newItems = backPaginationResponses.removeFirst()
timelineItems.insert(contentsOf: newItems, at: 0)
}
private(set) var paginateForwardsCallCount = 0
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
paginateForwardsCallCount += 1
return .success(())
}
func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
guard let roomProxy, let eventID = itemID.eventID else { return } guard let roomProxy, let eventID = itemID.eventID else { return }
_ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read) _ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read)
} }
func processItemAppearance(_ itemID: TimelineItemIdentifier) async { } toggleReactionToClosure = { [timelineProxy] reaction, eventOrTransactionID in
guard let timelineProxy else { return }
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { } _ = await timelineProxy.toggleReaction(reaction, to: eventOrTransactionID)
func toggleReaction(_ reaction: String, to eventID: TimelineItemIdentifier.EventOrTransactionID) async {
if let timelineProxy {
_ = await timelineProxy.toggleReaction(reaction, to: eventID)
}
} }
func edit(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, redactClosure = { [timelineProxy] eventOrTransactionID in
message: String, guard let timelineProxy else { return }
html: String?, _ = await timelineProxy.redact(eventOrTransactionID, reason: nil)
intentionalMentions: IntentionalMentions) async { }
callbacks.send(.messageSentOrEdited)
timelineItems[timelineItems.endIndex - 1] = TextRoomTimelineItem(id: lastInsertedMessageID, editMessageHtmlIntentionalMentionsClosure = { [weak self] _, message, _, _ in
guard let self else { return }
self.callbacks.send(.messageSentOrEdited)
self.timelineItems[self.timelineItems.endIndex - 1] = TextRoomTimelineItem(id: lastInsertedMessageID,
timestamp: .distantFuture, timestamp: .distantFuture,
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -139,99 +90,46 @@ class MockTimelineController: TimelineControllerProtocol {
sender: .test, sender: .test,
content: .init(body: message), content: .init(body: message),
properties: .init(isEdited: true)) properties: .init(isEdited: true))
self.callbacks.send(.updatedTimelineItems(timelineItems: self.timelineItems, isSwitchingTimelines: false))
} }
func editCaption(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, sendMessageHtmlInReplyToEventIDIntentionalMentionsClosure = { [weak self] message, _, _, _ in
message: String, guard let self else { return }
html: String?, self.callbacks.send(.messageSentOrEdited)
intentionalMentions: IntentionalMentions) async { } self.timelineItems.append(TextRoomTimelineItem(id: lastInsertedMessageID,
func removeCaption(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID) async { }
private(set) var redactCalled = false
func redact(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID) async {
if let timelineProxy {
_ = await timelineProxy.redact(eventOrTransactionID, reason: nil)
}
redactCalled = true
}
func pin(eventID: String) async { }
func unpin(eventID: String) async { }
func messageEventContent(for itemID: TimelineItemIdentifier) -> RoomMessageEventContentWithoutRelation? {
.init(noHandle: .init())
}
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
}
func sendHandle(for itemID: TimelineItemIdentifier) -> SendHandleProxy? {
nil
}
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
timelineItemsTimestamps[itemID] ?? .now
}
// MARK: - Sending
func sendMessage(_ message: String,
html: String?,
inReplyToEventID: String?,
intentionalMentions: IntentionalMentions) async {
callbacks.send(.messageSentOrEdited)
timelineItems.append(TextRoomTimelineItem(id: lastInsertedMessageID,
timestamp: .distantFuture, timestamp: .distantFuture,
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
canBeRepliedTo: true, canBeRepliedTo: true,
sender: .test, sender: .test,
content: .init(body: message))) content: .init(body: message)))
self.callbacks.send(.updatedTimelineItems(timelineItems: self.timelineItems, isSwitchingTimelines: false))
} }
func sendAudio(url: URL, sendAudioUrlAudioInfoCaptionRequestHandleClosure = { [weak self, timelineProxy] url, audioInfo, caption, requestHandle in
audioInfo: MatrixRustSDK.AudioInfo, self?.callbacks.send(.messageSentOrEdited)
caption: String?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendAudio(url: url, return await timelineProxy.sendAudio(url: url,
audioInfo: audioInfo, audioInfo: audioInfo,
caption: caption, caption: caption,
requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError) requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
func sendFile(url: URL, sendFileUrlFileInfoCaptionRequestHandleClosure = { [weak self, timelineProxy] url, fileInfo, caption, requestHandle in
fileInfo: MatrixRustSDK.FileInfo, self?.callbacks.send(.messageSentOrEdited)
caption: String?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendFile(url: url, return await timelineProxy.sendFile(url: url,
fileInfo: fileInfo, fileInfo: fileInfo,
caption: caption, caption: caption,
requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError) requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
func sendImage(url: URL, sendImageUrlThumbnailURLImageInfoCaptionRequestHandleClosure = { [weak self, timelineProxy] url, thumbnailURL, imageInfo, caption, requestHandle in
thumbnailURL: URL, self?.callbacks.send(.messageSentOrEdited)
imageInfo: MatrixRustSDK.ImageInfo,
caption: String?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendImage(url: url, return await timelineProxy.sendImage(url: url,
thumbnailURL: thumbnailURL, thumbnailURL: thumbnailURL,
@@ -239,17 +137,11 @@ class MockTimelineController: TimelineControllerProtocol {
caption: caption, caption: caption,
requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError) requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
func sendLocation(body: String, sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure = { [weak self, timelineProxy] body, geoURI, description, zoomLevel, assetType in
geoURI: GeoURI, self?.callbacks.send(.messageSentOrEdited)
description: String?,
zoomLevel: UInt8?,
assetType: MatrixRustSDK.AssetType?) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendLocation(body: body, return await timelineProxy.sendLocation(body: body,
geoURI: geoURI, geoURI: geoURI,
@@ -257,17 +149,11 @@ class MockTimelineController: TimelineControllerProtocol {
zoomLevel: zoomLevel, zoomLevel: zoomLevel,
assetType: assetType).mapError(TimelineControllerError.timelineProxyError) assetType: assetType).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
func sendVideo(url: URL, sendVideoUrlThumbnailURLVideoInfoCaptionRequestHandleClosure = { [weak self, timelineProxy] url, thumbnailURL, videoInfo, caption, requestHandle in
thumbnailURL: URL, self?.callbacks.send(.messageSentOrEdited)
videoInfo: MatrixRustSDK.VideoInfo,
caption: String?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendVideo(url: url, return await timelineProxy.sendVideo(url: url,
thumbnailURL: thumbnailURL, thumbnailURL: thumbnailURL,
@@ -275,55 +161,89 @@ class MockTimelineController: TimelineControllerProtocol {
caption: caption, caption: caption,
requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError) requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
func sendVoiceMessage(url: URL, sendVoiceMessageUrlAudioInfoWaveformRequestHandleClosure = { [weak self, timelineProxy] url, audioInfo, waveform, requestHandle in
audioInfo: MatrixRustSDK.AudioInfo, self?.callbacks.send(.messageSentOrEdited)
waveform: [Float],
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
return await timelineProxy.sendVoiceMessage(url: url, return await timelineProxy.sendVoiceMessage(url: url,
audioInfo: audioInfo, audioInfo: audioInfo,
waveform: waveform, waveform: waveform,
requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError) requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
} }
return .success(()) return .success(())
} }
// MARK: - Polls createPollQuestionAnswersPollKindClosure = { [weak self, timelineProxy] question, answers, pollKind in
self?.callbacks.send(.messageSentOrEdited)
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
callbacks.send(.messageSentOrEdited)
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.createPoll(question: question, answers: answers, pollKind: pollKind) _ = await timelineProxy.createPoll(question: question, answers: answers, pollKind: pollKind)
} }
return .success(()) return .success(())
} }
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> { editPollOriginalQuestionAnswersPollKindClosure = { [timelineProxy] eventID, question, answers, pollKind in
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind) _ = await timelineProxy.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind)
} }
return .success(()) return .success(())
} }
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> { sendPollResponsePollStartIDAnswersClosure = { [timelineProxy] pollStartID, answers in
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.sendPollResponse(pollStartID: pollStartID, answers: answers) _ = await timelineProxy.sendPollResponse(pollStartID: pollStartID, answers: answers)
} }
return .success(()) return .success(())
} }
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineControllerError> { endPollPollStartIDTextClosure = { [timelineProxy] pollStartID, text in
if let timelineProxy { if let timelineProxy {
_ = await timelineProxy.endPoll(pollStartID: pollStartID, text: text) _ = await timelineProxy.endPoll(pollStartID: pollStartID, text: text)
} }
return .success(()) return .success(())
} }
messageEventContentForReturnValue = .init(noHandle: .init())
debugInfoForReturnValue = .init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
eventTimestampForClosure = { [timelineItemsTimestamps] itemID in
timelineItemsTimestamps[itemID] ?? .now
}
}
func update(paginationState: TimelinePaginationState) {
self.paginationState = paginationState
callbacks.send(.paginationState(paginationState))
}
/// Configures `paginateBackwardsRequestSizeClosure` to simulate back pagination from the given response queue,
/// sending the appropriate `updatedTimelineItems` and `paginationState` callbacks each time.
func setupBackPagination(responses: [[RoomTimelineItemProtocol]]) {
var queue = responses
paginateBackwardsRequestSizeClosure = { [weak self] _ in
guard let self else { return .success(()) }
let paginating = TimelinePaginationState(backward: .paginating, forward: .endReached)
self.paginationState = paginating
self.callbacks.send(.paginationState(paginating))
guard !queue.isEmpty else {
let end = TimelinePaginationState(backward: .endReached, forward: .endReached)
self.paginationState = end
self.callbacks.send(.paginationState(end))
return .success(())
}
let newItems = queue.removeFirst()
self.timelineItems.insert(contentsOf: newItems, at: 0)
self.callbacks.send(.updatedTimelineItems(timelineItems: self.timelineItems, isSwitchingTimelines: false))
let final = TimelinePaginationState(backward: queue.isEmpty ? .endReached : .idle, forward: .endReached)
self.paginationState = final
self.callbacks.send(.paginationState(final))
return .success(())
}
}
} }
@@ -290,7 +290,7 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Some room name", avatarURL: nil)), roomProxy: JoinedRoomProxyMock(.init(name: "Some room name", avatarURL: nil)),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
emojiProvider: EmojiProvider(appSettings: appSettings), emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(), linkMetadataProvider: LinkMetadataProvider(),
@@ -308,7 +308,7 @@ class MockScreen: Identifiable {
return navigationStackCoordinator return navigationStackCoordinator
case .roomSmallTimeline: case .roomSmallTimeline:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.smallChunk) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.smallChunk))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -329,7 +329,7 @@ class MockScreen: Identifiable {
return navigationStackCoordinator return navigationStackCoordinator
case .roomSmallTimelineWithReactions: case .roomSmallTimelineWithReactions:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.default) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.default))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -349,7 +349,7 @@ class MockScreen: Identifiable {
return navigationStackCoordinator return navigationStackCoordinator
case .roomSmallTimelineWithReadReceipts: case .roomSmallTimelineWithReadReceipts:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.smallChunkWithReadReceipts) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.smallChunkWithReadReceipts))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -501,7 +501,7 @@ class MockScreen: Identifiable {
case .roomLayoutHighlight: case .roomLayoutHighlight:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.permalinkChunk) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.permalinkChunk))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Timeline highlight", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "Timeline highlight", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -536,7 +536,7 @@ class MockScreen: Identifiable {
case .roomWithDisclosedPolls: case .roomWithDisclosedPolls:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.disclosedPolls) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.disclosedPolls))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -558,7 +558,7 @@ class MockScreen: Identifiable {
case .roomWithUndisclosedPolls: case .roomWithUndisclosedPolls:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.undisclosedPolls) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.undisclosedPolls))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -580,7 +580,7 @@ class MockScreen: Identifiable {
case .roomWithOutgoingPolls: case .roomWithOutgoingPolls:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockTimelineController(timelineItems: RoomTimelineItemFixtures.outgoingPolls) let timelineController = TimelineControllerMock(.init(timelineItems: RoomTimelineItemFixtures.outgoingPolls))
let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()), let parameters = RoomScreenCoordinatorParameters(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)),
timelineController: timelineController, timelineController: timelineController,
@@ -732,7 +732,7 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new(topic: nil), let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new(topic: nil),
maxNumberOfOptions: 10, maxNumberOfOptions: 10,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
analytics: analytics, analytics: analytics,
userIndicatorController: UserIndicatorControllerMock())) userIndicatorController: UserIndicatorControllerMock()))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
@@ -46,6 +46,6 @@ struct EmojiPickerScreenViewModelTests {
viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent, viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent,
selectedEmojis: selectedEmojis, selectedEmojis: selectedEmojis,
emojiProvider: EmojiProvider(appSettings: .volatile()), emojiProvider: EmojiProvider(appSettings: .volatile()),
timelineController: MockTimelineController(timelineProxy: timelineProxy)) timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)))
} }
} }
@@ -399,7 +399,7 @@ struct LocationSharingScreenViewModelTests {
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil), viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil),
mapURLBuilder: appSettings.mapTilerSettings.publisher.value, mapURLBuilder: appSettings.mapTilerSettings.publisher.value,
roomProxy: roomProxyMock, roomProxy: roomProxyMock,
timelineController: MockTimelineController(timelineProxy: TimelineProxyMock(.init())), timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
liveLocationManager: LiveLocationManagerMock(.init()), liveLocationManager: LiveLocationManagerMock(.init()),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -434,7 +434,7 @@ struct LocationSharingScreenViewModelTests {
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true), viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
mapURLBuilder: appSettings.mapTilerSettings.publisher.value, mapURLBuilder: appSettings.mapTilerSettings.publisher.value,
roomProxy: JoinedRoomProxyMock(.init(members: members)), roomProxy: JoinedRoomProxyMock(.init(members: members)),
timelineController: MockTimelineController(timelineProxy: timelineProxy), timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
liveLocationManager: LiveLocationManagerMock(liveLocationManagerConfiguration), liveLocationManager: LiveLocationManagerMock(liveLocationManagerConfiguration),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -449,7 +449,7 @@ struct LocationSharingScreenViewModelTests {
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true), viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
mapURLBuilder: appSettings.mapTilerSettings.publisher.value, mapURLBuilder: appSettings.mapTilerSettings.publisher.value,
roomProxy: JoinedRoomProxyMock(.init(members: members)), roomProxy: JoinedRoomProxyMock(.init(members: members)),
timelineController: MockTimelineController(timelineProxy: timelineProxy), timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
liveLocationManager: liveLocationManagerMock, liveLocationManager: liveLocationManagerMock,
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -471,7 +471,7 @@ struct LocationSharingScreenViewModelTests {
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare), viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare),
mapURLBuilder: appSettings.mapTilerSettings.publisher.value, mapURLBuilder: appSettings.mapTilerSettings.publisher.value,
roomProxy: roomProxyMock, roomProxy: roomProxyMock,
timelineController: MockTimelineController(timelineProxy: TimelineProxyMock(.init())), timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
liveLocationManager: LiveLocationManagerMock(.init()), liveLocationManager: LiveLocationManagerMock(.init()),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -293,7 +293,7 @@ final class MediaUploadPreviewScreenViewModelTests {
isRoomEncrypted: true, isRoomEncrypted: true,
shouldShowCaptionWarning: true, shouldShowCaptionWarning: true,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings), mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings),
timelineController: MockTimelineController(timelineProxy: timelineProxy), timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
clientProxy: clientProxy, clientProxy: clientProxy,
userIndicatorController: userIndicatorController) userIndicatorController: userIndicatorController)
} }
+11 -11
View File
@@ -23,7 +23,7 @@ struct PillContextTests {
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
proxyMock.membersPublisher = subject.asCurrentValuePublisher() proxyMock.membersPublisher = subject.asCurrentValuePublisher()
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: userIndicatorController, userIndicatorController: userIndicatorController,
@@ -57,7 +57,7 @@ struct PillContextTests {
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
proxyMock.membersPublisher = subject.asCurrentValuePublisher() proxyMock.membersPublisher = subject.asCurrentValuePublisher()
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: userIndicatorController, userIndicatorController: userIndicatorController,
@@ -81,7 +81,7 @@ struct PillContextTests {
let id = "test_room" let id = "test_room"
let displayName = "Test" let displayName = "Test"
let proxyMock = JoinedRoomProxyMock(.init(id: id, name: displayName, avatarURL: avatarURL)) let proxyMock = JoinedRoomProxyMock(.init(id: id, name: displayName, avatarURL: avatarURL))
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: mockController, timelineController: mockController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -105,7 +105,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let clientMock = ClientProxyMock(.init()) let clientMock = ClientProxyMock(.init())
clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌") clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌")
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
@@ -132,7 +132,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: mockController, timelineController: mockController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -157,7 +157,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let clientMock = ClientProxyMock(.init()) let clientMock = ClientProxyMock(.init())
clientMock.roomSummaryForAliasReturnValue = .mock(id: "2", clientMock.roomSummaryForAliasReturnValue = .mock(id: "2",
name: "Foundation and Empire", name: "Foundation and Empire",
@@ -186,7 +186,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: mockController, timelineController: mockController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -211,7 +211,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let clientMock = ClientProxyMock(.init()) let clientMock = ClientProxyMock(.init())
clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌") clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌")
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
@@ -238,7 +238,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: mockController, timelineController: mockController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -263,7 +263,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let clientMock = ClientProxyMock(.init()) let clientMock = ClientProxyMock(.init())
clientMock.roomSummaryForAliasReturnValue = .mock(id: "2", clientMock.roomSummaryForAliasReturnValue = .mock(id: "2",
name: "Foundation and Empire", name: "Foundation and Empire",
@@ -292,7 +292,7 @@ struct PillContextTests {
let userIndicatorController = UserIndicatorControllerMock() let userIndicatorController = UserIndicatorControllerMock()
let proxyMock = JoinedRoomProxyMock(.init()) let proxyMock = JoinedRoomProxyMock(.init())
let mockController = MockTimelineController(roomProxy: proxyMock) let mockController = TimelineControllerMock(.init(roomProxy: proxyMock))
let mock = TimelineViewModel(roomProxy: proxyMock, let mock = TimelineViewModel(roomProxy: proxyMock,
timelineController: mockController, timelineController: mockController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -178,7 +178,7 @@ struct PollFormScreenViewModelTests {
private mutating func setupViewModel(mode: PollFormMode = .new(topic: nil)) { private mutating func setupViewModel(mode: PollFormMode = .new(topic: nil)) {
viewModel = PollFormScreenViewModel(mode: mode, viewModel = PollFormScreenViewModel(mode: mode,
timelineController: MockTimelineController(timelineProxy: timelineProxy), timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
analytics: AnalyticsServiceMock(.init()), analytics: AnalyticsServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
} }
@@ -14,11 +14,11 @@ import Testing
struct RoomPollsHistoryScreenViewModelTests { struct RoomPollsHistoryScreenViewModelTests {
var viewModel: RoomPollsHistoryScreenViewModelProtocol! var viewModel: RoomPollsHistoryScreenViewModelProtocol!
var interactionHandler: PollInteractionHandlerMock! var interactionHandler: PollInteractionHandlerMock!
var timelineController: MockTimelineController! var timelineController: TimelineControllerMock!
init() throws { init() throws {
interactionHandler = PollInteractionHandlerMock() interactionHandler = PollInteractionHandlerMock()
timelineController = MockTimelineController() timelineController = TimelineControllerMock(.init())
viewModel = RoomPollsHistoryScreenViewModel(pollInteractionHandler: interactionHandler, viewModel = RoomPollsHistoryScreenViewModel(pollInteractionHandler: interactionHandler,
timelineController: timelineController, timelineController: timelineController,
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
@@ -26,12 +26,12 @@ struct RoomPollsHistoryScreenViewModelTests {
@Test @Test
func backPaginate() async throws { func backPaginate() async throws {
timelineController.backPaginationResponses = [ timelineController.setupBackPagination(responses: [
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true), [PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
PollRoomTimelineItem.mock(poll: .endedDisclosed)] PollRoomTimelineItem.mock(poll: .endedDisclosed)]
] ])
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false]) let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
@@ -45,13 +45,13 @@ struct RoomPollsHistoryScreenViewModelTests {
@Test @Test
func backPaginateCanBackPaginate() async throws { func backPaginateCanBackPaginate() async throws {
timelineController.backPaginationResponses = [ timelineController.setupBackPagination(responses: [
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true), [PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
PollRoomTimelineItem.mock(poll: .endedDisclosed)], PollRoomTimelineItem.mock(poll: .endedDisclosed)],
[] []
] ])
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false]) let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
@@ -65,12 +65,12 @@ struct RoomPollsHistoryScreenViewModelTests {
@Test @Test
func backPaginateTwice() async throws { func backPaginateTwice() async throws {
timelineController.backPaginationResponses = [ timelineController.setupBackPagination(responses: [
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true), [PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false))], PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false))],
[PollRoomTimelineItem.mock(poll: .endedDisclosed)] [PollRoomTimelineItem.mock(poll: .endedDisclosed)]
] ])
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false]) let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
viewModel.context.send(viewAction: .loadMore) viewModel.context.send(viewAction: .loadMore)
@@ -84,13 +84,13 @@ struct RoomPollsHistoryScreenViewModelTests {
@Test @Test
func filters() async throws { func filters() async throws {
timelineController.backPaginationResponses = [ timelineController.setupBackPagination(responses: [
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true), [PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)), PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
PollRoomTimelineItem.mock(poll: .endedDisclosed)], PollRoomTimelineItem.mock(poll: .endedDisclosed)],
[] []
] ])
let deferredViewState = deferFulfillment(viewModel.context.$viewState) { value in let deferredViewState = deferFulfillment(viewModel.context.$viewState) { value in
!value.pollTimelineItems.isEmpty !value.pollTimelineItems.isEmpty
@@ -22,7 +22,7 @@ struct TimelineMediaPreviewViewModelTests {
var mediaProvider: MediaProviderMock! var mediaProvider: MediaProviderMock!
var photoLibraryManager: PhotoLibraryManagerMock! var photoLibraryManager: PhotoLibraryManagerMock!
var timelineController: MockTimelineController! var timelineController: TimelineControllerMock!
@Test @Test
mutating func loadingItem() async throws { mutating func loadingItem() async throws {
@@ -94,11 +94,10 @@ struct TimelineMediaPreviewViewModelTests {
mutating func loadingMoreItems() async throws { mutating func loadingMoreItems() async throws {
// Given a view model with a loaded item. // Given a view model with a loaded item.
try await loadingItem() try await loadingItem()
#expect(timelineController.paginateBackwardsCallCount == 0) #expect(timelineController.paginateBackwardsRequestSizeCallsCount == 0)
// When swiping to a "loading more" item and there are more media items to load. // When swiping to a "loading more" item and there are more media items to load.
timelineController.paginationState = .init(backward: .idle, forward: .endReached) timelineController.update(paginationState: .init(backward: .idle, forward: .endReached))
timelineController.backPaginationResponses.append(RoomTimelineItemFixtures.mediaChunk)
let failure = deferFailure(viewModel.state.previewControllerDriver, timeout: .seconds(1)) { $0.isItemLoaded } let failure = deferFailure(viewModel.state.previewControllerDriver, timeout: .seconds(1)) { $0.isItemLoaded }
context.send(viewAction: .updateCurrentItem(.loading(.paginatingBackwards))) context.send(viewAction: .updateCurrentItem(.loading(.paginatingBackwards)))
try await failure.fulfill() try await failure.fulfill()
@@ -106,7 +105,7 @@ struct TimelineMediaPreviewViewModelTests {
// Then there should no longer be a media preview and instead of loading any media, a pagination request should be made. // Then there should no longer be a media preview and instead of loading any media, a pagination request should be made.
#expect(mediaProvider.loadFileFromSourceFilenameCallsCount == 1) #expect(mediaProvider.loadFileFromSourceFilenameCallsCount == 1)
#expect(context.viewState.currentItem == .loading(.paginatingBackwards)) // Note: This item only changes when the preview controller handles the new items. #expect(context.viewState.currentItem == .loading(.paginatingBackwards)) // Note: This item only changes when the preview controller handles the new items.
#expect(timelineController.paginateBackwardsCallCount == 1) #expect(timelineController.paginateBackwardsRequestSizeCallsCount == 1)
} }
@Test @Test
@@ -117,7 +116,7 @@ struct TimelineMediaPreviewViewModelTests {
// When more items are added via a back pagination. // When more items are added via a back pagination.
let deferred = deferFulfillment(context.viewState.dataSource.previewItemsPaginationPublisher) { _ in true } let deferred = deferFulfillment(context.viewState.dataSource.previewItemsPaginationPublisher) { _ in true }
timelineController.backPaginationResponses.append(makeItems()) timelineController.setupBackPagination(responses: [makeItems()])
_ = await timelineController.paginateBackwards(requestSize: 20) _ = await timelineController.paginateBackwards(requestSize: 20)
try await deferred.fulfill() try await deferred.fulfill()
@@ -292,7 +291,7 @@ struct TimelineMediaPreviewViewModelTests {
private mutating func setupViewModel(initialItemIndex: Int = 0, photoLibraryAuthorizationDenied: Bool = false) { private mutating func setupViewModel(initialItemIndex: Int = 0, photoLibraryAuthorizationDenied: Bool = false) {
let initialItems = makeItems() let initialItems = makeItems()
timelineController = MockTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: initialItems) timelineController = TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: initialItems))
mediaProvider = MediaProviderMock(.init()) mediaProvider = MediaProviderMock(.init())
photoLibraryManager = PhotoLibraryManagerMock(.init(authorizationDenied: photoLibraryAuthorizationDenied)) photoLibraryManager = PhotoLibraryManagerMock(.init(authorizationDenied: photoLibraryAuthorizationDenied))
+25 -25
View File
@@ -35,7 +35,7 @@ final class TimelineViewModelTests {
] ]
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
// Then the messages should be grouped together. // Then the messages should be grouped together.
@@ -63,7 +63,7 @@ final class TimelineViewModelTests {
] ]
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
// Then the messages should be grouped by sender. // Then the messages should be grouped by sender.
@@ -89,7 +89,7 @@ final class TimelineViewModelTests {
] ]
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
// Then the first message should not be grouped but the other two should. // Then the first message should not be grouped but the other two should.
@@ -112,7 +112,7 @@ final class TimelineViewModelTests {
] ]
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
// Then the first and second messages should be grouped and the last one should not. // Then the first and second messages should be grouped and the last one should not.
@@ -135,7 +135,7 @@ final class TimelineViewModelTests {
] ]
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
// Then the messages should be grouped together. // Then the messages should be grouped together.
@@ -152,10 +152,10 @@ final class TimelineViewModelTests {
let items = [TextRoomTimelineItem(eventID: "t1"), let items = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
TextRoomTimelineItem(eventID: "t3")] TextRoomTimelineItem(eventID: "t3")]
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
#expect(timelineController.focusOnEventCallCount == 0) #expect(timelineController.focusOnEventTimelineSizeCallsCount == 0)
#expect(viewModel.context.viewState.timelineState.isLive) #expect(viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil) #expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
@@ -165,7 +165,7 @@ final class TimelineViewModelTests {
try await deferred.fulfill() try await deferred.fulfill()
// Then a new timeline should be loaded and the room focussed on that event. // Then a new timeline should be loaded and the room focussed on that event.
#expect(timelineController.focusOnEventCallCount == 1) #expect(timelineController.focusOnEventTimelineSizeCallsCount == 1)
#expect(!viewModel.context.viewState.timelineState.isLive) #expect(!viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate)) #expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate))
} }
@@ -176,10 +176,10 @@ final class TimelineViewModelTests {
let items = [TextRoomTimelineItem(eventID: "t1"), let items = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
TextRoomTimelineItem(eventID: "t3")] TextRoomTimelineItem(eventID: "t3")]
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
#expect(timelineController.focusOnEventCallCount == 0) #expect(timelineController.focusOnEventTimelineSizeCallsCount == 0)
#expect(viewModel.context.viewState.timelineState.isLive) #expect(viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil) #expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
@@ -189,7 +189,7 @@ final class TimelineViewModelTests {
try await deferred.fulfill() try await deferred.fulfill()
// Then the timeline should remain live and the item should be focussed. // Then the timeline should remain live and the item should be focussed.
#expect(timelineController.focusOnEventCallCount == 0) #expect(timelineController.focusOnEventTimelineSizeCallsCount == 0)
#expect(viewModel.context.viewState.timelineState.isLive) #expect(viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t1", appearance: .animated)) #expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t1", appearance: .animated))
} }
@@ -200,7 +200,7 @@ final class TimelineViewModelTests {
let items = [TextRoomTimelineItem(eventID: "t1"), let items = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
TextRoomTimelineItem(eventID: "t3")] TextRoomTimelineItem(eventID: "t3")]
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
@@ -208,7 +208,7 @@ final class TimelineViewModelTests {
await viewModel.focusOnEvent(eventID: "t4") await viewModel.focusOnEvent(eventID: "t4")
try await deferred.fulfill() try await deferred.fulfill()
#expect(timelineController.focusLiveCallCount == 0) #expect(timelineController.focusLiveCallsCount == 0)
#expect(!viewModel.context.viewState.timelineState.isLive) #expect(!viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate)) #expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate))
@@ -218,14 +218,14 @@ final class TimelineViewModelTests {
try await deferred.fulfill() try await deferred.fulfill()
// Then the timeline should switch back to being live and the event focus should be removed. // Then the timeline should switch back to being live and the event focus should be removed.
#expect(timelineController.focusLiveCallCount == 1) #expect(timelineController.focusLiveCallsCount == 1)
#expect(viewModel.context.viewState.timelineState.isLive) #expect(viewModel.context.viewState.timelineState.isLive)
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil) #expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
} }
@Test @Test
func initialFocusViewState() { func initialFocusViewState() {
let timelineController = MockTimelineController() let timelineController = TimelineControllerMock(.init())
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController) let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t10", appearance: .immediate)) #expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t10", appearance: .immediate))
@@ -285,14 +285,14 @@ final class TimelineViewModelTests {
private func readReceiptsConfiguration(with items: [RoomTimelineItemProtocol]) -> (TimelineViewModel, private func readReceiptsConfiguration(with items: [RoomTimelineItemProtocol]) -> (TimelineViewModel,
JoinedRoomProxyMock, JoinedRoomProxyMock,
TimelineProxyMock, TimelineProxyMock,
MockTimelineController) { TimelineControllerMock) {
let timelineProxy = TimelineProxyMock() let timelineProxy = TimelineProxyMock()
timelineProxy.sendReadReceiptForTypeReturnValue = .success(()) timelineProxy.sendReadReceiptForTypeReturnValue = .success(())
let roomProxy = JoinedRoomProxyMock(.init(name: "")) let roomProxy = JoinedRoomProxyMock(.init(name: ""))
roomProxy.timeline = timelineProxy roomProxy.timeline = timelineProxy
let timelineController = MockTimelineController(roomProxy: roomProxy, timelineItems: items) let timelineController = TimelineControllerMock(.init(roomProxy: roomProxy, timelineItems: items))
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
@@ -323,7 +323,7 @@ final class TimelineViewModelTests {
let appSettings = AppSettings.volatile() let appSettings = AppSettings.volatile()
// When showing them in a timeline. // When showing them in a timeline.
let timelineController = MockTimelineController(timelineItems: [message]) let timelineController = TimelineControllerMock(.init(timelineItems: [message]))
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "", members: [RoomMemberProxyMock.mockAlice, RoomMemberProxyMock.mockCharlie])), let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "", members: [RoomMemberProxyMock.mockAlice, RoomMemberProxyMock.mockCharlie])),
timelineController: timelineController, timelineController: timelineController,
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
@@ -352,7 +352,7 @@ final class TimelineViewModelTests {
members: [RoomMemberProxyMock.mockAdmin, members: [RoomMemberProxyMock.mockAdmin,
RoomMemberProxyMock.mockAlice], RoomMemberProxyMock.mockAlice],
ownUserID: RoomMemberProxyMock.mockAdmin.userID)), ownUserID: RoomMemberProxyMock.mockAdmin.userID)),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -391,7 +391,7 @@ final class TimelineViewModelTests {
members: [RoomMemberProxyMock.mockAdmin, members: [RoomMemberProxyMock.mockAdmin,
RoomMemberProxyMock.mockAlice], RoomMemberProxyMock.mockAlice],
ownUserID: RoomMemberProxyMock.mockAlice.userID)), ownUserID: RoomMemberProxyMock.mockAlice.userID)),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -430,7 +430,7 @@ final class TimelineViewModelTests {
members: [RoomMemberProxyMock.mockAdmin, members: [RoomMemberProxyMock.mockAdmin,
RoomMemberProxyMock.mockBanned[0]], RoomMemberProxyMock.mockBanned[0]],
ownUserID: RoomMemberProxyMock.mockAdmin.userID)), ownUserID: RoomMemberProxyMock.mockAdmin.userID)),
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -475,7 +475,7 @@ final class TimelineViewModelTests {
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -506,7 +506,7 @@ final class TimelineViewModelTests {
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(), timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()), userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(), mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
@@ -543,7 +543,7 @@ final class TimelineViewModelTests {
func tapSendInfoEncryptionAuthentictyDisplaysAlert() { func tapSendInfoEncryptionAuthentictyDisplaysAlert() {
// Given a room with an event whose authenticity could not be verified // Given a room with an event whose authenticity could not be verified
let items = [TextRoomTimelineItem(eventID: "t1", encryptionAuthenticity: .verificationViolation(color: .red))] let items = [TextRoomTimelineItem(eventID: "t1", encryptionAuthenticity: .verificationViolation(color: .red))]
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
#expect(viewModel.state.bindings.alertInfo == nil) #expect(viewModel.state.bindings.alertInfo == nil)
@@ -557,7 +557,7 @@ final class TimelineViewModelTests {
func tapSendInfoEncryptionForwarderDisplaysAlert() { func tapSendInfoEncryptionForwarderDisplaysAlert() {
// Given a room with an event whose key was forwarded // Given a room with an event whose key was forwarded
let items = [TextRoomTimelineItem(eventID: "t1", keyForwarder: .test)] let items = [TextRoomTimelineItem(eventID: "t1", keyForwarder: .test)]
let timelineController = MockTimelineController(timelineItems: items) let timelineController = TimelineControllerMock(.init(timelineItems: items))
let viewModel = makeViewModel(timelineController: timelineController) let viewModel = makeViewModel(timelineController: timelineController)
#expect(viewModel.state.bindings.alertInfo == nil) #expect(viewModel.state.bindings.alertInfo == nil)
@@ -205,7 +205,7 @@ struct VoiceMessageRecorderTests {
@Test @Test
func sendVoiceMessage_NoRecordingFile() async { func sendVoiceMessage_NoRecordingFile() async {
let timelineController = MockTimelineController() let timelineController = TimelineControllerMock(.init())
// If there is no recording file, an error is expected // If there is no recording file, an error is expected
audioRecorder.audioFileURL = nil audioRecorder.audioFileURL = nil
@@ -222,7 +222,7 @@ struct VoiceMessageRecorderTests {
// If the converter returns an error // If the converter returns an error
audioConverter.convertToOpusOggSourceURLDestinationURLThrowableError = AudioConverterError.conversionFailed(nil) audioConverter.convertToOpusOggSourceURLDestinationURLThrowableError = AudioConverterError.conversionFailed(nil)
let timelineController = MockTimelineController() let timelineController = TimelineControllerMock(.init())
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController, guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController,
audioConverter: audioConverter) else { audioConverter: audioConverter) else {
Issue.record("An error is expected") Issue.record("An error is expected")
@@ -239,7 +239,7 @@ struct VoiceMessageRecorderTests {
} }
let timelineProxy = TimelineProxyMock() let timelineProxy = TimelineProxyMock()
let timelineController = MockTimelineController(timelineProxy: timelineProxy) let timelineController = TimelineControllerMock(.init(timelineProxy: timelineProxy))
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic))
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController, guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController,
audioConverter: audioConverter) else { audioConverter: audioConverter) else {
@@ -258,7 +258,7 @@ struct VoiceMessageRecorderTests {
} }
let timelineProxy = TimelineProxyMock() let timelineProxy = TimelineProxyMock()
let timelineController = MockTimelineController(timelineProxy: timelineProxy) let timelineController = TimelineControllerMock(.init(timelineProxy: timelineProxy))
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic))
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController, guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController,
audioConverter: audioConverter) else { audioConverter: audioConverter) else {
@@ -279,7 +279,7 @@ struct VoiceMessageRecorderTests {
// If the media upload fails // If the media upload fails
let timelineProxy = TimelineProxyMock() let timelineProxy = TimelineProxyMock()
let timelineController = MockTimelineController(timelineProxy: timelineProxy) let timelineController = TimelineControllerMock(.init(timelineProxy: timelineProxy))
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) timelineProxy.sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue = .failure(.sdkError(SDKError.generic))
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController, guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(timelineController: timelineController,
audioConverter: audioConverter) else { audioConverter: audioConverter) else {
@@ -293,7 +293,7 @@ struct VoiceMessageRecorderTests {
let imageFileURL = try #require(Bundle(for: UnitTestsAppCoordinator.self).url(forResource: "test_voice_message", withExtension: "m4a"), "Test audio file is missing") let imageFileURL = try #require(Bundle(for: UnitTestsAppCoordinator.self).url(forResource: "test_voice_message", withExtension: "m4a"), "Test audio file is missing")
let timelineProxy = TimelineProxyMock() let timelineProxy = TimelineProxyMock()
let timelineController = MockTimelineController(timelineProxy: timelineProxy) let timelineController = TimelineControllerMock(.init(timelineProxy: timelineProxy))
audioRecorder.currentTime = 42 audioRecorder.currentTime = 42
audioRecorder.audioFileURL = imageFileURL audioRecorder.audioFileURL = imageFileURL
_ = await voiceMessageRecorder.startRecording() _ = await voiceMessageRecorder.startRecording()