diff --git a/wire-ios/Wire-iOS Tests/MessageDetails/MessageDetailsViewControllerTests.swift b/wire-ios/Wire-iOS Tests/MessageDetails/MessageDetailsViewControllerTests.swift index b38758b3a28..ee268a83c74 100644 --- a/wire-ios/Wire-iOS Tests/MessageDetails/MessageDetailsViewControllerTests.swift +++ b/wire-ios/Wire-iOS Tests/MessageDetails/MessageDetailsViewControllerTests.swift @@ -328,6 +328,53 @@ final class MessageDetailsViewControllerTests: XCTestCase { verify(detailsViewController) } + func testThatItShowsReactionWithSkinToneModifier() throws { + // GIVEN + conversation = createGroupConversation() + + let message = MockMessageFactory.textMessage(withText: "Message") + message.senderUser = SelfUser.provider?.providedSelfUser + message.conversationLike = conversation + message.deliveryState = .read + message.needsReadConfirmation = false + message.backingUsersReaction = ["🙌🏻": [otherUser]] + + // WHEN + let dataSource = MessageDetailsDataSource(message: message, userSession: userSession) + + // THEN + let reaction = try XCTUnwrap(dataSource.reactions.first) + XCTAssertEqual(dataSource.reactions.count, 1) + XCTAssertEqual(reaction.items.count, 1) + XCTAssertTrue(try XCTUnwrap(reaction.headerText).hasPrefix("🙌🏻 ")) + XCTAssertTrue(try XCTUnwrap(reaction.headerText).hasSuffix("(1)")) + } + + func testThatItShowsReactionWhenEmojiRepositoryDoesNotContainIt() throws { + // GIVEN + conversation = createGroupConversation() + + let message = MockMessageFactory.textMessage(withText: "Message") + message.senderUser = SelfUser.provider?.providedSelfUser + message.conversationLike = conversation + message.deliveryState = .read + message.needsReadConfirmation = false + message.backingUsersReaction = ["🫡": [otherUser]] + + // WHEN + let dataSource = MessageDetailsDataSource( + message: message, + userSession: userSession, + emojiRepository: EmptyEmojiRepository() + ) + + // THEN + let reaction = try XCTUnwrap(dataSource.reactions.first) + XCTAssertEqual(dataSource.reactions.count, 1) + XCTAssertEqual(reaction.headerText, "🫡 (1)") + XCTAssertEqual(reaction.items.count, 1) + } + // MARK: - Non-Combined Scenarios func testThatItShowsReceiptsOnly_Ephemeral() { @@ -491,3 +538,21 @@ final class MessageDetailsViewControllerTests: XCTestCase { } } + +private final class EmptyEmojiRepository: EmojiRepositoryInterface { + + func emojis(for category: EmojiCategory) -> [Emoji] { + [] + } + + func emoji(for id: String) -> Emoji? { + nil + } + + func registerRecentlyUsedEmojis(_ emojis: [Emoji.ID]) {} + + func fetchRecentlyUsedEmojis() -> [Emoji] { + [] + } + +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Message Details/MessageDetailsDataSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Message Details/MessageDetailsDataSource.swift index 7c8c0f1d116..ce97ed40a06 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Message Details/MessageDetailsDataSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Message Details/MessageDetailsDataSource.swift @@ -173,17 +173,30 @@ final class MessageDetailsDataSource: NSObject, ZMMessageObserver, UserObserving private func setupReactions() { reactions = message.usersReaction.lazy .compactMap { reaction, users in - guard let emoji = self.emojiRepository.emoji(for: reaction) else { return nil } - let name = emoji.localizedName ?? emoji.name + let items = MessageDetailsCellDescription.makeReactionCells(users) + guard !items.isEmpty else { return nil } + return MessageDetailsSectionDescription( - headerText: "\(emoji.value) \(name.capitalized) (\(users.count))", - items: MessageDetailsCellDescription.makeReactionCells(users) + headerText: self.reactionHeaderText(for: reaction, userCount: users.count), + items: items ) } - .filter { !$0.items.isEmpty } .sorted { $0.items.count > $1.items.count } } + private func reactionHeaderText(for reaction: String, userCount: Int) -> String { + guard let emoji = emoji(for: reaction) else { + return "\(reaction) (\(userCount))" + } + + let name = emoji.localizedName ?? emoji.name + return "\(reaction) \(name.capitalized) (\(userCount))" + } + + private func emoji(for reaction: String) -> Emoji? { + emojiRepository.emoji(for: reaction) ?? emojiRepository.emoji(for: reaction.removingEmojiSkinToneModifiers) + } + func setupReadReceipts() { readReceipts = [ MessageDetailsSectionDescription( @@ -210,3 +223,19 @@ final class MessageDetailsDataSource: NSObject, ZMMessageObserver, UserObserving } } + +private extension String { + + var removingEmojiSkinToneModifiers: String { + String(unicodeScalars.filter { !$0.isEmojiSkinToneModifier }) + } + +} + +private extension Unicode.Scalar { + + var isEmojiSkinToneModifier: Bool { + (0x1F3FB ... 0x1F3FF).contains(value) + } + +}