mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
Focus events on the live timeline when it has already loaded them
Opening a room on an event always built a detached timeline, even when the live timeline already contained that event, e.g. when tapping the notification for the newest message in a room. The timeline controller now stays on the live timeline in that case, scrolling to and highlighting the event, and only builds a detached timeline when the event isn't loaded.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
@testable import ElementX
|
||||
import MatrixRustSDK
|
||||
import Testing
|
||||
|
||||
@MainActor
|
||||
struct TimelineControllerTests {
|
||||
private let roomProxy = JoinedRoomProxyMock(.init())
|
||||
|
||||
@Test
|
||||
func initialFocusOnLoadedEventUsesTheLiveTimeline() async throws {
|
||||
let controller = makeController(liveEventIDs: ["$event"], initialFocussedEventID: "$event")
|
||||
try await waitForTimelineItems(controller)
|
||||
|
||||
#expect(!roomProxy.timelineFocusedOnEventEventIDNumberOfEventsCalled)
|
||||
#expect(controller.timelineKind == .live)
|
||||
}
|
||||
|
||||
@Test
|
||||
func initialFocusOnUnloadedEventLoadsADetachedTimeline() async throws {
|
||||
let controller = makeController(liveEventIDs: ["$other"], initialFocussedEventID: "$event")
|
||||
try await waitForTimelineItems(controller)
|
||||
|
||||
#expect(roomProxy.timelineFocusedOnEventEventIDNumberOfEventsReceivedArguments?.eventID == "$event")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func makeController(liveEventIDs: [String], initialFocussedEventID: String) -> TimelineController {
|
||||
let itemProxies = liveEventIDs.enumerated().map { index, eventID in
|
||||
TimelineItemProxy.event(EventTimelineItemProxy(item: .init(configuration: .init(eventID: eventID)), uniqueID: .init("\(index)")))
|
||||
}
|
||||
let paginationState = TimelinePaginationState(backward: .idle, forward: .endReached)
|
||||
|
||||
let provider = TimelineItemProviderMock()
|
||||
provider.kind = .live
|
||||
provider.itemProxies = itemProxies
|
||||
provider.paginationState = paginationState
|
||||
provider.membershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
|
||||
provider.updatePublisher = CurrentValueSubject((itemProxies, paginationState)).eraseToAnyPublisher()
|
||||
|
||||
roomProxy.timelineFocusedOnEventEventIDNumberOfEventsReturnValue = .failure(.eventNotFound)
|
||||
|
||||
return TimelineController(roomProxy: roomProxy,
|
||||
timelineProxy: TimelineProxyMock(.init(timelineItemProvider: provider)),
|
||||
initialFocussedEventID: initialFocussedEventID,
|
||||
timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org",
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")),
|
||||
mediaProvider: MediaProviderMock(.init()),
|
||||
userSettings: .volatile())
|
||||
}
|
||||
|
||||
/// The live timeline's items are only published once any initial focus attempt has finished.
|
||||
private func waitForTimelineItems(_ controller: TimelineController) async throws {
|
||||
let deferred = deferFulfillment(controller.callbacks) { callback in
|
||||
if case .updatedTimelineItems = callback {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user