mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Fix message completion trigger to work anywhere in the message (#3696)
* Fix message completion trigger to work anywhere in the message * Add tests for display suggestion in difference message positions * Fix suggestion trigger could support multiple mention symbol * Remove optional parameter type and tweak argument name to hide * Optimize the suggestions trigger using regex * Fix the cursor at wrong position after selected suggestion * Modify the rawSuggestionText pass data in right way * Modify mention symbol to use regex type * Update ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * Update generated mock --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
@@ -31,7 +31,7 @@ final class CompletionSuggestionServiceTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||
suggestions == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init()))]
|
||||
suggestions == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(), rawSuggestionText: "ali"))]
|
||||
}
|
||||
service.setSuggestionTrigger(.init(type: .user, text: "ali", range: .init()))
|
||||
try await deferred.fulfill()
|
||||
@@ -68,13 +68,13 @@ final class CompletionSuggestionServiceTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil))]
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil, rawSuggestionText: "ro"))]
|
||||
}
|
||||
service.setSuggestionTrigger(.init(type: .user, text: "ro", range: .init()))
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil))]
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil, rawSuggestionText: "every"))]
|
||||
}
|
||||
service.setSuggestionTrigger(.init(type: .user, text: "every", range: .init()))
|
||||
try await deferred.fulfill()
|
||||
@@ -94,17 +94,68 @@ final class CompletionSuggestionServiceTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil)),
|
||||
.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init())),
|
||||
.user(item: .init(id: bob.userID, displayName: bob.displayName, avatarURL: bob.avatarURL, range: .init()))]
|
||||
suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil, rawSuggestionText: "")),
|
||||
.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(), rawSuggestionText: "")),
|
||||
.user(item: .init(id: bob.userID, displayName: bob.displayName, avatarURL: bob.avatarURL, range: .init(), rawSuggestionText: ""))]
|
||||
}
|
||||
service.setSuggestionTrigger(.init(type: .user, text: "", range: .init()))
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testUserSuggestionInDifferentMessagePositions() async throws {
|
||||
let alice: RoomMemberProxyMock = .mockAlice
|
||||
let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe]
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: "test", members: members))
|
||||
let service = CompletionSuggestionService(roomProxy: roomProxyMock)
|
||||
|
||||
var deferred = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(location: 0, length: 3), rawSuggestionText: "al"))]
|
||||
}
|
||||
service.processTextMessage("@al hello", selectedRange: .init(location: 0, length: 1))
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(location: 5, length: 3), rawSuggestionText: "al"))]
|
||||
}
|
||||
service.processTextMessage("test @al", selectedRange: .init(location: 5, length: 1))
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(location: 5, length: 3), rawSuggestionText: "al"))]
|
||||
}
|
||||
service.processTextMessage("test @al test", selectedRange: .init(location: 5, length: 1))
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testUserSuggestionWithMultipleMentionSymbol() async throws {
|
||||
let alice: RoomMemberProxyMock = .mockAlice
|
||||
let bob: RoomMemberProxyMock = .mockBob
|
||||
let members: [RoomMemberProxyMock] = [alice, bob, .mockCharlie, .mockMe]
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: "test", members: members))
|
||||
let service = CompletionSuggestionService(roomProxy: roomProxyMock)
|
||||
|
||||
var deffered = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == [.user(item: .init(id: alice.userID, displayName: alice.displayName, avatarURL: alice.avatarURL, range: .init(location: 0, length: 3), rawSuggestionText: "al"))]
|
||||
}
|
||||
service.processTextMessage("@al test @bo", selectedRange: .init(location: 0, length: 1))
|
||||
try await deffered.fulfill()
|
||||
|
||||
deffered = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == [.user(item: .init(id: bob.userID, displayName: bob.displayName, avatarURL: bob.avatarURL, range: .init(location: 9, length: 3), rawSuggestionText: "bo"))]
|
||||
}
|
||||
service.processTextMessage("@al test @bo", selectedRange: .init(location: 9, length: 1))
|
||||
try await deffered.fulfill()
|
||||
|
||||
deffered = deferFulfillment(service.suggestionsPublisher) { suggestion in
|
||||
suggestion == []
|
||||
}
|
||||
service.processTextMessage("@al test @bo", selectedRange: .init(location: 4, length: 1))
|
||||
try await deffered.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
private extension MentionSuggestionItem {
|
||||
static func allUsersMention(roomAvatar: URL?) -> Self {
|
||||
MentionSuggestionItem(id: PillConstants.atRoom, displayName: PillConstants.everyone, avatarURL: roomAvatar, range: .init())
|
||||
static func allUsersMention(roomAvatar: URL?, rawSuggestionText: String) -> Self {
|
||||
MentionSuggestionItem(id: PillConstants.atRoom, displayName: PillConstants.everyone, avatarURL: roomAvatar, range: .init(), rawSuggestionText: rawSuggestionText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSuggestions() {
|
||||
let suggestions: [SuggestionItem] = [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil, range: .init())),
|
||||
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: .mockMXCAvatar, range: .init()))]
|
||||
let suggestions: [SuggestionItem] = [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil, range: .init(), rawSuggestionText: "")),
|
||||
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: .mockMXCAvatar, range: .init(), rawSuggestionText: ""))]
|
||||
let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions))
|
||||
|
||||
viewModel = ComposerToolbarViewModel(roomProxy: JoinedRoomProxyMock(.init()),
|
||||
@@ -117,7 +117,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSelectedUserSuggestion() {
|
||||
let suggestion = SuggestionItem.user(item: .init(id: "@test:matrix.org", displayName: "Test", avatarURL: nil, range: .init()))
|
||||
let suggestion = SuggestionItem.user(item: .init(id: "@test:matrix.org", displayName: "Test", avatarURL: nil, range: .init(), rawSuggestionText: ""))
|
||||
viewModel.context.send(viewAction: .selectedSuggestion(suggestion))
|
||||
|
||||
// The display name can be used for HTML injection in the rich text editor and it's useless anyway as the clients don't use it when resolving display names
|
||||
@@ -138,7 +138,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
viewModel.context.send(viewAction: .composerAppeared)
|
||||
await Task.yield()
|
||||
let userID = "@test:matrix.org"
|
||||
let suggestion = SuggestionItem.user(item: .init(id: userID, displayName: "Test", avatarURL: nil, range: .init()))
|
||||
let suggestion = SuggestionItem.user(item: .init(id: userID, displayName: "Test", avatarURL: nil, range: .init(), rawSuggestionText: ""))
|
||||
viewModel.context.send(viewAction: .selectedSuggestion(suggestion))
|
||||
|
||||
let attachment = wysiwygViewModel.textView.attributedText.attribute(.attachment, at: 0, effectiveRange: nil) as? PillTextAttachment
|
||||
@@ -773,6 +773,6 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
|
||||
private extension MentionSuggestionItem {
|
||||
static func allUsersMention(roomAvatar: URL?) -> Self {
|
||||
MentionSuggestionItem(id: PillConstants.atRoom, displayName: PillConstants.everyone, avatarURL: roomAvatar, range: .init())
|
||||
MentionSuggestionItem(id: PillConstants.atRoom, displayName: PillConstants.everyone, avatarURL: roomAvatar, range: .init(), rawSuggestionText: "")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user