Skip to content

Commit

Permalink
Merge pull request #5039 from mananjadhav/fix/single-emoji-size
Browse files Browse the repository at this point in the history
Fix: Size of the single emoji in chat along with updated Emoji Regex
  • Loading branch information
marcaaron authored Sep 13, 2021
2 parents 998a777 + 52d9827 commit 04bf0ce
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/CONST.js

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import _ from 'underscore';
import CONST from '../CONST';

/**
* Get the unicode code of an emoji in base 16.
* @param {String} input
* @returns {String}
*/
function getEmojiUnicode(input) {
if (input.length === 0) {
return '';
}

if (input.length === 1) {
return _.map(input.charCodeAt(0).toString().split(' '), val => parseInt(val, 10).toString(16)).join(' ');
}

const pairs = [];

// Some Emojis in UTF-16 are stored as pair of 2 Unicode characters (eg Flags)
// The first char is generally between the range U+D800 to U+DBFF called High surrogate
// & the second char between the range U+DC00 to U+DFFF called low surrogate
// More info in the following links:
// 1. https://docs.microsoft.com/en-us/windows/win32/intl/surrogates-and-supplementary-characters
// 2. https://thekevinscott.com/emojis-in-javascript/
for (let i = 0; i < input.length; i++) {
if (input.charCodeAt(i) >= 0xd800 && input.charCodeAt(i) <= 0xdbff) { // high surrogate
if (input.charCodeAt(i + 1) >= 0xdc00 && input.charCodeAt(i + 1) <= 0xdfff) { // low surrogate
pairs.push(
((input.charCodeAt(i) - 0xd800) * 0x400)
+ (input.charCodeAt(i + 1) - 0xdc00) + 0x10000,
);
}
} else if (input.charCodeAt(i) < 0xd800 || input.charCodeAt(i) > 0xdfff) {
// modifiers and joiners
pairs.push(input.charCodeAt(i));
}
}
return _.map(pairs, val => parseInt(val, 10).toString(16)).join(' ');
}

/**
* Function to remove Skin Tone and utf16 surrogates from Emoji
* @param {String} emojiCode
* @returns {String}
*/
function trimEmojiUnicode(emojiCode) {
return emojiCode.replace(/(fe0f|1f3fb|1f3fc|1f3fd|1f3fe|1f3ff)$/, '').trim();
}

/**
* Validates that this string is composed of a single emoji
*
* @param {String} message
* @returns {Boolean}
*/
function isSingleEmoji(message) {
const match = message.match(CONST.REGEX.EMOJIS);

if (!match) {
return false;
}

const matchedEmoji = match[0];
const matchedUnicode = getEmojiUnicode(matchedEmoji);
const currentMessageUnicode = trimEmojiUnicode(getEmojiUnicode(message));
return matchedUnicode === currentMessageUnicode;
}


export {
getEmojiUnicode,
trimEmojiUnicode,
isSingleEmoji,
};
17 changes: 0 additions & 17 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,6 @@ function isValidAddress(value) {
return !CONST.REGEX.PO_BOX.test(value);
}

/**
* Validates that this string is composed of a single emoji
*
* @param {String} message
* @returns {Boolean}
*/
function isSingleEmoji(message) {
const match = message.match(CONST.REGEX.EMOJIS);

if (!match) {
return false;
}

const matchedEmoji = match[0];
return message.length === matchedEmoji.length;
}

/**
* Validate date fields
Expand Down Expand Up @@ -127,5 +111,4 @@ export {
isValidIndustryCode,
isValidIdentity,
isValidZipCode,
isSingleEmoji,
};
2 changes: 1 addition & 1 deletion src/pages/home/report/ReportActionItemFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import themeColors from '../../../styles/themes/default';
import RenderHTML from '../../../components/RenderHTML';
import Text from '../../../components/Text';
import Tooltip from '../../../components/Tooltip';
import {isSingleEmoji} from '../../../libs/ValidationUtils';
import {isSingleEmoji} from '../../../libs/EmojiUtils';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import canUseTouchScreen from '../../../libs/canUseTouchscreen';
Expand Down
64 changes: 64 additions & 0 deletions tests/unit/EmojiRegexTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import _ from 'underscore';
import Emoji from '../../assets/emojis';
import CONST from '../../src/CONST';
import {isSingleEmoji} from '../../src/libs/EmojiUtils';

describe('EmojiRegexTest', () => {
it('matches all the emojis in the list', () => {
// Given the set of Emojis available in the application
const emojiMatched = _.every(Emoji, (emoji) => {
if (emoji.header === true || emoji.code === CONST.EMOJI_SPACER) {
return true;
}

// When we match every Emoji Code
const isEmojiMatched = isSingleEmoji(emoji.code);
let skinToneMatched = true;
if (emoji.types) {
// and every skin tone variant of the Emoji code
skinToneMatched = _.every(emoji.types, emojiWithSkinTone => isSingleEmoji(emojiWithSkinTone));
}
return skinToneMatched && isEmojiMatched;
});

// Then it should return true for every Emoji Code
expect(emojiMatched).toBe(true);
});

it('matches single emojis variants for size', () => {
// GIVEN an emoji that has the default Unicode representation WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('👉')).toBe(true);
expect(isSingleEmoji('😪️')).toBe(true);
expect(isSingleEmoji('😎️')).toBe(true);

// GIVEN an emoji that different cross-platform variations WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('🔫️')).toBe(true);
expect(isSingleEmoji('🛍')).toBe(true);
expect(isSingleEmoji('🕍')).toBe(true);

// GIVEN an emoji that is symbol/numerical WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('*️⃣')).toBe(true);
expect(isSingleEmoji('1️⃣️')).toBe(true);

// GIVEN an emoji that has text-variant WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('❤️')).toBe(true);
expect(isSingleEmoji('⁉️')).toBe(true);
expect(isSingleEmoji('✳️')).toBe(true);
expect(isSingleEmoji('☠️')).toBe(true);


// GIVEN an emoji that has skin tone attached WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('👶🏽')).toBe(true);
expect(isSingleEmoji('👩🏾')).toBe(true);
expect(isSingleEmoji('👊🏾')).toBe(true);

// GIVEN an emoji that is composite(family) with 4+ unicode pairs WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('👨‍👩‍👦️')).toBe(true);
expect(isSingleEmoji('👩‍👩‍👧‍👦️')).toBe(true);

// GIVEN an emoji that has a length of 2 (flags) WHEN we check if it's a single emoji THEN it should return true
expect(isSingleEmoji('🇺🇲')).toBe(true);
expect(isSingleEmoji('🇮🇳')).toBe(true);
expect(isSingleEmoji('🇺🇦️')).toBe(true);
});
});

0 comments on commit 04bf0ce

Please sign in to comment.