Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,30 @@
let mention = Mention(range: NSRange(location: 0, length: 8), user: selfUser)
verify(message: createMessage(messageText: messageText, mentions: [mention]))
}

func testThatItHandlesMalformedMentionRange_MissingAtSymbol() {

Check warning on line 176 in wire-ios/Wire-iOS Tests/ConversationCell/TextMessageMentionsTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testThatItHandlesMalformedMentionRange_MissingAtSymbol" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZ3gPAkI_lw3-Y3jDMWu&open=AZ3gPAkI_lw3-Y3jDMWu&pullRequest=4664
// Test case for the bug where another app incorrectly sends a mention range
// that starts one character after the '@' symbol (e.g., location: 7 instead of 6)
// This should be automatically corrected to include the '@'
let messageText = "Hello @Bruno! How are you?"

// Malformed range: starts at 'B' (index 7) instead of '@' (index 6)
let malformedMention = Mention(range: NSRange(location: 7, length: 5), user: otherUser)

// The fix should automatically adjust this to location: 6, length: 6
// and render correctly as "@Bruno" instead of "@@runo"
verify(message: createMessage(messageText: messageText, mentions: [malformedMention]))
}

func testThatItHandlesMultipleMentionsWithOneMalformed() {
let messageText = "Hey @Bruno and @Me, let's meet!"

// First mention is malformed (starts at 'B' instead of '@')
let malformedMention = Mention(range: NSRange(location: 5, length: 5), user: otherUser)

// Second mention is correct
let correctMention = Mention(range: NSRange(location: 15, length: 3), user: selfUser)

verify(message: createMessage(messageText: messageText, mentions: [malformedMention, correctMention]))
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import WireDataModel
import WireDesign
import WireFoundation
import WireLinkPreview
import WireLogging
import WireUtilities

extension NSAttributedString {
Expand Down Expand Up @@ -245,14 +246,57 @@ private extension String {
mutating func replaceMentionsWithTextMarkers(mentions: [Mention]) -> [TextMarker<Mention>] {
mentions.sorted(by: {
$0.range.location > $1.range.location
}).compactMap { mention in
guard let range = Range(mention.range, in: self) else { return nil }

let name = String(self[range].dropFirst()) // drop @
let textObject = TextMarker<Mention>(mention, replacementText: name)

}).compactMap { mention -> TextMarker<Mention>? in
// All mentions are expected to have a @ prefix and the range
// of the mention should include this prefix (and not just the
// name). Eg "Hello @bob" should have a mention range of
// NSRange(location: 6, length: 4). If it doesn't, it will
// be rendered incorrectly.
//
// But we suspect in some cases the sender might be only
// specifying a mention range that covers the name and not
// the @ prefix (i.e NSRange(location: 7, length: 3)).
//
// To handle this case, we check if the range includes the @
// and if it doesn't adjust the range so that it does.
var adjustedRange = mention.range

if adjustedRange.location > 0, adjustedRange.location < utf16.count {
let utf16View = self.utf16
let charIndex = utf16View.index(utf16View.startIndex, offsetBy: adjustedRange.location)

if charIndex < utf16View.endIndex {
let charAtLocation = utf16View[charIndex]

// If the range doesn't start with '@', check if the character before does
if charAtLocation != 64 { // '@' in UTF-16
let prevIndex = utf16View.index(before: charIndex)
if prevIndex >= utf16View.startIndex, utf16View[prevIndex] == 64 {
// Adjust range to include the '@'
adjustedRange.location -= 1
adjustedRange.length += 1
}
}
}
}

guard let range = Range(adjustedRange, in: self) else { return nil }
let name = String(self[range])

// Final validation: ensure the extracted name starts with '@'
guard name.hasPrefix("@") else {
WireLogger.messaging.error("Mention range does not start with '@': \(mention.range), text: '\(name)'")
return nil
}

// Create a corrected mention with the adjusted range
let correctedMention = Mention(range: adjustedRange, user: mention.user)

// Strip the '@' from the name since the mention rendering code adds it back
let nameWithoutAt = name.hasPrefix("@") ? String(name.dropFirst()) : name

let textObject = TextMarker<Mention>(correctedMention, replacementText: nameWithoutAt)
replaceSubrange(range, with: textObject.token)

return textObject
}
}
Expand Down
Loading