Event Permalink Tweaks (#2754)

* Handle errors focussing the timeline.

* Add a test for focussing on an existing timeline item.
This commit is contained in:
Doug
2024-04-29 10:00:41 +01:00
committed by GitHub
parent 231fc6defb
commit 7b2168a540
12 changed files with 100 additions and 8 deletions
@@ -234,11 +234,16 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) { switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) {
case .success: case .success:
state.timelineViewState.focussedEventID = eventID state.timelineViewState.focussedEventID = eventID
case .failure: case .failure(let error):
MXLog.error("Failed to focus on event \(eventID)") MXLog.error("Failed to focus on event \(eventID)")
if case .eventNotFound = error {
displayError(.toast(L10n.errorMessageNotFound))
} else {
displayError(.toast(L10n.commonFailed)) displayError(.toast(L10n.commonFailed))
} }
} }
}
// MARK: - Private // MARK: - Private
+13 -1
View File
@@ -163,8 +163,20 @@ class RoomProxy: RoomProxyProtocol {
do { do {
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString) let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
return .success(TimelineProxy(timeline: timeline, isLive: false)) return .success(TimelineProxy(timeline: timeline, isLive: false))
} catch let error as FocusEventError {
switch error {
case .InvalidEventId(_, let error):
MXLog.error("Invalid event \(eventID) Error: \(error)")
return .failure(.eventNotFound)
case .EventNotFound:
MXLog.error("Event \(eventID) not found.")
return .failure(.eventNotFound)
case .Other(let message):
MXLog.error("Failed to create a timeline focussed on event \(eventID) Error: \(message)")
return .failure(.sdkError(error))
}
} catch { } catch {
MXLog.error("Failed to create a timeline focussed on: \(eventID) with error: \(error)") MXLog.error("Unexpected error: \(error)")
return .failure(.sdkError(error)) return .failure(.sdkError(error))
} }
} }
@@ -23,6 +23,7 @@ enum RoomProxyError: Error {
case invalidURL case invalidURL
case invalidMedia case invalidMedia
case eventNotFound
} }
enum RoomProxyAction { enum RoomProxyAction {
@@ -241,11 +241,19 @@ enum RoomTimelineItemFixtures {
static var outgoingPolls: [RoomTimelineItemProtocol] { static var outgoingPolls: [RoomTimelineItemProtocol] {
[PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)] [PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)]
} }
static var permalinkChunk: [RoomTimelineItemProtocol] {
(1...20).map { index in
TextRoomTimelineItem(id: .init(timelineID: "\(index)", eventID: "$\(index)"),
text: "Message ID \(index)",
senderDisplayName: index > 10 ? "Alice" : "Bob")
}
}
} }
private extension TextRoomTimelineItem { private extension TextRoomTimelineItem {
init(text: String, senderDisplayName: String) { init(id: TimelineItemIdentifier? = nil, text: String, senderDisplayName: String) {
self.init(id: .random, self.init(id: id ?? .random,
timestamp: "10:47 am", timestamp: "10:47 am",
isOutgoing: senderDisplayName == "Alice", isOutgoing: senderDisplayName == "Alice",
isEditable: false, isEditable: false,
@@ -66,10 +66,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
activeTimeline = timeline activeTimeline = timeline
activeTimelineProvider = timeline.timelineProvider activeTimelineProvider = timeline.timelineProvider
return .success(()) return .success(())
case .failure: case .failure(let error):
if case .eventNotFound = error {
return .failure(.eventNotFound)
} else {
return .failure(.generic) return .failure(.generic)
} }
} }
}
func focusLive() { func focusLive() {
activeTimeline = roomProxy.timeline activeTimeline = roomProxy.timeline
@@ -32,6 +32,7 @@ enum RoomTimelineControllerAction {
enum RoomTimelineControllerError: Error { enum RoomTimelineControllerError: Error {
case generic case generic
case eventNotFound
} }
@MainActor @MainActor
@@ -95,6 +95,8 @@ class MockScreen: Identifiable {
let windowManager: SecureWindowManagerProtocol let windowManager: SecureWindowManagerProtocol
let navigationRootCoordinator: NavigationRootCoordinator let navigationRootCoordinator: NavigationRootCoordinator
private var client: UITestsSignalling.Client?
private var retainedState = [Any]() private var retainedState = [Any]()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@@ -393,6 +395,37 @@ class MockScreen: Identifiable {
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .roomLayoutHighlight:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.permalinkChunk
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(name: "Timeline highlight", avatarURL: URL.picturesDirectory)),
timelineController: timelineController,
mediaProvider: MockMediaProvider(),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
emojiProvider: EmojiProvider(),
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
do {
let client = try UITestsSignalling.Client(mode: .app)
client.signals.sink { [weak self] signal in
guard case .timeline(.focusOnEvent(let eventID)) = signal else { return }
coordinator.focusOnEvent(eventID: eventID)
try? client.send(.success)
}
.store(in: &cancellables)
} catch {
fatalError("Failure setting up signalling: \(error)")
}
self.client = client
return navigationStackCoordinator
case .roomWithDisclosedPolls: case .roomWithDisclosedPolls:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
@@ -34,6 +34,7 @@ enum UITestsScreenIdentifier: String {
case roomLayoutBottom case roomLayoutBottom
case roomLayoutMiddle case roomLayoutMiddle
case roomLayoutTop case roomLayoutTop
case roomLayoutHighlight
case roomMembersListScreenPendingInvites case roomMembersListScreenPendingInvites
case roomPlainNoAvatar case roomPlainNoAvatar
case roomRolesAndPermissionsFlow case roomRolesAndPermissionsFlow
@@ -27,11 +27,13 @@ enum UITestsSignal: Codable, Equatable {
case success case success
case timeline(Timeline) case timeline(Timeline)
enum Timeline: Codable { enum Timeline: Codable, Equatable {
/// Ask the app to back paginate. /// Ask the app to back paginate.
case paginate case paginate
/// Ask the app to simulate an incoming message. /// Ask the app to simulate an incoming message.
case incomingMessage case incomingMessage
/// Ask the app to simulate focussing on an event ID.
case focusOnEvent(String)
} }
/// Posts a notification. /// Posts a notification.
+19
View File
@@ -110,6 +110,25 @@ class RoomScreenUITests: XCTestCase {
try await app.assertScreenshot(.roomLayoutBottom, step: 1) try await app.assertScreenshot(.roomLayoutBottom, step: 1)
} }
func testTimelineLayoutHighlightExisting() async throws {
let client = try UITestsSignalling.Client(mode: .tests)
let app = Application.launch(.roomLayoutHighlight)
await client.waitForApp()
defer { try? client.stop() }
// Some time for the timeline to settle.
try await Task.sleep(for: .seconds(1))
// When tapping a permalink to an item in the timeline.
try await performOperation(.focusOnEvent("$5"), using: client)
// Some time for the timeline to settle.
try await Task.sleep(for: .seconds(1))
// Then the item should become highlighted.
try await app.assertScreenshot(.roomLayoutHighlight, step: 0)
}
func testTimelineReadReceipts() async throws { func testTimelineReadReceipts() async throws {
let app = Application.launch(.roomSmallTimelineWithReadReceipts) let app = Application.launch(.roomSmallTimelineWithReadReceipts)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85ccfdd71e9fa2e59b65b9731c8e0135e7e44349d7f3debdb5afcff0262576d0
size 233181
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79e3b444b2682f3697c1f27c898be7a3ed50d230c60d5c0fd33bfe9125056802
size 331437