Pinned Events Timeline actions and differentiation (#3182)

This commit is contained in:
Mauro
2024-08-22 17:35:44 +02:00
committed by GitHub
parent d54e08e815
commit 4eb1ed5d09
44 changed files with 671 additions and 200 deletions
+3
View File
@@ -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(),