mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Pinned Events Timeline actions and differentiation (#3182)
This commit is contained in:
@@ -28,6 +28,7 @@ class PillContextTests: XCTestCase {
|
||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -56,6 +57,7 @@ class PillContextTests: XCTestCase {
|
||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -77,6 +79,7 @@ class PillContextTests: XCTestCase {
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
@@ -37,9 +37,9 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(state.isLoading)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
XCTAssertNil(state.selectedPinEventID)
|
||||
XCTAssertNil(state.selectedPinnedEventID)
|
||||
XCTAssertEqual(state.displayedMessage.string, L10n.screenRoomPinnedBannerLoadingDescription)
|
||||
XCTAssertEqual(state.selectedPinIndex, 4)
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 4)
|
||||
XCTAssertEqual(state.count, 5)
|
||||
XCTAssertEqual(state.bannerIndicatorDescription.string, L10n.screenRoomPinnedBannerIndicatorDescription(L10n.screenRoomPinnedBannerIndicator(5, 5)))
|
||||
}
|
||||
@@ -48,42 +48,42 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
var state = PinnedEventsBannerState.loading(numbersOfEvents: 2)
|
||||
XCTAssertTrue(state.isLoading)
|
||||
state.setPinnedEventContents(["1": "test1", "2": "test2"])
|
||||
XCTAssertEqual(state, .loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2"], selectedPinEventID: "2")))
|
||||
XCTAssertEqual(state, .loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2"], selectedPinnedEventID: "2")))
|
||||
XCTAssertFalse(state.isLoading)
|
||||
}
|
||||
|
||||
func testLoaded() {
|
||||
let state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2"], selectedPinEventID: "2"))
|
||||
let state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2"], selectedPinnedEventID: "2"))
|
||||
XCTAssertFalse(state.isLoading)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
XCTAssertEqual(state.selectedPinEventID, "2")
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "2")
|
||||
XCTAssertEqual(state.displayedMessage.string, "test2")
|
||||
XCTAssertEqual(state.selectedPinIndex, 1)
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 1)
|
||||
XCTAssertEqual(state.count, 2)
|
||||
XCTAssertEqual(state.bannerIndicatorDescription.string, L10n.screenRoomPinnedBannerIndicatorDescription(L10n.screenRoomPinnedBannerIndicator(2, 2)))
|
||||
}
|
||||
|
||||
func testPreviousPin() {
|
||||
var state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2", "3": "test3"], selectedPinEventID: "1"))
|
||||
XCTAssertEqual(state.selectedPinEventID, "1")
|
||||
XCTAssertEqual(state.selectedPinIndex, 0)
|
||||
var state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2", "3": "test3"], selectedPinnedEventID: "1"))
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "1")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 0)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test1")
|
||||
|
||||
state.previousPin()
|
||||
XCTAssertEqual(state.selectedPinEventID, "3")
|
||||
XCTAssertEqual(state.selectedPinIndex, 2)
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "3")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 2)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test3")
|
||||
|
||||
state.previousPin()
|
||||
XCTAssertEqual(state.selectedPinEventID, "2")
|
||||
XCTAssertEqual(state.selectedPinIndex, 1)
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "2")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 1)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test2")
|
||||
}
|
||||
|
||||
func testSetContent() {
|
||||
var state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2", "3": "test3", "4": "test4"], selectedPinEventID: "2"))
|
||||
XCTAssertEqual(state.selectedPinEventID, "2")
|
||||
XCTAssertEqual(state.selectedPinIndex, 1)
|
||||
var state = PinnedEventsBannerState.loaded(state: .init(pinnedEventContents: ["1": "test1", "2": "test2", "3": "test3", "4": "test4"], selectedPinnedEventID: "2"))
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "2")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 1)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test2")
|
||||
XCTAssertEqual(state.count, 4)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
@@ -91,8 +91,8 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
// let's remove the selected item
|
||||
state.setPinnedEventContents(["1": "test1", "3": "test3", "4": "test4"])
|
||||
// new selected item is the new latest
|
||||
XCTAssertEqual(state.selectedPinEventID, "4")
|
||||
XCTAssertEqual(state.selectedPinIndex, 2)
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "4")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 2)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test4")
|
||||
XCTAssertEqual(state.count, 3)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
@@ -100,9 +100,9 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
// let's add a new item at the top
|
||||
state.setPinnedEventContents(["0": "test0", "1": "test1", "3": "test3", "4": "test4"])
|
||||
// selected item doesn't change
|
||||
XCTAssertEqual(state.selectedPinEventID, "4")
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "4")
|
||||
// but the index is updated
|
||||
XCTAssertEqual(state.selectedPinIndex, 3)
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 3)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test4")
|
||||
XCTAssertEqual(state.count, 4)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
@@ -110,9 +110,9 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
// let's add a new item at the bottom
|
||||
state.setPinnedEventContents(["0": "test0", "1": "test1", "3": "test3", "4": "test4", "5": "test5"])
|
||||
// selected item doesn't change
|
||||
XCTAssertEqual(state.selectedPinEventID, "4")
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "4")
|
||||
// and index stays the same
|
||||
XCTAssertEqual(state.selectedPinIndex, 3)
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 3)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test4")
|
||||
XCTAssertEqual(state.count, 5)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
@@ -120,12 +120,12 @@ class PinnedEventsBannerStateTests: XCTestCase {
|
||||
// set to tempty
|
||||
state.setPinnedEventContents([:])
|
||||
XCTAssertTrue(state.isEmpty)
|
||||
XCTAssertNil(state.selectedPinEventID)
|
||||
XCTAssertNil(state.selectedPinnedEventID)
|
||||
|
||||
// set to one item
|
||||
state.setPinnedEventContents(["6": "test6", "7": "test7"])
|
||||
XCTAssertEqual(state.selectedPinEventID, "7")
|
||||
XCTAssertEqual(state.selectedPinIndex, 1)
|
||||
XCTAssertEqual(state.selectedPinnedEventID, "7")
|
||||
XCTAssertEqual(state.selectedPinnedIndex, 1)
|
||||
XCTAssertEqual(state.displayedMessage.string, "test7")
|
||||
XCTAssertEqual(state.count, 2)
|
||||
XCTAssertFalse(state.isEmpty)
|
||||
|
||||
@@ -43,6 +43,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
// setup the room proxy actions publisher
|
||||
roomProxyMock.underlyingActionsPublisher = updateSubject.eraseToAnyPublisher()
|
||||
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
@@ -67,6 +68,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
XCTAssertTrue(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
|
||||
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
||||
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex, 1)
|
||||
|
||||
// setup the loaded pinned events injection in the timeline
|
||||
let pinnedTimelineMock = TimelineProxyMock()
|
||||
@@ -74,8 +76,8 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let providerUpdateSubject = PassthroughSubject<([TimelineItemProxy], PaginationState), Never>()
|
||||
pinnedTimelineProviderMock.underlyingUpdatePublisher = providerUpdateSubject.eraseToAnyPublisher()
|
||||
pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock
|
||||
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItemSDKMock(configuration: .init()), id: "1")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init()), id: "2"))]
|
||||
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2"))]
|
||||
|
||||
// check if the banner is now in a loaded state and is showing the counter
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
||||
@@ -85,18 +87,20 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.count, 2)
|
||||
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
||||
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex, 1)
|
||||
|
||||
// check if the banner is updating alongside the timeline
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
||||
viewState.pinnedEventsBannerState.count == 3
|
||||
}
|
||||
providerUpdateSubject.send(([.event(.init(item: EventTimelineItemSDKMock(configuration: .init()), id: "1")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init()), id: "2")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init()), id: "3"))], .initial))
|
||||
providerUpdateSubject.send(([.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test3")), id: "3"))], .initial))
|
||||
try await deferred.fulfill()
|
||||
XCTAssertFalse(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
|
||||
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex, 1)
|
||||
|
||||
// check how the scrolling changes the banner visibility
|
||||
viewModel.timelineHasScrolled(direction: .top)
|
||||
XCTAssertFalse(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
||||
@@ -105,6 +109,61 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
||||
}
|
||||
|
||||
func testPinnedEventsBannerSelection() async throws {
|
||||
ServiceLocator.shared.settings.pinningEnabled = true
|
||||
let timelineSubject = PassthroughSubject<TimelineProxyProtocol, Never>()
|
||||
let updateSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init())
|
||||
// setup a way to inject the mock of the pinned events timeline
|
||||
let pinnedTimelineMock = TimelineProxyMock()
|
||||
let pinnedTimelineProviderMock = RoomTimelineProviderMock()
|
||||
pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock
|
||||
pinnedTimelineProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], PaginationState), Never>().eraseToAnyPublisher()
|
||||
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2")),
|
||||
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test3")), id: "3"))]
|
||||
roomProxyMock.underlyingPinnedEventsTimeline = pinnedTimelineMock
|
||||
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: "test1",
|
||||
mediaProvider: MockMediaProvider(),
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics)
|
||||
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 deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
if case let .focusEvent(eventID) = action {
|
||||
return eventID == "test1"
|
||||
}
|
||||
return false
|
||||
}
|
||||
viewModel.context.send(viewAction: .tappedPinnedEventsBanner)
|
||||
try await deferred.fulfill()
|
||||
try await deferredAction.fulfill()
|
||||
|
||||
// check if the banner scrolls to the specific selected pin
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
||||
viewState.pinnedEventsBannerState.selectedPinnedIndex == 1
|
||||
}
|
||||
viewModel.setSelectedPinnedEventID("test2")
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testRoomInfoUpdate() async throws {
|
||||
let updateSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(id: "TestID", name: "StartingName", avatarURL: nil, hasOngoingCall: false))
|
||||
@@ -112,6 +171,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxyMock.canUserJoinCallUserIDReturnValue = .success(false)
|
||||
roomProxyMock.underlyingActionsPublisher = updateSubject.eraseToAnyPublisher()
|
||||
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
@@ -148,6 +208,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let ongoingCallRoomIDSubject = CurrentValueSubject<String?, Never>(nil)
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(id: "MyRoomID"))
|
||||
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
ongoingCallRoomIDPublisher: ongoingCallRoomIDSubject.asCurrentValuePublisher(),
|
||||
appMediator: AppMediatorMock.default,
|
||||
|
||||
@@ -345,6 +345,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
|
||||
let viewModel = TimelineViewModel(roomProxy: roomProxy,
|
||||
timelineController: timelineController,
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -369,6 +370,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
timelineController.timelineItems = [message]
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "", members: [RoomMemberProxyMock.mockAlice, RoomMemberProxyMock.mockCharlie])),
|
||||
timelineController: timelineController,
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -394,6 +396,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -422,6 +425,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||
let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
@@ -451,6 +455,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
TimelineViewModel(roomProxy: roomProxy ?? JoinedRoomProxyMock(.init(name: "")),
|
||||
focussedEventID: focussedEventID,
|
||||
timelineController: timelineController,
|
||||
isPinnedEventsTimeline: false,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
|
||||
Reference in New Issue
Block a user