notification.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*global mock, converse */
  2. const { Strophe, _ } = converse.env;
  3. const $msg = converse.env.$msg;
  4. const u = converse.env.utils;
  5. describe("Notifications", function () {
  6. // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
  7. describe("When show_desktop_notifications is set to true", function () {
  8. describe("And the desktop is not focused", function () {
  9. describe("an HTML5 Notification", function () {
  10. it("is shown when a new private message is received",
  11. mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
  12. await mock.waitForRoster(_converse, 'current');
  13. spyOn(_converse, 'showMessageNotification').and.callThrough();
  14. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  15. spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
  16. const message = 'This message will show a desktop notification';
  17. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
  18. msg = $msg({
  19. from: sender_jid,
  20. to: _converse.connection.jid,
  21. type: 'chat',
  22. id: u.getUniqueId()
  23. }).c('body').t(message).up()
  24. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  25. await _converse.handleMessageStanza(msg); // This will emit 'message'
  26. await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
  27. expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
  28. expect(_converse.showMessageNotification).toHaveBeenCalled();
  29. done();
  30. }));
  31. it("is shown when you are mentioned in a groupchat",
  32. mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
  33. await mock.waitForRoster(_converse, 'current');
  34. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
  35. const view = _converse.api.chatviews.get('lounge@montague.lit');
  36. if (!view.el.querySelectorAll('.chat-area').length) {
  37. view.renderChatArea();
  38. }
  39. let no_notification = false;
  40. if (typeof window.Notification === 'undefined') {
  41. no_notification = true;
  42. window.Notification = function () {
  43. return {
  44. 'close': function () {}
  45. };
  46. };
  47. }
  48. spyOn(_converse, 'showMessageNotification').and.callThrough();
  49. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  50. // Test mention with setting false
  51. const nick = mock.chatroom_names[0];
  52. const makeMsg = text => $msg({
  53. from: 'lounge@montague.lit/'+nick,
  54. id: u.getUniqueId(),
  55. to: 'romeo@montague.lit',
  56. type: 'groupchat'
  57. }).c('body').t(text).tree();
  58. _converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will NOT show a notification')));
  59. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  60. await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 0);
  61. expect(_converse.showMessageNotification).not.toHaveBeenCalled();
  62. // Test reference
  63. const message_with_ref = $msg({
  64. from: 'lounge@montague.lit/'+nick,
  65. id: u.getUniqueId(),
  66. to: 'romeo@montague.lit',
  67. type: 'groupchat'
  68. }).c('body').t('romeo: this will show a notification').up()
  69. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'0', 'end':'5', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).tree();
  70. _converse.connection._dataRecv(mock.createRequest(message_with_ref));
  71. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  72. await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
  73. expect(_converse.showMessageNotification).toHaveBeenCalled();
  74. // Test mention with setting true
  75. _converse.api.settings.set('notify_all_room_messages', true);
  76. _converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will show a notification')));
  77. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  78. await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 2);
  79. expect(_converse.showMessageNotification).toHaveBeenCalled();
  80. if (no_notification) {
  81. delete window.Notification;
  82. }
  83. done();
  84. }));
  85. it("is shown for headline messages",
  86. mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
  87. spyOn(_converse, 'showMessageNotification').and.callThrough();
  88. spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
  89. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  90. const stanza = $msg({
  91. 'type': 'headline',
  92. 'from': 'notify.example.com',
  93. 'to': 'romeo@montague.lit',
  94. 'xml:lang': 'en'
  95. })
  96. .c('subject').t('SIEVE').up()
  97. .c('body').t('<juliet@example.com> You got mail.').up()
  98. .c('x', {'xmlns': 'jabber:x:oob'})
  99. .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
  100. _converse.connection._dataRecv(mock.createRequest(stanza));
  101. await u.waitUntil(() => _converse.chatboxviews.keys().length);
  102. const view = _converse.chatboxviews.get('notify.example.com');
  103. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  104. expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
  105. expect(_converse.showMessageNotification).toHaveBeenCalled();
  106. done();
  107. }));
  108. it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
  109. _converse.allow_non_roster_messaging = false;
  110. spyOn(_converse, 'showMessageNotification').and.callThrough();
  111. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  112. const stanza = $msg({
  113. 'type': 'headline',
  114. 'from': 'someone@notify.example.com',
  115. 'to': 'romeo@montague.lit',
  116. 'xml:lang': 'en'
  117. })
  118. .c('subject').t('SIEVE').up()
  119. .c('body').t('<juliet@example.com> You got mail.').up()
  120. .c('x', {'xmlns': 'jabber:x:oob'})
  121. .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
  122. _converse.connection._dataRecv(mock.createRequest(stanza));
  123. expect(
  124. _.includes(_converse.chatboxviews.keys(),
  125. 'someone@notify.example.com')
  126. ).toBeFalsy();
  127. expect(_converse.showMessageNotification).not.toHaveBeenCalled();
  128. done();
  129. }));
  130. it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
  131. mock.initConverse(['rosterGroupsFetched'], {show_chat_state_notifications: true},
  132. async (done, _converse) => {
  133. await mock.waitForRoster(_converse, 'current', 3);
  134. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  135. spyOn(_converse, 'showChatStateNotification');
  136. const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  137. _converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
  138. await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
  139. expect(_converse.showChatStateNotification).toHaveBeenCalled();
  140. done()
  141. }));
  142. });
  143. });
  144. describe("When a new contact request is received", function () {
  145. it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
  146. spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
  147. spyOn(_converse, 'showContactRequestNotification');
  148. _converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
  149. expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
  150. expect(_converse.showContactRequestNotification).toHaveBeenCalled();
  151. done();
  152. }));
  153. });
  154. });
  155. describe("When play_sounds is set to true", function () {
  156. describe("A notification sound", function () {
  157. it("is played when the current user is mentioned in a groupchat",
  158. mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
  159. mock.createContacts(_converse, 'current');
  160. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
  161. _converse.play_sounds = true;
  162. spyOn(_converse, 'playSoundNotification');
  163. const view = _converse.chatboxviews.get('lounge@montague.lit');
  164. if (!view.el.querySelectorAll('.chat-area').length) {
  165. view.renderChatArea();
  166. }
  167. let text = 'This message will play a sound because it mentions romeo';
  168. let message = $msg({
  169. from: 'lounge@montague.lit/otheruser',
  170. id: '1',
  171. to: 'romeo@montague.lit',
  172. type: 'groupchat'
  173. }).c('body').t(text);
  174. _converse.api.settings.set('notify_all_room_messages', true);
  175. await view.model.handleMessageStanza(message.nodeTree);
  176. await u.waitUntil(() => _converse.playSoundNotification.calls.count());
  177. expect(_converse.playSoundNotification).toHaveBeenCalled();
  178. text = "This message won't play a sound";
  179. message = $msg({
  180. from: 'lounge@montague.lit/otheruser',
  181. id: '2',
  182. to: 'romeo@montague.lit',
  183. type: 'groupchat'
  184. }).c('body').t(text);
  185. await view.model.handleMessageStanza(message.nodeTree);
  186. expect(_converse.playSoundNotification, 1);
  187. _converse.play_sounds = false;
  188. text = "This message won't play a sound because it is sent by romeo";
  189. message = $msg({
  190. from: 'lounge@montague.lit/romeo',
  191. id: '3',
  192. to: 'romeo@montague.lit',
  193. type: 'groupchat'
  194. }).c('body').t(text);
  195. await view.model.handleMessageStanza(message.nodeTree);
  196. expect(_converse.playSoundNotification, 1);
  197. _converse.play_sounds = false;
  198. done();
  199. }));
  200. });
  201. });
  202. describe("A Favicon Message Counter", function () {
  203. it("is incremented when the message is received and the window is not focused",
  204. mock.initConverse(
  205. ['rosterGroupsFetched'], {'show_tab_notifications': false},
  206. async function (done, _converse) {
  207. await mock.waitForRoster(_converse, 'current');
  208. await mock.openControlBox(_converse);
  209. const favico = jasmine.createSpyObj('favico', ['badge']);
  210. spyOn(converse.env, 'Favico').and.returnValue(favico);
  211. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  212. const previous_state = _converse.windowState;
  213. const msg = $msg({
  214. from: sender_jid,
  215. to: _converse.connection.jid,
  216. type: 'chat',
  217. id: u.getUniqueId()
  218. }).c('body').t('This message will increment the message counter').up()
  219. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  220. _converse.windowState = 'hidden';
  221. spyOn(_converse.api, "trigger").and.callThrough();
  222. await _converse.handleMessageStanza(msg);
  223. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  224. expect(favico.badge.calls.count()).toBe(0);
  225. _converse.api.settings.set('show_tab_notifications', true);
  226. const msg2 = $msg({
  227. from: sender_jid,
  228. to: _converse.connection.jid,
  229. type: 'chat',
  230. id: u.getUniqueId()
  231. }).c('body').t('This message increment the message counter AND update the page title').up()
  232. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  233. await _converse.handleMessageStanza(msg2);
  234. await u.waitUntil(() => favico.badge.calls.count() === 1);
  235. expect(favico.badge.calls.mostRecent().args.pop()).toBe(2);
  236. const view = _converse.chatboxviews.get(sender_jid);
  237. expect(view.model.get('num_unread')).toBe(2);
  238. // Check that it's cleared when the window is focused
  239. _converse.windowState = 'hidden';
  240. _converse.saveWindowState({'type': 'focus'});
  241. await u.waitUntil(() => favico.badge.calls.count() === 2);
  242. expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);
  243. expect(view.model.get('num_unread')).toBe(0);
  244. _converse.windowSate = previous_state;
  245. done();
  246. }));
  247. it("is not incremented when the message is received and the window is focused",
  248. mock.initConverse(
  249. ['rosterGroupsFetched'], {},
  250. async function (done, _converse) {
  251. await mock.waitForRoster(_converse, 'current');
  252. await mock.openControlBox(_converse);
  253. const favico = jasmine.createSpyObj('favico', ['badge']);
  254. spyOn(converse.env, 'Favico').and.returnValue(favico);
  255. _converse.saveWindowState({'type': 'focus'});
  256. const message = 'This message will not increment the message counter';
  257. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
  258. msg = $msg({
  259. from: sender_jid,
  260. to: _converse.connection.jid,
  261. type: 'chat',
  262. id: u.getUniqueId()
  263. }).c('body').t(message).up()
  264. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  265. await _converse.handleMessageStanza(msg);
  266. setTimeout(() => {
  267. const view = _converse.chatboxviews.get(sender_jid);
  268. expect(view.model.get('num_unread')).toBe(0);
  269. expect(favico.badge.calls.count()).toBe(0);
  270. done();
  271. }, 500);
  272. }));
  273. it("is incremented from zero when chatbox was closed after viewing previously received messages and the window is not focused now",
  274. mock.initConverse(
  275. ['rosterGroupsFetched'], {},
  276. async function (done, _converse) {
  277. await mock.waitForRoster(_converse, 'current');
  278. const favico = jasmine.createSpyObj('favico', ['badge']);
  279. spyOn(converse.env, 'Favico').and.returnValue(favico);
  280. const message = 'This message will always increment the message counter from zero';
  281. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  282. const msgFactory = function () {
  283. return $msg({
  284. from: sender_jid,
  285. to: _converse.connection.jid,
  286. type: 'chat',
  287. id: u.getUniqueId()
  288. })
  289. .c('body').t(message).up()
  290. .c('active', {'xmlns': Strophe.NS.CHATSTATES})
  291. .tree();
  292. };
  293. // leave converse-chat page
  294. _converse.windowState = 'hidden';
  295. await _converse.handleMessageStanza(msgFactory());
  296. let view = _converse.chatboxviews.get(sender_jid);
  297. await u.waitUntil(() => favico.badge.calls.count() === 1, 1000);
  298. expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);
  299. // come back to converse-chat page
  300. _converse.saveWindowState({'type': 'focus'});
  301. await u.waitUntil(() => u.isVisible(view.el));
  302. await u.waitUntil(() => favico.badge.calls.count() === 2);
  303. expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);
  304. // close chatbox and leave converse-chat page again
  305. view.close();
  306. _converse.windowState = 'hidden';
  307. // check that msg_counter is incremented from zero again
  308. await _converse.handleMessageStanza(msgFactory());
  309. view = _converse.chatboxviews.get(sender_jid);
  310. await u.waitUntil(() => u.isVisible(view.el));
  311. await u.waitUntil(() => favico.badge.calls.count() === 3);
  312. expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);
  313. done();
  314. }));
  315. });
  316. });