Handle threaded pinned events (#4704)

* implemented focussing a threaded event through the banner tap

* view in timeline implementation for threaded events

* added a test and improved the existing one

* pr suggestions

* put the logic for fetching the event in the view models
This commit is contained in:
Mauro
2025-11-10 12:36:42 +01:00
committed by GitHub
parent 3e922ba80e
commit 2ccbe52bc6
13 changed files with 194 additions and 45 deletions
@@ -22,6 +22,7 @@ class RoomScreenViewModelTests: XCTestCase {
override func tearDown() {
viewModel = nil
AppSettings.resetAllSettings()
}
func testPinnedEventsBanner() async throws {
@@ -109,6 +110,7 @@ class RoomScreenViewModelTests: XCTestCase {
func testPinnedEventsBannerSelection() async throws {
let roomProxyMock = JoinedRoomProxyMock(.init())
roomProxyMock.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock())
// setup a way to inject the mock of the pinned events timeline
let pinnedTimelineMock = TimelineProxyMock()
let pinnedTimelineItemProviderMock = TimelineItemProviderMock()
@@ -160,6 +162,67 @@ class RoomScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
}
func testPinnedEventsBannerThreadedSelection() async throws {
ServiceLocator.shared.settings.threadsEnabled = true
let roomProxyMock = JoinedRoomProxyMock(.init())
let eventMock = TimelineEventSDKMock()
eventMock.threadRootEventIdReturnValue = "thread"
roomProxyMock.loadOrFetchEventDetailsForReturnValue = .success(eventMock)
// setup a way to inject the mock of the pinned events timeline
let pinnedTimelineMock = TimelineProxyMock()
let pinnedTimelineItemProviderMock = TimelineItemProviderMock()
pinnedTimelineMock.timelineItemProvider = pinnedTimelineItemProviderMock
pinnedTimelineItemProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], PaginationState), Never>().eraseToAnyPublisher()
pinnedTimelineItemProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))),
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))]
roomProxyMock.pinnedEventsTimelineReturnValue = .success(pinnedTimelineMock)
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
roomProxy: roomProxyMock,
initialSelectedPinnedEventID: "test1",
ongoingCallRoomIDPublisher: .init(.init(nil)),
appSettings: ServiceLocator.shared.settings,
appHooks: AppHooks(),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
self.viewModel = viewModel
// check if the banner is now in a loaded state and is showing the counter
var deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
!viewState.pinnedEventsBannerState.isLoading
}
try await deferred.fulfill()
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.count, 3)
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
// And that is actually displaying the `initialSelectedPinEventID` which is gthe first one in the list
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex, 0)
// check if the banner scrolls when tapping the previous pin
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
viewState.pinnedEventsBannerState.selectedPinnedIndex == 2
}
let deferredAction1 = deferFulfillment(viewModel.actions) { action in
if case let .focusEvent(threadRootEventID) = action {
return threadRootEventID == "thread"
}
return false
}
let deferredAction2 = deferFulfillment(viewModel.actions) { action in
if case let .displayThread(threadRootEventID, focussedEventID) = action {
return threadRootEventID == "thread" && focussedEventID == "test1"
}
return false
}
viewModel.context.send(viewAction: .tappedPinnedEventsBanner)
try await deferred.fulfill()
try await deferredAction1.fulfill()
try await deferredAction2.fulfill()
}
func testRoomInfoUpdate() async throws {
var configuration = JoinedRoomProxyMockConfiguration(id: "TestID", name: "StartingName", avatarURL: nil, hasOngoingCall: false)
let roomProxyMock = JoinedRoomProxyMock(configuration)