emojis.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. (function (root, factory) {
  2. define([
  3. "jasmine",
  4. "mock",
  5. "test-utils"
  6. ], factory);
  7. } (this, function (jasmine, mock, test_utils) {
  8. "use strict";
  9. const { Backbone, Promise, Strophe, $iq, $msg, $pres, b64_sha1, dayjs, sizzle, _ } = converse.env;
  10. const u = converse.env.utils;
  11. describe("Emojis", function () {
  12. describe("The emoji picker", function () {
  13. it("can be opened by clicking a button in the chat toolbar",
  14. mock.initConverse(
  15. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  16. async function (done, _converse) {
  17. await test_utils.waitForRoster(_converse, 'current');
  18. test_utils.openControlBox();
  19. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  20. await test_utils.openChatBoxFor(_converse, contact_jid);
  21. const view = _converse.chatboxviews.get(contact_jid);
  22. const toolbar = view.el.querySelector('ul.chat-toolbar');
  23. expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1);
  24. spyOn(view, 'toggleEmojiMenu').and.callThrough();
  25. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  26. toolbar.querySelector('li.toggle-smiley').click();
  27. await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
  28. const picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
  29. const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji'));
  30. item.click()
  31. expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
  32. toolbar.querySelector('li.toggle-smiley').click(); // Close the panel again
  33. done();
  34. }));
  35. it("is opened to autocomplete emojis in the textarea",
  36. mock.initConverse(
  37. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  38. async function (done, _converse) {
  39. const muc_jid = 'lounge@montague.lit';
  40. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  41. const view = _converse.chatboxviews.get(muc_jid);
  42. const textarea = view.el.querySelector('textarea.chat-textarea');
  43. textarea.value = ':gri';
  44. // Press tab
  45. const tab_event = {
  46. 'target': textarea,
  47. 'preventDefault': function preventDefault () {},
  48. 'stopPropagation': function stopPropagation () {},
  49. 'keyCode': 9,
  50. 'key': 'Tab'
  51. }
  52. view.onKeyDown(tab_event);
  53. await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
  54. let picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
  55. const input = picker.querySelector('.emoji-search');
  56. expect(input.value).toBe(':gri');
  57. let visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
  58. expect(visible_emojis.length).toBe(3);
  59. expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
  60. expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
  61. expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:');
  62. // Test that TAB autocompletes the to first match
  63. view.emoji_picker_view.onKeyDown(tab_event);
  64. visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
  65. expect(visible_emojis.length).toBe(1);
  66. expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
  67. expect(input.value).toBe(':grimacing:');
  68. // Check that ENTER now inserts the match
  69. const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
  70. view.emoji_picker_view.onKeyDown(enter_event);
  71. expect(input.value).toBe('');
  72. expect(textarea.value).toBe(':grimacing: ');
  73. // Test that username starting with : doesn't cause issues
  74. const presence = $pres({
  75. 'from': `${muc_jid}/:username`,
  76. 'id': '27C55F89-1C6A-459A-9EB5-77690145D624',
  77. 'to': _converse.jid
  78. })
  79. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  80. .c('item', {
  81. 'jid': 'some1@montague.lit',
  82. 'affiliation': 'member',
  83. 'role': 'participant'
  84. });
  85. _converse.connection._dataRecv(test_utils.createRequest(presence));
  86. textarea.value = ':use';
  87. view.onKeyDown(tab_event);
  88. await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
  89. picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
  90. expect(input.value).toBe(':use');
  91. visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
  92. expect(visible_emojis.length).toBe(0);
  93. done();
  94. }));
  95. it("allows you to search for particular emojis",
  96. mock.initConverse(
  97. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  98. async function (done, _converse) {
  99. const muc_jid = 'lounge@montague.lit';
  100. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  101. const view = _converse.chatboxviews.get(muc_jid);
  102. const toolbar = view.el.querySelector('ul.chat-toolbar');
  103. expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1);
  104. toolbar.querySelector('li.toggle-smiley').click();
  105. await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
  106. const picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
  107. const input = picker.querySelector('.emoji-search');
  108. expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
  109. expect(view.emoji_picker_view.model.get('query')).toBeUndefined();
  110. input.value = 'smiley';
  111. const event = {
  112. 'target': input,
  113. 'preventDefault': function preventDefault () {},
  114. 'stopPropagation': function stopPropagation () {}
  115. };
  116. view.emoji_picker_view.onKeyDown(event);
  117. await u.waitUntil(() => view.emoji_picker_view.model.get('query') === 'smiley');
  118. let visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
  119. expect(visible_emojis.length).toBe(2);
  120. expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
  121. expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
  122. // Check that pressing enter without an unambiguous match does nothing
  123. const enter_event = Object.assign({}, event, {'keyCode': 13});
  124. view.emoji_picker_view.onKeyDown(enter_event);
  125. expect(input.value).toBe('smiley');
  126. // Test that TAB autocompletes the to first match
  127. const tab_event = Object.assign({}, event, {'keyCode': 9, 'key': 'Tab'});
  128. view.emoji_picker_view.onKeyDown(tab_event);
  129. expect(input.value).toBe(':smiley:');
  130. visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
  131. expect(visible_emojis.length).toBe(1);
  132. expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
  133. // Check that ENTER now inserts the match
  134. view.emoji_picker_view.onKeyDown(enter_event);
  135. expect(input.value).toBe('');
  136. expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
  137. done();
  138. }));
  139. });
  140. describe("A Chat Message", function () {
  141. it("will display larger if it's only emojis",
  142. mock.initConverse(
  143. null, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {'use_system_emojis': true},
  144. async function (done, _converse) {
  145. await test_utils.waitForRoster(_converse, 'current');
  146. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  147. _converse.chatboxes.onMessage($msg({
  148. 'from': sender_jid,
  149. 'to': _converse.connection.jid,
  150. 'type': 'chat',
  151. 'id': _converse.connection.getUniqueId()
  152. }).c('body').t('😇').up()
  153. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  154. await new Promise(resolve => _converse.on('chatBoxInitialized', resolve));
  155. const view = _converse.api.chatviews.get(sender_jid);
  156. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  157. const chat_content = view.el.querySelector('.chat-content');
  158. let message = chat_content.querySelector('.chat-msg__text');
  159. expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);
  160. _converse.chatboxes.onMessage($msg({
  161. 'from': sender_jid,
  162. 'to': _converse.connection.jid,
  163. 'type': 'chat',
  164. 'id': _converse.connection.getUniqueId()
  165. }).c('body').t('😇 Hello world! 😇 😇').up()
  166. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  167. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  168. message = chat_content.querySelector('.message:last-child .chat-msg__text');
  169. expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
  170. // Test that a modified message that no longer contains only
  171. // emojis now renders normally again.
  172. const textarea = view.el.querySelector('textarea.chat-textarea');
  173. textarea.value = ':poop: :innocent:';
  174. view.onKeyDown({
  175. target: textarea,
  176. preventDefault: function preventDefault () {},
  177. keyCode: 13 // Enter
  178. });
  179. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  180. expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
  181. expect(chat_content.querySelector('.message:last-child .chat-msg__text').textContent).toBe('💩 😇');
  182. expect(textarea.value).toBe('');
  183. view.onKeyDown({
  184. target: textarea,
  185. keyCode: 38 // Up arrow
  186. });
  187. expect(textarea.value).toBe('💩 😇');
  188. expect(view.model.messages.at(2).get('correcting')).toBe(true);
  189. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg:last-child')), 500);
  190. textarea.value = textarea.value += 'This is no longer an emoji-only message';
  191. view.onKeyDown({
  192. target: textarea,
  193. preventDefault: function preventDefault () {},
  194. keyCode: 13 // Enter
  195. });
  196. await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
  197. expect(view.model.messages.models.length).toBe(3);
  198. message = chat_content.querySelector('.message:last-child .chat-msg__text');
  199. expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
  200. done()
  201. }));
  202. });
  203. });
  204. }));