emojis.js 12 KB

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