From 50cf0fec6b7241fa5d81c12567012310222e9404 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 10 Jul 2026 10:10:23 -0300 Subject: [PATCH 1/3] regression: Properly handle regional_indicator emojis --- apps/meteor/app/emoji-native/lib/generateEmojiData.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/emoji-native/lib/generateEmojiData.ts b/apps/meteor/app/emoji-native/lib/generateEmojiData.ts index 8a7f289e264f4..bcb14df2519f1 100644 --- a/apps/meteor/app/emoji-native/lib/generateEmojiData.ts +++ b/apps/meteor/app/emoji-native/lib/generateEmojiData.ts @@ -31,6 +31,12 @@ function hexFromEmoji(emoji: string): string { return [...emoji].map((cp) => cp.codePointAt(0)!.toString(16)).join('-'); } +function isRegionalIndicator(hexcode: string): boolean { + if (hexcode.includes('-')) return false; + const cp = parseInt(hexcode, 16); + return cp >= 0x1f1e6 && cp <= 0x1f1ff; +} + function getShortcodes(hexcode: string): string[] { const entry = (shortcodes as Record)[hexcode]; if (!entry) return []; @@ -55,7 +61,8 @@ function buildEmojiData() { // Skip component group (skin tones, hair styles) if (emojiData.group === 2) continue; - const category = groupToCategory[emojiData.group ?? -1]; + const isRegional = isRegionalIndicator(emojiData.hexcode); + const category = groupToCategory[emojiData.group ?? -1] ?? (isRegional ? 'flags' : undefined); if (!category) continue; const codes = getShortcodes(emojiData.hexcode); @@ -80,7 +87,7 @@ function buildEmojiData() { emojiList[key] = entry; // Only add to category if it's NOT a skin tone variant - if (!emojiData.tone) { + if (!emojiData.tone && !isRegional) { emojisByCategory[category].push(primaryShortcode); } From f6bced007acbf911c657f55657b8305b6a986844 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 10 Jul 2026 10:39:13 -0300 Subject: [PATCH 2/3] fix: missing emojis --- .../app/emoji-native/lib/generateEmojiData.ts | 9 +++++++-- .../app/emoji-native/lib/legacyEmojioneMap.ts | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/emoji-native/lib/generateEmojiData.ts b/apps/meteor/app/emoji-native/lib/generateEmojiData.ts index bcb14df2519f1..9fc5a5bc0f46c 100644 --- a/apps/meteor/app/emoji-native/lib/generateEmojiData.ts +++ b/apps/meteor/app/emoji-native/lib/generateEmojiData.ts @@ -105,10 +105,9 @@ function buildEmojiData() { if (!skin.tone) continue; const tones = Array.isArray(skin.tone) ? skin.tone : [skin.tone]; - const toneKey = `:${primaryShortcode}_tone${tones.join('-')}:`; const skinHex = hexFromEmoji(skin.emoji); - emojiList[toneKey] = { + const skinEntry: EmojiEntry = { uc_base: skinHex, uc_output: skinHex, uc_match: skinHex, @@ -118,6 +117,12 @@ function buildEmojiData() { emojiPackage: 'native', unicode: skin.emoji, }; + + const toneShortcodes = getShortcodes(skin.hexcode); + const fallbackShortcode = `${primaryShortcode}_tone${tones.join('-')}`; + for (const code of [...toneShortcodes, fallbackShortcode]) { + emojiList[`:${code}:`] = skinEntry; + } } } } diff --git a/apps/meteor/app/emoji-native/lib/legacyEmojioneMap.ts b/apps/meteor/app/emoji-native/lib/legacyEmojioneMap.ts index 3a16aecda02e5..f4f27d3cfd4f3 100644 --- a/apps/meteor/app/emoji-native/lib/legacyEmojioneMap.ts +++ b/apps/meteor/app/emoji-native/lib/legacyEmojioneMap.ts @@ -1407,4 +1407,21 @@ export const legacyEmojioneMap: Record = { 'za': 'πŸ‡ΏπŸ‡¦', 'zm': 'πŸ‡ΏπŸ‡²', 'zw': 'πŸ‡ΏπŸ‡Ό', + 'digit_zero': '0️⃣', + 'digit_one': '1️⃣', + 'digit_two': '2️⃣', + 'digit_three': '3️⃣', + 'digit_four': '4️⃣', + 'digit_five': '5️⃣', + 'digit_six': '6️⃣', + 'digit_seven': '7️⃣', + 'digit_eight': '8️⃣', + 'digit_nine': '9️⃣', + 'pound_symbol': '#️⃣', + 'asterisk_symbol': '*️⃣', + 'tone1': '🏻', + 'tone2': '🏼', + 'tone3': '🏽', + 'tone4': '🏾', + 'tone5': '🏿', }; From dca33a91d47cc3e1563caec8254317681f1e3633 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 10 Jul 2026 11:16:21 -0300 Subject: [PATCH 3/3] test: add tests --- .../unit/app/emoji/emojiNativeParser.spec.ts | 56 +++++++++++++++++++ packages/message-parser/tests/emoji.test.ts | 12 ++++ 2 files changed, 68 insertions(+) create mode 100644 apps/meteor/tests/unit/app/emoji/emojiNativeParser.spec.ts diff --git a/apps/meteor/tests/unit/app/emoji/emojiNativeParser.spec.ts b/apps/meteor/tests/unit/app/emoji/emojiNativeParser.spec.ts new file mode 100644 index 0000000000000..3cc7d2d1795b1 --- /dev/null +++ b/apps/meteor/tests/unit/app/emoji/emojiNativeParser.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { getEmojiData } from '../../../../app/emoji-native/lib/generateEmojiData'; +import { shortnameToUnicode } from '../../../../app/emoji-native/lib/shortnameToUnicode'; + +describe('emoji-native shortcode resolution', () => { + describe('shortnameToUnicode', () => { + it('resolves skin-tone variants stored under emojione alternate names', () => { + expect(shortnameToUnicode(':thumbsup_tone3:')).to.equal('πŸ‘πŸ½'); + expect(shortnameToUnicode(':yes_tone3:')).to.equal('πŸ‘πŸ½'); + expect(shortnameToUnicode(':clapping_hands_tone2:')).to.equal('πŸ‘πŸΌ'); + expect(shortnameToUnicode(':pray_tone4:')).to.equal('πŸ™πŸΎ'); + }); + + it('still resolves the emojibase primary skin-tone name', () => { + expect(shortnameToUnicode(':+1_tone3:')).to.equal('πŸ‘πŸ½'); + }); + + it('resolves regional indicator letters', () => { + expect(shortnameToUnicode(':regional_indicator_a:')).to.equal('πŸ‡¦'); + expect(shortnameToUnicode(':regional_indicator_z:')).to.equal('πŸ‡Ώ'); + }); + + it('resolves legacy emojione base aliases', () => { + expect(shortnameToUnicode(':digit_zero:')).to.equal('0️⃣'); + expect(shortnameToUnicode(':digit_nine:')).to.equal('9️⃣'); + expect(shortnameToUnicode(':pound_symbol:')).to.equal('#️⃣'); + expect(shortnameToUnicode(':asterisk_symbol:')).to.equal('*️⃣'); + expect(shortnameToUnicode(':tone3:')).to.equal('🏽'); + }); + + it('leaves unknown shortcodes untouched', () => { + expect(shortnameToUnicode(':not_a_real_emoji:')).to.equal(':not_a_real_emoji:'); + }); + + it('resolves multiple shortcodes within a single string', () => { + expect(shortnameToUnicode('hi :thumbsup_tone3: and :regional_indicator_a:')).to.equal('hi πŸ‘πŸ½ and πŸ‡¦'); + }); + }); + + describe('getEmojiData', () => { + it('registers regional indicators in emojiList but keeps them out of the picker', () => { + const { emojiList, emojisByCategory } = getEmojiData(); + expect(emojiList[':regional_indicator_a:']?.unicode).to.equal('πŸ‡¦'); + expect(emojisByCategory.flags).to.not.include('regional_indicator_a'); + }); + + it('registers alternate skin-tone shortnames alongside the primary', () => { + const { emojiList } = getEmojiData(); + expect(emojiList[':thumbsup_tone3:']?.unicode).to.equal('πŸ‘πŸ½'); + expect(emojiList[':yes_tone3:']?.unicode).to.equal('πŸ‘πŸ½'); + expect(emojiList[':+1_tone3:']?.unicode).to.equal('πŸ‘πŸ½'); + }); + }); +}); diff --git a/packages/message-parser/tests/emoji.test.ts b/packages/message-parser/tests/emoji.test.ts index 7739f7a91a14e..785d30b2634cc 100644 --- a/packages/message-parser/tests/emoji.test.ts +++ b/packages/message-parser/tests/emoji.test.ts @@ -30,6 +30,18 @@ test.each([ [':smile:', [bigEmoji([emoji('smile')])]], ['Hi :+1:', [paragraph([plain('Hi '), emoji('+1')])]], ['Hi :+1_tone4:', [paragraph([plain('Hi '), emoji('+1_tone4')])]], + ['Hi :thumbsup_tone3:', [paragraph([plain('Hi '), emoji('thumbsup_tone3')])]], + ['Hi :yes_tone3:', [paragraph([plain('Hi '), emoji('yes_tone3')])]], + ['Hi :clapping_hands_tone2:', [paragraph([plain('Hi '), emoji('clapping_hands_tone2')])]], + ['Hi :pray_tone4:', [paragraph([plain('Hi '), emoji('pray_tone4')])]], + ['Hi :regional_indicator_a:', [paragraph([plain('Hi '), emoji('regional_indicator_a')])]], + ['Hi :digit_zero:', [paragraph([plain('Hi '), emoji('digit_zero')])]], + ['Hi :pound_symbol:', [paragraph([plain('Hi '), emoji('pound_symbol')])]], + ['Hi :asterisk_symbol:', [paragraph([plain('Hi '), emoji('asterisk_symbol')])]], + ['Hi :tone3:', [paragraph([plain('Hi '), emoji('tone3')])]], + [':thumbsup_tone3:', [bigEmoji([emoji('thumbsup_tone3')])]], + [':regional_indicator_a:', [bigEmoji([emoji('regional_indicator_a')])]], + [':digit_zero:', [bigEmoji([emoji('digit_zero')])]], ])('parses %p', (input, output) => { expect(parse(input)).toEqual(output); });