Explorar o código

Associate `ChatRoomOccupant` to `ChatRoomMessage`

and use promises to indicate when an occupant or contact has been set
JC Brand %!s(int64=6) %!d(string=hai) anos
pai
achega
67bcc00f10

+ 1 - 1
.eslintrc.json

@@ -233,7 +233,7 @@
             }
         ],
         "prefer-numeric-literals": "error",
-        "prefer-promise-reject-errors": "error",
+        "prefer-promise-reject-errors": "off",
         "prefer-reflect": "off",
         "prefer-rest-params": "off",
         "prefer-spread": "off",

+ 8 - 7
spec/messages.js

@@ -2374,8 +2374,8 @@
             }).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('affiliation')).toBe('owner');
-            expect(view.model.messages.last().get('role')).toBe('moderator');
+            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__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author moderator');
 
@@ -2401,8 +2401,8 @@
             }).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().get('affiliation')).toBe('member');
-            expect(view.model.messages.last().get('role')).toBe('participant');
+            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__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant');
 
@@ -2422,8 +2422,8 @@
             view.model.sendMessage('hello world');
             await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
 
-            expect(view.model.messages.last().get('affiliation')).toBe('owner');
-            expect(view.model.messages.last().get('role')).toBe('moderator');
+            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__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author moderator');
             done();
@@ -2969,6 +2969,7 @@
                     async function (done, _converse) {
 
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'tom');
+
                 const view = _converse.api.chatviews.get('lounge@montague.lit');
                 ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
                     _converse.connection._dataRecv(test_utils.createRequest(
@@ -3023,7 +3024,7 @@
                 await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
                     'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
 
-                const correction = _converse.connection.send.calls.all()[1].args[0];
+                const correction = _converse.connection.send.calls.all()[2].args[0];
                 expect(correction.toLocaleString())
                     .toBe(`<message from="romeo@montague.lit/orchard" id="${correction.nodeTree.getAttribute("id")}" `+
                             `to="lounge@montague.lit" type="groupchat" `+

+ 8 - 8
spec/muc.js

@@ -1443,25 +1443,25 @@
                 await test_utils.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length)
                 expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1);
                 expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
-                var membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
+                const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
                 expect(membersonly.length).toBe(1);
                 expect(membersonly[0].getAttribute('type')).toBe('checkbox');
                 membersonly[0].checked = true;
 
-                var moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]');
+                const moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]');
                 expect(moderated.length).toBe(1);
                 expect(moderated[0].getAttribute('type')).toBe('checkbox');
                 moderated[0].checked = true;
 
-                var password = view.el.querySelectorAll('input[name="muc#roomconfig_roomsecret"]');
+                const password = view.el.querySelectorAll('input[name="muc#roomconfig_roomsecret"]');
                 expect(password.length).toBe(1);
                 expect(password[0].getAttribute('type')).toBe('password');
 
-                var allowpm = view.el.querySelectorAll('select[name="muc#roomconfig_allowpm"]');
+                const allowpm = view.el.querySelectorAll('select[name="muc#roomconfig_allowpm"]');
                 expect(allowpm.length).toBe(1);
                 allowpm[0].value = 'moderators';
 
-                var presencebroadcast = view.el.querySelectorAll('select[name="muc#roomconfig_presencebroadcast"]');
+                const presencebroadcast = view.el.querySelectorAll('select[name="muc#roomconfig_presencebroadcast"]');
                 expect(presencebroadcast.length).toBe(1);
                 presencebroadcast[0].value = ['moderator'];
 
@@ -4167,9 +4167,9 @@
 
                 // Check in reverse order that we requested all three lists
                 // (member, owner and admin).
-                var admin_iq_id = IQ_ids.pop();
-                var owner_iq_id = IQ_ids.pop();
-                var member_iq_id = IQ_ids.pop();
+                const admin_iq_id = IQ_ids.pop();
+                const owner_iq_id = IQ_ids.pop();
+                const member_iq_id = IQ_ids.pop();
 
                 expect(sent_IQs.pop().toLocaleString()).toBe(
                     `<iq id="${admin_iq_id}" to="coven@chat.shakespeare.lit" type="get" xmlns="jabber:client">`+

+ 41 - 81
spec/room_registration.js

@@ -78,90 +78,50 @@
             it("allows you to automatically register your nickname when joining a room",
                 mock.initConverse(
                     null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
-                    function (done, _converse) {
+                    async function (done, _converse) {
 
-                let view;
                 const IQ_stanzas = _converse.connection.IQ_stanzas;
                 const room_jid = 'coven@chat.shakespeare.lit';
-                _converse.api.rooms.open(room_jid, {'nick': 'romeo'})
-                .then(() => {
-                    return test_utils.waitUntil(() => _.filter(
-                        IQ_stanzas,
-                        iq => iq.querySelector(
-                            `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
-                        )).pop());
-                }).then(stanza => {
-                    const features_stanza = $iq({
-                        'from': room_jid,
-                        'id': stanza.getAttribute('id'),
-                        'to': 'romeo@montague.lit/desktop',
-                        'type': 'result'
-                    }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
-                        .c('identity', {
-                            'category': 'conference',
-                            'name': 'A Dark Cave',
-                            'type': 'text'
-                        }).up()
-                        .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
-                        .c('feature', {'var': 'jabber:iq:register'});
-                    _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
-                    view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
-                    return test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
-                }).then(stanza => {
-                    // 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 = $pres({
-                            to: _converse.connection.jid,
-                            from: room_jid,
-                            id: u.getUniqueId()
-                    }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
-                        .c('item').attrs({
-                            affiliation: 'owner',
-                            jid: _converse.bare_jid,
-                            role: 'moderator'
-                        }).up()
-                        .c('status').attrs({code:'110'});
-                    _converse.connection._dataRecv(test_utils.createRequest(presence));
-                    return test_utils.waitUntil(() => _.filter(
-                        _converse.connection.IQ_stanzas,
-                        iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
-                    ).pop());
-                }).then(stanza => {
-                    expect(Strophe.serialize(stanza))
-                    .toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
-                                `type="get" xmlns="jabber:client">`+
-                            `<query xmlns="jabber:iq:register"/></iq>`);
-                    view = _converse.chatboxviews.get(room_jid);
-                    const result = $iq({
-                        'from': view.model.get('jid'),
-                        'id': stanza.getAttribute('id'),
-                        'to': _converse.bare_jid,
-                        'type': 'result',
-                    }).c('query', {'type': 'jabber:iq:register'})
-                        .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
-                            .c('field', {
-                                'label': 'Desired Nickname',
-                                'type': 'text-single',
-                                'var': 'muc#register_roomnick'
-                            }).c('required');
-                    _converse.connection._dataRecv(test_utils.createRequest(result));
-                    return test_utils.waitUntil(() => _.filter(
-                        _converse.connection.IQ_stanzas,
-                        iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
-                    ).pop());
-                }).then(stanza => {
-                    expect(Strophe.serialize(stanza)).toBe(
-                        `<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
-                            `<query xmlns="jabber:iq:register">`+
-                                `<x type="submit" xmlns="jabber:x:data">`+
-                                    `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
-                                    `<field var="muc#register_roomnick"><value>romeo</value></field>`+
-                                `</x>`+
-                            `</query>`+
-                        `</iq>`);
-                    done();
-                }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
+                await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo');
+                const view = _converse.chatboxviews.get(room_jid);
+
+                let stanza = await test_utils.waitUntil(() => _.filter(
+                    _converse.connection.IQ_stanzas,
+                    iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
+                ).pop());
+
+                expect(Strophe.serialize(stanza))
+                .toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
+                            `type="get" xmlns="jabber:client">`+
+                        `<query xmlns="jabber:iq:register"/></iq>`);
+                const result = $iq({
+                    'from': view.model.get('jid'),
+                    'id': stanza.getAttribute('id'),
+                    'to': _converse.bare_jid,
+                    'type': 'result',
+                }).c('query', {'type': 'jabber:iq:register'})
+                    .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
+                        .c('field', {
+                            'label': 'Desired Nickname',
+                            'type': 'text-single',
+                            'var': 'muc#register_roomnick'
+                        }).c('required');
+                _converse.connection._dataRecv(test_utils.createRequest(result));
+                stanza = await test_utils.waitUntil(() => _.filter(
+                    _converse.connection.IQ_stanzas,
+                    iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
+                ).pop());
+
+                expect(Strophe.serialize(stanza)).toBe(
+                    `<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
+                        `<query xmlns="jabber:iq:register">`+
+                            `<x type="submit" xmlns="jabber:x:data">`+
+                                `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
+                                `<field var="muc#register_roomnick"><value>romeo</value></field>`+
+                            `</x>`+
+                        `</query>`+
+                    `</iq>`);
+                done();
             }));
         });
     });

+ 2 - 3
spec/spoilers.js

@@ -35,9 +35,8 @@
                     }).t(spoiler_hint)
                 .tree();
             await _converse.chatboxes.onMessage(msg);
-
-            await test_utils.waitUntil(() => _converse.api.chats.get().length === 2);
             const view = _converse.chatboxviews.get(sender_jid);
+            await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
             expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
             const message_content = view.el.querySelector('.chat-msg__text');
@@ -70,8 +69,8 @@
                       'xmlns': 'urn:xmpp:spoiler:0',
                     }).tree();
             await _converse.chatboxes.onMessage(msg);
-            await test_utils.waitUntil(() => _converse.api.chats.get().length === 2);
             const view = _converse.chatboxviews.get(sender_jid);
+            await new Promise((resolve, reject) => view.once('messageInserted', resolve));
             await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
             expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Mercutio')).toBeTruthy();
             const message_content = view.el.querySelector('.chat-msg__text');

+ 7 - 7
src/converse-chatview.js

@@ -171,10 +171,12 @@ converse.plugins.add('converse-chatview', {
                 if (this.model.vcard) {
                     this.model.vcard.on('change', this.debouncedRender, this);
                 }
-                this.model.on('rosterContactAdded', () => {
-                    this.model.contact.on('change:nickname', this.debouncedRender, this);
-                    this.debouncedRender();
-                });
+                if (this.model.rosterContactAdded) {
+                    this.model.rosterContactAdded.then(() => {
+                        this.model.contact.on('change:nickname', this.debouncedRender, this);
+                        this.debouncedRender();
+                    });
+                }
             },
 
             render () {
@@ -222,7 +224,7 @@ converse.plugins.add('converse-chatview', {
 
             initialize () {
                 _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
-                this.model.on('rosterContactAdded', this.registerContactEventHandlers, this);
+                this.model.rosterContactAdded.then(() => this.registerContactEventHandlers());
                 this.model.on('change', this.render, this);
                 this.registerContactEventHandlers();
                 /**
@@ -798,10 +800,8 @@ converse.plugins.add('converse-chatview', {
             async showMessage (message) {
                 const view = this.add(message.get('id'), new _converse.MessageView({'model': message}));
                 await view.render();
-
                 // Clear chat state notifications
                 sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content).forEach(u.removeElement);
-
                 this.insertMessage(view);
                 this.insertDayIndicator(view.el);
                 this.setScrollPosition(view.el);

+ 22 - 8
src/converse-message-view.js

@@ -89,13 +89,26 @@ converse.plugins.add('converse-message-view', {
                         this.render();
                     }
                 }, 50);
+
                 if (this.model.vcard) {
                     this.model.vcard.on('change', this.debouncedRender, this);
                 }
-                this.model.on('rosterContactAdded', () => {
-                    this.model.contact.on('change:nickname', this.debouncedRender, this);
-                    this.debouncedRender();
-                });
+
+                if (this.model.rosterContactAdded) {
+                    this.model.rosterContactAdded.then(() => {
+                        this.model.contact.on('change:nickname', this.debouncedRender, this);
+                        this.debouncedRender();
+                    });
+                }
+
+                if (this.model.occupantAdded) {
+                    this.model.occupantAdded.then(() => {
+                        this.model.occupant.on('change:role', this.debouncedRender, this);
+                        this.model.occupant.on('change:affiliation', this.debouncedRender, this);
+                        this.debouncedRender();
+                    });
+                }
+
                 this.model.on('change', this.onChanged, this);
                 this.model.on('destroy', this.fadeOut, this);
             },
@@ -170,16 +183,17 @@ converse.plugins.add('converse-message-view', {
             },
 
             async renderChatMessage () {
-                const is_me_message = this.isMeCommand(),
-                      time = dayjs(this.model.get('time')),
-                      role = this.model.vcard ? this.model.vcard.get('role') : null,
-                      roles = role ? role.split(',') : [];
+                const is_me_message = this.isMeCommand();
+                const time = dayjs(this.model.get('time'));
+                const role = this.model.vcard ? this.model.vcard.get('role') : null;
+                const roles = role ? role.split(',') : [];
 
                 const msg = u.stringToElement(tpl_message(
                     Object.assign(
                         this.model.toJSON(), {
                         '__': __,
                         'is_groupchat_message': this.model.get('type') === 'groupchat',
+                        'occupant': this.model.occupant,
                         'is_me_message': is_me_message,
                         'roles': roles,
                         'pretty_time': time.format(_converse.time_format),

+ 10 - 1
src/headless/converse-chatboxes.js

@@ -59,12 +59,16 @@ converse.plugins.add('converse-chatboxes', {
 
         const ModelWithContact = Backbone.Model.extend({
 
+            initialize () {
+                this.rosterContactAdded = u.getResolveablePromise();
+            },
+
             async setRosterContact (jid) {
                 const contact = await _converse.api.contacts.get(jid);
                 if (contact) {
                     this.contact = contact;
                     this.set('nickname', contact.get('nickname'));
-                    this.trigger('rosterContactAdded');
+                    this.rosterContactAdded.resolve();
                 }
             }
         });
@@ -88,10 +92,13 @@ converse.plugins.add('converse-chatboxes', {
             },
 
             initialize () {
+                ModelWithContact.prototype.initialize.apply(this, arguments);
+
                 if (this.get('type') === 'chat') {
                     this.setVCard();
                     this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
                 }
+
                 if (this.get('file')) {
                     this.on('change:put', this.uploadFile, this);
                 }
@@ -259,6 +266,8 @@ converse.plugins.add('converse-chatboxes', {
             },
 
             initialize () {
+                ModelWithContact.prototype.initialize.apply(this, arguments);
+
                 const jid = this.get('jid');
                 if (!jid) {
                     // XXX: The `validate` method will prevent this model

+ 45 - 24
src/headless/converse-muc.js

@@ -247,11 +247,38 @@ converse.plugins.add('converse-muc', {
          */
         _converse.ChatRoomMessage = _converse.Message.extend({
 
+            initialize () {
+                if (this.get('file')) {
+                    this.on('change:put', this.uploadFile, this);
+                }
+                if (this.isEphemeral()) {
+                    window.setTimeout(() => {
+                        try {
+                            this.destroy()
+                        } catch (e) {
+                            _converse.log(e, Strophe.LogLevel.ERROR);
+                        }
+                    }, 10000);
+                } else {
+                    this.occupantAdded = u.getResolveablePromise();
+                    this.setOccupant();
+                    this.setVCard();
+                }
+            },
+
+            setOccupant () {
+                const chatbox = _.get(this, 'collection.chatbox');
+                if (!chatbox) { return; }
+                const nick = Strophe.getResourceFromJid(this.get('from'));
+                this.occupant = chatbox.occupants.findWhere({'nick': nick});
+                this.occupantAdded.resolve();
+            },
+
             getVCardForChatroomOccupant () {
-                const chatbox = this.collection.chatbox,
-                      nick = Strophe.getResourceFromJid(this.get('from'));
+                const chatbox = _.get(this, 'collection.chatbox');
+                const nick = Strophe.getResourceFromJid(this.get('from'));
 
-                if (chatbox.get('nick') === nick) {
+                if (chatbox && chatbox.get('nick') === nick) {
                     return _converse.xmppstatus.vcard;
                 } else {
                     let vcard;
@@ -260,14 +287,21 @@ converse.plugins.add('converse-muc', {
                     }
                     if (!vcard) {
                         let jid;
-                        const occupant = chatbox.occupants.findWhere({'nick': nick});
-                        if (occupant && occupant.get('jid')) {
-                            jid = occupant.get('jid');
+                        if (this.occupant && this.occupant.get('jid')) {
+                            jid = this.occupant.get('jid');
                             this.save({'vcard_jid': jid}, {'silent': true});
                         } else {
                             jid = this.get('from');
                         }
-                        vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
+                        if (jid) {
+                            vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
+                        } else {
+                            _converse.log(
+                                `Could not assign VCard for message because no JID found! msgid: ${this.get('msgid')}`,
+                                Strophe.LogLevel.ERROR
+                            );
+                            return;
+                        }
                     }
                     return vcard;
                 }
@@ -278,7 +312,7 @@ converse.plugins.add('converse-muc', {
                     // VCards aren't supported
                     return;
                 }
-                if (this.get('type') === 'error') {
+                if (['error', 'info'].includes(this.get('type'))) {
                     return;
                 } else {
                     this.vcard = this.getVCardForChatroomOccupant();
@@ -389,7 +423,6 @@ converse.plugins.add('converse-muc', {
 
                     if (_converse.auto_register_muc_nickname &&
                             await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'))) {
-
                         this.registerNickname()
                     }
                 }
@@ -641,7 +674,7 @@ converse.plugins.add('converse-muc', {
                 [text, references] = this.parseTextForReferences(text);
                 const origin_id = _converse.connection.getUniqueId();
 
-                return this.addOccupantData({
+                return {
                     'id': origin_id,
                     'msgid': origin_id,
                     'origin_id': origin_id,
@@ -655,7 +688,7 @@ converse.plugins.add('converse-muc', {
                     'sender': 'me',
                     'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
                     'type': 'groupchat'
-                });
+                };
             },
 
             /**
@@ -1426,17 +1459,6 @@ converse.plugins.add('converse-muc', {
                 }
             },
 
-            addOccupantData (attrs) {
-                if (attrs.nick) {
-                    const occupant = this.occupants.findOccupant({'nick': attrs.nick});
-                    if (occupant) {
-                        attrs['affiliation'] = occupant.get('affiliation');
-                        attrs['role'] = occupant.get('role');
-                    }
-                }
-                return attrs;
-            },
-
             /**
              * Handler for all MUC messages sent to this groupchat.
              * @private
@@ -1460,13 +1482,12 @@ converse.plugins.add('converse-muc', {
                         this.isChatMarker(stanza)) {
                     return _converse.api.trigger('message', {'stanza': original_stanza});
                 }
-                let attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
+                const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
                 if (attrs.nick &&
                         !this.subjectChangeHandled(attrs) &&
                         !this.ignorableCSN(attrs) &&
                         (attrs['chat_state'] || !u.isEmptyMessage(attrs))) {
 
-                    attrs = this.addOccupantData(attrs);
                     const msg = this.correctMessage(attrs) || this.messages.create(attrs);
                     this.incrementUnreadMsgCounter(msg);
                 }

+ 5 - 3
src/headless/converse-vcard.js

@@ -130,8 +130,10 @@ converse.plugins.add('converse-vcard', {
 
         _converse.api.listen.on('statusInitialized', () => {
             const vcards = _converse.vcards;
-            const jid = _converse.session.get('bare_jid');
-            _converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
+            if (_converse.session) {
+                const jid = _converse.session.get('bare_jid');
+                _converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
+            }
         });
 
 
@@ -165,7 +167,7 @@ converse.plugins.add('converse-vcard', {
                  *     // Failure
                  * }).
                  */
-                'set' (jid, data) {
+                set (jid, data) {
                     return setVCard(jid, data);
                 },
 

+ 1 - 1
src/templates/message.html

@@ -6,7 +6,7 @@
     <div class="chat-msg__content chat-msg__content--{{{o.sender}}} {{{o.is_me_message ? 'chat-msg__content--action' : ''}}}">
         <span class="chat-msg__heading">
             {[ if (o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
-            <span class="chat-msg__author {{{o.is_groupchat_message && o.role ? o.role : ''}}}">{[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}}</span>
+            <span class="chat-msg__author {{{o.is_groupchat_message && o.occupant && o.occupant.get('role') ? o.occupant.get('role') : ''}}}">{[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}}</span>
             {[ if (!o.is_me_message) { ]}
                 {[o.roles.forEach(function (role) { ]} <span class="badge badge-secondary">{{{role}}}</span> {[ }); ]}
                 <time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>

+ 38 - 1
tests/utils.js

@@ -194,7 +194,7 @@
         // 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
-        var presence = $pres({
+        const presence = $pres({
                 to: _converse.connection.jid,
                 from: `${room_jid}/${nick}`,
                 id: u.getUniqueId()
@@ -207,6 +207,43 @@
             .c('status').attrs({code:'110'});
         _converse.connection._dataRecv(utils.createRequest(presence));
         await utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
+
+        // Now we return the (empty) member lists
+        const member_IQ = await utils.waitUntil(() => _.filter(
+            stanzas,
+            s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
+        ).pop());
+        const member_list_stanza = $iq({
+                'from': 'coven@chat.shakespeare.lit',
+                'id': member_IQ.getAttribute('id'),
+                'to': 'romeo@montague.lit/orchard',
+                'type': 'result'
+            }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
+        _converse.connection._dataRecv(utils.createRequest(member_list_stanza));
+
+        const admin_IQ = await utils.waitUntil(() => _.filter(
+            stanzas,
+            s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
+        ).pop());
+        const admin_list_stanza = $iq({
+                'from': 'coven@chat.shakespeare.lit',
+                'id': admin_IQ.getAttribute('id'),
+                'to': 'romeo@montague.lit/orchard',
+                'type': 'result'
+            }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
+        _converse.connection._dataRecv(utils.createRequest(admin_list_stanza));
+
+        const owner_IQ = await utils.waitUntil(() => _.filter(
+            stanzas,
+            s => sizzle(`iq[to="${room_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
+        ).pop());
+        const owner_list_stanza = $iq({
+                'from': 'coven@chat.shakespeare.lit',
+                'id': owner_IQ.getAttribute('id'),
+                'to': 'romeo@montague.lit/orchard',
+                'type': 'result'
+            }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
+        _converse.connection._dataRecv(utils.createRequest(owner_list_stanza));
     };
 
     utils.clearBrowserStorage = function () {