Quellcode durchsuchen

Don't allow PEP bookmarks if #publish-options is not advertised

JC Brand vor 7 Jahren
Ursprung
Commit
ba09996998
7 geänderte Dateien mit 577 neuen und 546 gelöschten Zeilen
  1. 305 260
      spec/bookmarks.js
  2. 4 4
      spec/chatbox.js
  3. 234 248
      spec/chatroom.js
  4. 1 1
      spec/protocol.js
  5. 22 21
      src/converse-bookmarks.js
  6. 0 7
      tests/mock.js
  7. 11 5
      tests/utils.js

+ 305 - 260
spec/bookmarks.js

@@ -23,212 +23,90 @@
         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) {
-                sent_stanza = iq;
-                IQ_id = sendIQ.bind(this)(iq, callback, errback);
-            });
-            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();
-
-            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();
-            });
-        }));
-
-        it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises(
-            null, ['rosterGroupsFetched'], {}, function (done, _converse) {
-
-            var jid = 'lounge@localhost';
-            _converse.bookmarks.create({
-                'jid': jid,
-                'autojoin': false,
-                'name':  'The Lounge',
-                'nick': ' Othello'
-            });
-            expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeTruthy();
-
-            jid = 'theplay@conference.shakespeare.lit';
-            _converse.bookmarks.create({
-                'jid': jid,
-                'autojoin': true,
-                'name':  'The Play',
-                'nick': ' Othello'
-            });
-            expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
-            done();
-        }));
-
-        describe("when bookmarked", function () {
-
-            it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises(
-                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
-
-                test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
-                var view = _converse.chatboxviews.get('lounge@localhost');
-
-                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(
-                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
-
+            test_utils.waitUntilDiscoConfirmed(
+                _converse, _converse.bare_jid,
+                [{'category': 'pubsub', 'type': 'pep'}],
+                ['http://jabber.org/protocol/pubsub#publish-options']
+            ).then(function () {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
+                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();
 
                 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();
 
                 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();
+                    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();
 
-                    _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();
+                    expect($bookmark.hasClass('on-button'), true);
 
-                    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'/>"+
+                                        "<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>"+
@@ -247,32 +125,186 @@
                             "</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();
                 });
-            }));
-        });
+            });
+        }));
 
-        describe("and when autojoin is set", function () {
+        it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises(
+            null, ['rosterGroupsFetched'], {}, function (done, _converse) {
 
-            it("will be be opened and joined automatically upon login", mock.initConverse(function (_converse) {
-                spyOn(_converse.api.rooms, 'open');
-                var jid = 'theplay@conference.shakespeare.lit';
-                var model = _converse.bookmarks.create({
+            test_utils.waitUntilDiscoConfirmed(
+                _converse, _converse.bare_jid,
+                [{'category': 'pubsub', 'type': 'pep'}],
+                ['http://jabber.org/protocol/pubsub#publish-options']
+            ).then(function () {
+                var jid = 'lounge@localhost';
+                _converse.bookmarks.create({
                     'jid': jid,
                     'autojoin': false,
-                    'name':  'The Play',
-                    'nick': ''
+                    'name':  'The Lounge',
+                    'nick': ' Othello'
                 });
-                expect(_converse.api.rooms.open).not.toHaveBeenCalled();
-                _converse.bookmarks.remove(model);
+                expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeTruthy();
 
+                jid = 'theplay@conference.shakespeare.lit';
                 _converse.bookmarks.create({
                     'jid': jid,
                     'autojoin': true,
-                    'name':  'Hamlet',
-                    'nick': ''
+                    'name':  'The Play',
+                    'nick': ' Othello'
+                });
+                expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
+                done();
+            });
+        }));
+
+        describe("when bookmarked", function () {
+
+            it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
+                test_utils.waitUntilDiscoConfirmed(
+                    _converse, _converse.bare_jid,
+                    [{'category': 'pubsub', 'type': 'pep'}],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                ).then(function () {
+                    test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
+                    var view = _converse.chatboxviews.get('lounge@localhost');
+
+                    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(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
+                test_utils.waitUntilDiscoConfirmed(
+                    _converse, _converse.bare_jid,
+                    [{'category': 'pubsub', 'type': 'pep'}],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                ).then(function () {
+                    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);
+
+                    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();
+                    });
+                });
+            }));
+        });
+
+        describe("and when autojoin is set", function () {
+
+            it("will be be opened and joined automatically upon login", mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {}, function (done, _converse) {
+
+                test_utils.waitUntilDiscoConfirmed(
+                    _converse, _converse.bare_jid,
+                    [{'category': 'pubsub', 'type': 'pep'}],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                ).then(function () {
+                    spyOn(_converse.api.rooms, 'open');
+                    var jid = 'theplay@conference.shakespeare.lit';
+                    var model = _converse.bookmarks.create({
+                        'jid': jid,
+                        'autojoin': false,
+                        'name':  'The Play',
+                        'nick': ''
+                    });
+                    expect(_converse.api.rooms.open).not.toHaveBeenCalled();
+                    _converse.bookmarks.remove(model);
+
+                    _converse.bookmarks.create({
+                        'jid': jid,
+                        'autojoin': true,
+                        'name':  'Hamlet',
+                        'nick': ''
+                    });
+                    expect(_converse.api.rooms.open).toHaveBeenCalled();
+                    done();
                 });
-                expect(_converse.api.rooms.open).toHaveBeenCalled();
             }));
         });
     });
@@ -283,53 +315,58 @@
             ['send'], ['rosterGroupsFetched', 'connected'], {},
             function (done, _converse) {
 
-            test_utils.openControlBox().openRoomsPanel(_converse);
-
-            test_utils.waitUntil(function () {
-                return _converse.bookmarks;
-            }, 300).then(function () {
-                /* The stored data is automatically pushed to all of the user's
-                 * connected resources.
-                 *
-                 * Publisher receives event notification
-                 * -------------------------------------
-                 * <message from='juliet@capulet.lit'
-                 *         to='juliet@capulet.lit/balcony'
-                 *         type='headline'
-                 *         id='rnfoo1'>
-                 * <event xmlns='http://jabber.org/protocol/pubsub#event'>
-                 *     <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>
-                 * </event>
-                 * </message>
-                 */
-                var stanza = $msg({
-                    'from': 'dummy@localhost',
-                    'to': 'dummy@localhost/resource',
-                    'type': 'headline',
-                    'id': 'rnfoo1'
-                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
-                    .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');
+            test_utils.waitUntilDiscoConfirmed(
+                _converse, _converse.bare_jid,
+                [{'category': 'pubsub', 'type': 'pep'}],
+                ['http://jabber.org/protocol/pubsub#publish-options']
+            ).then(function () {
+                test_utils.openControlBox().openRoomsPanel(_converse);
+                test_utils.waitUntil(function () {
+                    return _converse.bookmarks;
+                }, 300).then(function () {
+                    /* The stored data is automatically pushed to all of the user's
+                    * connected resources.
+                    *
+                    * Publisher receives event notification
+                    * -------------------------------------
+                    * <message from='juliet@capulet.lit'
+                    *         to='juliet@capulet.lit/balcony'
+                    *         type='headline'
+                    *         id='rnfoo1'>
+                    * <event xmlns='http://jabber.org/protocol/pubsub#event'>
+                    *     <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>
+                    * </event>
+                    * </message>
+                    */
+                    var stanza = $msg({
+                        'from': 'dummy@localhost',
+                        'to': 'dummy@localhost/resource',
+                        'type': 'headline',
+                        'id': 'rnfoo1'
+                    }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                        .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');
 
-                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                expect(_converse.bookmarks.length).toBe(1);
-                expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
-                done();
+                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                    expect(_converse.bookmarks.length).toBe(1);
+                    expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
+                    done();
+                });
             });
         }));
 
@@ -337,9 +374,11 @@
             ['send'], ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
             function (done, _converse) {
 
-            test_utils.waitUntil(function () {
-                return _converse.bookmarks;
-            }, 300).then(function () {
+            test_utils.waitUntilDiscoConfirmed(
+                _converse, _converse.bare_jid,
+                [{'category': 'pubsub', 'type': 'pep'}],
+                ['http://jabber.org/protocol/pubsub#publish-options']
+            ).then(function () {
                 /* Client requests all items
                 * -------------------------
                 *
@@ -420,9 +459,11 @@
             it("shows a list of bookmarks", mock.initConverseWithPromises(
                 ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
 
-                test_utils.waitUntil(function () {
-                    return _converse.bookmarks;
-                }, 300).then(function () {
+                test_utils.waitUntilDiscoConfirmed(
+                    _converse, _converse.bare_jid,
+                    [{'category': 'pubsub', 'type': 'pep'}],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                ).then(function () {
 
                     test_utils.openControlBox().openRoomsPanel(_converse);
                     var IQ_id;
@@ -495,9 +536,11 @@
             it("remembers the toggle state of the bookmarks list", mock.initConverseWithPromises(
                 ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) {
 
-                test_utils.waitUntil(function () {
-                    return _converse.bookmarks;
-                }, 300).then(function () {
+                test_utils.waitUntilDiscoConfirmed(
+                    _converse, _converse.bare_jid,
+                    [{'category': 'pubsub', 'type': 'pep'}],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                ).then(function () {
                     var IQ_id;
                     expect(_.filter(_converse.connection.send.calls.all(), function (call) {
                         var stanza = call.args[0];
@@ -561,9 +604,11 @@
             { hide_open_bookmarks: true },
             function (done, _converse) {
 
-            test_utils.waitUntil(function () {
-                return _converse.bookmarks;
-            }, 300).then(function () {
+            test_utils.waitUntilDiscoConfirmed(
+                _converse, _converse.bare_jid,
+                [{'category': 'pubsub', 'type': 'pep'}],
+                ['http://jabber.org/protocol/pubsub#publish-options']
+            ).then(function () {
                 test_utils.openControlBox().openRoomsPanel(_converse);
                 // XXX Create bookmarks view here, otherwise we need to mock stanza
                 // traffic for it to get created.

+ 4 - 4
spec/chatbox.js

@@ -57,7 +57,7 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
+                test_utils.waitUntilDiscoConfirmed(_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, 'localhost', 'vcard-temp')
+                    test_utils.waitUntilDiscoConfirmed(_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, 'localhost', 'vcard-temp')
+                        test_utils.waitUntilDiscoConfirmed(_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, 'localhost', 'vcard-temp')
+                        test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
                         .then(function () {
                             return test_utils.waitUntil(function () {
                                 return _converse.xmppstatus.get('fullname');

+ 234 - 248
spec/chatroom.js

@@ -826,7 +826,7 @@
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
 
-                test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp')
+                test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
                 .then(function () {
                     return test_utils.waitUntil(function () {
                         return _converse.xmppstatus.get('fullname');
@@ -891,221 +891,215 @@
                 _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
                 view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
 
-                test_utils.waitUntil(function () {
-                    return !_.isNull(view.el.querySelector('.toggle-bookmark'));
-                }, 300).then(function () {
+                spyOn(view, 'saveAffiliationAndRole').and.callThrough();
 
-                    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));
 
-                    // 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"/>
+                *          <status code="110"/>
+                *      </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('.toggle-chatbox-button')).is(':visible')).toBeTruthy();
 
-                    /* <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"/>
-                    *          <status code="110"/>
-                    *      </x>
-                    *  </presence></body>
+                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>
                     */
-                    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();
+                    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 !_.isNull(view.el.querySelector('.configure-chatroom-button'));
+                        return $(view.el.querySelector('form.chatroom-form')).length;
                     }, 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();
-                        });
+                        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));
             }));
@@ -1985,28 +1979,24 @@
                 var view = _converse.chatboxviews.get('lounge@localhost'),
                     trimmed_chatboxes = _converse.minimized_chats;
 
-                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();
-                });
+                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'",
@@ -2016,23 +2006,19 @@
 
                 test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
                 var view = _converse.chatboxviews.get('lounge@localhost');
-                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();
-                });
+                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();
             }));
         });
 

+ 1 - 1
spec/protocol.js

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

+ 22 - 21
src/converse-bookmarks.js

@@ -71,32 +71,28 @@
                     this.setBookmarkState();
                 },
 
-                renderHeading () {
+                renderBookmarkToggle () {
                     const { _converse } = this.__super__,
                           { __ } = _converse;
+                    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 = this.el.querySelector('.close-chatbox-button');
+                    close_button.insertAdjacentHTML('afterend', bookmark_button);
+                },
 
+                renderHeading () {
+                    this.__super__.renderHeading.apply(this, arguments);
+                    const { _converse } = this.__super__;
                     if (_converse.allow_bookmarks) {
                         _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);
+                            this.renderBookmarkToggle();
+                        }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     }
                 },
 
@@ -537,9 +533,14 @@
                 if (!_converse.allow_bookmarks) {
                     return;
                 }
-                // Only initialize bookmarks if the server supports PEP
-                _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then((identity) => {
-                    if (_.isNil(identity)) {
+                Promise.all([
+                    _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid),
+                    _converse.api.disco.supports(Strophe.NS.PUBSUB+'#publish-options', _converse.bare_jid)
+                ]).then((args) => {
+                    const identity = args[0],
+                          options_support = args[1];
+
+                    if (_.isNil(identity) || !options_support.supported) {
                         _converse.emit('bookmarksInitialized');
                         return;
                     }

+ 0 - 7
tests/mock.js

@@ -119,13 +119,6 @@
         }, 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;
     }

+ 11 - 5
tests/utils.js

@@ -14,7 +14,7 @@
     }
     utils.waitUntil = waitUntilPromise.default;
 
-    utils.waitUntilFeatureSupportConfirmed = function (_converse, entity_jid, feature_name) {
+    utils.waitUntilDiscoConfirmed = function (_converse, entity_jid, identities, features) {
         var IQ_disco, stanza;
         return utils.waitUntil(function () {
             IQ_disco = _.filter(_converse.connection.IQ_stanzas, function (iq) {
@@ -24,13 +24,19 @@
             return !_.isUndefined(IQ_disco);
         }, 300).then(function () {
             var info_IQ_id = IQ_disco.nodeTree.getAttribute('id');
-            stanza = $iq({
+            var stanza = $iq({
                 'type': 'result',
-                'from': 'localhost',
+                'from': entity_jid,
                 'to': 'dummy@localhost/resource',
                 'id': info_IQ_id
-            }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
-                .c('feature', {'var': feature_name});
+            }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'});
+
+            _.forEach(identities, function (identity) {
+                stanza.c('identity', {'category': 'pubsub', 'type': 'pep'}).up()
+            });
+            _.forEach(features, function (feature) {
+                stanza.c('feature', {'var': feature}).up();
+            });
             _converse.connection._dataRecv(utils.createRequest(stanza));
         });
     }