(function (root, factory) { define([ "jasmine", "mock", "test-utils" ], factory); } (this, function (jasmine, mock, test_utils) { "use strict"; const { Promise, Strophe, $iq, $msg, $pres, sizzle, _ } = converse.env; const u = converse.env.utils; describe("A Groupchat Message", function () { it("is specially marked when you are mentioned in it", mock.initConverse( null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); } const message = 'romeo: Your attention is required'; const nick = mock.chatroom_names[0], msg = $msg({ from: 'lounge@montague.lit/'+nick, id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t(message).tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy(); done(); })); it("can not be expected to have a unique id attribute", mock.initConverse( null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); } const id = u.getUniqueId(); let msg = $msg({ from: 'lounge@montague.lit/some1', id: id, to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('First message').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); msg = $msg({ from: 'lounge@montague.lit/some2', id: id, to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('Another message').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); done(); })); it("is ignored if it has the same stanza-id of an already received on", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'room@muc.example.com'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); spyOn(view.model, 'findDuplicateFromStanzaID').and.callThrough(); let stanza = u.toStanza(` Typical body text `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => _converse.api.chats.get().length); await u.waitUntil(() => view.model.messages.length === 1); await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 1); let result = await view.model.findDuplicateFromStanzaID.calls.all()[0].returnValue; expect(result).toBe(undefined); stanza = u.toStanza(` Typical body text `); spyOn(view.model, 'updateMessage'); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 2); result = await view.model.findDuplicateFromStanzaID.calls.all()[1].returnValue; expect(result instanceof _converse.Message).toBe(true); expect(view.model.messages.length).toBe(1); await u.waitUntil(() => view.model.updateMessage.calls.count()); done(); })); it("keeps track of the sender's role and affiliation", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); let msg = $msg({ from: 'lounge@montague.lit/romeo', id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('I wrote this message!').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat moderator owner'); let presence = $pres({ to:'romeo@montague.lit/orchard', from:'lounge@montague.lit/romeo', id: u.getUniqueId() }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'member', jid: 'romeo@montague.lit/orchard', role: 'participant' }).up() .c('status').attrs({code:'110'}).up() .c('status').attrs({code:'210'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); msg = $msg({ from: 'lounge@montague.lit/romeo', id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('Another message!').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.model.messages.last().occupant.get('affiliation')).toBe('member'); expect(view.model.messages.last().occupant.get('role')).toBe('participant'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat participant member'); presence = $pres({ to:'romeo@montague.lit/orchard', from:'lounge@montague.lit/romeo', id: u.getUniqueId() }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'owner', jid: 'romeo@montague.lit/orchard', role: 'moderator' }).up() .c('status').attrs({code:'110'}).up() .c('status').attrs({code:'210'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); view.model.sendMessage('hello world'); await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3); expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(3); expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat moderator owner'); const add_events = view.model.occupants._events.add.length; msg = $msg({ from: 'lounge@montague.lit/some1', id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('Message from someone not in the MUC right now').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.model.messages.last().occupant).toBeUndefined(); // Check that there's a new "add" event handler, for when the occupant appears. expect(view.model.occupants._events.add.length).toBe(add_events+1); // Check that the occupant gets added/removed to the message as it // gets removed or added. presence = $pres({ to:'romeo@montague.lit/orchard', from:'lounge@montague.lit/some1', id: u.getUniqueId() }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({jid: 'some1@montague.lit/orchard'}); _converse.connection._dataRecv(test_utils.createRequest(presence)); await u.waitUntil(() => view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); expect(view.model.messages.last().occupant.get('nick')).toBe('some1'); // Check that the "add" event handler was removed. expect(view.model.occupants._events.add.length).toBe(add_events); presence = $pres({ to:'romeo@montague.lit/orchard', type: 'unavailable', from:'lounge@montague.lit/some1', id: u.getUniqueId() }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({jid: 'some1@montague.lit/orchard'}); _converse.connection._dataRecv(test_utils.createRequest(presence)); await u.waitUntil(() => !view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); expect(view.model.messages.last().occupant).toBeUndefined(); // Check that there's a new "add" event handler, for when the occupant appears. expect(view.model.occupants._events.add.length).toBe(add_events+1); presence = $pres({ to:'romeo@montague.lit/orchard', from:'lounge@montague.lit/some1', id: u.getUniqueId() }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({jid: 'some1@montague.lit/orchard'}); _converse.connection._dataRecv(test_utils.createRequest(presence)); await u.waitUntil(() => view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); expect(view.model.messages.last().occupant.get('nick')).toBe('some1'); // Check that the "add" event handler was removed. expect(view.model.occupants._events.add.length).toBe(add_events); done(); })); it("keeps track whether you are the sender or not", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const msg = $msg({ from: 'lounge@montague.lit/romeo', id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('I wrote this message!').tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.model.messages.last().get('sender')).toBe('me'); done(); })); it("can be replaced with a correction", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const stanza = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/newguy' }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newguy@montague.lit/_converse.js-290929789', 'role': 'participant' }).tree(); _converse.connection._dataRecv(test_utils.createRequest(stanza)); const msg_id = u.getUniqueId(); await view.model.onMessage($msg({ 'from': 'lounge@montague.lit/newguy', 'to': _converse.connection.jid, 'type': 'groupchat', 'id': msg_id, }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelector('.chat-msg__text').textContent) .toBe('But soft, what light through yonder airlock breaks?'); await view.model.onMessage($msg({ 'from': 'lounge@montague.lit/newguy', 'to': _converse.connection.jid, 'type': 'groupchat', 'id': u.getUniqueId(), }).c('body').t('But soft, what light through yonder chimney breaks?').up() .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === 'But soft, what light through yonder chimney breaks?', 500); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); await view.model.onMessage($msg({ 'from': 'lounge@montague.lit/newguy', 'to': _converse.connection.jid, 'type': 'groupchat', 'id': u.getUniqueId(), }).c('body').t('But soft, what light through yonder window breaks?').up() .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === 'But soft, what light through yonder window breaks?', 500); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); view.el.querySelector('.chat-msg__content .fa-edit').click(); const modal = view.model.messages.at(0).message_versions_modal; await u.waitUntil(() => u.isVisible(modal.el), 1000); const older_msgs = modal.el.querySelectorAll('.older-msg'); expect(older_msgs.length).toBe(2); expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?'); expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME'); expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME'); expect(older_msgs[1].childNodes[1].textContent).toBe(': But soft, what light through yonder chimney breaks?'); done(); })); it("can be sent as a correction by using the up arrow", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const textarea = view.el.querySelector('textarea.chat-textarea'); expect(textarea.value).toBe(''); view.onKeyDown({ target: textarea, keyCode: 38 // Up arrow }); expect(textarea.value).toBe(''); textarea.value = 'But soft, what light through yonder airlock breaks?'; view.onKeyDown({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelector('.chat-msg__text').textContent) .toBe('But soft, what light through yonder airlock breaks?'); const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); expect(textarea.value).toBe(''); view.onKeyDown({ target: textarea, keyCode: 38 // Up arrow }); await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve)); expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?'); expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); spyOn(_converse.connection, 'send'); textarea.value = 'But soft, what light through yonder window breaks?'; view.onKeyDown({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); expect(_converse.connection.send).toHaveBeenCalled(); await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve)); const msg = _converse.connection.send.calls.all()[0].args[0]; expect(msg.toLocaleString()) .toBe(``+ `But soft, what light through yonder window breaks?`+ ``+ ``+ ``+ ``); expect(view.model.messages.models.length).toBe(1); const corrected_message = view.model.messages.at(0); expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid')); expect(corrected_message.get('correcting')).toBe(false); const older_versions = corrected_message.get('older_versions'); const keys = Object.keys(older_versions); expect(keys.length).toBe(1); expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); // Check that messages from other users are skipped await view.model.onMessage($msg({ 'from': muc_jid+'/someone-else', 'id': (new Date()).getTime(), 'to': 'romeo@montague.lit', 'type': 'groupchat' }).c('body').t('Hello world').tree()); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); // Test that pressing the down arrow cancels message correction expect(textarea.value).toBe(''); view.onKeyDown({ target: textarea, keyCode: 38 // Up arrow }); expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500); expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); view.onKeyDown({ target: textarea, keyCode: 40 // Down arrow }); expect(textarea.value).toBe(''); expect(view.model.messages.at(0).get('correcting')).toBe(false); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500); done(); })); it("will be shown as received upon MUC reflection", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { await test_utils.waitForRoster(_converse, 'current'); const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const textarea = view.el.querySelector('textarea.chat-textarea'); textarea.value = 'But soft, what light through yonder airlock breaks?'; view.onKeyDown({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0); const msg_obj = view.model.messages.at(0); const stanza = u.toStanza(` ${msg_obj.get('message')} `); await view.model.onMessage(stanza); await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1); expect(view.model.messages.length).toBe(1); const message = view.model.messages.at(0); expect(message.get('stanza_id lounge@montague.lit')).toBe('5f3dbc5e-e1d3-4077-a492-693f3769c7ad'); expect(message.get('origin_id')).toBe(msg_obj.get('origin_id')); done(); })); it("gets updated with its stanza-id upon MUC reflection", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'room@muc.example.com'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); view.model.sendMessage('hello world'); await u.waitUntil(() => _converse.api.chats.get().length); await u.waitUntil(() => view.model.messages.length === 1); const msg = view.model.messages.at(0); expect(msg.get('stanza_id')).toBeUndefined(); expect(msg.get('origin_id')).toBe(msg.get('origin_id')); const stanza = u.toStanza(` Hello world `); spyOn(view.model, 'updateMessage').and.callThrough(); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.updateMessage.calls.count() === 1); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad"); expect(view.model.messages.at(0).get('origin_id')).toBe(msg.get('origin_id')); done(); })); it("can cause a delivery receipt to be returned", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { await test_utils.waitForRoster(_converse, 'current'); const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const textarea = view.el.querySelector('textarea.chat-textarea'); textarea.value = 'But soft, what light through yonder airlock breaks?'; view.onKeyDown({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); const msg_obj = view.model.messages.at(0); const stanza = u.toStanza(` `); spyOn(_converse.api, "trigger").and.callThrough(); spyOn(view.model, "isReceipt").and.callThrough(); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.isReceipt.calls.count() === 1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); done(); })); it("can cause a chat marker to be returned", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { await test_utils.waitForRoster(_converse, 'current'); const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); const textarea = view.el.querySelector('textarea.chat-textarea'); textarea.value = 'But soft, what light through yonder airlock breaks?'; view.onKeyDown({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelector('.chat-msg .chat-msg__body').textContent.trim()) .toBe("But soft, what light through yonder airlock breaks?"); const msg_obj = view.model.messages.at(0); let stanza = u.toStanza(` `); spyOn(view.model, "isChatMarker").and.callThrough(); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.isChatMarker.calls.count() === 1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); stanza = u.toStanza(` `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.isChatMarker.calls.count() === 2); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); stanza = u.toStanza(` `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.isChatMarker.calls.count() === 3); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); stanza = u.toStanza(` 'tis I! `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await u.waitUntil(() => view.model.isChatMarker.calls.count() === 4); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0); done(); })); describe("when received", function () { it("highlights all users mentioned via XEP-0372 references", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom'); const view = _converse.api.chatviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( $pres({ 'to': 'tom@montague.lit/resource', 'from': `lounge@montague.lit/${nick}` }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': `${nick}@montague.lit/resource`, 'role': 'participant' })) ); }); const msg = $msg({ from: 'lounge@montague.lit/gibson', id: (new Date()).getTime(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('hello z3r0 tom mr.robot, how are you?').up() .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up() .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up() .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); const messages = view.el.querySelectorAll('.chat-msg__text'); expect(messages.length).toBe(1); expect(messages[0].classList.length).toEqual(1); expect(messages[0].innerHTML).toBe( 'hello z3r0 '+ 'tom '+ 'mr.robot, how are you?'); done(); })); }); describe("in which someone is mentioned", function () { it("gets parsed for mentions which get turned into references", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom'); const view = _converse.api.chatviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh', 'Link Mauve'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( $pres({ 'to': 'tom@montague.lit/resource', 'from': `lounge@montague.lit/${nick}` }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`, 'role': 'participant' }))); }); // Run a few unit tests for the parseTextForReferences method let [text, references] = view.model.parseTextForReferences('hello z3r0') expect(references.length).toBe(0); expect(text).toBe('hello z3r0'); [text, references] = view.model.parseTextForReferences('hello @z3r0') expect(references.length).toBe(1); expect(text).toBe('hello z3r0'); expect(JSON.stringify(references)) .toBe('[{"begin":6,"end":10,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"}]'); [text, references] = view.model.parseTextForReferences('hello @some1 @z3r0 @gibson @mr.robot, how are you?') expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?'); expect(JSON.stringify(references)) .toBe('[{"begin":13,"end":17,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"},'+ '{"begin":18,"end":24,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"},'+ '{"begin":25,"end":33,"value":"mr.robot","type":"mention","uri":"xmpp:mr.robot@montague.lit"}]'); [text, references] = view.model.parseTextForReferences('yo @gib') expect(text).toBe('yo @gib'); expect(references.length).toBe(0); [text, references] = view.model.parseTextForReferences('yo @gibsonian') expect(text).toBe('yo @gibsonian'); expect(references.length).toBe(0); [text, references] = view.model.parseTextForReferences('@gibson') expect(text).toBe('gibson'); expect(references.length).toBe(1); expect(JSON.stringify(references)) .toBe('[{"begin":0,"end":6,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]'); [text, references] = view.model.parseTextForReferences('hi @Link Mauve how are you?') expect(text).toBe('hi Link Mauve how are you?'); expect(references.length).toBe(1); expect(JSON.stringify(references)) .toBe('[{"begin":3,"end":13,"value":"Link Mauve","type":"mention","uri":"xmpp:Link-Mauve@montague.lit"}]'); [text, references] = view.model.parseTextForReferences('https://example.org/@gibson') expect(text).toBe('https://example.org/@gibson'); expect(references.length).toBe(0); expect(JSON.stringify(references)) .toBe('[]'); [text, references] = view.model.parseTextForReferences('mail@gibson.com') expect(text).toBe('mail@gibson.com'); expect(references.length).toBe(0); expect(JSON.stringify(references)) .toBe('[]'); [text, references] = view.model.parseTextForReferences( 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr') expect(text).toBe( 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr'); expect(references.length).toBe(0); expect(JSON.stringify(references)) .toBe('[]'); done(); })); it("parses for mentions as indicated with an @ preceded by a space or at the start of the text", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom'); const view = _converse.api.chatviews.get(muc_jid); ['NotAnAdress', 'darnuria'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( $pres({ 'to': 'tom@montague.lit/resource', 'from': `lounge@montague.lit/${nick}` }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`, 'role': 'participant' }))); }); // Test that we don't match @nick in email adresses. let [text, references] = view.model.parseTextForReferences('contact contact@NotAnAdress.eu'); expect(references.length).toBe(0); expect(text).toBe('contact contact@NotAnAdress.eu'); // Test that we don't match @nick in url [text, references] = view.model.parseTextForReferences('nice website https://darnuria.eu/@darnuria'); expect(references.length).toBe(0); expect(text).toBe('nice website https://darnuria.eu/@darnuria'); done(); })); it("can get corrected and given new references", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom'); const view = _converse.api.chatviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( $pres({ 'to': 'tom@montague.lit/resource', 'from': `lounge@montague.lit/${nick}` }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': `${nick}@montague.lit/resource`, 'role': 'participant' }))); }); const textarea = view.el.querySelector('textarea.chat-textarea'); textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?' const enter_event = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'stopPropagation': function stopPropagation () {}, 'keyCode': 13 // Enter } spyOn(_converse.connection, 'send'); view.onKeyDown(enter_event); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); const msg = _converse.connection.send.calls.all()[0].args[0]; expect(msg.toLocaleString()) .toBe(``+ `hello z3r0 gibson mr.robot, how are you?`+ ``+ ``+ ``+ ``+ ``+ ``); const first_msg = view.model.messages.findWhere({'message': 'hello z3r0 gibson mr.robot, how are you?'}); const action = view.el.querySelector('.chat-msg .chat-msg__action'); action.style.opacity = 1; action.click(); expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?'); expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500); textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?'; view.onKeyDown(enter_event); await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === 'hello z3r0 gibson sw0rdf1sh, how are you?', 500); const correction = _converse.connection.send.calls.all()[2].args[0]; expect(correction.toLocaleString()) .toBe(``+ `hello z3r0 gibson sw0rdf1sh, how are you?`+ ``+ ``+ ``+ ``+ ``+ ``+ ``); done(); })); it("includes XEP-0372 references to that person", mock.initConverse( null, ['rosterGroupsFetched'], {}, async function (done, _converse) { const muc_jid = 'lounge@montague.lit'; await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.api.chatviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.connection._dataRecv(test_utils.createRequest( $pres({ 'to': 'tom@montague.lit/resource', 'from': `lounge@montague.lit/${nick}` }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': `${nick}@montague.lit/resource`, 'role': 'participant' }))); }); spyOn(_converse.connection, 'send'); const textarea = view.el.querySelector('textarea.chat-textarea'); textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?' const enter_event = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'stopPropagation': function stopPropagation () {}, 'keyCode': 13 // Enter } view.onKeyDown(enter_event); const msg = _converse.connection.send.calls.all()[0].args[0]; expect(msg.toLocaleString()) .toBe(``+ `hello z3r0 gibson mr.robot, how are you?`+ ``+ ``+ ``+ ``+ ``+ ``); done(); })); }); }); }));