emoji_helpers.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * Copyright © 2016-2019 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, emojifyNew, enlargeSingleEmoji, shortnameToUnicode} from '../../src/helpers/emoji';
  21. const textVariantSelector = '\ufe0e';
  22. const emojiVariantSelector = '\ufe0f';
  23. const beer = '\ud83c\udf7b';
  24. const bird = '\ud83d\udc26';
  25. function makeEmoji(emojiString: string, codepoint?: string, imgCodepoint?: string): threema.EmojiInfo {
  26. if (codepoint === undefined) {
  27. codepoint = twemoji.convert.toCodePoint(emojiString);
  28. }
  29. const imgPath = imgCodepoint === undefined
  30. ? `emoji/png32/${codepoint}.png`
  31. : `emoji/png32/${imgCodepoint}.png`;
  32. return {
  33. emojiString: emojiString,
  34. imgPath: imgPath,
  35. codepoint: codepoint,
  36. }
  37. }
  38. describe('Emoji Helpers', () => {
  39. describe('emojify', () => {
  40. it('emojifies with img tag', function() {
  41. expect(emojify('hello 🐦'))
  42. .toEqual('hello <img class="em" draggable="false" '
  43. + 'alt="🐦" src="emoji/png32/1f426.png" data-c="1f426"/>');
  44. });
  45. it('ignores certain codepoints', function() {
  46. expect(emojify('©')).toEqual('©');
  47. expect(emojify('®')).toEqual('®');
  48. expect(emojify('™')).toEqual('™');
  49. });
  50. });
  51. describe('emojifyNew', () => {
  52. it('returns text unmodified', function() {
  53. expect(emojifyNew('hello world')).toEqual(['hello world']);
  54. });
  55. it('emojifies single emoji', function() {
  56. expect(emojifyNew(bird))
  57. .toEqual([makeEmoji(bird)]);
  58. });
  59. it('emojifies multiple emoji', function() {
  60. expect(emojifyNew(`${beer}${bird}`))
  61. .toEqual([makeEmoji(beer), makeEmoji(bird)]);
  62. });
  63. it('emojifies mixed content', function() {
  64. expect(emojifyNew(`hi ${bird}`))
  65. .toEqual(['hi ', makeEmoji(bird)]);
  66. expect(emojifyNew(`${bird} bird`))
  67. .toEqual([makeEmoji(bird), ' bird']);
  68. expect(emojifyNew(`hi ${bird} bird`))
  69. .toEqual(['hi ', makeEmoji(bird), ' bird']);
  70. expect(emojifyNew(`hi ${bird}${beer}`))
  71. .toEqual(['hi ', makeEmoji(bird), makeEmoji(beer)]);
  72. });
  73. it('ignores certain codepoints', function() {
  74. expect(emojifyNew('©')).toEqual(['©']);
  75. expect(emojifyNew('®')).toEqual(['®']);
  76. expect(emojifyNew('™')).toEqual(['™']);
  77. });
  78. it('properly handles variant selectors (text-default)', function() {
  79. // Copyright: Text-default
  80. const copy = '©';
  81. expect(emojifyNew(copy))
  82. .toEqual([copy]);
  83. expect(emojifyNew(copy + textVariantSelector))
  84. .toEqual([copy + textVariantSelector]);
  85. expect(emojifyNew(copy + emojiVariantSelector))
  86. .toEqual([makeEmoji(copy + emojiVariantSelector, 'a9-fe0f', 'a9')]);
  87. });
  88. it('properly handles variant selectors (emoji-default)', function() {
  89. // Exclamation mark: Emoji-default
  90. const exclamation = '\u2757';
  91. expect(emojifyNew(exclamation))
  92. .toEqual([makeEmoji(exclamation, '2757', '2757')]);
  93. expect(emojifyNew(exclamation + textVariantSelector))
  94. .toEqual([exclamation + textVariantSelector]);
  95. expect(emojifyNew(exclamation + emojiVariantSelector))
  96. .toEqual([makeEmoji(exclamation + emojiVariantSelector, '2757', '2757')]);
  97. });
  98. });
  99. describe('shortnameToUnicode', () => {
  100. it('converts valid shortnames', function() {
  101. expect(shortnameToUnicode('+1')).toEqual('\ud83d\udc4d\ufe0f');
  102. expect(shortnameToUnicode('thumbup')).toEqual('\ud83d\udc4d\ufe0f');
  103. expect(shortnameToUnicode('thumbsup')).toEqual('\ud83d\udc4d\ufe0f');
  104. });
  105. it('returns null for unknown shortcodes', function() {
  106. expect(shortnameToUnicode('sömbsöp')).toBeNull();
  107. });
  108. it('handles multi-codepoint emoji', function() {
  109. expect(shortnameToUnicode('ch')).toEqual('\ud83c\udde8\ud83c\udded');
  110. });
  111. });
  112. describe('enlargeSingleEmoji', function() {
  113. const process = (text) => {
  114. return enlargeSingleEmoji(text, true)
  115. };
  116. const singleEmojiClassName = 'large-emoji';
  117. const crazy = '<img class="em" draggable="false"'
  118. + ' alt="🤪" src="emoji/png32/1f92a.png" data-c="1f92a">';
  119. const crazyLarge = '<img class="em ' + singleEmojiClassName
  120. + '" draggable="false" alt="🤪" src="emoji/png64/1f92a.png" data-c="1f92a">';
  121. const copyright = '<img class="em anotherclass" draggable="false"'
  122. + ' alt="©️" src="emoji/png32/a9.png" data-c="a9">';
  123. const copyrightLarge = '<img class="em ' + singleEmojiClassName
  124. + ' anotherclass" draggable="false" alt="©️" src="emoji/png64/a9.png" data-c="a9">';
  125. it('enlarges 1 emoji', () => {
  126. expect(process(crazy)).toEqual(crazyLarge);
  127. });
  128. it('enlarges 2 emoji', () => {
  129. expect(process(crazy + copyright)).toEqual(crazyLarge + copyrightLarge);
  130. });
  131. it('enlarges 3 emoji', () => {
  132. expect(process(crazy + copyright + crazy)).toEqual(crazyLarge + copyrightLarge + crazyLarge);
  133. });
  134. it('does not enlarge 4 emoji', () => {
  135. expect(process(crazy + copyright + crazy + copyright)).toEqual(crazy + copyright + crazy + copyright);
  136. });
  137. it('does not enlarge if non-emoji characters are contained', () => {
  138. expect(process(crazy + ' ')).toEqual(crazy + ' ');
  139. expect(process(crazy + 'a' + crazy)).toEqual(crazy + 'a' + crazy);
  140. });
  141. it('does not modify non emoji text', () => {
  142. const text = 'emoji e1 e1-people em em-people hello';
  143. expect(process(text)).toEqual(text);
  144. });
  145. it('does nothing if enlarge flag is set to false', () => {
  146. expect(enlargeSingleEmoji(crazy, false)).toEqual(crazy);
  147. });
  148. });
  149. });