messages.js 81 KB

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