diff --git a/spec/muc.js b/spec/muc.js index edef30e71..088ff47ee 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -1633,23 +1633,21 @@ describe("Groupchats", function () { async function (done, _converse) { await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - var view = _converse.chatboxviews.get('lounge@montague.lit'), - occupants = view.el.querySelector('.occupant-list'); - var presence; + var view = _converse.chatboxviews.get('lounge@montague.lit'); + const occupants = view.el.querySelector('.occupant-list'); for (var i=0; i-1; i--) { const name = mock.chatroom_names[i]; // See example 21 https://xmpp.org/extensions/xep-0045.html#enter-pres - presence = $pres({ + const presence = $pres({ to:'romeo@montague.lit/pda', from:'lounge@montague.lit/'+name, type: 'unavailable' }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ - affiliation: mock.chatroom_roles[name].affiliation, + affiliation: "none", jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', role: 'none' - }).nodeTree; + }); _converse.connection._dataRecv(mock.createRequest(presence)); expect(occupants.querySelectorAll('li').length).toBe(i+1); } @@ -5308,7 +5306,7 @@ describe("Groupchats", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = u.toStanza(` + let stanza = u.toStanza(` This message will trigger a presence probe `); @@ -5316,21 +5314,21 @@ describe("Groupchats", function () { const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => view.model.messages.length); - const occupant = view.model.messages.at(0)?.occupant; + let occupant = view.model.messages.at(0)?.occupant; expect(occupant).toBeDefined(); expect(occupant.get('nick')).toBe('ralphm'); expect(occupant.get('affiliation')).toBeUndefined(); expect(occupant.get('role')).toBeUndefined(); const sent_stanzas = _converse.connection.sent_stanzas; - const probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches('presence[type="probe"]')).pop()); + let probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches('presence[type="probe"]')).pop()); expect(Strophe.serialize(probe)).toBe( ``+ `0`+ ``+ ``); - const presence = u.toStanza( + let presence = u.toStanza( ` @@ -5338,6 +5336,39 @@ describe("Groupchats", function () { `); _converse.connection._dataRecv(mock.createRequest(presence)); + expect(occupant.get('affiliation')).toBe('member'); + expect(occupant.get('role')).toBe('participant'); + + // Check that unavailable but affiliated occupants don't get destroyed + stanza = u.toStanza(` + + This message from an unavailable user will trigger a presence probe + `); + _converse.connection._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => view.model.messages.length === 2); + occupant = view.model.messages.at(1)?.occupant; + expect(occupant).toBeDefined(); + expect(occupant.get('nick')).toBe('gonePhising'); + expect(occupant.get('affiliation')).toBeUndefined(); + expect(occupant.get('role')).toBeUndefined(); + + probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/gonePhising"]`)).pop()); + expect(Strophe.serialize(probe)).toBe( + ``+ + `0`+ + ``+ + ``); + + presence = u.toStanza( + ` + + + + `); + _converse.connection._dataRecv(mock.createRequest(presence)); + + expect(view.model.occupants.length).toBe(3); expect(occupant.get('affiliation')).toBe('member'); expect(occupant.get('role')).toBe('participant'); done(); diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index 918c9b215..25a295a6b 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -1612,16 +1612,14 @@ converse.plugins.add('converse-muc', { return true; } const occupant = this.occupants.findOccupant(data); - if (data.type === 'unavailable' && occupant) { - if (!data.states.includes(converse.MUC_NICK_CHANGED_CODE) && !occupant.isMember()) { - // We only destroy the occupant if this is not a nickname change operation. - // and if they're not on the member lists. - // Before destroying we set the new data, so - // that we can show the disconnection message. - occupant.set(data); - occupant.destroy(); - return; - } + // Destroy an unavailable occupant if this isn't a nick change operation and if they're not affiliated + if (data.type === 'unavailable' && occupant && + !data.states.includes(converse.MUC_NICK_CHANGED_CODE) && + !['admin', 'owner', 'member'].includes(data['affiliation'])) { + // Before destroying we set the new data, so that we can show the disconnection message + occupant.set(data); + occupant.destroy(); + return; } const jid = data.jid || ''; const attributes = Object.assign(data, {