notification.js 17 KB

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