messages.js 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564
  1. (function (root, factory) {
  2. define([
  3. "jquery",
  4. "jasmine",
  5. "mock",
  6. "test-utils"
  7. ], factory);
  8. } (this, function ($, jasmine, mock, test_utils) {
  9. "use strict";
  10. const _ = converse.env._;
  11. const $iq = converse.env.$iq;
  12. const $msg = converse.env.$msg;
  13. const Strophe = converse.env.Strophe;
  14. const Promise = converse.env.Promise;
  15. const moment = converse.env.moment;
  16. const u = converse.env.utils;
  17. describe("A Chat Message", function () {
  18. describe("when received from someone else", function () {
  19. it("will open a chatbox and be displayed inside it",
  20. mock.initConverseWithPromises(
  21. null, ['rosterGroupsFetched'], {},
  22. function (done, _converse) {
  23. test_utils.createContacts(_converse, 'current');
  24. test_utils.openControlBox();
  25. test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300)
  26. .then(function () {
  27. spyOn(_converse, 'emit');
  28. const message = 'This is a received message';
  29. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  30. const msg = $msg({
  31. 'from': sender_jid,
  32. 'to': _converse.connection.jid,
  33. 'type': 'chat',
  34. 'id': (new Date()).getTime()
  35. }).c('body').t(message).up()
  36. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  37. // We don't already have an open chatbox for this user
  38. expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
  39. // onMessage is a handler for received XMPP messages
  40. _converse.chatboxes.onMessage(msg);
  41. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  42. // Check that the chatbox and its view now exist
  43. const chatbox = _converse.chatboxes.get(sender_jid);
  44. const chatboxview = _converse.chatboxviews.get(sender_jid);
  45. expect(chatbox).toBeDefined();
  46. expect(chatboxview).toBeDefined();
  47. // Check that the message was received and check the message parameters
  48. expect(chatbox.messages.length).toEqual(1);
  49. const msg_obj = chatbox.messages.models[0];
  50. expect(msg_obj.get('message')).toEqual(message);
  51. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
  52. expect(msg_obj.get('sender')).toEqual('them');
  53. expect(msg_obj.get('is_delayed')).toEqual(false);
  54. // Now check that the message appears inside the chatbox in the DOM
  55. const chat_content = chatboxview.el.querySelector('.chat-content');
  56. expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
  57. expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  58. return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
  59. .then(function () {
  60. expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Max Frankfurter');
  61. done();
  62. });
  63. });
  64. }));
  65. it("can be replaced with a correction",
  66. mock.initConverseWithPromises(
  67. null, ['rosterGroupsFetched'], {},
  68. function (done, _converse) {
  69. test_utils.createContacts(_converse, 'current', 1);
  70. test_utils.openControlBox();
  71. const message = 'This is a received message';
  72. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  73. test_utils.openChatBoxFor(_converse, sender_jid);
  74. const msg_id = u.getUniqueId();
  75. _converse.chatboxes.onMessage($msg({
  76. 'from': sender_jid,
  77. 'to': _converse.connection.jid,
  78. 'type': 'chat',
  79. 'id': msg_id,
  80. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  81. var chatboxview = _converse.chatboxviews.get(sender_jid);
  82. expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1);
  83. expect(chatboxview.el.querySelector('.chat-msg-text').textContent)
  84. .toBe('But soft, what light through yonder airlock breaks?');
  85. _converse.chatboxes.onMessage($msg({
  86. 'from': sender_jid,
  87. 'to': _converse.connection.jid,
  88. 'type': 'chat',
  89. 'id': u.getUniqueId(),
  90. }).c('body').t('But soft, what light through yonder window breaks?').up()
  91. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  92. expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1);
  93. test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg-text').textContent ===
  94. 'But soft, what light through yonder window breaks?').then(done);
  95. }));
  96. describe("when a chatbox is opened for someone who is not in the roster", function () {
  97. it("the VCard for that user is fetched and the chatbox updated with the results",
  98. mock.initConverseWithPromises(
  99. null, ['rosterGroupsFetched'], {},
  100. function (done, _converse) {
  101. _converse.allow_non_roster_messaging = true;
  102. spyOn(_converse, 'emit').and.callThrough();
  103. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  104. var vcard_fetched = false;
  105. spyOn(_converse.api.vcard, "get").and.callFake(function () {
  106. vcard_fetched = true;
  107. return Promise.resolve({
  108. 'fullname': mock.cur_names[0],
  109. 'vcard_updated': moment().format(),
  110. 'jid': sender_jid
  111. });
  112. });
  113. var message = 'This is a received message from someone not on the roster';
  114. var msg = $msg({
  115. from: sender_jid,
  116. to: _converse.connection.jid,
  117. type: 'chat',
  118. id: (new Date()).getTime()
  119. }).c('body').t(message).up()
  120. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  121. // We don't already have an open chatbox for this user
  122. expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
  123. _converse.chatboxes.onMessage(msg);
  124. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  125. // Check that the chatbox and its view now exist
  126. var chatbox = _converse.chatboxes.get(sender_jid);
  127. var chatboxview = _converse.chatboxviews.get(sender_jid);
  128. expect(chatbox).toBeDefined();
  129. expect(chatboxview).toBeDefined();
  130. var author_el = chatboxview.el.querySelector('.chat-msg-author');
  131. expect(chatbox.get('fullname') === sender_jid);
  132. expect( _.includes(author_el.textContent.trim(), 'max.frankfurter@localhost')).toBeTruthy();
  133. test_utils.waitUntil(function () { return vcard_fetched; }, 100)
  134. .then(function () {
  135. expect(_converse.api.vcard.get).toHaveBeenCalled();
  136. return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
  137. }).then(function () {
  138. var author_el = chatboxview.el.querySelector('.chat-msg-author');
  139. expect( _.includes(author_el.textContent.trim(), 'Max Frankfurter')).toBeTruthy();
  140. done();
  141. });
  142. }));
  143. });
  144. describe("who is not on the roster", function () {
  145. it("will open a chatbox and be displayed inside it if allow_non_roster_messaging is true",
  146. mock.initConverseWithPromises(
  147. null, ['rosterGroupsFetched'], {},
  148. function (done, _converse) {
  149. _converse.allow_non_roster_messaging = false;
  150. spyOn(_converse, 'emit');
  151. var message = 'This is a received message from someone not on the roster';
  152. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  153. var msg = $msg({
  154. from: sender_jid,
  155. to: _converse.connection.jid,
  156. type: 'chat',
  157. id: (new Date()).getTime()
  158. }).c('body').t(message).up()
  159. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  160. // We don't already have an open chatbox for this user
  161. expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
  162. var chatbox = _converse.chatboxes.get(sender_jid);
  163. expect(chatbox).not.toBeDefined();
  164. // onMessage is a handler for received XMPP messages
  165. _converse.chatboxes.onMessage(msg);
  166. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  167. // onMessage is a handler for received XMPP messages
  168. _converse.allow_non_roster_messaging =true;
  169. _converse.chatboxes.onMessage(msg);
  170. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  171. // Check that the chatbox and its view now exist
  172. chatbox = _converse.chatboxes.get(sender_jid);
  173. var chatboxview = _converse.chatboxviews.get(sender_jid);
  174. expect(chatbox).toBeDefined();
  175. expect(chatboxview).toBeDefined();
  176. // Check that the message was received and check the message parameters
  177. expect(chatbox.messages.length).toEqual(1);
  178. var msg_obj = chatbox.messages.models[0];
  179. expect(msg_obj.get('message')).toEqual(message);
  180. expect(msg_obj.get('fullname')).toEqual(undefined);
  181. expect(msg_obj.get('sender')).toEqual('them');
  182. expect(msg_obj.get('is_delayed')).toEqual(false);
  183. // Now check that the message appears inside the chatbox in the DOM
  184. var chat_content = chatboxview.el.querySelector('.chat-content');
  185. expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
  186. expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  187. expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('max.frankfurter@localhost');
  188. done();
  189. }));
  190. });
  191. describe("and for which then an error message is received from the server", function () {
  192. it("will have the error message displayed after itself",
  193. mock.initConverseWithPromises(
  194. null, ['rosterGroupsFetched'], {},
  195. function (done, _converse) {
  196. test_utils.createContacts(_converse, 'current');
  197. test_utils.openControlBox();
  198. // TODO: what could still be done for error
  199. // messages... if the <error> element has type
  200. // "cancel", then we know the messages wasn't sent,
  201. // and can give the user a nicer indication of
  202. // that.
  203. /* <message from="scotty@enterprise.com/_converse.js-84843526"
  204. * to="kirk@enterprise.com.com"
  205. * type="chat"
  206. * id="82bc02ce-9651-4336-baf0-fa04762ed8d2"
  207. * xmlns="jabber:client">
  208. * <body>yo</body>
  209. * <active xmlns="http://jabber.org/protocol/chatstates"/>
  210. * </message>
  211. */
  212. var sender_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  213. var fullname = _converse.xmppstatus.get('fullname');
  214. fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname;
  215. _converse.api.chats.open(sender_jid);
  216. var msg_text = 'This message will not be sent, due to an error';
  217. var view = _converse.chatboxviews.get(sender_jid);
  218. var message = view.model.messages.create({
  219. 'msgid': '82bc02ce-9651-4336-baf0-fa04762ed8d2',
  220. 'fullname': fullname,
  221. 'sender': 'me',
  222. 'time': moment().format(),
  223. 'message': msg_text
  224. });
  225. view.model.sendMessage(message);
  226. var $chat_content = $(view.el).find('.chat-content');
  227. var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
  228. expect(msg_txt).toEqual(msg_text);
  229. // We send another message, for which an error will
  230. // not be received, to test that errors appear
  231. // after the relevant message.
  232. msg_text = 'This message will be sent, and also receive an error';
  233. message = view.model.messages.create({
  234. 'msgid': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104',
  235. 'fullname': fullname,
  236. 'sender': 'me',
  237. 'time': moment().format(),
  238. 'message': msg_text
  239. });
  240. view.model.sendMessage(message);
  241. msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
  242. expect(msg_txt).toEqual(msg_text);
  243. /* <message xmlns="jabber:client"
  244. * to="scotty@enterprise.com/_converse.js-84843526"
  245. * type="error"
  246. * id="82bc02ce-9651-4336-baf0-fa04762ed8d2"
  247. * from="kirk@enterprise.com.com">
  248. * <error type="cancel">
  249. * <remote-server-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  250. * <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Server-to-server connection failed: Connecting failed: connection timeout</text>
  251. * </error>
  252. * </message>
  253. */
  254. var error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout';
  255. var stanza = $msg({
  256. 'to': _converse.connection.jid,
  257. 'type':'error',
  258. 'id':'82bc02ce-9651-4336-baf0-fa04762ed8d2',
  259. 'from': sender_jid
  260. })
  261. .c('error', {'type': 'cancel'})
  262. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  263. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  264. .t('Server-to-server connection failed: Connecting failed: connection timeout');
  265. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  266. expect($chat_content.find('.chat-error').text()).toEqual(error_txt);
  267. stanza = $msg({
  268. 'to': _converse.connection.jid,
  269. 'type':'error',
  270. 'id':'some-other-unused-id',
  271. 'from': sender_jid
  272. })
  273. .c('error', {'type': 'cancel'})
  274. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  275. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  276. .t('Server-to-server connection failed: Connecting failed: connection timeout');
  277. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  278. expect($chat_content.find('.chat-error').length).toEqual(2);
  279. // If the last message is already an error message,
  280. // then we don't render it another time.
  281. stanza = $msg({
  282. 'to': _converse.connection.jid,
  283. 'type':'error',
  284. 'id':'another-unused-id',
  285. 'from': sender_jid
  286. })
  287. .c('error', {'type': 'cancel'})
  288. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  289. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  290. .t('Server-to-server connection failed: Connecting failed: connection timeout');
  291. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  292. expect($chat_content.find('.chat-error').length).toEqual(2);
  293. // A different error message will however render
  294. stanza = $msg({
  295. 'to': _converse.connection.jid,
  296. 'type':'error',
  297. 'id':'another-id',
  298. 'from': sender_jid
  299. })
  300. .c('error', {'type': 'cancel'})
  301. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  302. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  303. .t('Something else went wrong as well');
  304. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  305. expect($chat_content.find('.chat-error').length).toEqual(3);
  306. done();
  307. }));
  308. });
  309. it("will cause the chat area to be scrolled down only if it was at the bottom originally",
  310. mock.initConverseWithPromises(
  311. null, ['rosterGroupsFetched'], {},
  312. function (done, _converse) {
  313. test_utils.createContacts(_converse, 'current');
  314. test_utils.openControlBox();
  315. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  316. test_utils.openChatBoxFor(_converse, sender_jid);
  317. var chatboxview = _converse.chatboxviews.get(sender_jid);
  318. spyOn(chatboxview, 'onScrolledDown').and.callThrough();
  319. // Create enough messages so that there's a scrollbar.
  320. var message = 'This message is received while the chat area is scrolled up';
  321. for (var i=0; i<20; i++) {
  322. _converse.chatboxes.onMessage($msg({
  323. from: sender_jid,
  324. to: _converse.connection.jid,
  325. type: 'chat',
  326. id: (new Date()).getTime()
  327. }).c('body').t('Message: '+i).up()
  328. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  329. }
  330. return test_utils.waitUntil(function () {
  331. return chatboxview.content.scrollTop;
  332. }, 1000).then(function () {
  333. return test_utils.waitUntil(function () {
  334. return !chatboxview.model.get('auto_scrolled');
  335. }, 500);
  336. }).then(function () {
  337. chatboxview.content.scrollTop = 0;
  338. return test_utils.waitUntil(function () {
  339. return chatboxview.model.get('scrolled');
  340. }, 900);
  341. }).then(function () {
  342. _converse.chatboxes.onMessage($msg({
  343. from: sender_jid,
  344. to: _converse.connection.jid,
  345. type: 'chat',
  346. id: (new Date()).getTime()
  347. }).c('body').t(message).up()
  348. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  349. // Now check that the message appears inside the chatbox in the DOM
  350. var $chat_content = $(chatboxview.el).find('.chat-content');
  351. var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
  352. expect(msg_txt).toEqual(message);
  353. return test_utils.waitUntil(function () {
  354. return u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator'));
  355. }, 500);
  356. }).then(function () {
  357. expect(chatboxview.model.get('scrolled')).toBe(true);
  358. expect(chatboxview.content.scrollTop).toBe(0);
  359. expect(u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator'))).toBeTruthy();
  360. // Scroll down again
  361. chatboxview.content.scrollTop = chatboxview.content.scrollHeight;
  362. return test_utils.waitUntil(function () {
  363. return !u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator'));
  364. }, 700);
  365. }).then(done);
  366. }));
  367. it("is ignored if it's intended for a different resource and filter_by_resource is set to true",
  368. mock.initConverseWithPromises(
  369. null, ['rosterGroupsFetched'], {},
  370. function (done, _converse) {
  371. test_utils.createContacts(_converse, 'current');
  372. test_utils.openControlBox();
  373. test_utils.waitUntil(function () {
  374. return $(_converse.rosterview.el).find('.roster-group').length;
  375. }, 300)
  376. .then(function () {
  377. // Send a message from a different resource
  378. var message, sender_jid, msg;
  379. spyOn(_converse, 'log');
  380. spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
  381. _converse.filter_by_resource = true;
  382. sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  383. msg = $msg({
  384. from: sender_jid,
  385. to: _converse.bare_jid+"/some-other-resource",
  386. type: 'chat',
  387. id: (new Date()).getTime()
  388. }).c('body').t("This message will not be shown").up()
  389. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  390. _converse.chatboxes.onMessage(msg);
  391. expect(_converse.log).toHaveBeenCalledWith(
  392. "onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource",
  393. Strophe.LogLevel.INFO);
  394. expect(_converse.chatboxes.getChatBox).not.toHaveBeenCalled();
  395. _converse.filter_by_resource = false;
  396. message = "This message sent to a different resource will be shown";
  397. msg = $msg({
  398. from: sender_jid,
  399. to: _converse.bare_jid+"/some-other-resource",
  400. type: 'chat',
  401. id: '134234623462346'
  402. }).c('body').t(message).up()
  403. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  404. _converse.chatboxes.onMessage(msg);
  405. expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
  406. var chatboxview = _converse.chatboxviews.get(sender_jid);
  407. var $chat_content = $(chatboxview.el).find('.chat-content:last');
  408. var msg_txt = $chat_content.find('.chat-msg').find('.chat-msg-text').text();
  409. expect(msg_txt).toEqual(message);
  410. done();
  411. });
  412. }));
  413. });
  414. it("can be received out of order, and will still be displayed in the right order",
  415. mock.initConverseWithPromises(
  416. null, ['rosterGroupsFetched'], {},
  417. function (done, _converse) {
  418. test_utils.createContacts(_converse, 'current');
  419. test_utils.openControlBox();
  420. test_utils.waitUntil(function () {
  421. return $(_converse.rosterview.el).find('.roster-group').length;
  422. }, 300)
  423. .then(function () {
  424. var message, msg;
  425. spyOn(_converse, 'log');
  426. spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
  427. _converse.filter_by_resource = true;
  428. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  429. /* <message id='aeb213' to='juliet@capulet.lit/chamber'>
  430. * <forwarded xmlns='urn:xmpp:forward:0'>
  431. * <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
  432. * <message xmlns='jabber:client'
  433. * to='juliet@capulet.lit/balcony'
  434. * from='romeo@montague.lit/orchard'
  435. * type='chat'>
  436. * <body>Call me but love, and I'll be new baptized; Henceforth I never will be Romeo.</body>
  437. * </message>
  438. * </forwarded>
  439. * </message>
  440. */
  441. msg = $msg({'id': 'aeb213', 'to': _converse.bare_jid})
  442. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  443. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T13:08:25Z'}).up()
  444. .c('message', {
  445. 'xmlns': 'jabber:client',
  446. 'to': _converse.bare_jid,
  447. 'from': sender_jid,
  448. 'type': 'chat'})
  449. .c('body').t("message")
  450. .tree();
  451. _converse.chatboxes.onMessage(msg);
  452. msg = $msg({'id': 'aeb214', 'to': _converse.bare_jid})
  453. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  454. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'}).up()
  455. .c('message', {
  456. 'xmlns': 'jabber:client',
  457. 'to': _converse.bare_jid,
  458. 'from': sender_jid,
  459. 'type': 'chat'})
  460. .c('body').t("Older message")
  461. .tree();
  462. _converse.chatboxes.onMessage(msg);
  463. msg = $msg({'id': 'aeb215', 'to': _converse.bare_jid})
  464. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  465. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}).up()
  466. .c('message', {
  467. 'xmlns': 'jabber:client',
  468. 'to': _converse.bare_jid,
  469. 'from': sender_jid,
  470. 'type': 'chat'})
  471. .c('body').t("Inbetween message").up()
  472. .tree();
  473. _converse.chatboxes.onMessage(msg);
  474. msg = $msg({'id': 'aeb216', 'to': _converse.bare_jid})
  475. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  476. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}).up()
  477. .c('message', {
  478. 'xmlns': 'jabber:client',
  479. 'to': _converse.bare_jid,
  480. 'from': sender_jid,
  481. 'type': 'chat'})
  482. .c('body').t("another inbetween message")
  483. .tree();
  484. _converse.chatboxes.onMessage(msg);
  485. msg = $msg({'id': 'aeb217', 'to': _converse.bare_jid})
  486. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  487. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'}).up()
  488. .c('message', {
  489. 'xmlns': 'jabber:client',
  490. 'to': _converse.bare_jid,
  491. 'from': sender_jid,
  492. 'type': 'chat'})
  493. .c('body').t("An earlier message on the next day")
  494. .tree();
  495. _converse.chatboxes.onMessage(msg);
  496. msg = $msg({'id': 'aeb218', 'to': _converse.bare_jid})
  497. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  498. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'}).up()
  499. .c('message', {
  500. 'xmlns': 'jabber:client',
  501. 'to': _converse.bare_jid,
  502. 'from': sender_jid,
  503. 'type': 'chat'})
  504. .c('body').t("newer message from the next day")
  505. .tree();
  506. _converse.chatboxes.onMessage(msg);
  507. // Insert <composing> message, to also check that
  508. // text messages are inserted correctly with
  509. // temporary chat events in the chat contents.
  510. msg = $msg({
  511. 'id': 'aeb219',
  512. 'to': _converse.bare_jid,
  513. 'xmlns': 'jabber:client',
  514. 'from': sender_jid,
  515. 'type': 'chat'})
  516. .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
  517. .tree();
  518. _converse.chatboxes.onMessage(msg);
  519. msg = $msg({
  520. 'id': 'aeb220',
  521. 'to': _converse.bare_jid,
  522. 'xmlns': 'jabber:client',
  523. 'from': sender_jid,
  524. 'type': 'chat'})
  525. .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
  526. .c('body').t("latest message")
  527. .tree();
  528. _converse.chatboxes.onMessage(msg);
  529. var chatboxview = _converse.chatboxviews.get(sender_jid);
  530. var $chat_content = $(chatboxview.el).find('.chat-content');
  531. chatboxview.clearSpinner(); //cleanup
  532. expect($chat_content[0].querySelectorAll('.date-separator').length).toEqual(4);
  533. var $day = $chat_content.find('.date-separator:first');
  534. expect($day.data('isodate')).toEqual(moment('2017-12-31T00:00:00').format());
  535. var $time = $chat_content.find('time:first');
  536. expect($time.text()).toEqual('Sunday Dec 31st 2017')
  537. $day = $chat_content.find('.date-separator:first');
  538. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Older message');
  539. var $el = $chat_content.find('.chat-msg:first').find('.chat-msg-text')
  540. expect($el.hasClass('chat-msg-followup')).toBe(false);
  541. expect($el.text()).toEqual('Older message');
  542. $time = $chat_content.find('time.separator-text:eq(1)');
  543. expect($time.text()).toEqual("Monday Jan 1st 2018");
  544. $day = $chat_content.find('.date-separator:eq(1)');
  545. expect($day.data('isodate')).toEqual(moment('2018-01-01T00:00:00').format());
  546. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Inbetween message');
  547. $el = $chat_content.find('.chat-msg:eq(1)');
  548. expect($el.find('.chat-msg-text').text()).toEqual('Inbetween message');
  549. expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('another inbetween message');
  550. $el = $chat_content.find('.chat-msg:eq(2)');
  551. expect($el.find('.chat-msg-text').text()).toEqual('another inbetween message');
  552. expect($el.hasClass('chat-msg-followup')).toBe(true);
  553. $time = $chat_content.find('time.separator-text:nth(2)');
  554. expect($time.text()).toEqual("Tuesday Jan 2nd 2018");
  555. $day = $chat_content.find('.date-separator:nth(2)');
  556. expect($day.data('isodate')).toEqual(moment('2018-01-02T00:00:00').format());
  557. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('An earlier message on the next day');
  558. $el = $chat_content.find('.chat-msg:eq(3)');
  559. expect($el.find('.chat-msg-text').text()).toEqual('An earlier message on the next day');
  560. expect($el.hasClass('chat-msg-followup')).toBe(false);
  561. $el = $chat_content.find('.chat-msg:eq(4)');
  562. expect($el.find('.chat-msg-text').text()).toEqual('message');
  563. expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('newer message from the next day');
  564. expect($el.hasClass('chat-msg-followup')).toBe(false);
  565. $day = $chat_content.find('.date-separator:last');
  566. expect($day.data('isodate')).toEqual(moment().startOf('day').format());
  567. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('latest message');
  568. expect($el.hasClass('chat-msg-followup')).toBe(false);
  569. done();
  570. });
  571. }));
  572. it("is ignored if it's a malformed headline message",
  573. mock.initConverseWithPromises(
  574. null, ['rosterGroupsFetched'], {},
  575. function (done, _converse) {
  576. test_utils.createContacts(_converse, 'current');
  577. test_utils.openControlBox();
  578. /* Ideally we wouldn't have to filter out headline
  579. * messages, but Prosody gives them the wrong 'type' :(
  580. */
  581. sinon.spy(_converse, 'log');
  582. sinon.spy(_converse.chatboxes, 'getChatBox');
  583. sinon.spy(u, 'isHeadlineMessage');
  584. var msg = $msg({
  585. from: 'localhost',
  586. to: _converse.bare_jid,
  587. type: 'chat',
  588. id: (new Date()).getTime()
  589. }).c('body').t("This headline message will not be shown").tree();
  590. _converse.chatboxes.onMessage(msg);
  591. expect(_converse.log.calledWith(
  592. "onMessage: Ignoring incoming headline message sent with type 'chat' from JID: localhost",
  593. Strophe.LogLevel.INFO
  594. )).toBeTruthy();
  595. expect(u.isHeadlineMessage.called).toBeTruthy();
  596. expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
  597. expect(_converse.chatboxes.getChatBox.called).toBeFalsy();
  598. // Remove sinon spies
  599. _converse.log.restore();
  600. _converse.chatboxes.getChatBox.restore();
  601. u.isHeadlineMessage.restore();
  602. done();
  603. }));
  604. it("can be a carbon message, as defined in XEP-0280",
  605. mock.initConverseWithPromises(
  606. null, ['rosterGroupsFetched'], {},
  607. function (done, _converse) {
  608. test_utils.createContacts(_converse, 'current');
  609. test_utils.openControlBox();
  610. // Send a message from a different resource
  611. spyOn(_converse, 'log');
  612. var msgtext = 'This is a carbon message';
  613. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  614. var msg = $msg({
  615. 'from': sender_jid,
  616. 'id': (new Date()).getTime(),
  617. 'to': _converse.connection.jid,
  618. 'type': 'chat',
  619. 'xmlns': 'jabber:client'
  620. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  621. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  622. .c('message', {
  623. 'xmlns': 'jabber:client',
  624. 'from': sender_jid,
  625. 'to': _converse.bare_jid+'/another-resource',
  626. 'type': 'chat'
  627. }).c('body').t(msgtext).tree();
  628. _converse.chatboxes.onMessage(msg);
  629. // Check that the chatbox and its view now exist
  630. var chatbox = _converse.chatboxes.get(sender_jid);
  631. var chatboxview = _converse.chatboxviews.get(sender_jid);
  632. expect(chatbox).toBeDefined();
  633. expect(chatboxview).toBeDefined();
  634. // Check that the message was received and check the message parameters
  635. expect(chatbox.messages.length).toEqual(1);
  636. var msg_obj = chatbox.messages.models[0];
  637. expect(msg_obj.get('message')).toEqual(msgtext);
  638. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[1]);
  639. expect(msg_obj.get('sender')).toEqual('them');
  640. expect(msg_obj.get('is_delayed')).toEqual(false);
  641. // Now check that the message appears inside the chatbox in the DOM
  642. var chat_content = chatboxview.el.querySelector('.chat-content');
  643. expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(msgtext);
  644. expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  645. return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Candice van der Knijff')
  646. .then(function () {
  647. expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
  648. done();
  649. });
  650. }));
  651. it("can be a carbon message that this user sent from a different client, as defined in XEP-0280",
  652. mock.initConverseWithPromises(
  653. null, ['rosterGroupsFetched'], {},
  654. function (done, _converse) {
  655. var contact, sent_stanza, IQ_id, stanza;
  656. test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']).then(function () {
  657. test_utils.createContacts(_converse, 'current');
  658. test_utils.openControlBox();
  659. // Send a message from a different resource
  660. spyOn(_converse, 'log');
  661. var msgtext = 'This is a sent carbon message';
  662. var recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  663. var msg = $msg({
  664. 'from': _converse.bare_jid,
  665. 'id': (new Date()).getTime(),
  666. 'to': _converse.connection.jid,
  667. 'type': 'chat',
  668. 'xmlns': 'jabber:client'
  669. }).c('sent', {'xmlns': 'urn:xmpp:carbons:2'})
  670. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  671. .c('message', {
  672. 'xmlns': 'jabber:client',
  673. 'from': _converse.bare_jid+'/another-resource',
  674. 'to': recipient_jid,
  675. 'type': 'chat'
  676. }).c('body').t(msgtext).tree();
  677. _converse.chatboxes.onMessage(msg);
  678. // Check that the chatbox and its view now exist
  679. var chatbox = _converse.chatboxes.get(recipient_jid);
  680. var chatboxview = _converse.chatboxviews.get(recipient_jid);
  681. expect(chatbox).toBeDefined();
  682. expect(chatboxview).toBeDefined();
  683. // Check that the message was received and check the message parameters
  684. expect(chatbox.messages.length).toEqual(1);
  685. var msg_obj = chatbox.messages.models[0];
  686. expect(msg_obj.get('message')).toEqual(msgtext);
  687. expect(msg_obj.get('fullname')).toEqual(_converse.xmppstatus.get('fullname'));
  688. expect(msg_obj.get('sender')).toEqual('me');
  689. expect(msg_obj.get('is_delayed')).toEqual(false);
  690. // Now check that the message appears inside the chatbox in the DOM
  691. var $chat_content = $(chatboxview.el).find('.chat-content');
  692. var msg_txt = $chat_content.find('.chat-msg').find('.chat-msg-text').text();
  693. expect(msg_txt).toEqual(msgtext);
  694. done();
  695. });
  696. }));
  697. it("will be discarded if it's a malicious message meant to look like a carbon copy",
  698. mock.initConverseWithPromises(
  699. null, ['rosterGroupsFetched'], {},
  700. function (done, _converse) {
  701. test_utils.createContacts(_converse, 'current');
  702. test_utils.openControlBox();
  703. /* <message from="mallory@evil.example" to="b@xmpp.example">
  704. * <received xmlns='urn:xmpp:carbons:2'>
  705. * <forwarded xmlns='urn:xmpp:forward:0'>
  706. * <message from="alice@xmpp.example" to="bob@xmpp.example/client1">
  707. * <body>Please come to Creepy Valley tonight, alone!</body>
  708. * </message>
  709. * </forwarded>
  710. * </received>
  711. * </message>
  712. */
  713. spyOn(_converse, 'log');
  714. var msgtext = 'Please come to Creepy Valley tonight, alone!';
  715. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  716. var impersonated_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  717. var msg = $msg({
  718. 'from': sender_jid,
  719. 'id': (new Date()).getTime(),
  720. 'to': _converse.connection.jid,
  721. 'type': 'chat',
  722. 'xmlns': 'jabber:client'
  723. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  724. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  725. .c('message', {
  726. 'xmlns': 'jabber:client',
  727. 'from': impersonated_jid,
  728. 'to': _converse.connection.jid,
  729. 'type': 'chat'
  730. }).c('body').t(msgtext).tree();
  731. _converse.chatboxes.onMessage(msg);
  732. // Check that chatbox for impersonated user is not created.
  733. var chatbox = _converse.chatboxes.get(impersonated_jid);
  734. expect(chatbox).not.toBeDefined();
  735. // Check that the chatbox for the malicous user is not created
  736. chatbox = _converse.chatboxes.get(sender_jid);
  737. expect(chatbox).not.toBeDefined();
  738. done();
  739. }));
  740. it("received for a minimized chat box will increment a counter on its header",
  741. mock.initConverseWithPromises(
  742. null, ['rosterGroupsFetched'], {},
  743. function (done, _converse) {
  744. test_utils.createContacts(_converse, 'current');
  745. test_utils.openControlBox();
  746. test_utils.waitUntil(function () {
  747. return $(_converse.rosterview.el).find('.roster-group').length;
  748. }, 300)
  749. .then(function () {
  750. var contact_name = mock.cur_names[0];
  751. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  752. spyOn(_converse, 'emit').and.callThrough();
  753. test_utils.openChatBoxFor(_converse, contact_jid);
  754. var chatview = _converse.chatboxviews.get(contact_jid);
  755. expect(u.isVisible(chatview.el)).toBeTruthy();
  756. expect(chatview.model.get('minimized')).toBeFalsy();
  757. chatview.el.querySelector('.toggle-chatbox-button').click();
  758. expect(chatview.model.get('minimized')).toBeTruthy();
  759. var message = 'This message is sent to a minimized chatbox';
  760. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  761. var msg = $msg({
  762. from: sender_jid,
  763. to: _converse.connection.jid,
  764. type: 'chat',
  765. id: (new Date()).getTime()
  766. }).c('body').t(message).up()
  767. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  768. _converse.chatboxes.onMessage(msg);
  769. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  770. var trimmed_chatboxes = _converse.minimized_chats;
  771. var trimmedview = trimmed_chatboxes.get(contact_jid);
  772. var $count = $(trimmedview.el).find('.message-count');
  773. expect(u.isVisible(chatview.el)).toBeFalsy();
  774. expect(trimmedview.model.get('minimized')).toBeTruthy();
  775. expect(u.isVisible($count[0])).toBeTruthy();
  776. expect($count.html()).toBe('1');
  777. _converse.chatboxes.onMessage(
  778. $msg({
  779. from: mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  780. to: _converse.connection.jid,
  781. type: 'chat',
  782. id: (new Date()).getTime()
  783. }).c('body').t('This message is also sent to a minimized chatbox').up()
  784. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
  785. );
  786. expect(u.isVisible(chatview.el)).toBeFalsy();
  787. expect(trimmedview.model.get('minimized')).toBeTruthy();
  788. $count = $(trimmedview.el).find('.message-count');
  789. expect(u.isVisible($count[0])).toBeTruthy();
  790. expect($count.html()).toBe('2');
  791. trimmedview.el.querySelector('.restore-chat').click();
  792. expect(trimmed_chatboxes.keys().length).toBe(0);
  793. done();
  794. });
  795. }));
  796. it("will indicate when it has a time difference of more than a day between it and its predecessor",
  797. mock.initConverseWithPromises(
  798. null, ['rosterGroupsFetched'], {},
  799. function (done, _converse) {
  800. test_utils.createContacts(_converse, 'current');
  801. test_utils.openControlBox();
  802. test_utils.waitUntil(function () {
  803. return $(_converse.rosterview.el).find('.roster-group').length;
  804. }, 300)
  805. .then(function () {
  806. spyOn(_converse, 'emit');
  807. var contact_name = mock.cur_names[1];
  808. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  809. test_utils.openChatBoxFor(_converse, contact_jid);
  810. test_utils.clearChatBoxMessages(_converse, contact_jid);
  811. var one_day_ago = moment();
  812. one_day_ago.subtract('days', 1);
  813. var message = 'This is a day old message';
  814. var chatbox = _converse.chatboxes.get(contact_jid);
  815. var chatboxview = _converse.chatboxviews.get(contact_jid);
  816. var $chat_content = $(chatboxview.el).find('.chat-content');
  817. var msg_obj;
  818. var msg_txt;
  819. var sender_txt;
  820. var msg = $msg({
  821. from: contact_jid,
  822. to: _converse.connection.jid,
  823. type: 'chat',
  824. id: one_day_ago.unix()
  825. }).c('body').t(message).up()
  826. .c('delay', { xmlns:'urn:xmpp:delay', from: 'localhost', stamp: one_day_ago.format() })
  827. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  828. _converse.chatboxes.onMessage(msg);
  829. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  830. expect(chatbox.messages.length).toEqual(1);
  831. msg_obj = chatbox.messages.models[0];
  832. expect(msg_obj.get('message')).toEqual(message);
  833. expect(msg_obj.get('fullname')).toEqual(contact_name);
  834. expect(msg_obj.get('sender')).toEqual('them');
  835. expect(msg_obj.get('is_delayed')).toEqual(true);
  836. return test_utils.waitUntil(() => chatbox.vcard.get('fullname') === 'Candice van der Knijff')
  837. .then(function () {
  838. var chat_content = chatboxview.el.querySelector('.chat-content');
  839. expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(message);
  840. expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  841. expect(chat_content.querySelector('span.chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
  842. var $day = $chat_content.find('.date-separator');
  843. expect($day.length).toEqual(1);
  844. expect($day.attr('class')).toEqual('message date-separator');
  845. expect($day.data('isodate')).toEqual(moment(one_day_ago.startOf('day')).format());
  846. var $time = $chat_content.find('time.separator-text');
  847. expect($time.text()).toEqual(moment(one_day_ago.startOf('day')).format("dddd MMM Do YYYY"));
  848. message = 'This is a current message';
  849. msg = $msg({
  850. from: contact_jid,
  851. to: _converse.connection.jid,
  852. type: 'chat',
  853. id: new Date().getTime()
  854. }).c('body').t(message).up()
  855. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  856. _converse.chatboxes.onMessage(msg);
  857. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  858. // Check that there is a <time> element, with the required
  859. // props.
  860. expect($chat_content[0].querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements
  861. var message_date = new Date();
  862. $day = $chat_content.find('.date-separator:last');
  863. expect($day.length).toEqual(1);
  864. expect($day.attr('class')).toEqual('message date-separator');
  865. expect($day.data('isodate')).toEqual(moment(message_date).startOf('day').format());
  866. $time = $chat_content.find('time.separator-text:last');
  867. expect($time.text()).toEqual(moment(message_date).startOf('day').format("dddd MMM Do YYYY"));
  868. // Normal checks for the 2nd message
  869. expect(chatbox.messages.length).toEqual(2);
  870. msg_obj = chatbox.messages.models[1];
  871. expect(msg_obj.get('message')).toEqual(message);
  872. expect(msg_obj.get('fullname')).toEqual(contact_name);
  873. expect(msg_obj.get('sender')).toEqual('them');
  874. expect(msg_obj.get('is_delayed')).toEqual(false);
  875. msg_txt = $chat_content.find('.chat-msg').last().find('.chat-msg-text').text();
  876. expect(msg_txt).toEqual(message);
  877. expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-text').textContent).toEqual(message);
  878. expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  879. expect(chat_content.querySelector('.chat-msg:last-child .chat-msg-author').textContent.trim()).toBe('Candice van der Knijff');
  880. done();
  881. });
  882. });
  883. }));
  884. it("can be sent from a chatbox, and will appear inside it",
  885. mock.initConverseWithPromises(
  886. null, ['rosterGroupsFetched'], {},
  887. function (done, _converse) {
  888. test_utils.createContacts(_converse, 'current');
  889. test_utils.openControlBox();
  890. spyOn(_converse, 'emit');
  891. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  892. test_utils.openChatBoxFor(_converse, contact_jid);
  893. expect(_converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
  894. var view = _converse.chatboxviews.get(contact_jid);
  895. var message = 'This message is sent from this chatbox';
  896. spyOn(view.model, 'sendMessage').and.callThrough();
  897. test_utils.sendMessage(view, message);
  898. expect(view.model.sendMessage).toHaveBeenCalled();
  899. expect(view.model.messages.length, 2);
  900. expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]);
  901. expect($(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text').text()).toEqual(message);
  902. done();
  903. }));
  904. it("is sanitized to prevent Javascript injection attacks",
  905. mock.initConverseWithPromises(
  906. null, ['rosterGroupsFetched'], {},
  907. function (done, _converse) {
  908. test_utils.createContacts(_converse, 'current');
  909. test_utils.openControlBox();
  910. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  911. test_utils.openChatBoxFor(_converse, contact_jid);
  912. var view = _converse.chatboxviews.get(contact_jid);
  913. var message = '<p>This message contains <em>some</em> <b>markup</b></p>';
  914. spyOn(view.model, 'sendMessage').and.callThrough();
  915. test_utils.sendMessage(view, message);
  916. expect(view.model.sendMessage).toHaveBeenCalled();
  917. var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  918. expect(msg.text()).toEqual(message);
  919. expect(msg.html()).toEqual('&lt;p&gt;This message contains &lt;em&gt;some&lt;/em&gt; &lt;b&gt;markup&lt;/b&gt;&lt;/p&gt;');
  920. done();
  921. }));
  922. it("can contain hyperlinks, which will be clickable",
  923. mock.initConverseWithPromises(
  924. null, ['rosterGroupsFetched'], {},
  925. function (done, _converse) {
  926. test_utils.createContacts(_converse, 'current');
  927. test_utils.openControlBox();
  928. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  929. test_utils.openChatBoxFor(_converse, contact_jid);
  930. var view = _converse.chatboxviews.get(contact_jid);
  931. var message = 'This message contains a hyperlink: www.opkode.com';
  932. spyOn(view.model, 'sendMessage').and.callThrough();
  933. test_utils.sendMessage(view, message);
  934. expect(view.model.sendMessage).toHaveBeenCalled();
  935. var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  936. expect(msg.text()).toEqual(message);
  937. expect(msg.html()).toEqual('This message contains a hyperlink: <a target="_blank" rel="noopener" href="http://www.opkode.com">www.opkode.com</a>');
  938. done();
  939. }));
  940. it("will have properly escaped URLs",
  941. mock.initConverseWithPromises(
  942. null, ['rosterGroupsFetched'], {},
  943. function (done, _converse) {
  944. test_utils.createContacts(_converse, 'current');
  945. test_utils.openControlBox();
  946. var message, msg;
  947. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  948. test_utils.openChatBoxFor(_converse, contact_jid);
  949. var view = _converse.chatboxviews.get(contact_jid);
  950. spyOn(view.model, 'sendMessage').and.callThrough();
  951. message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
  952. test_utils.sendMessage(view, message);
  953. expect(view.model.sendMessage).toHaveBeenCalled();
  954. msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  955. expect(msg.text()).toEqual(message);
  956. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
  957. message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
  958. test_utils.sendMessage(view, message);
  959. expect(view.model.sendMessage).toHaveBeenCalled();
  960. msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  961. expect(msg.text()).toEqual(message);
  962. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
  963. message = "https://en.wikipedia.org/wiki/Ender's_Game";
  964. test_utils.sendMessage(view, message);
  965. expect(view.model.sendMessage).toHaveBeenCalled();
  966. msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  967. expect(msg.text()).toEqual(message);
  968. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
  969. message = "https://en.wikipedia.org/wiki/Ender's_Game";
  970. test_utils.sendMessage(view, message);
  971. expect(view.model.sendMessage).toHaveBeenCalled();
  972. msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  973. expect(msg.text()).toEqual(message);
  974. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
  975. done();
  976. }));
  977. it("will render newlines", mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {}, function (done, _converse) {
  978. test_utils.createContacts(_converse, 'current');
  979. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  980. test_utils.openChatBoxFor(_converse, contact_jid);
  981. let stanza = Strophe.xmlHtmlNode(
  982. "<message from='"+contact_jid+"'"+
  983. " type='chat'"+
  984. " to='dummy@localhost/resource'>"+
  985. " <body>Hey\nHave you heard the news?</body>"+
  986. "</message>").firstChild;
  987. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  988. const view = _converse.chatboxviews.get(contact_jid);
  989. const chat_content = view.el.querySelector('.chat-content');
  990. expect(chat_content.querySelector('.chat-msg-text').innerHTML).toBe('Hey<br>Have you heard the news?');
  991. stanza = Strophe.xmlHtmlNode(
  992. "<message from='"+contact_jid+"'"+
  993. " type='chat'"+
  994. " to='dummy@localhost/resource'>"+
  995. " <body>Hey\n\n\nHave you heard the news?</body>"+
  996. "</message>").firstChild;
  997. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  998. expect(chat_content.querySelector('.message:last-child .chat-msg-text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
  999. stanza = Strophe.xmlHtmlNode(
  1000. "<message from='"+contact_jid+"'"+
  1001. " type='chat'"+
  1002. " to='dummy@localhost/resource'>"+
  1003. " <body>Hey\nHave you heard\nthe news?</body>"+
  1004. "</message>").firstChild;
  1005. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1006. expect(chat_content.querySelector('.message:last-child .chat-msg-text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
  1007. done();
  1008. }));
  1009. it("will render images from their URLs",
  1010. mock.initConverseWithPromises(
  1011. null, ['rosterGroupsFetched'], {},
  1012. function (done, _converse) {
  1013. test_utils.createContacts(_converse, 'current');
  1014. var base_url = document.URL.split(window.location.pathname)[0];
  1015. var message = base_url+"/logo/conversejs-filled.svg";
  1016. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1017. test_utils.openChatBoxFor(_converse, contact_jid);
  1018. var view = _converse.chatboxviews.get(contact_jid);
  1019. spyOn(view.model, 'sendMessage').and.callThrough();
  1020. test_utils.sendMessage(view, message);
  1021. test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length)
  1022. .then(() => {
  1023. expect(view.model.sendMessage).toHaveBeenCalled();
  1024. var msg = $(view.el).find('.chat-content .chat-msg').last().find('.chat-msg-text');
  1025. expect(msg.html().trim()).toEqual(
  1026. '<!-- src/templates/image.html -->\n'+
  1027. '<a href="'+base_url+'/logo/conversejs-filled.svg" target="_blank" rel="noopener"><img class="chat-image img-thumbnail"'+
  1028. ' src="' + message + '"></a>');
  1029. message += "?param1=val1&param2=val2";
  1030. test_utils.sendMessage(view, message);
  1031. return test_utils.waitUntil(function () {
  1032. return view.el.querySelectorAll('.chat-content .chat-image').length === 2;
  1033. }, 1000);
  1034. }).then(() => {
  1035. expect(view.model.sendMessage).toHaveBeenCalled();
  1036. var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  1037. expect(msg.html().trim()).toEqual(
  1038. '<!-- src/templates/image.html -->\n'+
  1039. '<a href="'+base_url+'/logo/conversejs-filled.svg?param1=val1&amp;param2=val2" target="_blank" rel="noopener"><img'+
  1040. ' class="chat-image img-thumbnail" src="'+message.replace(/&/g, '&amp;')+'"></a>')
  1041. // Test now with two images in one message
  1042. message += ' hello world '+base_url+"/logo/conversejs-filled.svg";
  1043. test_utils.sendMessage(view, message);
  1044. return test_utils.waitUntil(function () {
  1045. return view.el.querySelectorAll('.chat-content .chat-image').length === 4;
  1046. }, 1000);
  1047. }).then(function () {
  1048. expect(view.model.sendMessage).toHaveBeenCalled();
  1049. var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg-text');
  1050. expect(msg[0].textContent.trim()).toEqual('hello world');
  1051. expect(msg[0].querySelectorAll('img').length).toEqual(2);
  1052. done();
  1053. });
  1054. }));
  1055. it("will render the message time as configured",
  1056. mock.initConverseWithPromises(
  1057. null, ['rosterGroupsFetched'], {},
  1058. function (done, _converse) {
  1059. test_utils.createContacts(_converse, 'current');
  1060. _converse.time_format = 'hh:mm';
  1061. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1062. test_utils.openChatBoxFor(_converse, contact_jid);
  1063. var view = _converse.chatboxviews.get(contact_jid);
  1064. var message = 'This message is sent from this chatbox';
  1065. test_utils.sendMessage(view, message);
  1066. var chatbox = _converse.chatboxes.get(contact_jid);
  1067. expect(chatbox.messages.models.length, 1);
  1068. var msg_object = chatbox.messages.models[0];
  1069. var msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg-author');
  1070. expect(msg_author.textContent.trim()).toBe('Max Mustermann');
  1071. var msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg-time');
  1072. var time = moment(msg_object.get('time')).format(_converse.time_format);
  1073. expect(msg_time.textContent).toBe(time);
  1074. done();
  1075. }));
  1076. it("will be correctly identified and rendered as a followup message",
  1077. mock.initConverseWithPromises(
  1078. null, ['rosterGroupsFetched'], {},
  1079. function (done, _converse) {
  1080. test_utils.createContacts(_converse, 'current');
  1081. test_utils.openControlBox();
  1082. test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group').length, 300)
  1083. .then(function () {
  1084. const base_time = new Date();
  1085. const ONE_MINUTE_LATER = 60000;
  1086. jasmine.clock().install();
  1087. jasmine.clock().mockDate(base_time);
  1088. var message, msg;
  1089. spyOn(_converse, 'log');
  1090. spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
  1091. _converse.filter_by_resource = true;
  1092. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1093. _converse.chatboxes.onMessage($msg({
  1094. 'from': sender_jid,
  1095. 'to': _converse.connection.jid,
  1096. 'type': 'chat',
  1097. 'id': (new Date()).getTime()
  1098. }).c('body').t('A message').up()
  1099. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  1100. jasmine.clock().tick(3*ONE_MINUTE_LATER);
  1101. _converse.chatboxes.onMessage($msg({
  1102. 'from': sender_jid,
  1103. 'to': _converse.connection.jid,
  1104. 'type': 'chat',
  1105. 'id': (new Date()).getTime()
  1106. }).c('body').t("Another message 3 minutes later").up()
  1107. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  1108. jasmine.clock().tick(11*ONE_MINUTE_LATER);
  1109. _converse.chatboxes.onMessage($msg({
  1110. 'from': sender_jid,
  1111. 'to': _converse.connection.jid,
  1112. 'type': 'chat',
  1113. 'id': (new Date()).getTime()
  1114. }).c('body').t("Another message 14 minutes since we started").up()
  1115. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  1116. jasmine.clock().tick(1000);
  1117. // Insert <composing> message, to also check that
  1118. // text messages are inserted correctly with
  1119. // temporary chat events in the chat contents.
  1120. _converse.chatboxes.onMessage($msg({
  1121. 'id': 'aeb219',
  1122. 'to': _converse.bare_jid,
  1123. 'xmlns': 'jabber:client',
  1124. 'from': sender_jid,
  1125. 'type': 'chat'})
  1126. .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
  1127. .tree());
  1128. jasmine.clock().tick(1*ONE_MINUTE_LATER);
  1129. _converse.chatboxes.onMessage($msg({
  1130. 'from': sender_jid,
  1131. 'to': _converse.connection.jid,
  1132. 'type': 'chat',
  1133. 'id': (new Date()).getTime()
  1134. }).c('body').t("Another message 1 minute and 1 second since the previous one").up()
  1135. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  1136. jasmine.clock().tick(1*ONE_MINUTE_LATER);
  1137. var view = _converse.chatboxviews.get(sender_jid);
  1138. test_utils.sendMessage(view, "Another message within 10 minutes, but from a different person");
  1139. var chat_content = view.el.querySelector('.chat-content');
  1140. expect(chat_content.querySelectorAll('.message').length).toBe(6);
  1141. expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5);
  1142. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
  1143. expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
  1144. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
  1145. expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
  1146. "Another message 3 minutes later");
  1147. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
  1148. expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
  1149. "Another message 14 minutes since we started");
  1150. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
  1151. expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
  1152. "Another message 1 minute and 1 second since the previous one");
  1153. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
  1154. expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
  1155. "Another message within 10 minutes, but from a different person");
  1156. // Let's add a delayed, inbetween message
  1157. _converse.chatboxes.onMessage($msg({'id': 'aeb218', 'to': _converse.bare_jid})
  1158. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  1159. .c('delay', {'xmlns': 'urn:xmpp:delay',
  1160. 'stamp': moment(base_time).add(5, 'minutes').format()
  1161. }).up()
  1162. .c('message', {
  1163. 'xmlns': 'jabber:client',
  1164. 'to': _converse.bare_jid,
  1165. 'from': sender_jid,
  1166. 'type': 'chat'})
  1167. .c('body').t("A delayed message, sent 5 minutes since we started")
  1168. .tree());
  1169. expect(chat_content.querySelectorAll('.message').length).toBe(7);
  1170. expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6);
  1171. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
  1172. expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
  1173. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
  1174. expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
  1175. "Another message 3 minutes later");
  1176. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true);
  1177. expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
  1178. "A delayed message, sent 5 minutes since we started");
  1179. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
  1180. expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
  1181. "Another message 14 minutes since we started");
  1182. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
  1183. expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
  1184. "Another message 1 minute and 1 second since the previous one");
  1185. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
  1186. _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
  1187. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  1188. .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':moment(base_time).add(4, 'minutes').format()}).up()
  1189. .c('message', {
  1190. 'xmlns': 'jabber:client',
  1191. 'to': sender_jid,
  1192. 'from': _converse.bare_jid+"/some-other-resource",
  1193. 'type': 'chat'})
  1194. .c('body').t("A carbon message 4 minutes later")
  1195. .tree());
  1196. expect(chat_content.querySelectorAll('.message').length).toBe(8);
  1197. expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7);
  1198. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
  1199. expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
  1200. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
  1201. expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
  1202. "Another message 3 minutes later");
  1203. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
  1204. expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
  1205. "A carbon message 4 minutes later");
  1206. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
  1207. expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
  1208. "A delayed message, sent 5 minutes since we started");
  1209. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
  1210. expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
  1211. "Another message 14 minutes since we started");
  1212. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
  1213. expect(chat_content.querySelector('.message:nth-child(7) .chat-msg-text').textContent).toBe(
  1214. "Another message 1 minute and 1 second since the previous one");
  1215. expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
  1216. expect(chat_content.querySelector('.message:nth-child(8) .chat-msg-text').textContent).toBe(
  1217. "Another message within 10 minutes, but from a different person");
  1218. jasmine.clock().uninstall();
  1219. done();
  1220. });
  1221. }));
  1222. describe("which contains a OOB URL", function () {
  1223. it("will render audio from oob mp3 URLs",
  1224. mock.initConverseWithPromises(
  1225. null, ['rosterGroupsFetched'], {},
  1226. function (done, _converse) {
  1227. test_utils.createContacts(_converse, 'current');
  1228. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1229. test_utils.openChatBoxFor(_converse, contact_jid);
  1230. var view = _converse.chatboxviews.get(contact_jid);
  1231. spyOn(view.model, 'sendMessage').and.callThrough();
  1232. var stanza = Strophe.xmlHtmlNode(
  1233. "<message from='"+contact_jid+"'"+
  1234. " type='chat'"+
  1235. " to='dummy@localhost/resource'>"+
  1236. " <body>Have you heard this funny audio?</body>"+
  1237. " <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
  1238. "</message>").firstChild;
  1239. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1240. test_utils.waitUntil(function () {
  1241. return view.el.querySelectorAll('.chat-content .chat-msg audio').length;
  1242. }, 1000).then(function () {
  1243. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1244. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you heard this funny audio?</span>');
  1245. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1246. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1247. '<!-- src/templates/audio.html -->'+
  1248. '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
  1249. '<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
  1250. // If the <url> and <body> contents is the same, don't duplicate.
  1251. var stanza = Strophe.xmlHtmlNode(
  1252. "<message from='"+contact_jid+"'"+
  1253. " type='chat'"+
  1254. " to='dummy@localhost/resource'>"+
  1255. " <body>http://localhost/audio.mp3</body>"+
  1256. " <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
  1257. "</message>").firstChild;
  1258. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1259. msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
  1260. expect(msg.innerHTML).toEqual('');
  1261. media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
  1262. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1263. '<!-- src/templates/audio.html -->'+
  1264. '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
  1265. '<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
  1266. done();
  1267. });
  1268. }));
  1269. it("will render video from oob mp4 URLs",
  1270. mock.initConverseWithPromises(
  1271. null, ['rosterGroupsFetched'], {},
  1272. function (done, _converse) {
  1273. test_utils.createContacts(_converse, 'current');
  1274. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1275. test_utils.openChatBoxFor(_converse, contact_jid);
  1276. var view = _converse.chatboxviews.get(contact_jid);
  1277. spyOn(view.model, 'sendMessage').and.callThrough();
  1278. var stanza = Strophe.xmlHtmlNode(
  1279. "<message from='"+contact_jid+"'"+
  1280. " type='chat'"+
  1281. " to='dummy@localhost/resource'>"+
  1282. " <body>Have you seen this funny video?</body>"+
  1283. " <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
  1284. "</message>").firstChild;
  1285. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1286. test_utils.waitUntil(function () {
  1287. return view.el.querySelectorAll('.chat-content .chat-msg video').length;
  1288. }, 2000).then(function () {
  1289. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1290. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny video?</span>');
  1291. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1292. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1293. '<!-- src/templates/video.html -->'+
  1294. '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
  1295. '<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
  1296. // If the <url> and <body> contents is the same, don't duplicate.
  1297. var stanza = Strophe.xmlHtmlNode(
  1298. "<message from='"+contact_jid+"'"+
  1299. " type='chat'"+
  1300. " to='dummy@localhost/resource'>"+
  1301. " <body>http://localhost/video.mp4</body>"+
  1302. " <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
  1303. "</message>").firstChild;
  1304. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1305. msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
  1306. expect(msg.innerHTML).toEqual('');
  1307. media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
  1308. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1309. '<!-- src/templates/video.html -->'+
  1310. '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
  1311. '<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
  1312. done();
  1313. });
  1314. }));
  1315. it("will render download links for files from oob URLs",
  1316. mock.initConverseWithPromises(
  1317. null, ['rosterGroupsFetched'], {},
  1318. function (done, _converse) {
  1319. test_utils.createContacts(_converse, 'current');
  1320. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1321. test_utils.openChatBoxFor(_converse, contact_jid);
  1322. var view = _converse.chatboxviews.get(contact_jid);
  1323. spyOn(view.model, 'sendMessage').and.callThrough();
  1324. var stanza = Strophe.xmlHtmlNode(
  1325. "<message from='"+contact_jid+"'"+
  1326. " type='chat'"+
  1327. " to='dummy@localhost/resource'>"+
  1328. " <body>Have you downloaded this funny file?</body>"+
  1329. " <x xmlns='jabber:x:oob'><url>http://localhost/funny.pdf</url></x>"+
  1330. "</message>").firstChild;
  1331. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1332. test_utils.waitUntil(function () {
  1333. return view.el.querySelectorAll('.chat-content .chat-msg a').length;
  1334. }, 1000).then(function () {
  1335. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1336. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you downloaded this funny file?</span>');
  1337. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1338. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1339. '<!-- src/templates/file.html -->'+
  1340. '<a target="_blank" rel="noopener" href="http://localhost/funny.pdf">Download: "funny.pdf</a>');
  1341. done();
  1342. });
  1343. }));
  1344. it("will render images from oob URLs",
  1345. mock.initConverseWithPromises(
  1346. null, ['rosterGroupsFetched'], {},
  1347. function (done, _converse) {
  1348. test_utils.createContacts(_converse, 'current');
  1349. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1350. test_utils.openChatBoxFor(_converse, contact_jid);
  1351. var view = _converse.chatboxviews.get(contact_jid);
  1352. spyOn(view.model, 'sendMessage').and.callThrough();
  1353. var base_url = document.URL.split(window.location.pathname)[0];
  1354. var url = base_url+"/logo/conversejs-filled.svg";
  1355. var stanza = Strophe.xmlHtmlNode(
  1356. "<message from='"+contact_jid+"'"+
  1357. " type='chat'"+
  1358. " to='dummy@localhost/resource'>"+
  1359. " <body>Have you seen this funny image?</body>"+
  1360. " <x xmlns='jabber:x:oob'><url>"+url+"</url></x>"+
  1361. "</message>").firstChild;
  1362. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1363. test_utils.waitUntil(function () {
  1364. return view.el.querySelectorAll('.chat-content .chat-msg img').length;
  1365. }, 2000).then(function () {
  1366. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1367. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny image?</span>');
  1368. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1369. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1370. '<!-- src/templates/image.html -->'+
  1371. '<a href="http://localhost:8000/logo/conversejs-filled.svg" target="_blank" rel="noopener">'+
  1372. '<img class="chat-image img-thumbnail" src="http://localhost:8000/logo/conversejs-filled.svg">'+
  1373. '</a>');
  1374. done();
  1375. });
  1376. }));
  1377. });
  1378. });
  1379. }));