From 9d6bbc92c978beeec70bec1f2d4a65791fa3f985 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 00:54:34 -0500 Subject: move glEmojiTag method to emoji helper --- app/assets/javascripts/awards_handler.js | 5 +- app/assets/javascripts/behaviors/gl_emoji.js | 67 +---- app/assets/javascripts/behaviors/index.js | 2 +- app/assets/javascripts/emoji/index.js | 76 ++++- app/assets/javascripts/gfm_auto_complete.js | 3 +- spec/javascripts/emoji_spec.js | 429 +++++++++++++++++++++++++++ spec/javascripts/gl_emoji_spec.js | 429 --------------------------- 7 files changed, 496 insertions(+), 515 deletions(-) create mode 100644 spec/javascripts/emoji_spec.js delete mode 100644 spec/javascripts/gl_emoji_spec.js diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 0950b52caf5..6f0384ebebe 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,7 +2,6 @@ /* global Flash */ import Cookies from 'js-cookie'; -import { glEmojiTag } from './behaviors/gl_emoji'; import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; @@ -30,7 +29,7 @@ function renderCategory(name, emojiList, opts = {}) { ${emojiList.map(emojiName => `
  • @@ -369,7 +368,7 @@ export default class AwardsHandler { createAwardButtonForVotesBlock(votesBlock, emojiName) { const buttonHtml = ` `; diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 17422f5cece..8156e491a42 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,66 +1,10 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, normalizeEmojiName } from '../emoji'; +import { emojiImageTag, emojiFallbackImageSrc } from '../emoji'; import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -function emojiImageTag(name, src) { - return `:${name}:`; -} - -function assembleFallbackImageSrc(inputName) { - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; - - return fallbackImageSrc; -} - -function glEmojiTag(inputName, options) { - const opts = { sprite: false, forceFallback: false, ...options }; - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - - const fallbackImageSrc = assembleFallbackImageSrc(name); - const fallbackSpriteClass = `emoji-${name}`; - - const classList = []; - if (opts.forceFallback && opts.sprite) { - classList.push('emoji-icon'); - classList.push(fallbackSpriteClass); - } - const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; - const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; - let contents = emojiInfo.moji; - if (opts.forceFallback && !opts.sprite) { - contents = emojiImageTag(name, fallbackImageSrc); - } - - return ` - - ${contents} - - `; -} - -function installGlEmojiElement() { +export default function installGlEmojiElement() { const GlEmojiElementProto = Object.create(HTMLElement.prototype); GlEmojiElementProto.createdCallback = function createdCallback() { const emojiUnicode = this.textContent.trim(); @@ -91,7 +35,7 @@ function installGlEmojiElement() { } else if (hasImageFallback) { this.innerHTML = emojiImageTag(name, fallbackSrc); } else { - const src = assembleFallbackImageSrc(name); + const src = emojiFallbackImageSrc(name); this.innerHTML = emojiImageTag(name, src); } } @@ -101,8 +45,3 @@ function installGlEmojiElement() { prototype: GlEmojiElementProto, }); } - -export { - installGlEmojiElement, - glEmojiTag, -}; diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index 5b931e6cfa6..44b2c974b9e 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -1,7 +1,7 @@ import './autosize'; import './bind_in_out'; import './details_behavior'; -import { installGlEmojiElement } from './gl_emoji'; +import installGlEmojiElement from './gl_emoji'; import './quick_submit'; import './requires_input'; import './toggler_behavior'; diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 7c3bab1e4a9..bbecd1e41d1 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,27 +1,27 @@ import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; -const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; +export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; -function normalizeEmojiName(name) { +export function normalizeEmojiName(name) { return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; } -function isEmojiNameValid(name) { +export function isEmojiNameValid(name) { return validEmojiNames.indexOf(name) >= 0; } -function filterEmojiNames(filter) { +export function filterEmojiNames(filter) { const match = filter.toLowerCase(); return validEmojiNames.filter(name => name.indexOf(match) >= 0); } -function filterEmojiNamesByAlias(filter) { +export function filterEmojiNamesByAlias(filter) { return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); } let emojiByCategory; -function getEmojiByCategory(category = null) { +export function getEmojiByCategory(category = null) { if (!emojiByCategory) { emojiByCategory = { activity: [], @@ -43,13 +43,57 @@ function getEmojiByCategory(category = null) { return category ? emojiByCategory[category] : emojiByCategory; } -export { - emojiMap, - emojiAliases, - normalizeEmojiName, - filterEmojiNames, - filterEmojiNamesByAlias, - getEmojiByCategory, - isEmojiNameValid, - validEmojiNames, -}; +export function getEmojiInfo(query) { + let name = normalizeEmojiName(query); + let emojiInfo = emojiMap[name]; + + // Fallback to question mark for unknown emojis + if (!emojiInfo) { + name = 'grey_question'; + emojiInfo = emojiMap[name]; + } + + return { ...emojiInfo, name }; +} + +export function emojiFallbackImageSrc(inputName) { + const { name, digest } = getEmojiInfo(inputName); + return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`; +} + +export function emojiImageTag(name, src) { + return `:${name}:`; +} + +export function glEmojiTag(inputName, options) { + const opts = { sprite: false, forceFallback: false, ...options }; + const { name, ...emojiInfo } = getEmojiInfo(inputName); + + const fallbackImageSrc = emojiFallbackImageSrc(name); + const fallbackSpriteClass = `emoji-${name}`; + + const classList = []; + if (opts.forceFallback && opts.sprite) { + classList.push('emoji-icon'); + classList.push(fallbackSpriteClass); + } + const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; + const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; + let contents = emojiInfo.moji; + if (opts.forceFallback && !opts.sprite) { + contents = emojiImageTag(name, fallbackImageSrc); + } + + return ` + + ${contents} + + `; +} diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 9d057fd22a8..f99bac7da1a 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,5 +1,4 @@ -import { glEmojiTag } from './behaviors/gl_emoji'; -import { validEmojiNames } from './emoji'; +import { validEmojiNames, glEmojiTag } from './emoji'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js new file mode 100644 index 00000000000..fa11c602ec3 --- /dev/null +++ b/spec/javascripts/emoji_spec.js @@ -0,0 +1,429 @@ +import { glEmojiTag } from '~/emoji'; +import isEmojiUnicodeSupported, { + isFlagEmoji, + isKeycapEmoji, + isSkinToneComboEmoji, + isHorceRacingSkinToneComboEmoji, + isPersonZwjEmoji, +} from '~/emoji/support/is_emoji_unicode_supported'; + +const emptySupportMap = { + personZwj: false, + horseRacing: false, + flag: false, + skinToneModifier: false, + '9.0': false, + '8.0': false, + '7.0': false, + 6.1: false, + '6.0': false, + 5.2: false, + 5.1: false, + 4.1: false, + '4.0': false, + 3.2: false, + '3.0': false, + 1.1: false, +}; + +const emojiFixtureMap = { + bomb: { + name: 'bomb', + moji: 'πŸ’£', + unicodeVersion: '6.0', + }, + construction_worker_tone5: { + name: 'construction_worker_tone5', + moji: 'πŸ‘·πŸΏ', + unicodeVersion: '8.0', + }, + five: { + name: 'five', + moji: '5️⃣', + unicodeVersion: '3.0', + }, + grey_question: { + name: 'grey_question', + moji: '❔', + unicodeVersion: '6.0', + }, +}; + +function markupToDomElement(markup) { + const div = document.createElement('div'); + div.innerHTML = markup; + return div.firstElementChild; +} + +function testGlEmojiImageFallback(element, name, src) { + expect(element.tagName.toLowerCase()).toBe('img'); + expect(element.getAttribute('src')).toBe(src); + expect(element.getAttribute('title')).toBe(`:${name}:`); + expect(element.getAttribute('alt')).toBe(`:${name}:`); +} + +const defaults = { + forceFallback: false, + sprite: false, +}; + +function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) { + const opts = Object.assign({}, defaults, options); + expect(element.tagName.toLowerCase()).toBe('gl-emoji'); + expect(element.dataset.name).toBe(name); + expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0); + expect(element.dataset.unicodeVersion).toBe(unicodeVersion); + + const fallbackSpriteClass = `emoji-${name}`; + if (opts.sprite) { + expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass); + } + + if (opts.forceFallback && opts.sprite) { + expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`); + } + + if (opts.forceFallback && !opts.sprite) { + // Check for image fallback + testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc); + } else { + // Otherwise make sure things are still unicode text + expect(element.textContent.trim()).toBe(unicodeMoji); + } +} + +describe('gl_emoji', () => { + describe('glEmojiTag', () => { + it('bomb emoji', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); + }); + + it('bomb emoji with image fallback', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + forceFallback: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); + }); + + it('bomb emoji with sprite fallback readiness', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + sprite: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + sprite: true, + }, + ); + }); + it('bomb emoji with sprite fallback', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + forceFallback: true, + sprite: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + sprite: true, + }, + ); + }); + + it('question mark when invalid emoji name given', () => { + const name = 'invalid_emoji'; + const emojiKey = 'grey_question'; + const markup = glEmojiTag(name); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); + }); + + it('question mark with image fallback when invalid emoji name given', () => { + const name = 'invalid_emoji'; + const emojiKey = 'grey_question'; + const markup = glEmojiTag(name, { + forceFallback: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); + }); + }); + + describe('isFlagEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isFlagEmoji('')).toBeFalsy(); + }); + it('should detect flag_ac', () => { + expect(isFlagEmoji('πŸ‡¦πŸ‡¨')).toBeTruthy(); + }); + it('should detect flag_us', () => { + expect(isFlagEmoji('πŸ‡ΊπŸ‡Έ')).toBeTruthy(); + }); + it('should detect flag_zw', () => { + expect(isFlagEmoji('πŸ‡ΏπŸ‡Ό')).toBeTruthy(); + }); + it('should not detect flags', () => { + expect(isFlagEmoji('🎏')).toBeFalsy(); + }); + it('should not detect triangular_flag_on_post', () => { + expect(isFlagEmoji('🚩')).toBeFalsy(); + }); + it('should not detect single letter', () => { + expect(isFlagEmoji('πŸ‡¦')).toBeFalsy(); + }); + it('should not detect >2 letters', () => { + expect(isFlagEmoji('πŸ‡¦πŸ‡§πŸ‡¨')).toBeFalsy(); + }); + }); + + describe('isKeycapEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isKeycapEmoji('')).toBeFalsy(); + }); + it('should detect one(keycap)', () => { + expect(isKeycapEmoji('1️⃣')).toBeTruthy(); + }); + it('should detect nine(keycap)', () => { + expect(isKeycapEmoji('9️⃣')).toBeTruthy(); + }); + it('should not detect ten(keycap)', () => { + expect(isKeycapEmoji('πŸ”Ÿ')).toBeFalsy(); + }); + it('should not detect hash(keycap)', () => { + expect(isKeycapEmoji('#⃣')).toBeFalsy(); + }); + }); + + describe('isSkinToneComboEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isSkinToneComboEmoji('')).toBeFalsy(); + }); + it('should detect hand_splayed_tone5', () => { + expect(isSkinToneComboEmoji('πŸ–πŸΏ')).toBeTruthy(); + }); + it('should not detect hand_splayed', () => { + expect(isSkinToneComboEmoji('πŸ–')).toBeFalsy(); + }); + it('should detect lifter_tone1', () => { + expect(isSkinToneComboEmoji('πŸ‹πŸ»')).toBeTruthy(); + }); + it('should not detect lifter', () => { + expect(isSkinToneComboEmoji('πŸ‹')).toBeFalsy(); + }); + it('should detect rowboat_tone4', () => { + expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy(); + }); + it('should not detect rowboat', () => { + expect(isSkinToneComboEmoji('🚣')).toBeFalsy(); + }); + it('should not detect individual tone emoji', () => { + expect(isSkinToneComboEmoji('🏻')).toBeFalsy(); + }); + }); + + describe('isHorceRacingSkinToneComboEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy(); + }); + it('should detect horse_racing_tone2', () => { + expect(isHorceRacingSkinToneComboEmoji('πŸ‡πŸΌ')).toBeTruthy(); + }); + it('should not detect horse_racing', () => { + expect(isHorceRacingSkinToneComboEmoji('πŸ‡')).toBeFalsy(); + }); + }); + + describe('isPersonZwjEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isPersonZwjEmoji('')).toBeFalsy(); + }); + it('should detect couple_mm', () => { + expect(isPersonZwjEmoji('πŸ‘¨β€β€οΈβ€πŸ‘¨')).toBeTruthy(); + }); + it('should not detect couple_with_heart', () => { + expect(isPersonZwjEmoji('πŸ’‘')).toBeFalsy(); + }); + it('should not detect couplekiss', () => { + expect(isPersonZwjEmoji('πŸ’')).toBeFalsy(); + }); + it('should detect family_mmb', () => { + expect(isPersonZwjEmoji('πŸ‘¨β€πŸ‘¨β€πŸ‘¦')).toBeTruthy(); + }); + it('should detect family_mwgb', () => { + expect(isPersonZwjEmoji('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦')).toBeTruthy(); + }); + it('should not detect family', () => { + expect(isPersonZwjEmoji('πŸ‘ͺ')).toBeFalsy(); + }); + it('should detect kiss_ww', () => { + expect(isPersonZwjEmoji('πŸ‘©β€β€οΈβ€πŸ’‹β€πŸ‘©')).toBeTruthy(); + }); + it('should not detect girl', () => { + expect(isPersonZwjEmoji('πŸ‘§')).toBeFalsy(); + }); + it('should not detect girl_tone5', () => { + expect(isPersonZwjEmoji('πŸ‘§πŸΏ')).toBeFalsy(); + }); + it('should not detect man', () => { + expect(isPersonZwjEmoji('πŸ‘¨')).toBeFalsy(); + }); + it('should not detect woman', () => { + expect(isPersonZwjEmoji('πŸ‘©')).toBeFalsy(); + }); + }); + + describe('isEmojiUnicodeSupported', () => { + it('should gracefully handle empty string with unicode support', () => { + const isSupported = isEmojiUnicodeSupported( + { '1.0': true }, + '', + '1.0', + ); + expect(isSupported).toBeTruthy(); + }); + it('should gracefully handle empty string without unicode support', () => { + const isSupported = isEmojiUnicodeSupported( + {}, + '', + '1.0', + ); + expect(isSupported).toBeFalsy(); + }); + it('bomb(6.0) with 6.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '6.0': true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeTruthy(); + }); + + it('bomb(6.0) without 6.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = emptySupportMap; + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('bomb(6.0) without 6.0 but with 9.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '9.0': true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('construction_worker_tone5(8.0) without skin tone modifier support', () => { + const emojiKey = 'construction_worker_tone5'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + skinToneModifier: false, + '9.0': true, + '8.0': true, + '7.0': true, + 6.1: true, + '6.0': true, + 5.2: true, + 5.1: true, + 4.1: true, + '4.0': true, + 3.2: true, + '3.0': true, + 1.1: true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('use native keycap on >=57 chrome', () => { + const emojiKey = 'five'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '3.0': true, + meta: { + isChrome: true, + chromeVersion: 57, + }, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeTruthy(); + }); + + it('fallback keycap on <57 chrome', () => { + const emojiKey = 'five'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '3.0': true, + meta: { + isChrome: true, + chromeVersion: 50, + }, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + }); +}); diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js deleted file mode 100644 index be1ced4aaf9..00000000000 --- a/spec/javascripts/gl_emoji_spec.js +++ /dev/null @@ -1,429 +0,0 @@ -import { glEmojiTag } from '~/behaviors/gl_emoji'; -import isEmojiUnicodeSupported, { - isFlagEmoji, - isKeycapEmoji, - isSkinToneComboEmoji, - isHorceRacingSkinToneComboEmoji, - isPersonZwjEmoji, -} from '~/emoji/support/is_emoji_unicode_supported'; - -const emptySupportMap = { - personZwj: false, - horseRacing: false, - flag: false, - skinToneModifier: false, - '9.0': false, - '8.0': false, - '7.0': false, - 6.1: false, - '6.0': false, - 5.2: false, - 5.1: false, - 4.1: false, - '4.0': false, - 3.2: false, - '3.0': false, - 1.1: false, -}; - -const emojiFixtureMap = { - bomb: { - name: 'bomb', - moji: 'πŸ’£', - unicodeVersion: '6.0', - }, - construction_worker_tone5: { - name: 'construction_worker_tone5', - moji: 'πŸ‘·πŸΏ', - unicodeVersion: '8.0', - }, - five: { - name: 'five', - moji: '5️⃣', - unicodeVersion: '3.0', - }, - grey_question: { - name: 'grey_question', - moji: '❔', - unicodeVersion: '6.0', - }, -}; - -function markupToDomElement(markup) { - const div = document.createElement('div'); - div.innerHTML = markup; - return div.firstElementChild; -} - -function testGlEmojiImageFallback(element, name, src) { - expect(element.tagName.toLowerCase()).toBe('img'); - expect(element.getAttribute('src')).toBe(src); - expect(element.getAttribute('title')).toBe(`:${name}:`); - expect(element.getAttribute('alt')).toBe(`:${name}:`); -} - -const defaults = { - forceFallback: false, - sprite: false, -}; - -function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) { - const opts = Object.assign({}, defaults, options); - expect(element.tagName.toLowerCase()).toBe('gl-emoji'); - expect(element.dataset.name).toBe(name); - expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0); - expect(element.dataset.unicodeVersion).toBe(unicodeVersion); - - const fallbackSpriteClass = `emoji-${name}`; - if (opts.sprite) { - expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass); - } - - if (opts.forceFallback && opts.sprite) { - expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`); - } - - if (opts.forceFallback && !opts.sprite) { - // Check for image fallback - testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc); - } else { - // Otherwise make sure things are still unicode text - expect(element.textContent.trim()).toBe(unicodeMoji); - } -} - -describe('gl_emoji', () => { - describe('glEmojiTag', () => { - it('bomb emoji', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - ); - }); - - it('bomb emoji with image fallback', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - forceFallback: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - }); - - it('bomb emoji with sprite fallback readiness', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - sprite: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - sprite: true, - }, - ); - }); - it('bomb emoji with sprite fallback', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - forceFallback: true, - sprite: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - sprite: true, - }, - ); - }); - - it('question mark when invalid emoji name given', () => { - const name = 'invalid_emoji'; - const emojiKey = 'grey_question'; - const markup = glEmojiTag(name); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - ); - }); - - it('question mark with image fallback when invalid emoji name given', () => { - const name = 'invalid_emoji'; - const emojiKey = 'grey_question'; - const markup = glEmojiTag(name, { - forceFallback: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - }); - }); - - describe('isFlagEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isFlagEmoji('')).toBeFalsy(); - }); - it('should detect flag_ac', () => { - expect(isFlagEmoji('πŸ‡¦πŸ‡¨')).toBeTruthy(); - }); - it('should detect flag_us', () => { - expect(isFlagEmoji('πŸ‡ΊπŸ‡Έ')).toBeTruthy(); - }); - it('should detect flag_zw', () => { - expect(isFlagEmoji('πŸ‡ΏπŸ‡Ό')).toBeTruthy(); - }); - it('should not detect flags', () => { - expect(isFlagEmoji('🎏')).toBeFalsy(); - }); - it('should not detect triangular_flag_on_post', () => { - expect(isFlagEmoji('🚩')).toBeFalsy(); - }); - it('should not detect single letter', () => { - expect(isFlagEmoji('πŸ‡¦')).toBeFalsy(); - }); - it('should not detect >2 letters', () => { - expect(isFlagEmoji('πŸ‡¦πŸ‡§πŸ‡¨')).toBeFalsy(); - }); - }); - - describe('isKeycapEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isKeycapEmoji('')).toBeFalsy(); - }); - it('should detect one(keycap)', () => { - expect(isKeycapEmoji('1️⃣')).toBeTruthy(); - }); - it('should detect nine(keycap)', () => { - expect(isKeycapEmoji('9️⃣')).toBeTruthy(); - }); - it('should not detect ten(keycap)', () => { - expect(isKeycapEmoji('πŸ”Ÿ')).toBeFalsy(); - }); - it('should not detect hash(keycap)', () => { - expect(isKeycapEmoji('#⃣')).toBeFalsy(); - }); - }); - - describe('isSkinToneComboEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isSkinToneComboEmoji('')).toBeFalsy(); - }); - it('should detect hand_splayed_tone5', () => { - expect(isSkinToneComboEmoji('πŸ–πŸΏ')).toBeTruthy(); - }); - it('should not detect hand_splayed', () => { - expect(isSkinToneComboEmoji('πŸ–')).toBeFalsy(); - }); - it('should detect lifter_tone1', () => { - expect(isSkinToneComboEmoji('πŸ‹πŸ»')).toBeTruthy(); - }); - it('should not detect lifter', () => { - expect(isSkinToneComboEmoji('πŸ‹')).toBeFalsy(); - }); - it('should detect rowboat_tone4', () => { - expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy(); - }); - it('should not detect rowboat', () => { - expect(isSkinToneComboEmoji('🚣')).toBeFalsy(); - }); - it('should not detect individual tone emoji', () => { - expect(isSkinToneComboEmoji('🏻')).toBeFalsy(); - }); - }); - - describe('isHorceRacingSkinToneComboEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy(); - }); - it('should detect horse_racing_tone2', () => { - expect(isHorceRacingSkinToneComboEmoji('πŸ‡πŸΌ')).toBeTruthy(); - }); - it('should not detect horse_racing', () => { - expect(isHorceRacingSkinToneComboEmoji('πŸ‡')).toBeFalsy(); - }); - }); - - describe('isPersonZwjEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isPersonZwjEmoji('')).toBeFalsy(); - }); - it('should detect couple_mm', () => { - expect(isPersonZwjEmoji('πŸ‘¨β€β€οΈβ€πŸ‘¨')).toBeTruthy(); - }); - it('should not detect couple_with_heart', () => { - expect(isPersonZwjEmoji('πŸ’‘')).toBeFalsy(); - }); - it('should not detect couplekiss', () => { - expect(isPersonZwjEmoji('πŸ’')).toBeFalsy(); - }); - it('should detect family_mmb', () => { - expect(isPersonZwjEmoji('πŸ‘¨β€πŸ‘¨β€πŸ‘¦')).toBeTruthy(); - }); - it('should detect family_mwgb', () => { - expect(isPersonZwjEmoji('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦')).toBeTruthy(); - }); - it('should not detect family', () => { - expect(isPersonZwjEmoji('πŸ‘ͺ')).toBeFalsy(); - }); - it('should detect kiss_ww', () => { - expect(isPersonZwjEmoji('πŸ‘©β€β€οΈβ€πŸ’‹β€πŸ‘©')).toBeTruthy(); - }); - it('should not detect girl', () => { - expect(isPersonZwjEmoji('πŸ‘§')).toBeFalsy(); - }); - it('should not detect girl_tone5', () => { - expect(isPersonZwjEmoji('πŸ‘§πŸΏ')).toBeFalsy(); - }); - it('should not detect man', () => { - expect(isPersonZwjEmoji('πŸ‘¨')).toBeFalsy(); - }); - it('should not detect woman', () => { - expect(isPersonZwjEmoji('πŸ‘©')).toBeFalsy(); - }); - }); - - describe('isEmojiUnicodeSupported', () => { - it('should gracefully handle empty string with unicode support', () => { - const isSupported = isEmojiUnicodeSupported( - { '1.0': true }, - '', - '1.0', - ); - expect(isSupported).toBeTruthy(); - }); - it('should gracefully handle empty string without unicode support', () => { - const isSupported = isEmojiUnicodeSupported( - {}, - '', - '1.0', - ); - expect(isSupported).toBeFalsy(); - }); - it('bomb(6.0) with 6.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '6.0': true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeTruthy(); - }); - - it('bomb(6.0) without 6.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = emptySupportMap; - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('bomb(6.0) without 6.0 but with 9.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '9.0': true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('construction_worker_tone5(8.0) without skin tone modifier support', () => { - const emojiKey = 'construction_worker_tone5'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - skinToneModifier: false, - '9.0': true, - '8.0': true, - '7.0': true, - 6.1: true, - '6.0': true, - 5.2: true, - 5.1: true, - 4.1: true, - '4.0': true, - 3.2: true, - '3.0': true, - 1.1: true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('use native keycap on >=57 chrome', () => { - const emojiKey = 'five'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '3.0': true, - meta: { - isChrome: true, - chromeVersion: 57, - }, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeTruthy(); - }); - - it('fallback keycap on <57 chrome', () => { - const emojiKey = 'five'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '3.0': true, - meta: { - isChrome: true, - chromeVersion: 50, - }, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - }); -}); -- cgit v1.2.1