diff --git a/ElementX/Sources/Other/AccessibilityIdentifiers.swift b/ElementX/Sources/Other/AccessibilityIdentifiers.swift index cec73edd1..2e2dd6649 100644 --- a/ElementX/Sources/Other/AccessibilityIdentifiers.swift +++ b/ElementX/Sources/Other/AccessibilityIdentifiers.swift @@ -183,6 +183,12 @@ enum A11yIdentifiers { let openComposeOptions = "composer_toolbar-open_compose_options" let closeFormattingOptions = "composer_toolbar-close-formatting-options" } + + let timelineItemActionMenuAction = TimelineItemActionMenuAction() + + struct TimelineItemActionMenuAction { + let edit = "timeline_item_action_menu-edit" + } } struct RoomDetailsScreen { diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift index b5c61bbee..cd27b9fc9 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift @@ -154,6 +154,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable { Label(L10n.actionCopyCaption, icon: \.copy) case .edit: Label(L10n.actionEdit, icon: \.edit) + .accessibilityIdentifier(A11yIdentifiers.roomScreen.timelineItemActionMenuAction.edit) case .addCaption: Label(L10n.actionAddCaption, icon: \.edit) case .editCaption: diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift index dc014c132..b85f5cba9 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift @@ -13,6 +13,9 @@ import Foundation import MatrixRustSDK class MockTimelineController: TimelineControllerProtocol { + private let lastInsertedMessageID: TimelineItemIdentifier = .event(uniqueID: .init("last_message"), + eventOrTransactionID: .eventID("last_message")) + /// An array of timeline item arrays that will be inserted in order for each back pagination request. var backPaginationResponses: [[RoomTimelineItemProtocol]] = [] /// An array of timeline items that will be appended in order when ``simulateIncomingItems()`` is called. @@ -35,7 +38,12 @@ class MockTimelineController: TimelineControllerProtocol { } } - var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default + var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default { + didSet { + callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false)) + } + } + var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:] private var client: UITestsSignalling.Client? @@ -122,7 +130,18 @@ class MockTimelineController: TimelineControllerProtocol { func edit(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, message: String, html: String?, - intentionalMentions: IntentionalMentions) async { } + intentionalMentions: IntentionalMentions) async { + callbacks.send(.messageSentOrEdited) + + timelineItems[timelineItems.endIndex - 1] = TextRoomTimelineItem(id: lastInsertedMessageID, + timestamp: .distantFuture, + isOutgoing: true, + isEditable: true, + canBeRepliedTo: true, + sender: .test, + content: .init(body: message), + properties: .init(isEdited: true)) + } func editCaption(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID, message: String, @@ -164,12 +183,24 @@ class MockTimelineController: TimelineControllerProtocol { func sendMessage(_ message: String, html: String?, inReplyToEventID: String?, - intentionalMentions: IntentionalMentions) async { } + intentionalMentions: IntentionalMentions) async { + callbacks.send(.messageSentOrEdited) + + timelineItems.append(TextRoomTimelineItem(id: lastInsertedMessageID, + timestamp: .distantFuture, + isOutgoing: true, + isEditable: true, + canBeRepliedTo: true, + sender: .test, + content: .init(body: message))) + } func sendAudio(url: URL, audioInfo: MatrixRustSDK.AudioInfo, caption: String?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendAudio(url: url, audioInfo: audioInfo, @@ -184,6 +215,8 @@ class MockTimelineController: TimelineControllerProtocol { fileInfo: MatrixRustSDK.FileInfo, caption: String?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendFile(url: url, fileInfo: fileInfo, @@ -199,6 +232,8 @@ class MockTimelineController: TimelineControllerProtocol { imageInfo: MatrixRustSDK.ImageInfo, caption: String?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendImage(url: url, thumbnailURL: thumbnailURL, @@ -215,6 +250,8 @@ class MockTimelineController: TimelineControllerProtocol { description: String?, zoomLevel: UInt8?, assetType: MatrixRustSDK.AssetType?) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendLocation(body: body, geoURI: geoURI, @@ -231,6 +268,8 @@ class MockTimelineController: TimelineControllerProtocol { videoInfo: MatrixRustSDK.VideoInfo, caption: String?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendVideo(url: url, thumbnailURL: thumbnailURL, @@ -246,6 +285,8 @@ class MockTimelineController: TimelineControllerProtocol { audioInfo: MatrixRustSDK.AudioInfo, waveform: [Float], requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { return await timelineProxy.sendVoiceMessage(url: url, audioInfo: audioInfo, @@ -259,6 +300,8 @@ class MockTimelineController: TimelineControllerProtocol { // MARK: - Polls func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result { + callbacks.send(.messageSentOrEdited) + if let timelineProxy { _ = await timelineProxy.createPoll(question: question, answers: answers, pollKind: pollKind) } @@ -326,7 +369,6 @@ class MockTimelineController: TimelineControllerProtocol { let incomingItem = incomingItems.removeFirst() timelineItems.append(incomingItem) - callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false)) try client?.send(.success) } @@ -342,7 +384,6 @@ class MockTimelineController: TimelineControllerProtocol { let newItems = backPaginationResponses.removeFirst() timelineItems.insert(contentsOf: newItems, at: 0) - callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false)) try client?.send(.success) } diff --git a/UITests/Sources/UserSessionScreenTests.swift b/UITests/Sources/UserSessionScreenTests.swift index 4231fce00..2248457f4 100644 --- a/UITests/Sources/UserSessionScreenTests.swift +++ b/UITests/Sources/UserSessionScreenTests.swift @@ -34,7 +34,7 @@ class UserSessionScreenTests: XCTestCase { } func testUserSessionFlows() async throws { - let app = Application.launch(.userSessionScreen) + let app = Application.launch(.userSessionScreen, disableTimelineAccessibility: false) app.swipeDown() // Make sure the header shows a large title @@ -44,7 +44,35 @@ class UserSessionScreenTests: XCTestCase { XCTAssert(app.buttons[firstRoomName].waitForExistence(timeout: 5.0)) try await Task.sleep(for: .seconds(1)) try await app.assertScreenshot(step: Step.roomScreen) - + + var composer = app.textViews[A11yIdentifiers.roomScreen.messageComposer] + composer.tap(.center) + composer.typeText("sugar honey ice") + + app.buttons[A11yIdentifiers.roomScreen.sendButton].tap() + + let latestMessage = app.textViews.matching(NSPredicate(format: "value CONTAINS 'sugar honey ice'")).firstMatch + XCTAssert(latestMessage.waitForExistence(timeout: 5.0)) + + // Tapping on the mesage text view or the cell doesn't work. + // Use the sender static text instead. + let latestEventSender = app.staticTexts["@test.matrix.org"] + XCTAssert(latestEventSender.waitForExistence(timeout: 5.0)) + latestEventSender.press(forDuration: 5.0) + + let editButton = app.buttons[A11yIdentifiers.roomScreen.timelineItemActionMenuAction.edit] + XCTAssert(editButton.waitForExistence(timeout: 5.0)) + editButton.tap() + + composer = app.textViews[A11yIdentifiers.roomScreen.messageComposer] + composer.tap(.center) + composer.typeText(" & tea") + + app.buttons[A11yIdentifiers.roomScreen.sendButton].tap() + + let latestEditedMessage = app.textViews.matching(NSPredicate(format: "value CONTAINS 'sugar honey ice & tea'")).firstMatch + XCTAssert(latestEditedMessage.waitForExistence(timeout: 5.0)) + app.buttons[A11yIdentifiers.roomScreen.composerToolbar.openComposeOptions].tap(.center) try await app.assertScreenshot(step: Step.composerAttachments) } diff --git a/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPad-en-GB-3.png b/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPad-en-GB-3.png index 9d5a00afe..eee7a8bd4 100644 --- a/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPad-en-GB-3.png +++ b/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPad-en-GB-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69e336fcc27867de462eeb74a83cb50a62cf278920199a56792632e4329cc250 -size 619016 +oid sha256:bf0f3e45df8d7b61fc004ec7d9378950a0bc26c3d96249c2260a254bd8b92abc +size 644781 diff --git a/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPhone-en-GB-3.png b/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPhone-en-GB-3.png index 2f61ca4b4..08d23b3d8 100644 --- a/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPhone-en-GB-3.png +++ b/UITests/Sources/__Snapshots__/Application/userSessionScreen.testUserSessionFlows-iPhone-en-GB-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15c02e01fd99fab8b93a4c9b8922da79aee505d6141cf01fb7b9bfb9243867ef -size 463451 +oid sha256:843e4dbadd9547a96fa24f21b28825f838a6ea352fd9d8f3abed910e5fc5c634 +size 417228