Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/test/util/EmojiTests.swift
1 views
//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import XCTest
@testable import Signal
@testable import SignalServiceKit

class EmojiTests: XCTestCase {
    func testSimpleEmojiCases() {
        XCTAssertFalse("".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertTrue("😃".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertFalse("😃😃".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertFalse("a".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertFalse(" 😃".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertFalse("😃 ".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertTrue("👨‍👩‍👧‍👦".isSingleEmojiUsingEmojiWithSkinTones)
        XCTAssertTrue("👨🏿‍🤝‍👨🏻".isSingleEmojiUsingEmojiWithSkinTones)

        XCTAssertFalse("".isSingleEmoji)
        XCTAssertTrue("😃".isSingleEmoji)
        XCTAssertFalse("😃😃".isSingleEmoji)
        XCTAssertFalse("a".isSingleEmoji)
        XCTAssertFalse(" 😃".isSingleEmoji)
        XCTAssertFalse("😃 ".isSingleEmoji)
        XCTAssertTrue("👨‍👩‍👧‍👦".isSingleEmoji)
        XCTAssertTrue("👨🏿‍🤝‍👨🏻".isSingleEmoji)
    }

    func testEmojiCounts() {
        XCTAssertEqual("".glyphCount, 0)
        XCTAssertEqual("😃".glyphCount, 1)
        XCTAssertEqual("😃😃".glyphCount, 2)
        XCTAssertEqual("a".glyphCount, 1)
        XCTAssertEqual(" 😃".glyphCount, 2)
        XCTAssertEqual("😃 ".glyphCount, 2)
        XCTAssertEqual("👨‍👩‍👧‍👦".glyphCount, 1)
        // CoreText considers this two glyphs,
        // but glyphCount now uses String.count.
        XCTAssertEqual("👨🏿‍🤝‍👨🏻".glyphCount, 1)

        XCTAssertEqual("".count, 0)
        XCTAssertEqual("😃".count, 1)
        XCTAssertEqual("😃😃".count, 2)
        XCTAssertEqual("a".count, 1)
        XCTAssertEqual(" 😃".count, 2)
        XCTAssertEqual("😃 ".count, 2)
        XCTAssertEqual("👨‍👩‍👧‍👦".count, 1)
        XCTAssertEqual("👨🏿‍🤝‍👨🏻".count, 1)
    }

    func testFancyEmojiCases() {
        do {
            // Valid emoji with skin tones.
            let fancyEmoji = EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.mediumDark]).rawValue
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Invalid emoji with skin tones.
            let fancyEmoji = EmojiWithSkinTones(baseEmoji: .blueberries, skinTones: [.mediumDark]).rawValue
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Black Diamond Suit Emoji
            let fancyEmoji = "\u{2666}" // ♦
            // EmojiWithSkinTones doesn't recognize this as an emoji...
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            // But isSingleEmoji does...
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Black Diamond Suit Emoji
            // Adding 'Variation Selector-16':
            let fancyEmoji = "\u{2666}\u{FE0F}" // ♦️
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Thumbs up sign:
            let fancyEmoji = "\u{1F44D}" // 👍
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Thumbs up sign:
            // Adding 'Emoji Modifier Fitzpatrick Type-4':
            let fancyEmoji = "\u{1F44D}\u{1F3FD}" // 👍🏽
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Man, Woman, Girl, Boy
            let fancyEmoji = "\u{1F468}\u{1F469}\u{1F467}\u{1F466}" // 👨👩👧👦
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertFalse(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 4)
        }

        do {
            // Man, Woman, Girl, Boy
            // Adding 'Zero Width Joiner' between each
            let fancyEmoji = "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}" // 👨‍👩‍👧‍👦
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // This emoji has two skin tones.
            // CoreText considers this two glyphs.
            let fancyEmoji = "👨🏿‍🤝‍👨🏻"
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            let fancyEmoji = "🏳"
            // EmojiWithSkinTones doesn't recognize this as an emoji...
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            // But isSingleEmoji does...
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            let fancyEmoji = "🌈️"
            // EmojiWithSkinTones doesn't recognize this as an emoji...
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            // But isSingleEmoji does...
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Not an emoji.
            let fancyEmoji = "a"
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertFalse(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Empty string.
            let fancyEmoji = ""
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertFalse(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 0)
        }

        do {
            // Not an emoji; just a isolated modifier.
            // 'Emoji Modifier Fitzpatrick Type-4':
            let fancyEmoji = "\u{1F3FD}"
            // But this is considered an emoji by all measures.
            XCTAssertTrue(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }

        do {
            // Not an emoji; just a isolated modifier.
            // 'Variation Selector-16':
            let fancyEmoji = "\u{FE0F}"
            // EmojiWithSkinTones doesn't recognize this as an emoji...
            XCTAssertFalse(fancyEmoji.isSingleEmojiUsingEmojiWithSkinTones)
            // But isSingleEmoji does...
            XCTAssertTrue(fancyEmoji.isSingleEmoji)
            XCTAssertEqual(fancyEmoji.count, 1)
        }
    }

    func testMoreEmojiCases() {
        let moreEmojis = [
            "😍",
            "👩🏽",
            "👨‍🦰",
            "👨🏿‍🦰",
            "👨‍🦱",
            "👨🏿‍🦱",
            "🦹🏿‍♂️",
            "👾",
            "🙇",
            "💁",
            "🙅",
            "🙆",
            "🙋",
            "🙎",
            "🙍",
            "🐵",
            "🙈",
            "🙉",
            "🙊",
            "❤️",
            "💔",
            "💌",
            "💕",
            "💞",
            "💓",
            "💗",
            "💖",
            "💘",
            "💝",
            "💟",
            "💜",
            "💛",
            "💚",
            "💙",
            "✋🏿",
            "💪🏿",
            "👐🏿",
            "🙌🏿",
            "👏🏿",
            "🙏🏿",
            "👨‍👩‍👦",
            "👨‍👩‍👧‍👦",
            "👨‍👨‍👦",
            "👩‍👩‍👧",
            "👨‍👦",
            "👨‍👧‍👦",
            "👩‍👦",
            "👩‍👧‍👦",
            "🚾",
            "🆒",
            "🆓",
            "🆕",
            "🆖",
            "🆗",
            "🆙",
            "🏧",
            "0️⃣",
            "1️⃣",
            "2️⃣",
            "3️⃣",
            "4️⃣",
            "5️⃣",
            "6️⃣",
            "7️⃣",
            "8️⃣",
            "9️⃣",
            "🔟",
            "🇦🇫",
            "🇦🇲",
            "🇺🇸",
            "🇷🇺",
            "🇸🇦",
            "🇸🇦",
            "🇨🇦",
        ]
        for emoji in moreEmojis {
            if !emoji.isSingleEmojiUsingEmojiWithSkinTones {
                Logger.warn("!isSingleEmojiUsingEmojiWithSkinTones: '\(emoji)'")
            }
            XCTAssertTrue(emoji.isSingleEmojiUsingEmojiWithSkinTones)

            if !emoji.isSingleEmoji {
                Logger.warn("!isSingleEmoji: '\(emoji)'")
            }
            XCTAssertTrue(emoji.isSingleEmoji)

            if emoji.count != 1 {
                Logger.warn("'\(emoji)': \(emoji.count) != 1")
            }
            XCTAssertEqual(emoji.count, 1)
        }
    }
}