Просмотр исходного кода

Only initialize bookmarks and show icon if PEP is supported

which we check by checking if the PEP identity is provided.
https://xmpp.org/extensions/xep-0163.html#support
JC Brand 7 лет назад
Родитель
Сommit
5f3761dc7f
10 измененных файлов с 688 добавлено и 600 удалено
  1. 369 333
      spec/bookmarks.js
  2. 4 4
      spec/chatbox.js
  3. 253 233
      spec/chatroom.js
  4. 2 2
      spec/disco.js
  5. 1 1
      spec/protocol.js
  6. 44 25
      src/converse-bookmarks.js
  7. 1 0
      src/converse-core.js
  8. 3 0
      src/converse-muc.js
  9. 8 0
      tests/mock.js
  10. 3 2
      tests/utils.js

+ 369 - 333
spec/bookmarks.js

@@ -12,6 +12,7 @@
 } (this, function (jasmine, $, converse, utils, mock, test_utils) {
     "use strict";
     var $iq = converse.env.$iq,
+        Backbone = converse.env.Backbone,
         Strophe = converse.env.Strophe,
         _ = converse.env._,
         u = converse.env.utils;
@@ -20,7 +21,7 @@
 
         it("can be bookmarked", mock.initConverseWithPromises(
             null, ['rosterGroupsFetched'], {}, function (done, _converse) {
-
+                
             var sent_stanza, IQ_id;
             var sendIQ = _converse.connection.sendIQ;
             spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -30,105 +31,110 @@
             spyOn(_converse.connection, 'getUniqueId').and.callThrough();
 
             test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
+
             var jid = 'theplay@conference.shakespeare.lit';
             var view = _converse.chatboxviews.get(jid);
             spyOn(view, 'renderBookmarkForm').and.callThrough();
             spyOn(view, 'closeForm').and.callThrough();
 
-            var $bookmark = $(view.el).find('.icon-pushpin');
-            $bookmark[0].click();
-            expect(view.renderBookmarkForm).toHaveBeenCalled();
-
-            view.el.querySelector('.button-cancel').click();
-            expect(view.closeForm).toHaveBeenCalled();
-            expect($bookmark.hasClass('on-button'), false);
-
-            $bookmark[0].click();
-            expect(view.renderBookmarkForm).toHaveBeenCalled();
-
-            /* Client uploads data:
-             * --------------------
-             *  <iq from='juliet@capulet.lit/balcony' type='set' id='pip1'>
-             *      <pubsub xmlns='http://jabber.org/protocol/pubsub'>
-             *          <publish node='storage:bookmarks'>
-             *              <item id='current'>
-             *                  <storage xmlns='storage:bookmarks'>
-             *                      <conference name='The Play&apos;s the Thing'
-             *                                  autojoin='true'
-             *                                  jid='theplay@conference.shakespeare.lit'>
-             *                          <nick>JC</nick>
-             *                      </conference>
-             *                  </storage>
-             *              </item>
-             *          </publish>
-             *          <publish-options>
-             *              <x xmlns='jabber:x:data' type='submit'>
-             *                  <field var='FORM_TYPE' type='hidden'>
-             *                      <value>http://jabber.org/protocol/pubsub#publish-options</value>
-             *                  </field>
-             *                  <field var='pubsub#persist_items'>
-             *                      <value>true</value>
-             *                  </field>
-             *                  <field var='pubsub#access_model'>
-             *                      <value>whitelist</value>
-             *                  </field>
-             *              </x>
-             *          </publish-options>
-             *      </pubsub>
-             *  </iq>
-             */
-            expect(view.model.get('bookmarked')).toBeFalsy();
-            var $form = $(view.el).find('.chatroom-form');
-            $form.find('input[name="name"]').val('Play&apos;s the Thing');
-            $form.find('input[name="autojoin"]').prop('checked', true);
-            $form.find('input[name="nick"]').val('JC');
-            view.el.querySelector('.button-primary').click();
-
-            expect(view.model.get('bookmarked')).toBeTruthy();
-            expect($bookmark.hasClass('on-button'), true);
-
-            expect(sent_stanza.toLocaleString()).toBe(
-                "<iq type='set' from='dummy@localhost/resource' xmlns='jabber:client' id='"+IQ_id+"'>"+
-                    "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"+
-                        "<publish node='storage:bookmarks'>"+
-                            "<item id='current'>"+
-                                "<storage xmlns='storage:bookmarks'>"+
-                                    "<conference name='Play&amp;apos;s the Thing' autojoin='true' jid='theplay@conference.shakespeare.lit'>"+
-                                        "<nick>JC</nick>"+
-                                    "</conference>"+
-                                "</storage>"+
-                            "</item>"+
-                        "</publish>"+
-                        "<publish-options>"+
-                            "<x xmlns='jabber:x:data' type='submit'>"+
-                                "<field var='FORM_TYPE' type='hidden'>"+
-                                    "<value>http://jabber.org/protocol/pubsub#publish-options</value>"+
-                                "</field>"+
-                                "<field var='pubsub#persist_items'>"+
-                                    "<value>true</value>"+
-                                "</field>"+
-                                "<field var='pubsub#access_model'>"+
-                                    "<value>whitelist</value>"+
-                                "</field>"+
-                            "</x>"+
-                        "</publish-options>"+
-                    "</pubsub>"+
-                "</iq>"
-            );
-
-            /* Server acknowledges successful storage
-             *
-             * <iq to='juliet@capulet.lit/balcony' type='result' id='pip1'/>
-             */
-            var stanza = $iq({
-                'to':_converse.connection.jid,
-                'type':'result',
-                'id':IQ_id
+            test_utils.waitUntil(function () {
+                return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+            }, 300).then(function () {
+                var $bookmark = $(view.el).find('.icon-pushpin');
+                $bookmark[0].click();
+                expect(view.renderBookmarkForm).toHaveBeenCalled();
+
+                view.el.querySelector('.button-cancel').click();
+                expect(view.closeForm).toHaveBeenCalled();
+                expect($bookmark.hasClass('on-button'), false);
+
+                $bookmark[0].click();
+                expect(view.renderBookmarkForm).toHaveBeenCalled();
+
+                /* Client uploads data:
+                * --------------------
+                *  <iq from='juliet@capulet.lit/balcony' type='set' id='pip1'>
+                *      <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+                *          <publish node='storage:bookmarks'>
+                *              <item id='current'>
+                *                  <storage xmlns='storage:bookmarks'>
+                *                      <conference name='The Play&apos;s the Thing'
+                *                                  autojoin='true'
+                *                                  jid='theplay@conference.shakespeare.lit'>
+                *                          <nick>JC</nick>
+                *                      </conference>
+                *                  </storage>
+                *              </item>
+                *          </publish>
+                *          <publish-options>
+                *              <x xmlns='jabber:x:data' type='submit'>
+                *                  <field var='FORM_TYPE' type='hidden'>
+                *                      <value>http://jabber.org/protocol/pubsub#publish-options</value>
+                *                  </field>
+                *                  <field var='pubsub#persist_items'>
+                *                      <value>true</value>
+                *                  </field>
+                *                  <field var='pubsub#access_model'>
+                *                      <value>whitelist</value>
+                *                  </field>
+                *              </x>
+                *          </publish-options>
+                *      </pubsub>
+                *  </iq>
+                */
+                expect(view.model.get('bookmarked')).toBeFalsy();
+                var $form = $(view.el).find('.chatroom-form');
+                $form.find('input[name="name"]').val('Play&apos;s the Thing');
+                $form.find('input[name="autojoin"]').prop('checked', true);
+                $form.find('input[name="nick"]').val('JC');
+                view.el.querySelector('.button-primary').click();
+
+                expect(view.model.get('bookmarked')).toBeTruthy();
+                expect($bookmark.hasClass('on-button'), true);
+
+                expect(sent_stanza.toLocaleString()).toBe(
+                    "<iq type='set' from='dummy@localhost/resource' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                        "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"+
+                            "<publish node='storage:bookmarks'>"+
+                                "<item id='current'>"+
+                                    "<storage xmlns='storage:bookmarks'>"+
+                                        "<conference name='Play&amp;apos;s the Thing' autojoin='true' jid='theplay@conference.shakespeare.lit'>"+
+                                            "<nick>JC</nick>"+
+                                        "</conference>"+
+                                    "</storage>"+
+                                "</item>"+
+                            "</publish>"+
+                            "<publish-options>"+
+                                "<x xmlns='jabber:x:data' type='submit'>"+
+                                    "<field var='FORM_TYPE' type='hidden'>"+
+                                        "<value>http://jabber.org/protocol/pubsub#publish-options</value>"+
+                                    "</field>"+
+                                    "<field var='pubsub#persist_items'>"+
+                                        "<value>true</value>"+
+                                    "</field>"+
+                                    "<field var='pubsub#access_model'>"+
+                                        "<value>whitelist</value>"+
+                                    "</field>"+
+                                "</x>"+
+                            "</publish-options>"+
+                        "</pubsub>"+
+                    "</iq>"
+                );
+
+                /* Server acknowledges successful storage
+                *
+                * <iq to='juliet@capulet.lit/balcony' type='result' id='pip1'/>
+                */
+                var stanza = $iq({
+                    'to':_converse.connection.jid,
+                    'type':'result',
+                    'id':IQ_id
+                });
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                // We ignore this IQ stanza... (unless it's an error stanza), so
+                // nothing to test for here.
+                done();
             });
-            _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            // We ignore this IQ stanza... (unless it's an error stanza), so
-            // nothing to test for here.
-            done();
         }));
 
         it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises(
@@ -161,13 +167,18 @@
 
                 test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
                 var view = _converse.chatboxviews.get('lounge@localhost');
-                var $bookmark_icon = $(view.el.querySelector('.icon-pushpin'));
-                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
-                view.model.set('bookmarked', true);
-                expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
-                view.model.set('bookmarked', false);
-                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
-                done();
+
+                test_utils.waitUntil(function () {
+                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+                }, 300).then(function () {
+                    var bookmark_icon = view.el.querySelector('.icon-pushpin');
+                    expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
+                    view.model.set('bookmarked', true);
+                    expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy();
+                    view.model.set('bookmarked', false);
+                    expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
+                    done();
+                });
             }));
 
             it("can be unbookmarked", mock.initConverseWithPromises(
@@ -175,61 +186,68 @@
 
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
+
                 test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
                 var jid = 'theplay@conference.shakespeare.lit';
                 var view = _converse.chatboxviews.get(jid);
-                spyOn(view, 'toggleBookmark').and.callThrough();
-                spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
-                view.delegateEvents();
-                _converse.bookmarks.create({
-                    'jid': view.model.get('jid'),
-                    'autojoin': false,
-                    'name':  'The Play',
-                    'nick': ' Othello'
-                });
-                expect(_converse.bookmarks.length).toBe(1);
-                expect(view.model.get('bookmarked')).toBeTruthy();
-                var $bookmark_icon = $(view.el.querySelector('.icon-pushpin'));
-                expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
 
-                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
-                    sent_stanza = iq;
-                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                test_utils.waitUntil(function () {
+                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+                }, 300).then(function () {
+                    spyOn(view, 'toggleBookmark').and.callThrough();
+                    spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
+                    view.delegateEvents();
+
+                    _converse.bookmarks.create({
+                        'jid': view.model.get('jid'),
+                        'autojoin': false,
+                        'name':  'The Play',
+                        'nick': ' Othello'
+                    });
+                    expect(_converse.bookmarks.length).toBe(1);
+                    expect(view.model.get('bookmarked')).toBeTruthy();
+                    var $bookmark_icon = $(view.el.querySelector('.icon-pushpin'));
+                    expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
+
+                    spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
+                        sent_stanza = iq;
+                        IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                    });
+                    spyOn(_converse.connection, 'getUniqueId').and.callThrough();
+                    $bookmark_icon[0].click();
+                    expect(view.toggleBookmark).toHaveBeenCalled();
+                    expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
+                    expect(_converse.bookmarks.length).toBe(0);
+
+                    // Check that an IQ stanza is sent out, containing no
+                    // conferences to bookmark (since we removed the one and
+                    // only bookmark).
+                    expect(sent_stanza.toLocaleString()).toBe(
+                        "<iq type='set' from='dummy@localhost/resource' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                            "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"+
+                                "<publish node='storage:bookmarks'>"+
+                                    "<item id='current'>"+
+                                        "<storage xmlns='storage:bookmarks'/>"+
+                                    "</item>"+
+                                "</publish>"+
+                                "<publish-options>"+
+                                    "<x xmlns='jabber:x:data' type='submit'>"+
+                                        "<field var='FORM_TYPE' type='hidden'>"+
+                                            "<value>http://jabber.org/protocol/pubsub#publish-options</value>"+
+                                        "</field>"+
+                                        "<field var='pubsub#persist_items'>"+
+                                            "<value>true</value>"+
+                                        "</field>"+
+                                        "<field var='pubsub#access_model'>"+
+                                            "<value>whitelist</value>"+
+                                        "</field>"+
+                                    "</x>"+
+                                "</publish-options>"+
+                            "</pubsub>"+
+                        "</iq>"
+                    );
+                    done();
                 });
-                spyOn(_converse.connection, 'getUniqueId').and.callThrough();
-                $bookmark_icon[0].click();
-                expect(view.toggleBookmark).toHaveBeenCalled();
-                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
-                expect(_converse.bookmarks.length).toBe(0);
-
-                // Check that an IQ stanza is sent out, containing no
-                // conferences to bookmark (since we removed the one and
-                // only bookmark).
-                expect(sent_stanza.toLocaleString()).toBe(
-                    "<iq type='set' from='dummy@localhost/resource' xmlns='jabber:client' id='"+IQ_id+"'>"+
-                        "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"+
-                            "<publish node='storage:bookmarks'>"+
-                                "<item id='current'>"+
-                                    "<storage xmlns='storage:bookmarks'/>"+
-                                "</item>"+
-                            "</publish>"+
-                            "<publish-options>"+
-                                "<x xmlns='jabber:x:data' type='submit'>"+
-                                    "<field var='FORM_TYPE' type='hidden'>"+
-                                        "<value>http://jabber.org/protocol/pubsub#publish-options</value>"+
-                                    "</field>"+
-                                    "<field var='pubsub#persist_items'>"+
-                                        "<value>true</value>"+
-                                    "</field>"+
-                                    "<field var='pubsub#access_model'>"+
-                                        "<value>whitelist</value>"+
-                                    "</field>"+
-                                "</x>"+
-                            "</publish-options>"+
-                        "</pubsub>"+
-                    "</iq>"
-                );
-                done();
             }));
         });
 
@@ -308,88 +326,21 @@
         }));
 
         it("can be retrieved from the XMPP server", mock.initConverseWithPromises(
-            ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
-
-            /* Client requests all items
-             * -------------------------
-             *
-             *  <iq from='juliet@capulet.lit/randomID' type='get' id='retrieve1'>
-             *  <pubsub xmlns='http://jabber.org/protocol/pubsub'>
-             *      <items node='storage:bookmarks'/>
-             *  </pubsub>
-             *  </iq>
-             */
-            var IQ_id;
-            expect(_.filter(_converse.connection.send.calls.all(), function (call) {
-                var stanza = call.args[0];
-                if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
-                    return;
-                }
-                // XXX: Wrapping in a div is a workaround for PhantomJS
-                var div = document.createElement('div');
-                div.appendChild(stanza);
-                if (div.innerHTML ===
-                    '<iq from="dummy@localhost/resource" type="get" '+
-                         'xmlns="jabber:client" id="'+stanza.getAttribute('id')+'">'+
-                    '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
-                        '<items node="storage:bookmarks"></items>'+
-                    '</pubsub>'+
-                    '</iq>') {
-                    IQ_id = stanza.getAttribute('id');
-                    return true;
-                }
-            }).length).toBe(1);
-
-            /*
-             * Server returns all items
-             * ------------------------
-             * <iq type='result'
-             *     to='juliet@capulet.lit/randomID'
-             *     id='retrieve1'>
-             * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
-             *     <items node='storage:bookmarks'>
-             *     <item id='current'>
-             *         <storage xmlns='storage:bookmarks'>
-             *         <conference name='The Play&apos;s the Thing'
-             *                     autojoin='true'
-             *                     jid='theplay@conference.shakespeare.lit'>
-             *             <nick>JC</nick>
-             *         </conference>
-             *         </storage>
-             *     </item>
-             *     </items>
-             * </pubsub>
-             * </iq>
-             */
-            expect(_converse.bookmarks.models.length).toBe(0);
-            var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
-                .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
-                    .c('items', {'node': 'storage:bookmarks'})
-                        .c('item', {'id': 'current'})
-                            .c('storage', {'xmlns': 'storage:bookmarks'})
-                                .c('conference', {
-                                    'name': 'The Play&apos;s the Thing',
-                                    'autojoin': 'true',
-                                    'jid': 'theplay@conference.shakespeare.lit'
-                                }).c('nick').t('JC').up().up()
-                                .c('conference', {
-                                    'name': 'Another room',
-                                    'autojoin': 'false',
-                                    'jid': 'another@conference.shakespeare.lit'
-                                }).c('nick').t('JC').up().up();
-            _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            expect(_converse.bookmarks.models.length).toBe(2);
-            expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
-            expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
-            done();
-        }));
-
-        describe("The rooms panel", function () {
-
-            it("shows a list of bookmarks", mock.initConverseWithPromises(
-                ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
+            ['send'], ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
+            function (done, _converse) {
 
-                test_utils.openControlBox().openRoomsPanel(_converse);
+            test_utils.waitUntil(function () {
+                return _converse.bookmarks;
+            }, 300).then(function () {
+                /* Client requests all items
+                * -------------------------
+                *
+                *  <iq from='juliet@capulet.lit/randomID' type='get' id='retrieve1'>
+                *  <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+                *      <items node='storage:bookmarks'/>
+                *  </pubsub>
+                *  </iq>
+                */
                 var IQ_id;
                 expect(_.filter(_converse.connection.send.calls.all(), function (call) {
                     var stanza = call.args[0];
@@ -411,6 +362,28 @@
                     }
                 }).length).toBe(1);
 
+                /*
+                * Server returns all items
+                * ------------------------
+                * <iq type='result'
+                *     to='juliet@capulet.lit/randomID'
+                *     id='retrieve1'>
+                * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+                *     <items node='storage:bookmarks'>
+                *     <item id='current'>
+                *         <storage xmlns='storage:bookmarks'>
+                *         <conference name='The Play&apos;s the Thing'
+                *                     autojoin='true'
+                *                     jid='theplay@conference.shakespeare.lit'>
+                *             <nick>JC</nick>
+                *         </conference>
+                *         </storage>
+                *     </item>
+                *     </items>
+                * </pubsub>
+                * </iq>
+                */
+                expect(_converse.bookmarks.models.length).toBe(0);
                 var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
                     .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
                         .c('items', {'node': 'storage:bookmarks'})
@@ -418,97 +391,156 @@
                                 .c('storage', {'xmlns': 'storage:bookmarks'})
                                     .c('conference', {
                                         'name': 'The Play&apos;s the Thing',
-                                        'autojoin': 'false',
+                                        'autojoin': 'true',
                                         'jid': 'theplay@conference.shakespeare.lit'
                                     }).c('nick').t('JC').up().up()
-                                    .c('conference', {
-                                        'name': '1st Bookmark',
-                                        'autojoin': 'false',
-                                        'jid': 'first@conference.shakespeare.lit'
-                                    }).c('nick').t('JC').up().up()
-                                    .c('conference', {
-                                        'name': 'Bookmark with a very very long name that will be shortened',
-                                        'autojoin': 'false',
-                                        'jid': 'longname@conference.shakespeare.lit'
-                                    }).c('nick').t('JC').up().up()
                                     .c('conference', {
                                         'name': 'Another room',
                                         'autojoin': 'false',
                                         'jid': 'another@conference.shakespeare.lit'
                                     }).c('nick').t('JC').up().up();
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                expect(_converse.bookmarks.models.length).toBe(2);
+                expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
+                expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
+                done();
+            });
+        }));
+
+        describe("The rooms panel", function () {
+
+            it("shows a list of bookmarks", mock.initConverseWithPromises(
+                ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
 
                 test_utils.waitUntil(function () {
-                    return $('#chatrooms dl.bookmarks dd').length;
+                    return _converse.bookmarks;
                 }, 300).then(function () {
-                    expect($('#chatrooms dl.bookmarks dd').length).toBe(4);
-                    expect($('#chatrooms dl.bookmarks dd a').text().trim()).toBe(
-                        "1st Bookmark  Another room  Bookmark with a very very long name that will be shortened  The Play&apos;s the Thing")
-
-                    spyOn(window, 'confirm').and.returnValue(true);
-                    $('#chatrooms dl.bookmarks dd:nth-child(2) a:nth-child(2)')[0].click();
-                    expect(window.confirm).toHaveBeenCalled();
-
-                    return test_utils.waitUntil(function () {
-                        return $('#chatrooms dl.bookmarks dd a').text().trim() ===
-                            "1st Bookmark  Bookmark with a very very long name that will be shortened  The Play&apos;s the Thing";
-                    }, 300)
-                }).then(done);
+
+                    test_utils.openControlBox().openRoomsPanel(_converse);
+                    var IQ_id;
+                    expect(_.filter(_converse.connection.send.calls.all(), function (call) {
+                        var stanza = call.args[0];
+                        if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
+                            return;
+                        }
+                        // XXX: Wrapping in a div is a workaround for PhantomJS
+                        var div = document.createElement('div');
+                        div.appendChild(stanza);
+                        if (div.innerHTML ===
+                            '<iq from="dummy@localhost/resource" type="get" '+
+                                'xmlns="jabber:client" id="'+stanza.getAttribute('id')+'">'+
+                            '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
+                                '<items node="storage:bookmarks"></items>'+
+                            '</pubsub>'+
+                            '</iq>') {
+                            IQ_id = stanza.getAttribute('id');
+                            return true;
+                        }
+                    }).length).toBe(1);
+
+                    var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
+                        .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
+                            .c('items', {'node': 'storage:bookmarks'})
+                                .c('item', {'id': 'current'})
+                                    .c('storage', {'xmlns': 'storage:bookmarks'})
+                                        .c('conference', {
+                                            'name': 'The Play&apos;s the Thing',
+                                            'autojoin': 'false',
+                                            'jid': 'theplay@conference.shakespeare.lit'
+                                        }).c('nick').t('JC').up().up()
+                                        .c('conference', {
+                                            'name': '1st Bookmark',
+                                            'autojoin': 'false',
+                                            'jid': 'first@conference.shakespeare.lit'
+                                        }).c('nick').t('JC').up().up()
+                                        .c('conference', {
+                                            'name': 'Bookmark with a very very long name that will be shortened',
+                                            'autojoin': 'false',
+                                            'jid': 'longname@conference.shakespeare.lit'
+                                        }).c('nick').t('JC').up().up()
+                                        .c('conference', {
+                                            'name': 'Another room',
+                                            'autojoin': 'false',
+                                            'jid': 'another@conference.shakespeare.lit'
+                                        }).c('nick').t('JC').up().up();
+                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                    test_utils.waitUntil(function () {
+                        return $('#chatrooms dl.bookmarks dd').length;
+                    }, 300).then(function () {
+                        expect($('#chatrooms dl.bookmarks dd').length).toBe(4);
+                        expect($('#chatrooms dl.bookmarks dd a').text().trim()).toBe(
+                            "1st Bookmark  Another room  Bookmark with a very very long name that will be shortened  The Play&apos;s the Thing")
+
+                        spyOn(window, 'confirm').and.returnValue(true);
+                        $('#chatrooms dl.bookmarks dd:nth-child(2) a:nth-child(2)')[0].click();
+                        expect(window.confirm).toHaveBeenCalled();
+
+                        return test_utils.waitUntil(function () {
+                            return $('#chatrooms dl.bookmarks dd a').text().trim() ===
+                                "1st Bookmark  Bookmark with a very very long name that will be shortened  The Play&apos;s the Thing";
+                        }, 300)
+                    }).then(done);
+                });
             }));
 
             it("remembers the toggle state of the bookmarks list", mock.initConverseWithPromises(
                 ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
 
-                var IQ_id;
-                expect(_.filter(_converse.connection.send.calls.all(), function (call) {
-                    var stanza = call.args[0];
-                    if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
-                        return;
-                    }
-                    // XXX: Wrapping in a div is a workaround for PhantomJS
-                    var div = document.createElement('div');
-                    div.appendChild(stanza);
-                    if (div.innerHTML ===
-                        '<iq from="dummy@localhost/resource" type="get" '+
-                            'xmlns="jabber:client" id="'+stanza.getAttribute('id')+'">'+
-                        '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
-                            '<items node="storage:bookmarks"></items>'+
-                        '</pubsub>'+
-                        '</iq>') {
-                        IQ_id = stanza.getAttribute('id');
-                        return true;
-                    }
-                }).length).toBe(1);
-
-                var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
-                    .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
-                        .c('items', {'node': 'storage:bookmarks'})
-                            .c('item', {'id': 'current'})
-                                .c('storage', {'xmlns': 'storage:bookmarks'});
-                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-
-                _converse.bookmarks.create({
-                    'jid': 'theplay@conference.shakespeare.lit',
-                    'autojoin': false,
-                    'name':  'The Play',
-                    'nick': ''
-                });
-                test_utils.openControlBox().openRoomsPanel(_converse);
-
                 test_utils.waitUntil(function () {
-                    return $('#chatrooms dl.bookmarks dd:visible').length;
+                    return _converse.bookmarks;
                 }, 300).then(function () {
-                    expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeFalsy();
-                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
-                    $('#chatrooms .bookmarks-toggle')[0].click();
-                    expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeTruthy();
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
-                    $('#chatrooms .bookmarks-toggle')[0].click();
-                    expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeFalsy();
-                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
-                    done();
+                    var IQ_id;
+                    expect(_.filter(_converse.connection.send.calls.all(), function (call) {
+                        var stanza = call.args[0];
+                        if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
+                            return;
+                        }
+                        // XXX: Wrapping in a div is a workaround for PhantomJS
+                        var div = document.createElement('div');
+                        div.appendChild(stanza);
+                        if (div.innerHTML ===
+                            '<iq from="dummy@localhost/resource" type="get" '+
+                                'xmlns="jabber:client" id="'+stanza.getAttribute('id')+'">'+
+                            '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
+                                '<items node="storage:bookmarks"></items>'+
+                            '</pubsub>'+
+                            '</iq>') {
+                            IQ_id = stanza.getAttribute('id');
+                            return true;
+                        }
+                    }).length).toBe(1);
+
+                    var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
+                        .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
+                            .c('items', {'node': 'storage:bookmarks'})
+                                .c('item', {'id': 'current'})
+                                    .c('storage', {'xmlns': 'storage:bookmarks'});
+                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                    _converse.bookmarks.create({
+                        'jid': 'theplay@conference.shakespeare.lit',
+                        'autojoin': false,
+                        'name':  'The Play',
+                        'nick': ''
+                    });
+                    test_utils.openControlBox().openRoomsPanel(_converse);
+
+                    test_utils.waitUntil(function () {
+                        return $('#chatrooms dl.bookmarks dd:visible').length;
+                    }, 300).then(function () {
+                        expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeFalsy();
+                        expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                        expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                        $('#chatrooms .bookmarks-toggle')[0].click();
+                        expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeTruthy();
+                        expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
+                        $('#chatrooms .bookmarks-toggle')[0].click();
+                        expect($('#chatrooms dl.bookmarks').hasClass('collapsed')).toBeFalsy();
+                        expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                        expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                        done();
+                    });
                 });
             }));
         });
@@ -521,36 +553,40 @@
             { hide_open_bookmarks: true },
             function (done, _converse) {
 
-            test_utils.openControlBox().openRoomsPanel(_converse);
-            // XXX Create bookmarks view here, otherwise we need to mock stanza
-            // traffic for it to get created.
-            _converse.bookmarksview = new _converse.BookmarksView(
-                {'model': _converse.bookmarks}
-            );
-            _converse.emit('bookmarksInitialized');
-
-            // Check that it's there
-            var jid = 'room@conference.example.org';
-            _converse.bookmarks.create({
-                'jid': jid,
-                'autojoin': false,
-                'name':  'The Play',
-                'nick': ' Othello'
-            });
+            test_utils.waitUntil(function () {
+                return _converse.bookmarks;
+            }, 300).then(function () {
+                test_utils.openControlBox().openRoomsPanel(_converse);
+                // XXX Create bookmarks view here, otherwise we need to mock stanza
+                // traffic for it to get created.
+                _converse.bookmarksview = new _converse.BookmarksView(
+                    {'model': _converse.bookmarks}
+                );
+                _converse.emit('bookmarksInitialized');
 
-            expect(_converse.bookmarks.length).toBe(1);
-            var room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
-            expect(room_els.length).toBe(1);
+                // Check that it's there
+                var jid = 'room@conference.example.org';
+                _converse.bookmarks.create({
+                    'jid': jid,
+                    'autojoin': false,
+                    'name':  'The Play',
+                    'nick': ' Othello'
+                });
 
-            // Check that it disappears once the room is opened
-            var bookmark = _converse.bookmarksview.el.querySelector(".open-room");
-            bookmark.click();
-            expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
-            // Check that it reappears once the room is closed
-            var view = _converse.chatboxviews.get(jid);
-            view.close();
-            expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeFalsy();
-            done();
+                expect(_converse.bookmarks.length).toBe(1);
+                var room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
+                expect(room_els.length).toBe(1);
+
+                // Check that it disappears once the room is opened
+                var bookmark = _converse.bookmarksview.el.querySelector(".open-room");
+                bookmark.click();
+                expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
+                // Check that it reappears once the room is closed
+                var view = _converse.chatboxviews.get(jid);
+                view.close();
+                expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeFalsy();
+                done();
+            });
         }));
     });
 }));

+ 4 - 4
spec/chatbox.js

@@ -57,7 +57,7 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                 .then(function () {
                     return test_utils.waitUntil(function () {
                         return _converse.xmppstatus.get('fullname');
@@ -1228,7 +1228,7 @@
                         function (done, _converse) {
 
                     var contact, sent_stanza, IQ_id, stanza;
-                    test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                    test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                     .then(function () {
                         return test_utils.waitUntil(function () {
                             return _converse.xmppstatus.get('fullname');
@@ -1842,7 +1842,7 @@
                             function (done, _converse) {
 
                         var contact, sent_stanza, IQ_id, stanza;
-                        test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                        test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                         .then(function () {
                             return test_utils.waitUntil(function () {
                                 return _converse.xmppstatus.get('fullname');
@@ -1989,7 +1989,7 @@
                             function (done, _converse) {
 
                         var contact, sent_stanza, IQ_id, stanza;
-                        test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                        test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                         .then(function () {
                             return test_utils.waitUntil(function () {
                                 return _converse.xmppstatus.get('fullname');

+ 253 - 233
spec/chatroom.js

@@ -762,7 +762,6 @@
                 });
                 var view = _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
 
-                spyOn(view, 'generateHeadingHTML').and.callThrough();
                 var features_stanza = $iq({
                         from: 'coven@chat.shakespeare.lit',
                         'id': IQ_id,
@@ -791,10 +790,12 @@
                             .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
                                 .c('value').t(0);
                 _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
-
-                expect(view.generateHeadingHTML).toHaveBeenCalled();
-                expect($(view.el.querySelector('.chatroom-description')).text()).toBe('This is the description');
-                done();
+                test_utils.waitUntil(function () {
+                    return _.get(view.el.querySelector('.chatroom-description'), 'textContent');
+                }).then(function () {
+                    expect($(view.el.querySelector('.chatroom-description')).text()).toBe('This is the description');
+                    done();
+                });
             }));
 
             it("will specially mark messages in which you are mentioned",
@@ -825,8 +826,7 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-
-                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                 .then(function () {
                     return test_utils.waitUntil(function () {
                         return _converse.xmppstatus.get('fullname');
@@ -890,19 +890,24 @@
 
                 _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
                 view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
-                spyOn(view, 'saveAffiliationAndRole').and.callThrough();
 
-                // We pretend this is a new room, so no disco info is returned.
-                var features_stanza = $iq({
-                        from: 'coven@chat.shakespeare.lit',
-                        'id': IQ_id,
-                        'to': 'dummy@localhost/desktop',
-                        'type': 'error'
-                    }).c('error', {'type': 'cancel'})
-                        .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
-                _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
+                test_utils.waitUntil(function () {
+                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+                }, 300).then(function () {
 
-                /* <presence to="dummy@localhost/_converse.js-29092160"
+                    spyOn(view, 'saveAffiliationAndRole').and.callThrough();
+
+                    // We pretend this is a new room, so no disco info is returned.
+                    var features_stanza = $iq({
+                            from: 'coven@chat.shakespeare.lit',
+                            'id': IQ_id,
+                            'to': 'dummy@localhost/desktop',
+                            'type': 'error'
+                        }).c('error', {'type': 'cancel'})
+                            .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
+                    _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
+
+                    /* <presence to="dummy@localhost/_converse.js-29092160"
                     *           from="coven@chat.shakespeare.lit/some1">
                     *      <x xmlns="http://jabber.org/protocol/muc#user">
                     *          <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
@@ -910,191 +915,198 @@
                     *      </x>
                     *  </presence></body>
                     */
-                var presence = $pres({
-                        to: 'dummy@localhost/_converse.js-29092160',
-                        from: 'coven@chat.shakespeare.lit/some1'
-                    }).c('x', {xmlns: Strophe.NS.MUC_USER})
-                    .c('item', {
-                        'affiliation': 'owner',
-                        'jid': 'dummy@localhost/_converse.js-29092160',
-                        'role': 'moderator'
-                    }).up()
-                    .c('status', {code: '110'});
-                _converse.connection._dataRecv(test_utils.createRequest(presence));
-                expect(view.saveAffiliationAndRole).toHaveBeenCalled();
-                expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy();
-                expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy();
-                expect($(view.el.querySelector('.toggle-bookmark')).is(':visible')).toBeTruthy();
-                view.el.querySelector('.configure-chatroom-button').click();
-
-                /* Check that an IQ is sent out, asking for the
-                 * configuration form.
-                 * See: // http://xmpp.org/extensions/xep-0045.html#example-163
-                 *
-                 *  <iq from='crone1@shakespeare.lit/desktop'
-                 *      id='config1'
-                 *      to='coven@chat.shakespeare.lit'
-                 *      type='get'>
-                 *  <query xmlns='http://jabber.org/protocol/muc#owner'/>
-                 *  </iq>
-                 */
-                expect(sent_IQ.toLocaleString()).toBe(
-                    "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
-                        "<query xmlns='http://jabber.org/protocol/muc#owner'/>"+
-                    "</iq>");
-
-                /* Server responds with the configuration form.
-                 * See: // http://xmpp.org/extensions/xep-0045.html#example-165
-                 */
-                var config_stanza = $iq({from: 'coven@chat.shakespeare.lit',
-                    'id': IQ_id,
-                    'to': 'dummy@localhost/desktop',
-                    'type': 'result'})
-                .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'})
-                    .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'})
-                        .c('title').t('Configuration for "coven" Room').up()
-                        .c('instructions').t('Complete this form to modify the configuration of your room.').up()
-                        .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'})
-                            .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
-                        .c('field', {
-                            'label': 'Natural-Language Room Name',
-                            'type': 'text-single',
-                            'var': 'muc#roomconfig_roomname'})
-                            .c('value').t('A Dark Cave').up().up()
-                        .c('field', {
-                            'label': 'Short Description of Room',
-                            'type': 'text-single',
-                            'var': 'muc#roomconfig_roomdesc'})
-                            .c('value').t('The place for all good witches!').up().up()
-                        .c('field', {
-                            'label': 'Enable Public Logging?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_enablelogging'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Allow Occupants to Change Subject?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_changesubject'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Allow Occupants to Invite Others?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_allowinvites'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Who Can Send Private Messages?',
-                            'type': 'list-single',
-                            'var': 'muc#roomconfig_allowpm'})
-                            .c('value').t('anyone').up()
-                            .c('option', {'label': 'Anyone'})
-                                .c('value').t('anyone').up().up()
-                            .c('option', {'label': 'Anyone with Voice'})
-                                .c('value').t('participants').up().up()
-                            .c('option', {'label': 'Moderators Only'})
-                                .c('value').t('moderators').up().up()
-                            .c('option', {'label': 'Nobody'})
-                                .c('value').t('none').up().up().up()
-                        .c('field', {
-                            'label': 'Roles for which Presence is Broadcasted',
-                            'type': 'list-multi',
-                            'var': 'muc#roomconfig_presencebroadcast'})
-                            .c('value').t('moderator').up()
-                            .c('value').t('participant').up()
-                            .c('value').t('visitor').up()
-                            .c('option', {'label': 'Moderator'})
-                                .c('value').t('moderator').up().up()
-                            .c('option', {'label': 'Participant'})
-                                .c('value').t('participant').up().up()
-                            .c('option', {'label': 'Visitor'})
-                                .c('value').t('visitor').up().up().up()
-                        .c('field', {
-                            'label': 'Roles and Affiliations that May Retrieve Member List',
-                            'type': 'list-multi',
-                            'var': 'muc#roomconfig_getmemberlist'})
-                            .c('value').t('moderator').up()
-                            .c('value').t('participant').up()
-                            .c('value').t('visitor').up()
-                            .c('option', {'label': 'Moderator'})
-                                .c('value').t('moderator').up().up()
-                            .c('option', {'label': 'Participant'})
-                                .c('value').t('participant').up().up()
-                            .c('option', {'label': 'Visitor'})
-                                .c('value').t('visitor').up().up().up()
-                        .c('field', {
-                            'label': 'Make Room Publicly Searchable?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_publicroom'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Make Room Publicly Searchable?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_publicroom'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Make Room Persistent?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_persistentroom'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Make Room Moderated?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_moderatedroom'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Make Room Members Only?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_membersonly'})
-                            .c('value').t(0).up().up()
-                        .c('field', {
-                            'label': 'Password Required for Entry?',
-                            'type': 'boolean',
-                            'var': 'muc#roomconfig_passwordprotectedroom'})
-                            .c('value').t(1).up().up()
-                        .c('field', {'type': 'fixed'})
-                            .c('value').t('If a password is required to enter this room,'+
-                                        'you must specify the password below.').up().up()
-                        .c('field', {
-                            'label': 'Password',
-                            'type': 'text-private',
-                            'var': 'muc#roomconfig_roomsecret'})
-                            .c('value').t('cauldronburn');
-                _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
+                    var presence = $pres({
+                            to: 'dummy@localhost/_converse.js-29092160',
+                            from: 'coven@chat.shakespeare.lit/some1'
+                        }).c('x', {xmlns: Strophe.NS.MUC_USER})
+                        .c('item', {
+                            'affiliation': 'owner',
+                            'jid': 'dummy@localhost/_converse.js-29092160',
+                            'role': 'moderator'
+                        }).up()
+                        .c('status', {code: '110'});
+                    _converse.connection._dataRecv(test_utils.createRequest(presence));
+                    expect(view.saveAffiliationAndRole).toHaveBeenCalled();
+                    expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy();
+                    expect($(view.el.querySelector('.toggle-bookmark')).is(':visible')).toBeTruthy();
 
-                test_utils.waitUntil(function () {
-                    return $(view.el.querySelector('form.chatroom-form')).length;
-                }, 300).then(function () {
-                    expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1);
-                    expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
-                    var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]'));
-                    expect($membersonly.length).toBe(1);
-                    expect($membersonly.attr('type')).toBe('checkbox');
-                    $membersonly.prop('checked', true);
-
-                    var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]'));
-                    expect($moderated.length).toBe(1);
-                    expect($moderated.attr('type')).toBe('checkbox');
-                    $moderated.prop('checked', true);
-
-                    var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]'));
-                    expect($password.length).toBe(1);
-                    expect($password.attr('type')).toBe('password');
-
-                    var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]'));
-                    expect($allowpm.length).toBe(1);
-                    $allowpm.val('moderators');
-
-                    var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]'));
-                    expect($presencebroadcast.length).toBe(1);
-                    $presencebroadcast.val(['moderator']);
-
-                    view.el.querySelector('input[type="submit"]').click();
-
-                    var $sent_stanza = $(sent_IQ.toLocaleString());
-                    expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1');
-                    expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1');
-                    expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators');
-                    expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator');
-                    done();
+                    test_utils.waitUntil(function () {
+                        return !_.isNull(view.el.querySelector('.configure-chatroom-button'));
+                    }, 300).then(function () {
+                        expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy();
+
+                        view.el.querySelector('.configure-chatroom-button').click();
+
+                        /* Check that an IQ is sent out, asking for the
+                        * configuration form.
+                        * See: // http://xmpp.org/extensions/xep-0045.html#example-163
+                        *
+                        *  <iq from='crone1@shakespeare.lit/desktop'
+                        *      id='config1'
+                        *      to='coven@chat.shakespeare.lit'
+                        *      type='get'>
+                        *  <query xmlns='http://jabber.org/protocol/muc#owner'/>
+                        *  </iq>
+                        */
+                        expect(sent_IQ.toLocaleString()).toBe(
+                            "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                                "<query xmlns='http://jabber.org/protocol/muc#owner'/>"+
+                            "</iq>");
+
+                        /* Server responds with the configuration form.
+                        * See: // http://xmpp.org/extensions/xep-0045.html#example-165
+                        */
+                        var config_stanza = $iq({from: 'coven@chat.shakespeare.lit',
+                            'id': IQ_id,
+                            'to': 'dummy@localhost/desktop',
+                            'type': 'result'})
+                        .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'})
+                            .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'})
+                                .c('title').t('Configuration for "coven" Room').up()
+                                .c('instructions').t('Complete this form to modify the configuration of your room.').up()
+                                .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'})
+                                    .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
+                                .c('field', {
+                                    'label': 'Natural-Language Room Name',
+                                    'type': 'text-single',
+                                    'var': 'muc#roomconfig_roomname'})
+                                    .c('value').t('A Dark Cave').up().up()
+                                .c('field', {
+                                    'label': 'Short Description of Room',
+                                    'type': 'text-single',
+                                    'var': 'muc#roomconfig_roomdesc'})
+                                    .c('value').t('The place for all good witches!').up().up()
+                                .c('field', {
+                                    'label': 'Enable Public Logging?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_enablelogging'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Allow Occupants to Change Subject?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_changesubject'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Allow Occupants to Invite Others?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_allowinvites'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Who Can Send Private Messages?',
+                                    'type': 'list-single',
+                                    'var': 'muc#roomconfig_allowpm'})
+                                    .c('value').t('anyone').up()
+                                    .c('option', {'label': 'Anyone'})
+                                        .c('value').t('anyone').up().up()
+                                    .c('option', {'label': 'Anyone with Voice'})
+                                        .c('value').t('participants').up().up()
+                                    .c('option', {'label': 'Moderators Only'})
+                                        .c('value').t('moderators').up().up()
+                                    .c('option', {'label': 'Nobody'})
+                                        .c('value').t('none').up().up().up()
+                                .c('field', {
+                                    'label': 'Roles for which Presence is Broadcasted',
+                                    'type': 'list-multi',
+                                    'var': 'muc#roomconfig_presencebroadcast'})
+                                    .c('value').t('moderator').up()
+                                    .c('value').t('participant').up()
+                                    .c('value').t('visitor').up()
+                                    .c('option', {'label': 'Moderator'})
+                                        .c('value').t('moderator').up().up()
+                                    .c('option', {'label': 'Participant'})
+                                        .c('value').t('participant').up().up()
+                                    .c('option', {'label': 'Visitor'})
+                                        .c('value').t('visitor').up().up().up()
+                                .c('field', {
+                                    'label': 'Roles and Affiliations that May Retrieve Member List',
+                                    'type': 'list-multi',
+                                    'var': 'muc#roomconfig_getmemberlist'})
+                                    .c('value').t('moderator').up()
+                                    .c('value').t('participant').up()
+                                    .c('value').t('visitor').up()
+                                    .c('option', {'label': 'Moderator'})
+                                        .c('value').t('moderator').up().up()
+                                    .c('option', {'label': 'Participant'})
+                                        .c('value').t('participant').up().up()
+                                    .c('option', {'label': 'Visitor'})
+                                        .c('value').t('visitor').up().up().up()
+                                .c('field', {
+                                    'label': 'Make Room Publicly Searchable?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_publicroom'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Make Room Publicly Searchable?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_publicroom'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Make Room Persistent?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_persistentroom'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Make Room Moderated?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_moderatedroom'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Make Room Members Only?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_membersonly'})
+                                    .c('value').t(0).up().up()
+                                .c('field', {
+                                    'label': 'Password Required for Entry?',
+                                    'type': 'boolean',
+                                    'var': 'muc#roomconfig_passwordprotectedroom'})
+                                    .c('value').t(1).up().up()
+                                .c('field', {'type': 'fixed'})
+                                    .c('value').t('If a password is required to enter this room,'+
+                                                'you must specify the password below.').up().up()
+                                .c('field', {
+                                    'label': 'Password',
+                                    'type': 'text-private',
+                                    'var': 'muc#roomconfig_roomsecret'})
+                                    .c('value').t('cauldronburn');
+                        _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
+
+                        test_utils.waitUntil(function () {
+                            return $(view.el.querySelector('form.chatroom-form')).length;
+                        }, 300).then(function () {
+                            expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1);
+                            expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
+                            var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]'));
+                            expect($membersonly.length).toBe(1);
+                            expect($membersonly.attr('type')).toBe('checkbox');
+                            $membersonly.prop('checked', true);
+
+                            var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]'));
+                            expect($moderated.length).toBe(1);
+                            expect($moderated.attr('type')).toBe('checkbox');
+                            $moderated.prop('checked', true);
+
+                            var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]'));
+                            expect($password.length).toBe(1);
+                            expect($password.attr('type')).toBe('password');
+
+                            var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]'));
+                            expect($allowpm.length).toBe(1);
+                            $allowpm.val('moderators');
+
+                            var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]'));
+                            expect($presencebroadcast.length).toBe(1);
+                            $presencebroadcast.val(['moderator']);
+
+                            view.el.querySelector('input[type="submit"]').click();
+
+                            var $sent_stanza = $(sent_IQ.toLocaleString());
+                            expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1');
+                            expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1');
+                            expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators');
+                            expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator');
+                            done();
+                        });
+                    });
                 }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
             }));
 
@@ -1973,24 +1985,28 @@
                 var view = _converse.chatboxviews.get('lounge@localhost'),
                     trimmed_chatboxes = _converse.minimized_chats;
 
-                spyOn(view, 'minimize').and.callThrough();
-                spyOn(view, 'maximize').and.callThrough();
-                spyOn(_converse, 'emit');
-                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
-                view.el.querySelector('.toggle-chatbox-button').click();
-
-                expect(view.minimize).toHaveBeenCalled();
-                expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
-                expect(u.isVisible(view.el)).toBeFalsy();
-                expect(view.model.get('minimized')).toBeTruthy();
-                expect(view.minimize).toHaveBeenCalled();
-                var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
-                trimmedview.el.querySelector("a.restore-chat").click();
-                expect(view.maximize).toHaveBeenCalled();
-                expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
-                expect(view.model.get('minimized')).toBeFalsy();
-                expect(_converse.emit.calls.count(), 3);
-                done();
+                test_utils.waitUntil(function () {
+                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+                }, 300).then(function () {
+                    spyOn(view, 'minimize').and.callThrough();
+                    spyOn(view, 'maximize').and.callThrough();
+                    spyOn(_converse, 'emit');
+                    view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                    view.el.querySelector('.toggle-chatbox-button').click();
+
+                    expect(view.minimize).toHaveBeenCalled();
+                    expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
+                    expect(u.isVisible(view.el)).toBeFalsy();
+                    expect(view.model.get('minimized')).toBeTruthy();
+                    expect(view.minimize).toHaveBeenCalled();
+                    var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
+                    trimmedview.el.querySelector("a.restore-chat").click();
+                    expect(view.maximize).toHaveBeenCalled();
+                    expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
+                    expect(view.model.get('minimized')).toBeFalsy();
+                    expect(_converse.emit.calls.count(), 3);
+                    done();
+                });
             }));
 
             it("can be closed again by clicking a DOM element with class 'close-chatbox-button'",
@@ -2000,19 +2016,23 @@
 
                 test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
                 var view = _converse.chatboxviews.get('lounge@localhost');
-                spyOn(view, 'close').and.callThrough();
-                spyOn(_converse, 'emit');
-                spyOn(view, 'leave');
-                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
-                view.el.querySelector('.close-chatbox-button').click();
-                expect(view.close).toHaveBeenCalled();
-                expect(view.leave).toHaveBeenCalled();
-                // XXX: After refactoring, the chat box only gets closed
-                // once we have confirmation from the server. To test this,
-                // we would have to mock the returned presence stanza.
-                // See the "leave" method on the ChatRoomView.
-                // expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
-                done();
+                test_utils.waitUntil(function () {
+                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
+                }, 300).then(function () {
+                    spyOn(view, 'close').and.callThrough();
+                    spyOn(_converse, 'emit');
+                    spyOn(view, 'leave');
+                    view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                    view.el.querySelector('.close-chatbox-button').click();
+                    expect(view.close).toHaveBeenCalled();
+                    expect(view.leave).toHaveBeenCalled();
+                    // XXX: After refactoring, the chat box only gets closed
+                    // once we have confirmation from the server. To test this,
+                    // we would have to mock the returned presence stanza.
+                    // See the "leave" method on the ChatRoomView.
+                    // expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
+                    done();
+                });
             }));
         });
 

+ 2 - 2
spec/disco.js

@@ -79,7 +79,7 @@
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
                     var entities = _converse.disco_entities;
-                    expect(entities.length).toBe(1);
+                    expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
                     expect(entities.get(_converse.domain).features.length).toBe(5);
                     expect(entities.get(_converse.domain).identities.length).toBe(3);
                     expect(entities.get('localhost').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
@@ -159,7 +159,7 @@
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
                     entities = _converse.disco_entities;
-                    expect(entities.length).toBe(4);
+                    expect(entities.length).toBe(5); // We have an extra entity, which is the user's JID
                     expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
                     expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
                     done();

+ 1 - 1
spec/protocol.js

@@ -55,7 +55,7 @@
                     function (done, _converse) {
 
                 var contact, sent_stanza, IQ_id, stanza;
-                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'vcard-temp')
+                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
                 .then(function () {
                     return test_utils.waitUntil(function () {
                         return _converse.xmppstatus.get('fullname');

+ 44 - 25
src/converse-bookmarks.js

@@ -71,26 +71,33 @@
                     this.setBookmarkState();
                 },
 
-                generateHeadingHTML () {
+                renderHeading () {
                     const { _converse } = this.__super__,
-                        { __ } = _converse,
-                        html = this.__super__.generateHeadingHTML.apply(this, arguments);
+                          { __ } = _converse;
+
                     if (_converse.allow_bookmarks) {
-                        const div = document.createElement('div');
-                        div.innerHTML = html;
-                        const bookmark_button = tpl_chatroom_bookmark_toggle(
-                            _.assignIn(
-                                this.model.toJSON(),
-                                {
-                                    info_toggle_bookmark: __('Bookmark this room'),
-                                    bookmarked: this.model.get('bookmarked')
-                                }
-                            ));
-                        const close_button = div.querySelector('.close-chatbox-button');
-                        close_button.insertAdjacentHTML('afterend', bookmark_button);
-                        return div.innerHTML;
+                        _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then((identity) => {
+                            if (_.isNil(identity)) {
+                                return;
+                            }
+                            const div = document.createElement('div');
+                            div.innerHTML = this.generateHeadingHTML();
+
+                            const bookmark_button = tpl_chatroom_bookmark_toggle(
+                                _.assignIn(
+                                    this.model.toJSON(),
+                                    {
+                                        info_toggle_bookmark: __('Bookmark this room'),
+                                        bookmarked: this.model.get('bookmarked')
+                                    }
+                                ));
+                            const close_button = div.querySelector('.close-chatbox-button');
+                            close_button.insertAdjacentHTML('afterend', bookmark_button);
+                            this.el.querySelector('.chat-head-chatroom').innerHTML = div.innerHTML;
+                        });
+                    } else {
+                        return this.__super__.renderHeading.apply(this, arguments);
                     }
-                    return html;
                 },
 
                 checkForReservedNick () {
@@ -112,6 +119,9 @@
 
                 onBookmarked () {
                     const icon = this.el.querySelector('.icon-pushpin');
+                    if (_.isNull(icon)) {
+                        return;
+                    }
                     if (this.model.get('bookmarked')) {
                         icon.classList.add('button-on');
                     } else {
@@ -520,14 +530,23 @@
                 if (!_converse.allow_bookmarks) {
                     return;
                 }
-                _converse.bookmarks = new _converse.Bookmarks();
-                _converse.bookmarks.fetchBookmarks().then(() => {
-                    _converse.bookmarksview = new _converse.BookmarksView(
-                        {'model': _converse.bookmarks}
-                    );
-                })
-                .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR))
-                .then(() => {
+                // Only initialize bookmarks if the server supports PEP
+                _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then((identity) => {
+                    if (_.isNil(identity)) {
+                        _converse.emit('bookmarksInitialized');
+                        return;
+                    }
+                    _converse.bookmarks = new _converse.Bookmarks();
+                    _converse.bookmarks.fetchBookmarks().then(() => {
+                        _converse.bookmarksview = new _converse.BookmarksView(
+                            {'model': _converse.bookmarks}
+                        );
+                    }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR))
+                      .then(() => {
+                          _converse.emit('bookmarksInitialized');
+                      });
+                }).catch((e) => {
+                    _converse.log(e, Strophe.LogLevel.ERROR);
                     _converse.emit('bookmarksInitialized');
                 });
             };

+ 1 - 0
src/converse-core.js

@@ -1473,6 +1473,7 @@
         this.connfeedback = new this.ConnectionFeedback();
 
         this.XMPPStatus = Backbone.Model.extend({
+
             initialize () {
                 this.set({
                     'status' : this.getStatus()

+ 3 - 0
src/converse-muc.js

@@ -326,6 +326,9 @@
                     );
                 }
                 const promises = [_converse.api.waitUntil('roomsAutoJoined')]
+                if (_converse.allow_bookmarks) {
+                    promises.push( _converse.api.waitUntil('bookmarksInitialized'));
+                }
                 Promise.all(promises).then(() => {
                     _converse.api.rooms.open(jid);
                 });

+ 8 - 0
tests/mock.js

@@ -118,6 +118,14 @@
             'debug': false
         }, settings || {}));
         _converse.ChatBoxViews.prototype.trimChat = function () {};
+
+        var entity = _converse.api.disco.entities.get(_converse.bare_jid, true);
+        entity.identities.create({
+            'category': 'pubsub',
+            'type': 'pep'
+        });
+        entity.waitUntilFeaturesDiscovered.resolve();
+
         window.converse_disable_effects = true;
         return _converse;
     }

+ 3 - 2
tests/utils.js

@@ -14,11 +14,12 @@
     }
     utils.waitUntil = waitUntilPromise.default;
 
-    utils.waitUntilFeatureSupportConfirmed = function (_converse, feature_name) {
+    utils.waitUntilFeatureSupportConfirmed = function (_converse, entity_jid, feature_name) {
         var IQ_disco, stanza;
         return utils.waitUntil(function () {
             IQ_disco = _.filter(_converse.connection.IQ_stanzas, function (iq) {
-                return iq.nodeTree.querySelector('query[xmlns="http://jabber.org/protocol/disco#info"]');
+                return iq.nodeTree.querySelector('query[xmlns="http://jabber.org/protocol/disco#info"]') &&
+                    iq.nodeTree.getAttribute('to') === entity_jid;
             }).pop();
             return !_.isUndefined(IQ_disco);
         }, 300).then(function () {