corrections.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*global mock, converse */
  2. const { Promise, $msg, $pres, Strophe, sizzle } = converse.env;
  3. const u = converse.env.utils;
  4. describe("A Chat Message", function () {
  5. it("can be sent as a correction by using the up arrow",
  6. mock.initConverse(
  7. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  8. async function (done, _converse) {
  9. await mock.waitForRoster(_converse, 'current', 1);
  10. await mock.openControlBox(_converse);
  11. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  12. await mock.openChatBoxFor(_converse, contact_jid)
  13. const view = _converse.api.chatviews.get(contact_jid);
  14. const textarea = view.el.querySelector('textarea.chat-textarea');
  15. expect(textarea.value).toBe('');
  16. view.onKeyDown({
  17. target: textarea,
  18. keyCode: 38 // Up arrow
  19. });
  20. expect(textarea.value).toBe('');
  21. textarea.value = 'But soft, what light through yonder airlock breaks?';
  22. view.onKeyDown({
  23. target: textarea,
  24. preventDefault: function preventDefault () {},
  25. keyCode: 13 // Enter
  26. });
  27. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  28. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  29. expect(view.el.querySelector('.chat-msg__text').textContent)
  30. .toBe('But soft, what light through yonder airlock breaks?');
  31. const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
  32. expect(textarea.value).toBe('');
  33. view.onKeyDown({
  34. target: textarea,
  35. keyCode: 38 // Up arrow
  36. });
  37. expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
  38. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  39. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  40. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  41. spyOn(_converse.connection, 'send');
  42. textarea.value = 'But soft, what light through yonder window breaks?';
  43. view.onKeyDown({
  44. target: textarea,
  45. preventDefault: function preventDefault () {},
  46. keyCode: 13 // Enter
  47. });
  48. expect(_converse.connection.send).toHaveBeenCalled();
  49. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  50. const msg = _converse.connection.send.calls.all()[0].args[0];
  51. expect(msg.toLocaleString())
  52. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  53. `to="mercutio@montague.lit" type="chat" `+
  54. `xmlns="jabber:client">`+
  55. `<body>But soft, what light through yonder window breaks?</body>`+
  56. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  57. `<request xmlns="urn:xmpp:receipts"/>`+
  58. `<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
  59. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  60. `</message>`);
  61. expect(view.model.messages.models.length).toBe(1);
  62. const corrected_message = view.model.messages.at(0);
  63. expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
  64. expect(corrected_message.get('correcting')).toBe(false);
  65. const older_versions = corrected_message.get('older_versions');
  66. const keys = Object.keys(older_versions);
  67. expect(keys.length).toBe(1);
  68. expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
  69. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  70. await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
  71. // Test that pressing the down arrow cancels message correction
  72. expect(textarea.value).toBe('');
  73. view.onKeyDown({
  74. target: textarea,
  75. keyCode: 38 // Up arrow
  76. });
  77. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  78. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  79. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  80. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  81. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  82. view.onKeyDown({
  83. target: textarea,
  84. keyCode: 40 // Down arrow
  85. });
  86. expect(textarea.value).toBe('');
  87. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  88. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  89. await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
  90. textarea.value = 'It is the east, and Juliet is the one.';
  91. view.onKeyDown({
  92. target: textarea,
  93. preventDefault: function preventDefault () {},
  94. keyCode: 13 // Enter
  95. });
  96. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  97. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  98. textarea.value = 'Arise, fair sun, and kill the envious moon';
  99. view.onKeyDown({
  100. target: textarea,
  101. preventDefault: function preventDefault () {},
  102. keyCode: 13 // Enter
  103. });
  104. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  105. expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
  106. view.onKeyDown({
  107. target: textarea,
  108. keyCode: 38 // Up arrow
  109. });
  110. expect(textarea.value).toBe('Arise, fair sun, and kill the envious moon');
  111. expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
  112. expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
  113. expect(view.model.messages.at(2).get('correcting')).toBe(true);
  114. await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
  115. textarea.selectionEnd = 0; // Happens by pressing up,
  116. // but for some reason not in tests, so we set it manually.
  117. view.onKeyDown({
  118. target: textarea,
  119. keyCode: 38 // Up arrow
  120. });
  121. expect(textarea.value).toBe('It is the east, and Juliet is the one.');
  122. expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
  123. expect(view.model.messages.at(1).get('correcting')).toBe(true);
  124. expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
  125. await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view.el)[1]), 500);
  126. textarea.value = 'It is the east, and Juliet is the sun.';
  127. view.onKeyDown({
  128. target: textarea,
  129. preventDefault: function preventDefault () {},
  130. keyCode: 13 // Enter
  131. });
  132. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  133. await u.waitUntil(() => textarea.value === '');
  134. const messages = view.el.querySelectorAll('.chat-msg');
  135. expect(messages.length).toBe(3);
  136. expect(messages[0].querySelector('.chat-msg__text').textContent)
  137. .toBe('But soft, what light through yonder window breaks?');
  138. expect(messages[1].querySelector('.chat-msg__text').textContent)
  139. .toBe('It is the east, and Juliet is the sun.');
  140. expect(messages[2].querySelector('.chat-msg__text').textContent)
  141. .toBe('Arise, fair sun, and kill the envious moon');
  142. expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
  143. expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
  144. expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
  145. done();
  146. }));
  147. it("can be sent as a correction by clicking the pencil icon",
  148. mock.initConverse(
  149. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  150. async function (done, _converse) {
  151. await mock.waitForRoster(_converse, 'current', 1);
  152. await mock.openControlBox(_converse);
  153. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  154. await mock.openChatBoxFor(_converse, contact_jid);
  155. const view = _converse.api.chatviews.get(contact_jid);
  156. const textarea = view.el.querySelector('textarea.chat-textarea');
  157. textarea.value = 'But soft, what light through yonder airlock breaks?';
  158. view.onKeyDown({
  159. target: textarea,
  160. preventDefault: function preventDefault () {},
  161. keyCode: 13 // Enter
  162. });
  163. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  164. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  165. expect(view.el.querySelector('.chat-msg__text').textContent)
  166. .toBe('But soft, what light through yonder airlock breaks?');
  167. expect(textarea.value).toBe('');
  168. const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
  169. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .chat-msg__action').length === 2);
  170. let action = view.el.querySelector('.chat-msg .chat-msg__action');
  171. expect(action.textContent.trim()).toBe('Edit');
  172. action.style.opacity = 1;
  173. action.click();
  174. expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
  175. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  176. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  177. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
  178. spyOn(_converse.connection, 'send');
  179. textarea.value = 'But soft, what light through yonder window breaks?';
  180. view.onKeyDown({
  181. target: textarea,
  182. preventDefault: function preventDefault () {},
  183. keyCode: 13 // Enter
  184. });
  185. expect(_converse.connection.send).toHaveBeenCalled();
  186. const msg = _converse.connection.send.calls.all()[0].args[0];
  187. expect(msg.toLocaleString())
  188. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  189. `to="mercutio@montague.lit" type="chat" `+
  190. `xmlns="jabber:client">`+
  191. `<body>But soft, what light through yonder window breaks?</body>`+
  192. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  193. `<request xmlns="urn:xmpp:receipts"/>`+
  194. `<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
  195. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  196. `</message>`);
  197. expect(view.model.messages.models.length).toBe(1);
  198. const corrected_message = view.model.messages.at(0);
  199. expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
  200. expect(corrected_message.get('correcting')).toBe(false);
  201. const older_versions = corrected_message.get('older_versions');
  202. const keys = Object.keys(older_versions);
  203. expect(keys.length).toBe(1);
  204. expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
  205. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false);
  206. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  207. // Test that clicking the pencil icon a second time cancels editing.
  208. action = view.el.querySelector('.chat-msg .chat-msg__action');
  209. action.style.opacity = 1;
  210. action.click();
  211. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  212. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  213. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  214. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === true);
  215. action = view.el.querySelector('.chat-msg .chat-msg__action');
  216. action.style.opacity = 1;
  217. action.click();
  218. expect(textarea.value).toBe('');
  219. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  220. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  221. await u.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
  222. // Test that messages from other users don't have the pencil icon
  223. _converse.handleMessageStanza(
  224. $msg({
  225. 'from': contact_jid,
  226. 'to': _converse.connection.jid,
  227. 'type': 'chat',
  228. 'id': u.getUniqueId()
  229. }).c('body').t('Hello').up()
  230. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
  231. );
  232. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  233. expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2);
  234. // Test confirmation dialog
  235. spyOn(window, 'confirm').and.returnValue(true);
  236. textarea.value = 'But soft, what light through yonder airlock breaks?';
  237. action = view.el.querySelector('.chat-msg .chat-msg__action');
  238. action.style.opacity = 1;
  239. action.click();
  240. expect(window.confirm).toHaveBeenCalledWith(
  241. 'You have an unsent message which will be lost if you continue. Are you sure?');
  242. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  243. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  244. textarea.value = 'But soft, what light through yonder airlock breaks?'
  245. action.click();
  246. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  247. expect(window.confirm.calls.count()).toBe(2);
  248. expect(window.confirm.calls.argsFor(0)).toEqual(
  249. ['You have an unsent message which will be lost if you continue. Are you sure?']);
  250. expect(window.confirm.calls.argsFor(1)).toEqual(
  251. ['You have an unsent message which will be lost if you continue. Are you sure?']);
  252. done();
  253. }));
  254. describe("when received from someone else", function () {
  255. it("can be replaced with a correction",
  256. mock.initConverse(
  257. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  258. async function (done, _converse) {
  259. await mock.waitForRoster(_converse, 'current', 1);
  260. await mock.openControlBox(_converse);
  261. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  262. const msg_id = u.getUniqueId();
  263. const view = await mock.openChatBoxFor(_converse, sender_jid);
  264. _converse.handleMessageStanza($msg({
  265. 'from': sender_jid,
  266. 'to': _converse.connection.jid,
  267. 'type': 'chat',
  268. 'id': msg_id,
  269. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  270. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  271. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  272. expect(view.el.querySelector('.chat-msg__text').textContent)
  273. .toBe('But soft, what light through yonder airlock breaks?');
  274. _converse.handleMessageStanza($msg({
  275. 'from': sender_jid,
  276. 'to': _converse.connection.jid,
  277. 'type': 'chat',
  278. 'id': u.getUniqueId(),
  279. }).c('body').t('But soft, what light through yonder chimney breaks?').up()
  280. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  281. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  282. expect(view.el.querySelector('.chat-msg__text').textContent)
  283. .toBe('But soft, what light through yonder chimney breaks?');
  284. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  285. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  286. expect(view.model.messages.models.length).toBe(1);
  287. _converse.handleMessageStanza($msg({
  288. 'from': sender_jid,
  289. 'to': _converse.connection.jid,
  290. 'type': 'chat',
  291. 'id': u.getUniqueId(),
  292. }).c('body').t('But soft, what light through yonder window breaks?').up()
  293. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  294. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  295. expect(view.el.querySelector('.chat-msg__text').textContent)
  296. .toBe('But soft, what light through yonder window breaks?');
  297. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  298. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  299. view.el.querySelector('.chat-msg__content .fa-edit').click();
  300. const modal = await u.waitUntil(() => view.el.querySelector('converse-chat-message').message_versions_modal);
  301. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  302. const older_msgs = modal.el.querySelectorAll('.older-msg');
  303. expect(older_msgs.length).toBe(2);
  304. expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
  305. expect(older_msgs[0].childNodes[2].textContent).toBe('But soft, what light through yonder airlock breaks?');
  306. expect(view.model.messages.models.length).toBe(1);
  307. done();
  308. }));
  309. });
  310. });
  311. describe("A Groupchat Message", function () {
  312. it("can be replaced with a correction",
  313. mock.initConverse(
  314. ['rosterGroupsFetched'], {},
  315. async function (done, _converse) {
  316. const muc_jid = 'lounge@montague.lit';
  317. await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  318. const view = _converse.api.chatviews.get(muc_jid);
  319. const stanza = $pres({
  320. to: 'romeo@montague.lit/_converse.js-29092160',
  321. from: 'coven@chat.shakespeare.lit/newguy'
  322. })
  323. .c('x', {xmlns: Strophe.NS.MUC_USER})
  324. .c('item', {
  325. 'affiliation': 'none',
  326. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  327. 'role': 'participant'
  328. }).tree();
  329. _converse.connection._dataRecv(mock.createRequest(stanza));
  330. const msg_id = u.getUniqueId();
  331. await view.model.handleMessageStanza($msg({
  332. 'from': 'lounge@montague.lit/newguy',
  333. 'to': _converse.connection.jid,
  334. 'type': 'groupchat',
  335. 'id': msg_id,
  336. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  337. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
  338. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  339. expect(view.el.querySelector('.chat-msg__text').textContent)
  340. .toBe('But soft, what light through yonder airlock breaks?');
  341. await view.model.handleMessageStanza($msg({
  342. 'from': 'lounge@montague.lit/newguy',
  343. 'to': _converse.connection.jid,
  344. 'type': 'groupchat',
  345. 'id': u.getUniqueId(),
  346. }).c('body').t('But soft, what light through yonder chimney breaks?').up()
  347. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  348. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  349. 'But soft, what light through yonder chimney breaks?', 500);
  350. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  351. await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
  352. await view.model.handleMessageStanza($msg({
  353. 'from': 'lounge@montague.lit/newguy',
  354. 'to': _converse.connection.jid,
  355. 'type': 'groupchat',
  356. 'id': u.getUniqueId(),
  357. }).c('body').t('But soft, what light through yonder window breaks?').up()
  358. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  359. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  360. 'But soft, what light through yonder window breaks?', 500);
  361. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  362. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  363. const edit = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
  364. edit.click();
  365. const modal = await u.waitUntil(() => view.el.querySelector('converse-chat-message').message_versions_modal);
  366. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  367. const older_msgs = modal.el.querySelectorAll('.older-msg');
  368. expect(older_msgs.length).toBe(2);
  369. expect(older_msgs[0].childNodes[2].textContent).toBe('But soft, what light through yonder airlock breaks?');
  370. expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
  371. expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME');
  372. expect(older_msgs[1].childNodes[2].textContent).toBe('But soft, what light through yonder chimney breaks?');
  373. done();
  374. }));
  375. it("keeps the same position in history after a correction",
  376. mock.initConverse(
  377. ['rosterGroupsFetched'], {},
  378. async function (done, _converse) {
  379. const muc_jid = 'lounge@montague.lit';
  380. await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  381. const view = _converse.api.chatviews.get(muc_jid);
  382. const stanza = $pres({
  383. to: 'romeo@montague.lit/_converse.js-29092160',
  384. from: 'coven@chat.shakespeare.lit/newguy'
  385. })
  386. .c('x', {xmlns: Strophe.NS.MUC_USER})
  387. .c('item', {
  388. 'affiliation': 'none',
  389. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  390. 'role': 'participant'
  391. }).tree();
  392. _converse.connection._dataRecv(mock.createRequest(stanza));
  393. const msg_id = u.getUniqueId();
  394. // Receiving the first message
  395. await view.model.handleMessageStanza($msg({
  396. 'from': 'lounge@montague.lit/newguy',
  397. 'to': _converse.connection.jid,
  398. 'type': 'groupchat',
  399. 'id': msg_id,
  400. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  401. // Receiving own message to check order against
  402. await view.model.handleMessageStanza($msg({
  403. 'from': 'lounge@montague.lit/romeo',
  404. 'to': _converse.connection.jid,
  405. 'type': 'groupchat',
  406. 'id': msg_id,
  407. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  408. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
  409. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  410. expect(view.el.querySelectorAll('.chat-msg__text')[0].textContent)
  411. .toBe('But soft, what light through yonder airlock breaks?');
  412. expect(view.el.querySelectorAll('.chat-msg__text')[1].textContent)
  413. .toBe('But soft, what light through yonder airlock breaks?');
  414. // First message correction
  415. await view.model.handleMessageStanza($msg({
  416. 'from': 'lounge@montague.lit/newguy',
  417. 'to': _converse.connection.jid,
  418. 'type': 'groupchat',
  419. 'id': u.getUniqueId(),
  420. }).c('body').t('But soft, what light through yonder chimney breaks?').up()
  421. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  422. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  423. 'But soft, what light through yonder chimney breaks?', 500);
  424. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  425. await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
  426. // Second message correction
  427. await view.model.handleMessageStanza($msg({
  428. 'from': 'lounge@montague.lit/newguy',
  429. 'to': _converse.connection.jid,
  430. 'type': 'groupchat',
  431. 'id': u.getUniqueId(),
  432. }).c('body').t('But soft, what light through yonder window breaks?').up()
  433. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  434. // Second own message
  435. await view.model.handleMessageStanza($msg({
  436. 'from': 'lounge@montague.lit/romeo',
  437. 'to': _converse.connection.jid,
  438. 'type': 'groupchat',
  439. 'id': u.getUniqueId(),
  440. }).c('body').t('But soft, what light through yonder window breaks?').tree());
  441. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[0].textContent ===
  442. 'But soft, what light through yonder window breaks?', 500);
  443. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
  444. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[2].textContent ===
  445. 'But soft, what light through yonder window breaks?', 500);
  446. expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
  447. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  448. const edit = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit'));
  449. edit.click();
  450. const modal = await u.waitUntil(() => view.el.querySelectorAll('converse-chat-message')[0].message_versions_modal);
  451. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  452. const older_msgs = modal.el.querySelectorAll('.older-msg');
  453. expect(older_msgs.length).toBe(2);
  454. expect(older_msgs[0].childNodes[2].textContent).toBe('But soft, what light through yonder airlock breaks?');
  455. expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
  456. expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME');
  457. expect(older_msgs[1].childNodes[2].textContent).toBe('But soft, what light through yonder chimney breaks?');
  458. done();
  459. }));
  460. it("can be sent as a correction by using the up arrow",
  461. mock.initConverse(
  462. ['rosterGroupsFetched'], {},
  463. async function (done, _converse) {
  464. const muc_jid = 'lounge@montague.lit';
  465. await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  466. const view = _converse.api.chatviews.get(muc_jid);
  467. const textarea = view.el.querySelector('textarea.chat-textarea');
  468. expect(textarea.value).toBe('');
  469. view.onKeyDown({
  470. target: textarea,
  471. keyCode: 38 // Up arrow
  472. });
  473. expect(textarea.value).toBe('');
  474. textarea.value = 'But soft, what light through yonder airlock breaks?';
  475. view.onKeyDown({
  476. target: textarea,
  477. preventDefault: function preventDefault () {},
  478. keyCode: 13 // Enter
  479. });
  480. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
  481. expect(view.el.querySelector('.chat-msg__text').textContent)
  482. .toBe('But soft, what light through yonder airlock breaks?');
  483. const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
  484. expect(textarea.value).toBe('');
  485. view.onKeyDown({
  486. target: textarea,
  487. keyCode: 38 // Up arrow
  488. });
  489. expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
  490. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  491. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  492. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
  493. spyOn(_converse.connection, 'send');
  494. textarea.value = 'But soft, what light through yonder window breaks?';
  495. view.onKeyDown({
  496. target: textarea,
  497. preventDefault: function preventDefault () {},
  498. keyCode: 13 // Enter
  499. });
  500. expect(_converse.connection.send).toHaveBeenCalled();
  501. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  502. const msg = _converse.connection.send.calls.all()[0].args[0];
  503. expect(msg.toLocaleString())
  504. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  505. `to="lounge@montague.lit" type="groupchat" `+
  506. `xmlns="jabber:client">`+
  507. `<body>But soft, what light through yonder window breaks?</body>`+
  508. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  509. `<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
  510. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  511. `</message>`);
  512. expect(view.model.messages.models.length).toBe(1);
  513. const corrected_message = view.model.messages.at(0);
  514. expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
  515. expect(corrected_message.get('correcting')).toBe(false);
  516. const older_versions = corrected_message.get('older_versions');
  517. const keys = Object.keys(older_versions);
  518. expect(keys.length).toBe(1);
  519. expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
  520. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  521. expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
  522. // Check that messages from other users are skipped
  523. await view.model.handleMessageStanza($msg({
  524. 'from': muc_jid+'/someone-else',
  525. 'id': u.getUniqueId(),
  526. 'to': 'romeo@montague.lit',
  527. 'type': 'groupchat'
  528. }).c('body').t('Hello world').tree());
  529. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  530. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  531. // Test that pressing the down arrow cancels message correction
  532. expect(textarea.value).toBe('');
  533. view.onKeyDown({
  534. target: textarea,
  535. keyCode: 38 // Up arrow
  536. });
  537. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  538. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  539. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  540. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  541. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  542. view.onKeyDown({
  543. target: textarea,
  544. keyCode: 40 // Down arrow
  545. });
  546. expect(textarea.value).toBe('');
  547. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  548. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  549. await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  550. done();
  551. }));
  552. });