UNPKG

converse.js

Version:
873 lines (761 loc) 145 kB
/*global mock, converse */ const { $pres, Strophe, sizzle, stx, u } = converse.env; describe("Groupchats", function () { beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza })); describe("An instant groupchat", function () { it("will be created when muc_instant_rooms is set to true", mock.initConverse(['chatBoxesFetched'], { vcard: { nickname: '' } }, async function (_converse) { let IQ_stanzas = _converse.api.connection.get().IQ_stanzas; const { api } = _converse; const muc_jid = 'lounge@montague.lit'; const nick = 'nicky'; const promise = api.rooms.open(muc_jid); await mock.waitForMUCDiscoInfo(_converse, muc_jid); await mock.waitForReservedNick(_converse, muc_jid, ''); const muc = await promise; await muc.initialized; spyOn(muc, 'join').and.callThrough(); const view = _converse.chatboxviews.get(muc_jid); const input = await u.waitUntil(() => view.querySelector('input[name="nick"]'), 1000); expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED); input.value = nick; view.querySelector('input[type=submit]').click(); expect(view.model.join).toHaveBeenCalled(); await mock.waitForNewMUCDiscoInfo(_converse, muc_jid); _converse.api.connection.get().IQ_stanzas = []; await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); // The user has just entered the room (because join was called) // and receives their own presence from the server. // See example 24: // https://xmpp.org/extensions/xep-0045.html#enter-pres const presence = stx`<presence id="5025e055-036c-4bc5-a227-706e7e352053" to="romeo@montague.lit/orchard" from="lounge@montague.lit/nicky" xmlns="jabber:client"> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="owner" jid="romeo@montague.lit/orchard" role="moderator"/> <status code="110"/> <status code="201"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED); await mock.returnMemberLists(_converse, muc_jid); const num_info_msgs = await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length); expect(num_info_msgs).toBe(1); const info_texts = Array.from(view.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent.trim()); expect(info_texts[0]).toBe('A new groupchat has been created'); const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); expect(csntext.trim()).toEqual("nicky has entered the groupchat"); // An instant room is created by saving the default configuratoin. const selector = `query[xmlns="${Strophe.NS.MUC_OWNER}"]`; IQ_stanzas = _converse.api.connection.get().IQ_stanzas; const iq = await u.waitUntil(() => IQ_stanzas.filter((s) => sizzle(selector, s).length).pop()); expect(iq).toEqualStanza(stx` <iq id="${iq.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client"> <query xmlns="http://jabber.org/protocol/muc#owner"> <x type="submit" xmlns="jabber:x:data"/> </query> </iq>`); })); }); describe("A Groupchat", function () { it("will be visible when opened as the first chat in fullscreen-view", mock.initConverse(['discoInitialized'], { 'view_mode': 'fullscreen', 'auto_join_rooms': ['orchard@chat.shakespeare.lit']}, async function (_converse) { const { api } = _converse; const muc_jid = 'orchard@chat.shakespeare.lit'; api.rooms.get(muc_jid); await mock.waitForMUCDiscoInfo(_converse, muc_jid); await mock.waitForReservedNick(_converse, muc_jid, 'romeo'); await mock.receiveOwnMUCPresence(_converse, muc_jid, 'romeo'); await api.waitUntil('roomsAutoJoined'); const room = await u.waitUntil(() => _converse.chatboxes.get(muc_jid)); expect(room.get('hidden')).toBe(false); })); it("Can be configured to show cached messages before being joined", mock.initConverse(['discoInitialized'], { muc_show_logs_before_join: true, archived_messages_page_size: 2, muc_nickname_from_jid: false, muc_clear_messages_on_leave: false, vcard: { nickname: '' }, }, async function (_converse) { const { api } = _converse; const muc_jid = 'orchard@chat.shakespeare.lit'; const nick = 'romeo'; api.rooms.open(muc_jid); await mock.waitForMUCDiscoInfo(_converse, muc_jid); await mock.waitForReservedNick(_converse, muc_jid); const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid)); await view.model.messages.fetched; view.model.messages.create({ 'type': 'groupchat', 'to': muc_jid, 'from': `${_converse.bare_jid}/orchard`, 'body': 'Hello world', 'message': 'Hello world', 'time': '2021-02-02T12:00:00Z' }); expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED); await u.waitUntil(() => view.querySelectorAll('converse-chat-message').length === 1); const sel = 'converse-message-history converse-chat-message .chat-msg__text'; await u.waitUntil(() => view.querySelector(sel)?.textContent.trim()); expect(view.querySelector(sel).textContent.trim()).toBe('Hello world') const nick_input = await u.waitUntil(() => view.querySelector('[name="nick"]')); nick_input.value = nick; view.querySelector('.muc-nickname-form input[type="submit"]').click(); _converse.api.connection.get().IQ_stanzas = []; await mock.waitForMUCDiscoInfo(_converse, muc_jid); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); await mock.receiveOwnMUCPresence(_converse, muc_jid, nick); })); it("maintains its state across reloads", mock.initConverse([], { clear_messages_on_reconnection: true, enable_smacks: false }, async function (_converse) { const { api } = _converse; const nick = 'romeo'; const sent_IQs = _converse.api.connection.get().IQ_stanzas; const muc_jid = 'lounge@montague.lit' await mock.openAndEnterMUC(_converse, muc_jid, nick, [], []); const view = _converse.chatboxviews.get(muc_jid); let iq_get = await u.waitUntil(() => sent_IQs.filter((iq) => sizzle(`query[xmlns="${Strophe.NS.MAM}"]`, iq).length).pop()); expect(iq_get).toEqualStanza(stx` <iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client"> <query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}"> <set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set> </query> </iq>`); const first_msg_id = _converse.api.connection.get().getUniqueId(); const last_msg_id = _converse.api.connection.get().getUniqueId(); _converse.api.connection.get()._dataRecv(mock.createRequest( stx`<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="${muc_jid}"> <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}"> <forwarded xmlns="urn:xmpp:forward:0"> <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:15:23Z"/> <message from="${muc_jid}/some1" type="groupchat"> <body>1st Message</body> </message> </forwarded> </result> </message>`)); let message = stx`<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="${muc_jid}"> <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}"> <forwarded xmlns="urn:xmpp:forward:0"> <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:16:23Z"/> <message from="${muc_jid}/some1" type="groupchat"> <body>2nd Message</body> </message> </forwarded> </result> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); const result = stx`<iq type='result' id='${iq_get.getAttribute('id')}' xmlns="jabber:client"> <fin xmlns='urn:xmpp:mam:2' complete="true"> <set xmlns='http://jabber.org/protocol/rsm'> <first index='0'>${first_msg_id}</first> <last>${last_msg_id}</last> <count>2</count> </set> </fin> </iq>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); while (sent_IQs.length) { sent_IQs.pop(); } // Clear so that we don't match the older query await _converse.api.connection.reconnect(); await mock.waitForMUCDiscoInfo(_converse, muc_jid, []); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); // The user has just entered the room (because join was called) // and receives their own presence from the server. // See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres await mock.receiveOwnMUCPresence(_converse, muc_jid, nick); message = stx` <message xmlns="jabber:client" type="groupchat" id="918172de-d5c5-4da4-b388-446ef4a05bec" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet"> <body>Wherefore art though?</body> <active xmlns="http://jabber.org/protocol/chatstates"/> <origin-id xmlns="urn:xmpp:sid:0" id="918172de-d5c5-4da4-b388-446ef4a05bec"/> <stanza-id xmlns="urn:xmpp:sid:0" id="88cc9c93-a8f4-4dd5-b02a-d19855eb6303" by="${muc_jid}"/> <delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:47Z" from="juliet@shakespeare.lit"/> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); message = stx` <message xmlns="jabber:client" type="groupchat" id="awQo6a-mi-Wa6NYh" to="${_converse.jid}" from="${muc_jid}/ews000" xml:lang="en"> <composing xmlns="http://jabber.org/protocol/chatstates"/> <no-store xmlns="urn:xmpp:hints"/> <no-permanent-store xmlns="urn:xmpp:hints"/> <delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:54Z" from="juliet@shakespeare.lit"/> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); const affs = api.settings.get('muc_fetch_members'); const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []); await mock.returnMemberLists(_converse, muc_jid, [], all_affiliations); iq_get = await u.waitUntil(() => sent_IQs.filter((iq) => sizzle(`query[xmlns="${Strophe.NS.MAM}"]`, iq).length).pop()); expect(iq_get).toEqualStanza(stx` <iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client"> <query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}"> <x xmlns="jabber:x:data" type="submit"> <field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field> <field var="start"><value>2020-07-14T17:46:47.000Z</value></field> </x> <set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set> </query> </iq>`); })); it("shows a new messages indicator when you're scrolled up", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterMUC(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); const message = stx` <message xmlns="jabber:client" type="groupchat" id="918172de-d5c5-4da4-b388-446ef4a05bec" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet"> <body>Wherefore art though?</body> <active xmlns="http://jabber.org/protocol/chatstates"/> <origin-id xmlns="urn:xmpp:sid:0" id="918172de-d5c5-4da4-b388-446ef4a05bec"/> <stanza-id xmlns="urn:xmpp:sid:0" id="88cc9c93-a8f4-4dd5-b02a-d19855eb6303" by="${muc_jid}"/> <delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:47Z" from="juliet@shakespeare.lit"/> </message>`; view.model.ui.set('scrolled', true); // hack _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => view.model.messages.length); const chat_new_msgs_indicator = await u.waitUntil(() => view.querySelector('.new-msgs-indicator')); chat_new_msgs_indicator.click(); expect(view.model.ui.get('scrolled')).toBeFalsy(); await u.waitUntil(() => !u.isVisible(chat_new_msgs_indicator)); })); describe("topic", function () { it("is shown the header", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterMUC(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; let stanza = stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>${text}</subject> <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/> <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const view = _converse.chatboxviews.get('jdev@conference.jabber.org'); await new Promise(resolve => view.model.once('change:subject', resolve)); const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc'), 1000); expect(head_desc?.textContent.trim()).toBe(text); stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>This is a message subject</subject> <body>This is a message</body> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); expect(sizzle('.chat-msg__subject', view).length).toBe(1); expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__text').length).toBe(1); expect(sizzle('.chat-msg__text').pop().textContent.trim()).toBe('This is a message'); expect(view.querySelector('.chat-head__desc').textContent.trim()).toBe(text); })); it("can be toggled by the user", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterMUC(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; let stanza = stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>${text}</subject> <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/> <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const view = _converse.chatboxviews.get('jdev@conference.jabber.org'); await new Promise(resolve => view.model.once('change:subject', resolve)); const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc')); expect(head_desc?.textContent.trim()).toBe(text); stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>This is a message subject</subject> <body>This is a message</body> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); expect(sizzle('.chat-msg__subject', view).length).toBe(1); expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__text').length).toBe(1); expect(sizzle('.chat-msg__text').pop().textContent.trim()).toBe('This is a message'); const topic_el = view.querySelector('.chat-head__desc'); expect(topic_el.textContent.trim()).toBe(text); expect(u.isVisible(topic_el)).toBe(true); await u.waitUntil(() => view.querySelector('.hide-topic').textContent.trim() === 'Hide topic'); const toggle = view.querySelector('.hide-topic'); expect(toggle.textContent.trim()).toBe('Hide topic'); toggle.click(); await u.waitUntil(() => view.querySelector('.hide-topic').textContent.trim() === 'Show topic'); })); it("will always be shown when it's new", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterMUC(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; let stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>${text}</subject> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const view = _converse.chatboxviews.get('jdev@conference.jabber.org'); await new Promise(resolve => view.model.once('change:subject', resolve)); const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc')); expect(head_desc?.textContent.trim()).toBe(text); let topic_el = view.querySelector('.chat-head__desc'); expect(topic_el.textContent.trim()).toBe(text); expect(u.isVisible(topic_el)).toBe(true); const toggle = view.querySelector('.hide-topic'); expect(toggle.textContent.trim()).toBe('Hide topic'); toggle.click(); await u.waitUntil(() => !u.isVisible(topic_el)); stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>Another topic</subject> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => u.isVisible(view.querySelector('.chat-head__desc'))); topic_el = view.querySelector('.chat-head__desc'); expect(topic_el.textContent.trim()).toBe('Another topic'); })); it("causes an info message to be shown when received in real-time", mock.initConverse([], {}, async function (_converse) { spyOn(_converse.ChatRoom.prototype, 'handleSubjectChange').and.callThrough(); await mock.openAndEnterMUC(_converse, 'jdev@conference.jabber.org', 'romeo'); const view = _converse.chatboxviews.get('jdev@conference.jabber.org'); _converse.api.connection.get()._dataRecv(mock.createRequest(stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>This is an older topic</subject> <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/> <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/> </message>`)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count()); expect(sizzle('.chat-info__message', view).length).toBe(0); const desc = await u.waitUntil(() => view.querySelector('.chat-head__desc')); expect(desc.textContent.trim()).toBe('This is an older topic'); _converse.api.connection.get()._dataRecv(mock.createRequest(stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>This is a new topic</subject> </message>`)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 2); await u.waitUntil(() => sizzle('.chat-info__message', view).pop()?.textContent.trim() === 'Topic set by ralphm'); await u.waitUntil(() => desc.textContent.trim() === 'This is a new topic'); // Doesn't show multiple subsequent topic change notifications _converse.api.connection.get()._dataRecv(mock.createRequest(stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm"> <subject>Yet another topic</subject> </message>`)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 3); await u.waitUntil(() => desc.textContent.trim() === 'Yet another topic'); expect(sizzle('.chat-info__message', view).length).toBe(1); // Sow multiple subsequent topic change notification from someone else _converse.api.connection.get()._dataRecv(mock.createRequest(stx` <message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/some1"> <subject>Some1's topic</subject> </message>`)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 4); await u.waitUntil(() => desc.textContent.trim() === "Some1's topic"); expect(sizzle('.chat-info__message', view).length).toBe(2); const el = sizzle('.chat-info__message', view).pop(); expect(el.textContent.trim()).toBe('Topic set by some1'); // Removes current topic const stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/some1"> <subject/> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 5); await u.waitUntil(() => view.querySelector('.chat-head__desc') === null); await u.waitUntil(() => view.querySelector('converse-chat-message:last-child .chat-info').textContent.trim() === "Topic cleared by some1"); })); }); it("restores cached messages when it reconnects and clear_messages_on_reconnection and muc_clear_messages_on_leave are false", mock.initConverse([], { 'clear_messages_on_reconnection': false, 'muc_clear_messages_on_leave': false }, async function (_converse) { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterMUC(_converse, muc_jid , 'romeo'); const model = _converse.chatboxes.get(muc_jid); const message = 'Hello world'; const nick = mock.chatroom_names[0]; const msg = stx`<message from="lounge@montague.lit/${nick}" id="${u.getUniqueId()}" to="romeo@montague.lit" type="groupchat" xmlns="jabber:client"> <body>${message}</body> </message>`; await model.handleMessageStanza(msg); await u.waitUntil(() => document.querySelector('converse-chat-message')); await model.close(); await u.waitUntil(() => !document.querySelector('converse-chat-message')); _converse.api.connection.get().IQ_stanzas = []; await mock.openAndEnterMUC(_converse, muc_jid , 'romeo'); await u.waitUntil(() => document.querySelector('converse-chat-message')); expect(model.messages.length).toBe(1); expect(document.querySelectorAll('converse-chat-message').length).toBe(1); })); it("clears cached messages when it reconnects and clear_messages_on_reconnection is true", mock.initConverse([], {'clear_messages_on_reconnection': true}, async function (_converse) { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterMUC(_converse, muc_jid , 'romeo'); const view = _converse.chatboxviews.get(muc_jid); const message = 'Hello world'; const nick = mock.chatroom_names[0]; const msg = stx`<message from="lounge@montague.lit/${nick}" id="${u.getUniqueId()}" to="romeo@montague.lit" type="groupchat" xmlns="jabber:client"> <body>${message}</body> </message>`; await view.model.handleMessageStanza(msg); await view.model.close(); _converse.api.connection.get().IQ_stanzas = []; await mock.openAndEnterMUC(_converse, muc_jid , 'romeo'); expect(view.model.messages.length).toBe(0); expect(view.querySelector('converse-chat-history')).toBe(null); })); it("is opened when an xmpp: URI is clicked inside another groupchat", mock.initConverse([], {}, async function (_converse) { await mock.waitForRoster(_converse, 'current'); await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo'); const view = _converse.chatboxviews.get('lounge@montague.lit'); if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); } expect(_converse.chatboxes.length).toEqual(2); const message = 'Please go to xmpp:coven@chat.shakespeare.lit?join'; const nick = mock.chatroom_names[0]; const msg = stx`<message from="lounge@montague.lit/${nick}" id="${u.getUniqueId()}" type="groupchat" to="romeo@montague.lit" xmlns="jabber:client"> <body>${message}</body> </message>`; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelector('.chat-msg__text a')); view.querySelector('.chat-msg__text a').click(); await mock.waitForMUCDiscoInfo(_converse, 'coven@chat.shakespeare.lit'); await mock.waitForReservedNick(_converse, 'coven@chat.shakespeare.lit', 'romeo'); await u.waitUntil(() => _converse.chatboxes.length === 3) expect(_converse.chatboxes.pluck('id').includes('coven@chat.shakespeare.lit')).toBe(true); })); it("shows a notification if it's not anonymous", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { const muc_jid = 'coven@chat.shakespeare.lit'; const nick = 'romeo'; _converse.api.rooms.open(muc_jid); await mock.waitForMUCDiscoInfo(_converse, muc_jid); await mock.waitForReservedNick(_converse, muc_jid, nick); const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid)); const presence = stx`<presence to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/some1" xmlns="jabber:client"> <x xmlns="${Strophe.NS.MUC_USER}"> <item affiliation="owner" jid="romeo@montague.lit/_converse.js-29092160" role="moderator"/> <status code="110"/> <status code="100"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED)); await mock.returnMemberLists(_converse, muc_jid, [], ['member', 'admin', 'owner']); const num_info_msgs = await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length); expect(num_info_msgs).toBe(1); expect(sizzle('div.chat-info', view).pop().textContent.trim()).toBe("This groupchat is not anonymous"); const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); expect(csntext.trim()).toEqual("some1 has entered the groupchat"); })); it("shows join/leave messages when users enter or exit a groupchat", mock.initConverse(['chatBoxesFetched'], {'muc_fetch_members': false}, async function (_converse) { const muc_jid = 'coven@chat.shakespeare.lit'; const nick = 'some1'; const room_creation_promise = _converse.api.rooms.open(muc_jid, {nick}); await mock.waitForMUCDiscoInfo(_converse, muc_jid); const sent_stanzas = _converse.api.connection.get().sent_stanzas; await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length)); await _converse.api.waitUntil('chatRoomViewInitialized'); /* We don't show join/leave messages for existing occupants. We * know about them because we receive their presences before we * receive our own. */ let presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/oldguy' }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'oldguy@montague.lit/_converse.js-290929789', 'role': 'participant' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); /* <presence to="romeo@montague.lit/_converse.js-29092160" * from="coven@chat.shakespeare.lit/some1"> * <x xmlns="http://jabber.org/protocol/muc#user"> * <item affiliation="owner" jid="romeo@montague.lit/_converse.js-29092160" role="moderator"/> * <status code="110"/> * </x> * </presence></body> */ presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/some1' }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'owner', 'jid': 'romeo@montague.lit/_converse.js-29092160', 'role': 'moderator' }).up() .c('status', {code: '110'}); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const view = await u.waitUntil(() => _converse.chatboxviews.get('coven@chat.shakespeare.lit')); const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications')?.textContent); expect(csntext.trim()).toEqual("some1 has entered the groupchat"); await room_creation_promise; await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED)); await view.model.messages.fetched; presence = $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' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newguy have entered the groupchat"); const msg = stx`<message from="coven@chat.shakespeare.lit/some1" to="romeo@montague.lit" id="${u.getUniqueId()}" type="groupchat" xmlns="jabber:client"> <body>hello world</body> </message>`; _converse.api.connection.get()._dataRecv(mock.createRequest(msg)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); // Add another entrant, otherwise the above message will be // collapsed if "newguy" leaves immediately again presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/newgirl' }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newgirl@montague.lit/_converse.js-213098781', 'role': 'participant' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1, newguy and newgirl have entered the groupchat"); // Don't show duplicate join messages presence = $pres({ to: 'romeo@montague.lit/_converse.js-290918392', 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' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); /* <presence * from='coven@chat.shakespeare.lit/thirdwitch' * to='crone1@shakespeare.lit/desktop' * type='unavailable'> * <status>Disconnected: Replaced by new connection</status> * <x xmlns='http://jabber.org/protocol/muc#user'> * <item affiliation='member' * jid='hag66@shakespeare.lit/pda' * role='none'/> * </x> * </presence> */ presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', type: 'unavailable', from: 'coven@chat.shakespeare.lit/newguy' }) .c('status', 'Disconnected: Replaced by new connection').up() .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newguy@montague.lit/_converse.js-290929789', 'role': 'none' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newgirl have entered the groupchat\nnewguy has left the groupchat"); // When the user immediately joins again, we collapse the // multiple join/leave messages. presence = $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' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1, newgirl and newguy have entered the groupchat"); presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', type: 'unavailable', 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': 'none' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newgirl have entered the groupchat\nnewguy has left the groupchat"); presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/nomorenicks' }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'nomorenicks@montague.lit/_converse.js-290929789', 'role': 'participant' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1, newgirl and nomorenicks have entered the groupchat\nnewguy has left the groupchat"); presence = $pres({ to: 'romeo@montague.lit/_converse.js-290918392', type: 'unavailable', from: 'coven@chat.shakespeare.lit/nomorenicks' }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'nomorenicks@montague.lit/_converse.js-290929789', 'role': 'none' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newgirl have entered the groupchat\nnewguy and nomorenicks have left the groupchat"); presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', from: 'coven@chat.shakespeare.lit/nomorenicks' }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'nomorenicks@montague.lit/_converse.js-290929789', 'role': 'participant' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1, newgirl and nomorenicks have entered the groupchat\nnewguy has left the groupchat"); // Test a member joining and leaving presence = $pres({ to: 'romeo@montague.lit/_converse.js-290918392', from: 'coven@chat.shakespeare.lit/insider' }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'member', 'jid': 'insider@montague.lit/_converse.js-290929789', 'role': 'participant' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); /* <presence * from='coven@chat.shakespeare.lit/thirdwitch' * to='crone1@shakespeare.lit/desktop' * type='unavailable'> * <status>Disconnected: Replaced by new connection</status> * <x xmlns='http://jabber.org/protocol/muc#user'> * <item affiliation='member' * jid='hag66@shakespeare.lit/pda' * role='none'/> * </x> * </presence> */ presence = $pres({ to: 'romeo@montague.lit/_converse.js-29092160', type: 'unavailable', from: 'coven@chat.shakespeare.lit/insider' }) .c('status', 'Disconnected: Replaced by new connection').up() .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'member', 'jid': 'insider@montague.lit/_converse.js-290929789', 'role': 'none' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1, newgirl and nomorenicks have entered the groupchat\nnewguy and insider have left the groupchat"); expect(view.model.occupants.length).toBe(5); expect(view.model.occupants.findWhere({'jid': 'insider@montague.lit'}).get('presence')).toBe('offline'); // New girl leaves presence = $pres({ 'to': 'romeo@montague.lit/_converse.js-29092160', 'type': 'unavailable', 'from': 'coven@chat.shakespeare.lit/newgirl' }) .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newgirl@montague.lit/_converse.js-213098781', 'role': 'none' }); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and nomorenicks have entered the groupchat\nnewguy, insider and newgirl have left the groupchat"); expect(view.model.occupants.length).toBe(4); })); it("combines subsequent join/leave messages when users enter or exit a groupchat", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { await mock.openAndEnterMUC(_converse, 'coven@chat.shakespeare.lit', 'romeo') const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo has entered the groupchat"); let presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fabio"> <c xmlns="http://jabber.org/protocol/caps" node="http://conversations.im" ver="INI3xjRUioclBTP/aACfWi5m9UY=" hash="sha-1"/> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="participant"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and fabio have entered the groupchat"); presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide"> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="participant"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, fabio and Dele Olajide have entered the groupchat"); presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/jcbrand"> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="owner" jid="jc@opkode.com/converse.js-30645022" role="moderator"/> <status code="110"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, fabio and others have entered the groupchat"); presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide"> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="none"/> </x> </presence>`; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, fabio and jcbrand have entered the groupchat\nDele Olajide has left the groupchat"); presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide"> <x xmlns="http://jabber.org/protocol/muc#user"> <item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-74567907" role="participant"/>