messages.js 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  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.text()).toEqual('Older message');
  513. $time = $chat_content.find('time:eq(1)');
  514. expect($time.text()).toEqual("Monday Jan 1st 2018");
  515. $day = $chat_content.find('.date-separator:eq(1)');
  516. expect($day.data('isodate')).toEqual(moment('2018-01-01T00:00:00').format());
  517. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Inbetween message');
  518. $el = $chat_content.find('.chat-msg:eq(1)');
  519. expect($el.find('.chat-msg-text').text()).toEqual('Inbetween message');
  520. expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('another inbetween message');
  521. $el = $chat_content.find('.chat-msg:eq(2)');
  522. expect($el.find('.chat-msg-text').text()).toEqual('another inbetween message');
  523. $time = $chat_content.find('time:nth(2)');
  524. expect($time.text()).toEqual("Tuesday Jan 2nd 2018");
  525. $day = $chat_content.find('.date-separator:nth(2)');
  526. expect($day.data('isodate')).toEqual(moment('2018-01-02T00:00:00').format());
  527. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('An earlier message on the next day');
  528. $el = $chat_content.find('.chat-msg:eq(3)');
  529. expect($el.find('.chat-msg-text').text()).toEqual('An earlier message on the next day');
  530. $el = $chat_content.find('.chat-msg:eq(4)');
  531. expect($el.find('.chat-msg-text').text()).toEqual('message');
  532. expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('newer message from the next day');
  533. $day = $chat_content.find('.date-separator:last');
  534. expect($day.data('isodate')).toEqual(moment().startOf('day').format());
  535. expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('latest message');
  536. done();
  537. });
  538. }));
  539. it("is ignored if it's a malformed headline message",
  540. mock.initConverseWithPromises(
  541. null, ['rosterGroupsFetched'], {},
  542. function (done, _converse) {
  543. test_utils.createContacts(_converse, 'current');
  544. test_utils.openControlBox();
  545. /* Ideally we wouldn't have to filter out headline
  546. * messages, but Prosody gives them the wrong 'type' :(
  547. */
  548. sinon.spy(_converse, 'log');
  549. sinon.spy(_converse.chatboxes, 'getChatBox');
  550. sinon.spy(utils, 'isHeadlineMessage');
  551. var msg = $msg({
  552. from: 'localhost',
  553. to: _converse.bare_jid,
  554. type: 'chat',
  555. id: (new Date()).getTime()
  556. }).c('body').t("This headline message will not be shown").tree();
  557. _converse.chatboxes.onMessage(msg);
  558. expect(_converse.log.calledWith(
  559. "onMessage: Ignoring incoming headline message sent with type 'chat' from JID: localhost",
  560. Strophe.LogLevel.INFO
  561. )).toBeTruthy();
  562. expect(utils.isHeadlineMessage.called).toBeTruthy();
  563. expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
  564. expect(_converse.chatboxes.getChatBox.called).toBeFalsy();
  565. // Remove sinon spies
  566. _converse.log.restore();
  567. _converse.chatboxes.getChatBox.restore();
  568. utils.isHeadlineMessage.restore();
  569. done();
  570. }));
  571. it("can be a carbon message, as defined in XEP-0280",
  572. mock.initConverseWithPromises(
  573. null, ['rosterGroupsFetched'], {},
  574. function (done, _converse) {
  575. test_utils.createContacts(_converse, 'current');
  576. test_utils.openControlBox();
  577. // Send a message from a different resource
  578. spyOn(_converse, 'log');
  579. var msgtext = 'This is a carbon message';
  580. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  581. var msg = $msg({
  582. 'from': sender_jid,
  583. 'id': (new Date()).getTime(),
  584. 'to': _converse.connection.jid,
  585. 'type': 'chat',
  586. 'xmlns': 'jabber:client'
  587. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  588. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  589. .c('message', {
  590. 'xmlns': 'jabber:client',
  591. 'from': sender_jid,
  592. 'to': _converse.bare_jid+'/another-resource',
  593. 'type': 'chat'
  594. }).c('body').t(msgtext).tree();
  595. _converse.chatboxes.onMessage(msg);
  596. // Check that the chatbox and its view now exist
  597. var chatbox = _converse.chatboxes.get(sender_jid);
  598. var chatboxview = _converse.chatboxviews.get(sender_jid);
  599. expect(chatbox).toBeDefined();
  600. expect(chatboxview).toBeDefined();
  601. // Check that the message was received and check the message parameters
  602. expect(chatbox.messages.length).toEqual(1);
  603. var msg_obj = chatbox.messages.models[0];
  604. expect(msg_obj.get('message')).toEqual(msgtext);
  605. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[1]);
  606. expect(msg_obj.get('sender')).toEqual('them');
  607. expect(msg_obj.get('delayed')).toEqual(false);
  608. // Now check that the message appears inside the chatbox in the DOM
  609. var chat_content = chatboxview.el.querySelector('.chat-content');
  610. expect(chat_content.querySelector('.chat-msg .chat-msg-text').textContent).toEqual(msgtext);
  611. expect(chat_content.querySelector('.chat-msg-time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
  612. expect(chat_content.querySelector('span.chat-msg-author').textContent).toBe('Candice van der Knijff');
  613. done();
  614. }));
  615. it("can be a carbon message that this user sent from a different client, as defined in XEP-0280",
  616. mock.initConverseWithPromises(
  617. null, ['rosterGroupsFetched'], {},
  618. function (done, _converse) {
  619. var contact, sent_stanza, IQ_id, stanza;
  620. test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
  621. .then(function () {
  622. return test_utils.waitUntil(function () {
  623. return _converse.xmppstatus.get('fullname');
  624. }, 300);
  625. }).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. describe("which contains a OOB URL", function () {
  1010. it("will render audio from oob mp3 URLs",
  1011. mock.initConverseWithPromises(
  1012. null, ['rosterGroupsFetched'], {},
  1013. function (done, _converse) {
  1014. test_utils.createContacts(_converse, 'current');
  1015. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1016. test_utils.openChatBoxFor(_converse, contact_jid);
  1017. var view = _converse.chatboxviews.get(contact_jid);
  1018. spyOn(view.model, 'sendMessage').and.callThrough();
  1019. var stanza = Strophe.xmlHtmlNode(
  1020. "<message from='"+contact_jid+"'"+
  1021. " type='chat'"+
  1022. " to='dummy@localhost/resource'>"+
  1023. " <body>Have you heard this funny audio?</body>"+
  1024. " <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
  1025. "</message>").firstChild;
  1026. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1027. test_utils.waitUntil(function () {
  1028. return view.el.querySelectorAll('.chat-content .chat-msg audio').length;
  1029. }, 1000).then(function () {
  1030. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1031. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you heard this funny audio?</span>');
  1032. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1033. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1034. '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
  1035. '<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
  1036. // If the <url> and <body> contents is the same, don't duplicate.
  1037. var stanza = Strophe.xmlHtmlNode(
  1038. "<message from='"+contact_jid+"'"+
  1039. " type='chat'"+
  1040. " to='dummy@localhost/resource'>"+
  1041. " <body>http://localhost/audio.mp3</body>"+
  1042. " <x xmlns='jabber:x:oob'><url>http://localhost/audio.mp3</url></x>"+
  1043. "</message>").firstChild;
  1044. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1045. msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
  1046. expect(msg.innerHTML).toEqual('');
  1047. media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
  1048. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1049. '<audio controls=""><source src="http://localhost/audio.mp3" type="audio/mpeg"></audio>'+
  1050. '<a target="_blank" rel="noopener" href="http://localhost/audio.mp3">Download audio file</a>');
  1051. done();
  1052. });
  1053. }));
  1054. it("will render video from oob mp4 URLs",
  1055. mock.initConverseWithPromises(
  1056. null, ['rosterGroupsFetched'], {},
  1057. function (done, _converse) {
  1058. test_utils.createContacts(_converse, 'current');
  1059. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1060. test_utils.openChatBoxFor(_converse, contact_jid);
  1061. var view = _converse.chatboxviews.get(contact_jid);
  1062. spyOn(view.model, 'sendMessage').and.callThrough();
  1063. var stanza = Strophe.xmlHtmlNode(
  1064. "<message from='"+contact_jid+"'"+
  1065. " type='chat'"+
  1066. " to='dummy@localhost/resource'>"+
  1067. " <body>Have you seen this funny video?</body>"+
  1068. " <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
  1069. "</message>").firstChild;
  1070. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1071. test_utils.waitUntil(function () {
  1072. return view.el.querySelectorAll('.chat-content .chat-msg video').length;
  1073. }, 2000).then(function () {
  1074. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1075. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny video?</span>');
  1076. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1077. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1078. '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
  1079. '<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
  1080. // If the <url> and <body> contents is the same, don't duplicate.
  1081. var stanza = Strophe.xmlHtmlNode(
  1082. "<message from='"+contact_jid+"'"+
  1083. " type='chat'"+
  1084. " to='dummy@localhost/resource'>"+
  1085. " <body>http://localhost/video.mp4</body>"+
  1086. " <x xmlns='jabber:x:oob'><url>http://localhost/video.mp4</url></x>"+
  1087. "</message>").firstChild;
  1088. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1089. msg = view.el.querySelector('.chat-msg:last-child .chat-msg-text');
  1090. expect(msg.innerHTML).toEqual('');
  1091. media = view.el.querySelector('.chat-msg:last-child .chat-msg-media');
  1092. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1093. '<video controls=""><source src="http://localhost/video.mp4" type="video/mp4"></video>'+
  1094. '<a target="_blank" rel="noopener" href="http://localhost/video.mp4">Download video file</a>');
  1095. done();
  1096. });
  1097. }));
  1098. it("will render download links for files from oob URLs",
  1099. mock.initConverseWithPromises(
  1100. null, ['rosterGroupsFetched'], {},
  1101. function (done, _converse) {
  1102. test_utils.createContacts(_converse, 'current');
  1103. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1104. test_utils.openChatBoxFor(_converse, contact_jid);
  1105. var view = _converse.chatboxviews.get(contact_jid);
  1106. spyOn(view.model, 'sendMessage').and.callThrough();
  1107. var stanza = Strophe.xmlHtmlNode(
  1108. "<message from='"+contact_jid+"'"+
  1109. " type='chat'"+
  1110. " to='dummy@localhost/resource'>"+
  1111. " <body>Have you downloaded this funny file?</body>"+
  1112. " <x xmlns='jabber:x:oob'><url>http://localhost/funny.pdf</url></x>"+
  1113. "</message>").firstChild;
  1114. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1115. test_utils.waitUntil(function () {
  1116. return view.el.querySelectorAll('.chat-content .chat-msg a').length;
  1117. }, 1000).then(function () {
  1118. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1119. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you downloaded this funny file?</span>');
  1120. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1121. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1122. '<a target="_blank" rel="noopener" href="http://localhost/funny.pdf">Download: "funny.pdf</a>');
  1123. done();
  1124. });
  1125. }));
  1126. it("will render images from oob URLs",
  1127. mock.initConverseWithPromises(
  1128. null, ['rosterGroupsFetched'], {},
  1129. function (done, _converse) {
  1130. test_utils.createContacts(_converse, 'current');
  1131. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1132. test_utils.openChatBoxFor(_converse, contact_jid);
  1133. var view = _converse.chatboxviews.get(contact_jid);
  1134. spyOn(view.model, 'sendMessage').and.callThrough();
  1135. var base_url = document.URL.split(window.location.pathname)[0];
  1136. var url = base_url+"/logo/conversejs-filled.svg";
  1137. var stanza = Strophe.xmlHtmlNode(
  1138. "<message from='"+contact_jid+"'"+
  1139. " type='chat'"+
  1140. " to='dummy@localhost/resource'>"+
  1141. " <body>Have you seen this funny image?</body>"+
  1142. " <x xmlns='jabber:x:oob'><url>"+url+"</url></x>"+
  1143. "</message>").firstChild;
  1144. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1145. test_utils.waitUntil(function () {
  1146. return view.el.querySelectorAll('.chat-content .chat-msg img').length;
  1147. }, 2000).then(function () {
  1148. var msg = view.el.querySelector('.chat-msg .chat-msg-text');
  1149. expect(msg.outerHTML).toEqual('<span class="chat-msg-text">Have you seen this funny image?</span>');
  1150. var media = view.el.querySelector('.chat-msg .chat-msg-media');
  1151. expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
  1152. '<a href="http://localhost:8000/logo/conversejs-filled.svg" target="_blank" rel="noopener">'+
  1153. '<img class="chat-image img-thumbnail" src="http://localhost:8000/logo/conversejs-filled.svg">'+
  1154. '</a>');
  1155. done();
  1156. });
  1157. }));
  1158. });
  1159. });
  1160. }));