emoji_helpers.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /**
  2. * Copyright © 2016-2020 Threema GmbH (https://threema.ch/).
  3. *
  4. * This file is part of Threema Web.
  5. *
  6. * Threema Web is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or (at
  9. * your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. import twemoji from 'twemoji';
  20. import {emojify, enlargeSingleEmoji, parseEmoji, shortnameToUtf8} from '../../src/helpers/emoji';
  21. const textVariantSelector = '\ufe0e';
  22. const emojiVariantSelector = '\ufe0f';
  23. const beer = '\ud83c\udf7b';
  24. const bird = '\ud83d\udc26';
  25. const biohazard = '\u2623';
  26. function makeEmoji(emojiString: string, codepoint?: string, imgCodepoint?: string): threema.EmojiInfo {
  27. if (codepoint === undefined) {
  28. codepoint = twemoji.convert.toCodePoint(emojiString);
  29. }
  30. const imgPath = imgCodepoint === undefined
  31. ? `emoji/png32/${codepoint}.png`
  32. : `emoji/png32/${imgCodepoint}.png`;
  33. return {
  34. emojiString: emojiString,
  35. imgPath: imgPath,
  36. codepoint: codepoint,
  37. }
  38. }
  39. describe('Emoji Helpers', () => {
  40. describe('emojify', () => {
  41. it('emojifies with img tag', function() {
  42. expect(emojify('hello 🐦'))
  43. .toEqual('hello <img class="em" draggable="false" '
  44. + 'alt="🐦" src="emoji/png32/1f426.png" data-c="1f426">');
  45. });
  46. it('ignores certain codepoints', function() {
  47. expect(emojify('©')).toEqual('©');
  48. expect(emojify('®')).toEqual('®');
  49. expect(emojify('™')).toEqual('™');
  50. });
  51. });
  52. describe('parseEmoji', () => {
  53. it('returns text unmodified', function() {
  54. expect(parseEmoji('hello world')).toEqual(['hello world']);
  55. });
  56. it('emojifies single emoji', function() {
  57. expect(parseEmoji(bird))
  58. .toEqual([makeEmoji(bird)]);
  59. expect(parseEmoji(beer))
  60. .toEqual([makeEmoji(beer)]);
  61. });
  62. it('emojifies multiple emoji', function() {
  63. expect(parseEmoji(`${beer}${bird}`))
  64. .toEqual([makeEmoji(beer), makeEmoji(bird)]);
  65. });
  66. it('emojifies mixed content', function() {
  67. expect(parseEmoji(`hi ${bird}`))
  68. .toEqual(['hi ', makeEmoji(bird)]);
  69. expect(parseEmoji(`${bird} bird`))
  70. .toEqual([makeEmoji(bird), ' bird']);
  71. expect(parseEmoji(`hi ${bird} bird`))
  72. .toEqual(['hi ', makeEmoji(bird), ' bird']);
  73. expect(parseEmoji(`hi ${bird}${beer}`))
  74. .toEqual(['hi ', makeEmoji(bird), makeEmoji(beer)]);
  75. });
  76. it('emojifies most text-default codepoints', function() {
  77. expect(parseEmoji(biohazard))
  78. .toEqual([makeEmoji(biohazard)]);
  79. });
  80. it('ignores certain codepoints', function() {
  81. expect(parseEmoji('©')).toEqual(['©']);
  82. expect(parseEmoji('®')).toEqual(['®']);
  83. expect(parseEmoji('™')).toEqual(['™']);
  84. });
  85. it('properly handles variant selectors (text-default)', function() {
  86. // Copyright: Text-default
  87. const copy = '©';
  88. expect(parseEmoji(copy))
  89. .toEqual([copy]);
  90. expect(parseEmoji(copy + textVariantSelector))
  91. .toEqual([copy + textVariantSelector]);
  92. expect(parseEmoji(copy + emojiVariantSelector))
  93. .toEqual([makeEmoji(copy + emojiVariantSelector, 'a9-fe0f', 'a9')]);
  94. });
  95. it('properly handles variant selectors (emoji-default)', function() {
  96. // Exclamation mark: Emoji-default
  97. const exclamation = '\u2757';
  98. expect(parseEmoji(exclamation))
  99. .toEqual([makeEmoji(exclamation, '2757', '2757')]);
  100. expect(parseEmoji(exclamation + textVariantSelector))
  101. .toEqual([exclamation + textVariantSelector]);
  102. expect(parseEmoji(exclamation + emojiVariantSelector))
  103. .toEqual([makeEmoji(exclamation + emojiVariantSelector, '2757-fe0f', '2757')]);
  104. });
  105. });
  106. describe('shortnameToUtf8', () => {
  107. it('converts valid shortnames', function() {
  108. expect(shortnameToUtf8('+1')).toEqual('\ud83d\udc4d\ufe0f');
  109. expect(shortnameToUtf8('thumbsup')).toEqual('\ud83d\udc4d\ufe0f');
  110. });
  111. it('returns null for unknown shortcodes', function() {
  112. expect(shortnameToUtf8('sömbsöp')).toBeNull();
  113. });
  114. it('handles multi-codepoint emoji', function() {
  115. expect(shortnameToUtf8('flag_ch')).toEqual('\ud83c\udde8\ud83c\udded');
  116. });
  117. it('ignores case', function() {
  118. expect(shortnameToUtf8('Flag_CH')).toEqual('\ud83c\udde8\ud83c\udded');
  119. });
  120. });
  121. describe('enlargeSingleEmoji', function() {
  122. const process = (text) => {
  123. return enlargeSingleEmoji(text, true)
  124. };
  125. const singleEmojiClassName = 'large-emoji';
  126. const crazy = '<img class="em" draggable="false"'
  127. + ' alt="🤪" src="emoji/png32/1f92a.png" data-c="1f92a">';
  128. const crazyLarge = '<img class="em ' + singleEmojiClassName
  129. + '" draggable="false" alt="🤪" src="emoji/png64/1f92a.png" data-c="1f92a">';
  130. const copyright = '<img class="em anotherclass" draggable="false"'
  131. + ' alt="©️" src="emoji/png32/a9.png" data-c="a9">';
  132. const copyrightLarge = '<img class="em ' + singleEmojiClassName
  133. + ' anotherclass" draggable="false" alt="©️" src="emoji/png64/a9.png" data-c="a9">';
  134. it('enlarges 1 emoji', () => {
  135. expect(process(crazy)).toEqual(crazyLarge);
  136. });
  137. it('enlarges 2 emoji', () => {
  138. expect(process(crazy + copyright)).toEqual(crazyLarge + copyrightLarge);
  139. });
  140. it('enlarges 3 emoji', () => {
  141. expect(process(crazy + copyright + crazy)).toEqual(crazyLarge + copyrightLarge + crazyLarge);
  142. });
  143. it('does not enlarge 4 emoji', () => {
  144. expect(process(crazy + copyright + crazy + copyright)).toEqual(crazy + copyright + crazy + copyright);
  145. });
  146. it('does not enlarge if non-emoji characters are contained', () => {
  147. expect(process(crazy + ' ')).toEqual(crazy + ' ');
  148. expect(process(crazy + 'a' + crazy)).toEqual(crazy + 'a' + crazy);
  149. });
  150. it('does not modify non emoji text', () => {
  151. const text = 'emoji e1 e1-people em em-people hello';
  152. expect(process(text)).toEqual(text);
  153. });
  154. it('does nothing if enlarge flag is set to false', () => {
  155. expect(enlargeSingleEmoji(crazy, false)).toEqual(crazy);
  156. });
  157. });
  158. });