messages.js 95 KB

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