瀏覽代碼

Wait for promises before opening chats in API methods

JC Brand 7 年之前
父節點
當前提交
506aa33131
共有 14 個文件被更改,包括 1958 次插入1913 次删除
  1. 3 0
      CHANGES.md
  2. 24 19
      dist/converse.js
  3. 93 97
      spec/bookmarks.js
  4. 356 344
      spec/chatbox.js
  5. 436 441
      spec/chatroom.js
  6. 291 295
      spec/http-file-upload.js
  7. 509 491
      spec/messages.js
  8. 43 36
      spec/minchats.js
  9. 105 102
      spec/roomslist.js
  10. 26 22
      spec/spoilers.js
  11. 10 9
      spec/user-details-modal.js
  12. 1 2
      src/converse-muc-views.js
  13. 7 2
      src/i18n.js
  14. 54 53
      tests/utils.js

+ 3 - 0
CHANGES.md

@@ -39,6 +39,9 @@
 - New API method `_converse.api.vcard.update`.
 - The `contactStatusChanged` event has been renamed to `contactPresenceChanged`
   and a event `presenceChanged` is now also triggered on the contact.
+- `_converse.api.chats.open` and `_converse.api.rooms.open` now returns a 
+  `Presence` which resolves with the `Backbone.Model` representing the chat
+  object.
 
 ## UI changes
 

+ 24 - 19
dist/converse.js

@@ -76149,17 +76149,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           this.model.on('show', this.show, this);
           this.model.occupants.on('add', this.showJoinNotification, this);
           this.model.occupants.on('remove', this.showLeaveNotification, this);
-          this.model.occupants.on('change:show', occupant => {
-            if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) {
-              return;
-            }
-
-            if (occupant.get('show') === 'offline') {
-              this.showLeaveNotification(occupant);
-            } else if (occupant.get('show') === 'online') {
-              this.showJoinNotification(occupant);
-            }
-          });
+          this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this);
           this.createEmojiPicker();
           this.createOccupantsView();
           this.render().insertIntoDOM();
@@ -76169,8 +76159,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             const handler = () => {
               if (!u.isPersistableModel(this.model)) {
                 // Happens during tests, nothing to do if this
-                // is a hanging chatbox (i.e. not in the
-                // collection anymore).
+                // is a hanging chatbox (i.e. not in the collection anymore).
                 return;
               }
 
@@ -77047,6 +77036,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
         },
 
+        showJoinOrLeaveNotification(occupant) {
+          if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) {
+            return;
+          }
+
+          if (occupant.get('show') === 'offline') {
+            this.showLeaveNotification(occupant);
+          } else if (occupant.get('show') === 'online') {
+            this.showJoinNotification(occupant);
+          }
+        },
+
         showJoinNotification(occupant) {
           if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
             return;
@@ -77094,10 +77095,9 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         showLeaveNotification(occupant) {
           const nick = occupant.get('nick'),
                 stat = occupant.get('status'),
-                last_el = this.content.lastElementChild,
-                last_msg_date = last_el.getAttribute('data-isodate');
+                last_el = this.content.lastElementChild;
 
-          if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_msg_date).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) {
+          if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_el.getAttribute('data-isodate')).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) {
             let message;
 
             if (_.isNil(stat)) {
@@ -77128,7 +77128,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
               'data': `data-leave="${nick}"`
             };
 
-            if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
+            if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
               last_el.outerHTML = tpl_info(data);
             } else {
               const el = u.stringToElement(tpl_info(data));
@@ -83843,8 +83843,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
         xhr.onload = function () {
           if (xhr.status >= 200 && xhr.status < 400) {
-            jed_instance = new Jed(window.JSON.parse(xhr.responseText));
-            resolve();
+            try {
+              const data = window.JSON.parse(xhr.responseText);
+              jed_instance = new Jed(data);
+              resolve();
+            } catch (e) {
+              xhr.onerror(e);
+            }
           } else {
             xhr.onerror();
           }

+ 93 - 97
spec/bookmarks.js

@@ -34,24 +34,24 @@
                 });
                 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('.toggle-bookmark');
-                    $bookmark[0].click();
+                let view;
+                test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC')
+                .then(() => {
+                    var jid = 'theplay@conference.shakespeare.lit';
+                    view = _converse.chatboxviews.get(jid);
+                    spyOn(view, 'renderBookmarkForm').and.callThrough();
+                    spyOn(view, 'closeForm').and.callThrough();
+                    return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
+                }).then(() => {
+                    var bookmark = view.el.querySelector('.toggle-bookmark');
+                    bookmark.click();
                     expect(view.renderBookmarkForm).toHaveBeenCalled();
 
                     view.el.querySelector('.button-cancel').click();
                     expect(view.closeForm).toHaveBeenCalled();
-                    expect($bookmark.hasClass('on-button'), false);
+                    expect(u.hasClass('on-button', bookmark), false);
 
-                    $bookmark[0].click();
+                    bookmark.click();
                     expect(view.renderBookmarkForm).toHaveBeenCalled();
 
                     /* Client uploads data:
@@ -93,7 +93,7 @@
                     view.el.querySelector('.btn-primary').click();
 
                     expect(view.model.get('bookmarked')).toBeTruthy();
-                    expect($bookmark.hasClass('on-button'), true);
+                    expect(u.hasClass('on-button', bookmark), true);
 
                     expect(sent_stanza.toLocaleString()).toBe(
                         "<iq type='set' from='dummy@localhost/resource' xmlns='jabber:client' id='"+IQ_id+"'>"+
@@ -175,100 +175,96 @@
             it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises(
                 null, ['rosterGroupsFetched'], {}, function (done, _converse) {
 
+                let view;
                 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('.toggle-bookmark');
-                        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();
-                    });
+                ).then(() => test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'))
+                .then(() => {
+                    view = _converse.chatboxviews.get('lounge@localhost');
+                    return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')))
+                }).then(function () {
+                    var bookmark_icon = view.el.querySelector('.toggle-bookmark');
+                    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) {
 
+                let sent_stanza, IQ_id, view, sendIQ;
+
                 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');
+                ).then(() => {
+                    sendIQ = _converse.connection.sendIQ;
+                    return test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
+                }).then(() => {
                     var jid = 'theplay@conference.shakespeare.lit';
-                    var view = _converse.chatboxviews.get(jid);
+                    view = _converse.chatboxviews.get(jid);
+                    return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
+                }).then(function () {
+                    spyOn(view, 'toggleBookmark').and.callThrough();
+                    spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
+                    view.delegateEvents();
 
-                    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('.toggle-bookmark'));
-                        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();
+                    _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('.toggle-bookmark');
+                    expect(u.hasClass('button-on', bookmark_icon)).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.click();
+                    expect(view.toggleBookmark).toHaveBeenCalled();
+                    expect(u.hasClass('button-on', bookmark_icon)).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();
                 });
             }));
         });
@@ -585,9 +581,8 @@
                         'name':  'The Play',
                         'nick': ''
                     });
-                    test_utils.waitUntil(function () {
-                        return $('#chatrooms .bookmarks.rooms-list .room-item:visible').length;
-                    }, 300).then(function () {
+                    test_utils.waitUntil(() => $('#chatrooms .bookmarks.rooms-list .room-item:visible').length
+                    ).then(function () {
                         expect($('#chatrooms .bookmarks.rooms-list').hasClass('collapsed')).toBeFalsy();
                         expect($('#chatrooms .bookmarks.rooms-list .room-item:visible').length).toBe(1);
                         expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
@@ -612,6 +607,7 @@
             { hide_open_bookmarks: true },
             function (done, _converse) {
 
+            const jid = 'room@conference.example.org';
             test_utils.waitUntilDiscoConfirmed(
                 _converse, _converse.bare_jid,
                 [{'category': 'pubsub', 'type': 'pep'}],
@@ -625,14 +621,12 @@
                 _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'
                 });
-
                 expect(_converse.bookmarks.length).toBe(1);
                 var room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
                 expect(room_els.length).toBe(1);
@@ -640,9 +634,11 @@
                 // Check that it disappears once the room is opened
                 var bookmark = _converse.bookmarksview.el.querySelector(".open-room");
                 bookmark.click();
+                return test_utils.waitUntil(() => _converse.chatboxviews.get(jid));
+            }).then(() => {
                 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);
+                const view = _converse.chatboxviews.get(jid);
                 view.close();
                 expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeFalsy();
                 done();

文件差異過大導致無法顯示
+ 356 - 344
spec/chatbox.js


文件差異過大導致無法顯示
+ 436 - 441
spec/chatroom.js


+ 291 - 295
spec/http-file-upload.js

@@ -225,25 +225,28 @@
                         [{'category': 'server', 'type':'IM'}],
                         ['http://jabber.org/protocol/disco#items'], [], 'info').then(function () {
 
-                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items').then(function () {
-                            test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []).then(function () {
-                                test_utils.createContacts(_converse, 'current');
-                                var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                                test_utils.openChatBoxFor(_converse, contact_jid);
-                                var view = _converse.chatboxviews.get(contact_jid);
-                                test_utils.waitUntil(function () {
-                                    return view.el.querySelector('.upload-file');
-                                }, 150).then(function () {
-                                    expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
-                                    done();
-                                });
-                            });
+                        let contact_jid, view;
+
+                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items')
+                        .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []))
+                        .then(() => {
+                            test_utils.createContacts(_converse, 'current', 3);
+                            _converse.emit('rosterContactsFetched');
+
+                            contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
+                            return test_utils.openChatBoxFor(_converse, contact_jid);
+                        }).then(() => {
+                            view = _converse.chatboxviews.get(contact_jid);
+                            test_utils.waitUntil(() => view.el.querySelector('.upload-file'));
+                        }).then(() => {
+                            expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
+                            done();
                         });
                     });
                 }));
 
                 it("appears in MUC chats", mock.initConverseWithPromises(
-                        null, ['rosterGroupsFetched'], {},
+                        null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                         function (done, _converse) {
 
                     test_utils.waitUntilDiscoConfirmed(
@@ -251,19 +254,15 @@
                         [{'category': 'server', 'type':'IM'}],
                         ['http://jabber.org/protocol/disco#items'], [], 'info').then(function () {
 
-                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items').then(function () {
-                            test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []).then(function () {
-                                test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
-                                    var view = _converse.chatboxviews.get('lounge@localhost');
-                                    test_utils.waitUntil(function () {
-                                        return view.el.querySelector('.upload-file');
-                                    }).then(function () {
-                                        expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
-                                        done();
-                                    });
-                                });
-                            });
-                        });
+                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items')
+                        .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []))
+                        .then(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'))
+                        .then(() => test_utils.waitUntil(() => _converse.chatboxviews.get('lounge@localhost').el.querySelector('.upload-file')))
+                        .then(() => {
+                            const view = _converse.chatboxviews.get('lounge@localhost');
+                            expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
+                            done();
+                        }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     });
                 }));
 
@@ -277,101 +276,104 @@
 
                             var send_backup = XMLHttpRequest.prototype.send;
                             var IQ_stanzas = _converse.connection.IQ_stanzas;
+                            let contact_jid;
 
-                            test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () {
-                                test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () {
-                                    test_utils.createContacts(_converse, 'current');
-                                    var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                                    test_utils.openChatBoxFor(_converse, contact_jid);
-                                    var view = _converse.chatboxviews.get(contact_jid);
-                                    var file = {
-                                        'type': 'image/jpeg',
-                                        'size': '23456' ,
-                                        'lastModifiedDate': "",
-                                        'name': "my-juliet.jpg"
-                                    };
-                                    view.model.sendFiles([file]);
-                                    return test_utils.waitUntil(function () {
-                                        return _.filter(IQ_stanzas, function (iq) {
-                                            return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request');
-                                        }).length > 0;
-                                    }).then(function () {
-                                        var iq = IQ_stanzas.pop();
-                                        expect(iq.toLocaleString()).toBe(
-                                            "<iq from='dummy@localhost/resource' "+
-                                                "to='upload.montague.tld' "+
-                                                "type='get' "+
-                                                "xmlns='jabber:client' "+
-                                                "id='"+iq.nodeTree.getAttribute('id')+"'>"+
-                                            "<request xmlns='urn:xmpp:http:upload:0' "+
-                                                "filename='my-juliet.jpg' "+
-                                                "size='23456' "+
-                                                "content-type='image/jpeg'/>"+
-                                            "</iq>");
-
-                                        var base_url = document.URL.split(window.location.pathname)[0];
-                                        var message = base_url+"/logo/conversejs-filled.svg";
-
-                                        var stanza = Strophe.xmlHtmlNode(
-                                            "<iq from='upload.montague.tld'"+
-                                            "    id='"+iq.nodeTree.getAttribute('id')+"'"+
-                                            "    to='dummy@localhost/resource'"+
-                                            "    type='result'>"+
-                                            "<slot xmlns='urn:xmpp:http:upload:0'>"+
-                                            "    <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
-                                            "    <header name='Authorization'>Basic Base64String==</header>"+
-                                            "    <header name='Cookie'>foo=bar; user=romeo</header>"+
-                                            "    </put>"+
-                                            "    <get url='"+message+"' />"+
-                                            "</slot>"+
-                                            "</iq>").firstElementChild;
-
-                                        spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
-                                            const message = view.model.messages.at(0);
-                                            expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
-                                            message.set('progress', 0.5);
-                                            expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5');
-                                            message.set('progress', 1);
-                                            expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
-                                            message.save({
-                                                'upload': _converse.SUCCESS,
-                                                'oob_url': message.get('get'),
-                                                'message': message.get('get')
-                                            });
-                                        });
-                                        var sent_stanza;
-                                        spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
-                                            sent_stanza = stanza;
+                           test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items')
+                            .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []))
+                            .then(() => {
+                                test_utils.createContacts(_converse, 'current');
+                                _converse.emit('rosterContactsFetched');
+                                contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
+                                return test_utils.openChatBoxFor(_converse, contact_jid);
+                            }).then(() => {
+                                var view = _converse.chatboxviews.get(contact_jid);
+                                var file = {
+                                    'type': 'image/jpeg',
+                                    'size': '23456' ,
+                                    'lastModifiedDate': "",
+                                    'name': "my-juliet.jpg"
+                                };
+                                view.model.sendFiles([file]);
+                                return test_utils.waitUntil(function () {
+                                    return _.filter(IQ_stanzas, function (iq) {
+                                        return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request');
+                                    }).length > 0;
+                                }).then(function () {
+                                    var iq = IQ_stanzas.pop();
+                                    expect(iq.toLocaleString()).toBe(
+                                        "<iq from='dummy@localhost/resource' "+
+                                            "to='upload.montague.tld' "+
+                                            "type='get' "+
+                                            "xmlns='jabber:client' "+
+                                            "id='"+iq.nodeTree.getAttribute('id')+"'>"+
+                                        "<request xmlns='urn:xmpp:http:upload:0' "+
+                                            "filename='my-juliet.jpg' "+
+                                            "size='23456' "+
+                                            "content-type='image/jpeg'/>"+
+                                        "</iq>");
+
+                                    var base_url = document.URL.split(window.location.pathname)[0];
+                                    var message = base_url+"/logo/conversejs-filled.svg";
+
+                                    var stanza = Strophe.xmlHtmlNode(
+                                        "<iq from='upload.montague.tld'"+
+                                        "    id='"+iq.nodeTree.getAttribute('id')+"'"+
+                                        "    to='dummy@localhost/resource'"+
+                                        "    type='result'>"+
+                                        "<slot xmlns='urn:xmpp:http:upload:0'>"+
+                                        "    <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
+                                        "    <header name='Authorization'>Basic Base64String==</header>"+
+                                        "    <header name='Cookie'>foo=bar; user=romeo</header>"+
+                                        "    </put>"+
+                                        "    <get url='"+message+"' />"+
+                                        "</slot>"+
+                                        "</iq>").firstElementChild;
+
+                                    spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
+                                        const message = view.model.messages.at(0);
+                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
+                                        message.set('progress', 0.5);
+                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5');
+                                        message.set('progress', 1);
+                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
+                                        message.save({
+                                            'upload': _converse.SUCCESS,
+                                            'oob_url': message.get('get'),
+                                            'message': message.get('get')
                                         });
-                                        _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                                    });
+                                    var sent_stanza;
+                                    spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
+                                        sent_stanza = stanza;
+                                    });
+                                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
+                                    return test_utils.waitUntil(function () {
+                                        return sent_stanza;
+                                    }, 1000).then(function () {
+                                        expect(sent_stanza.toLocaleString()).toBe(
+                                            "<message from='dummy@localhost/resource' "+
+                                                "to='irini.vlastuin@localhost' "+
+                                                "type='chat' "+
+                                                "id='"+sent_stanza.nodeTree.getAttribute('id')+"' xmlns='jabber:client'>"+
+                                                    "<body>"+message+"</body>"+
+                                                    "<active xmlns='http://jabber.org/protocol/chatstates'/>"+
+                                                    "<x xmlns='jabber:x:oob'>"+
+                                                        "<url>"+message+"</url>"+
+                                                    "</x>"+
+                                            "</message>");
                                         return test_utils.waitUntil(function () {
-                                            return sent_stanza;
-                                        }, 1000).then(function () {
-                                            expect(sent_stanza.toLocaleString()).toBe(
-                                                "<message from='dummy@localhost/resource' "+
-                                                    "to='irini.vlastuin@localhost' "+
-                                                    "type='chat' "+
-                                                    "id='"+sent_stanza.nodeTree.getAttribute('id')+"' xmlns='jabber:client'>"+
-                                                        "<body>"+message+"</body>"+
-                                                        "<active xmlns='http://jabber.org/protocol/chatstates'/>"+
-                                                        "<x xmlns='jabber:x:oob'>"+
-                                                            "<url>"+message+"</url>"+
-                                                        "</x>"+
-                                                "</message>");
-                                            return test_utils.waitUntil(function () {
-                                                return view.el.querySelector('.chat-image');
-                                            }, 1000);
-                                        }).then(function () {
-                                            // Check that the image renders
-                                            expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
-                                                `<!-- src/templates/image.html -->\n`+
-                                                `<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
-                                                    `<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
-                                                `</a>`);
-                                            XMLHttpRequest.prototype.send = send_backup;
-                                            done();
-                                        });
+                                            return view.el.querySelector('.chat-image');
+                                        }, 1000);
+                                    }).then(function () {
+                                        // Check that the image renders
+                                        expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
+                                            `<!-- src/templates/image.html -->\n`+
+                                            `<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
+                                                `<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
+                                            `</a>`);
+                                        XMLHttpRequest.prototype.send = send_backup;
+                                        done();
                                     });
                                 });
                             });
@@ -487,56 +489,53 @@
                         });
                     }));
 
-                    it("shows and error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) {
-                        var IQ_stanzas = _converse.connection.IQ_stanzas;
-                        var IQ_ids =  _converse.connection.IQ_ids;
-                        var send_backup = XMLHttpRequest.prototype.send;
+                    it("shows an error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) {
+                        const IQ_stanzas = _converse.connection.IQ_stanzas;
+                        const IQ_ids =  _converse.connection.IQ_ids;
+                        const send_backup = XMLHttpRequest.prototype.send;
+                        let view, contact_jid;
+
+                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [])
+                        .then(() => test_utils.waitUntil(() => _.filter(
+                            IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length
+                        )).then(() => {
+                            var stanza = _.find(IQ_stanzas, function (iq) {
+                                return iq.nodeTree.querySelector(
+                                    'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                            });
+                            var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
 
-                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []).then(function () {
-                            test_utils.waitUntil(function () {
+                            stanza = $iq({
+                                'type': 'result',
+                                'from': 'localhost',
+                                'to': 'dummy@localhost/resource',
+                                'id': info_IQ_id
+                            }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
+                                .c('identity', {
+                                    'category': 'server',
+                                    'type': 'im'}).up()
+                                .c('feature', {
+                                    'var': 'http://jabber.org/protocol/disco#info'}).up()
+                                .c('feature', {
+                                    'var': 'http://jabber.org/protocol/disco#items'});
+                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                            return _converse.api.disco.entities.get();
+                        }).then(function (entities) {
+                            expect(entities.length).toBe(2);
+                            expect(_.includes(entities.pluck('jid'), 'localhost')).toBe(true);
+                            expect(_.includes(entities.pluck('jid'), 'dummy@localhost')).toBe(true);
+
+                            expect(entities.get(_converse.domain).features.length).toBe(2);
+                            expect(entities.get(_converse.domain).identities.length).toBe(1);
+
+                            return test_utils.waitUntil(function () {
+                                // Converse.js sees that the entity has a disco#items feature,
+                                // so it will make a query for it.
                                 return _.filter(IQ_stanzas, function (iq) {
-                                    return iq.nodeTree.querySelector(
-                                        'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                                    return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]');
                                 }).length > 0;
-                            }, 300).then(function () {
-                                var stanza = _.find(IQ_stanzas, function (iq) {
-                                    return iq.nodeTree.querySelector(
-                                        'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
-                                });
-                                var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
-
-                                stanza = $iq({
-                                    'type': 'result',
-                                    'from': 'localhost',
-                                    'to': 'dummy@localhost/resource',
-                                    'id': info_IQ_id
-                                }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
-                                    .c('identity', {
-                                        'category': 'server',
-                                        'type': 'im'}).up()
-                                    .c('feature', {
-                                        'var': 'http://jabber.org/protocol/disco#info'}).up()
-                                    .c('feature', {
-                                        'var': 'http://jabber.org/protocol/disco#items'});
-                                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-
-                                _converse.api.disco.entities.get().then(function(entities) {
-                                    expect(entities.length).toBe(2);
-                                    expect(_.includes(entities.pluck('jid'), 'localhost')).toBe(true);
-                                    expect(_.includes(entities.pluck('jid'), 'dummy@localhost')).toBe(true);
-
-                                    expect(entities.get(_converse.domain).features.length).toBe(2);
-                                    expect(entities.get(_converse.domain).identities.length).toBe(1);
-
-                                    return test_utils.waitUntil(function () {
-                                        // Converse.js sees that the entity has a disco#items feature,
-                                        // so it will make a query for it.
-                                        return _.filter(IQ_stanzas, function (iq) {
-                                            return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]');
-                                        }).length > 0;
-                                    }, 300);
-                                });
-                            }).then(function () {
+                            }, 300);
+                        }).then(function () {
                             var stanza = _.find(IQ_stanzas, function (iq) {
                                 return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]');
                             });
@@ -550,77 +549,77 @@
                                 .c('item', {
                                     'jid': 'upload.localhost',
                                     'name': 'HTTP File Upload'});
-                                _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                                _converse.api.disco.entities.get().then(function (entities) {
-                                    expect(entities.length).toBe(2);
-                                    expect(entities.get('localhost').items.length).toBe(1);
-                                    return test_utils.waitUntil(function () {
-                                        // Converse.js sees that the entity has a disco#info feature,
-                                        // so it will make a query for it.
-                                        return _.filter(IQ_stanzas, function (iq) {
-                                            return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
-                                        }).length > 0;
-                                    }, 300);
-                                });
-                            }).then(function () {
-                                var stanza = _.find(IQ_stanzas, function (iq) {
-                                    return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
-                                });
-                                var IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
-                                expect(stanza.toLocaleString()).toBe(
-                                    "<iq from='dummy@localhost/resource' to='upload.localhost' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
-                                        "<query xmlns='http://jabber.org/protocol/disco#info'/>"+
-                                    "</iq>");
-
-                                // Upload service responds and reports a maximum file size of 5MiB
-                                stanza = $iq({'type': 'result', 'to': 'dummy@localhost/resource', 'id': IQ_id, 'from': 'upload.localhost'})
-                                    .c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
-                                        .c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up()
-                                        .c('feature', {'var':'urn:xmpp:http:upload:0'}).up()
-                                        .c('x', {'type':'result', 'xmlns':'jabber:x:data'})
-                                            .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
-                                                .c('value').t('urn:xmpp:http:upload:0').up().up()
-                                            .c('field', {'var':'max-file-size'})
-                                                .c('value').t('5242880');
-                                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-
-                                _converse.api.disco.entities.get().then(function (entities) {
-                                    expect(entities.get('localhost').items.get('upload.localhost').identities.where({'category': 'store'}).length).toBe(1);
-                                    _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(
-                                        function (result) {
-                                            test_utils.createContacts(_converse, 'current');
-                                            var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                                            test_utils.openChatBoxFor(_converse, contact_jid);
-                                            var view = _converse.chatboxviews.get(contact_jid);
-                                            var file = {
-                                                'type': 'image/jpeg',
-                                                'size': '5242881',
-                                                'lastModifiedDate': "",
-                                                'name': "my-juliet.jpg"
-                                            };
-                                            view.model.sendFiles([file]);
-                                            return test_utils.waitUntil(function () {
-                                                return view.el.querySelectorAll('.message').length;
-                                            }).then(function () {
-                                                const messages = view.el.querySelectorAll('.message.chat-error');
-                                                expect(messages.length).toBe(1);
-                                                expect(messages[0].textContent).toBe(
-                                                    'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.');
-                                                done();
-                                            });
-                                        }
-                                    );
-                                }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
-                            })
-                        });
+                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                            _converse.api.disco.entities.get().then(function (entities) {
+                                expect(entities.length).toBe(2);
+                                expect(entities.get('localhost').items.length).toBe(1);
+                                return test_utils.waitUntil(function () {
+                                    // Converse.js sees that the entity has a disco#info feature,
+                                    // so it will make a query for it.
+                                    return _.filter(IQ_stanzas, function (iq) {
+                                        return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                                    }).length > 0;
+                                }, 300);
+                            });
+                        }).then(function () {
+                            var stanza = _.find(IQ_stanzas, function (iq) {
+                                return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                            });
+                            var IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
+                            expect(stanza.toLocaleString()).toBe(
+                                "<iq from='dummy@localhost/resource' to='upload.localhost' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                                    "<query xmlns='http://jabber.org/protocol/disco#info'/>"+
+                                "</iq>");
+
+                            // Upload service responds and reports a maximum file size of 5MiB
+                            stanza = $iq({'type': 'result', 'to': 'dummy@localhost/resource', 'id': IQ_id, 'from': 'upload.localhost'})
+                                .c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
+                                    .c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up()
+                                    .c('feature', {'var':'urn:xmpp:http:upload:0'}).up()
+                                    .c('x', {'type':'result', 'xmlns':'jabber:x:data'})
+                                        .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
+                                            .c('value').t('urn:xmpp:http:upload:0').up().up()
+                                        .c('field', {'var':'max-file-size'})
+                                            .c('value').t('5242880');
+                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                            return _converse.api.disco.entities.get();
+                        }).then(function (entities) {
+                            expect(entities.get('localhost').items.get('upload.localhost').identities.where({'category': 'store'}).length).toBe(1);
+                            return _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain);
+                        }).then(function (result) {
+                            test_utils.createContacts(_converse, 'current');
+                            _converse.emit('rosterContactsFetched');
+
+                            contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
+                            return test_utils.openChatBoxFor(_converse, contact_jid);
+                        }).then(() => {
+                            view = _converse.chatboxviews.get(contact_jid);
+                            var file = {
+                                'type': 'image/jpeg',
+                                'size': '5242881',
+                                'lastModifiedDate': "",
+                                'name': "my-juliet.jpg"
+                            };
+                            view.model.sendFiles([file]);
+                            return test_utils.waitUntil(() => view.el.querySelectorAll('.message').length)
+                        }).then(function () {
+                            const messages = view.el.querySelectorAll('.message.chat-error');
+                            expect(messages.length).toBe(1);
+                            expect(messages[0].textContent).toBe(
+                                'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.');
+                            done();
+                        }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
                     }));
                 });
             });
 
             describe("While a file is being uploaded", function () {
 
-                it("shows a progress bar", mock.initConverseWithAsync(function (done, _converse) {
+                it("shows a progress bar", mock.initConverseWithPromises(
+                            null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
+
                     test_utils.waitUntilDiscoConfirmed(
                         _converse, _converse.domain,
                         [{'category': 'server', 'type':'IM'}],
@@ -628,77 +627,74 @@
 
                         var send_backup = XMLHttpRequest.prototype.send;
                         var IQ_stanzas = _converse.connection.IQ_stanzas;
-
-                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () {
-                            test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () {
-                                test_utils.createContacts(_converse, 'current');
-                                var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                                test_utils.openChatBoxFor(_converse, contact_jid);
-                                var view = _converse.chatboxviews.get(contact_jid);
-                                var file = {
-                                    'type': 'image/jpeg',
-                                    'size': '23456' ,
-                                    'lastModifiedDate': "",
-                                    'name': "my-juliet.jpg"
-                                };
-                                view.model.sendFiles([file]);
-                                return test_utils.waitUntil(function () {
-                                    return _.filter(IQ_stanzas, function (iq) {
-                                        return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request');
-                                    }).length > 0;
-                                }).then(function () {
-                                    var iq = IQ_stanzas.pop();
-                                    expect(iq.toLocaleString()).toBe(
-                                        "<iq from='dummy@localhost/resource' "+
-                                            "to='upload.montague.tld' "+
-                                            "type='get' "+
-                                            "xmlns='jabber:client' "+
-                                            "id='"+iq.nodeTree.getAttribute('id')+"'>"+
-                                        "<request xmlns='urn:xmpp:http:upload:0' "+
-                                            "filename='my-juliet.jpg' "+
-                                            "size='23456' "+
-                                            "content-type='image/jpeg'/>"+
-                                        "</iq>");
-
-                                    var base_url = document.URL.split(window.location.pathname)[0];
-                                    var message = base_url+"/logo/conversejs-filled.svg";
-
-                                    var stanza = Strophe.xmlHtmlNode(
-                                        "<iq from='upload.montague.tld'"+
-                                        "    id='"+iq.nodeTree.getAttribute('id')+"'"+
-                                        "    to='dummy@localhost/resource'"+
-                                        "    type='result'>"+
-                                        "<slot xmlns='urn:xmpp:http:upload:0'>"+
-                                        "    <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
-                                        "    <header name='Authorization'>Basic Base64String==</header>"+
-                                        "    <header name='Cookie'>foo=bar; user=romeo</header>"+
-                                        "    </put>"+
-                                        "    <get url='"+message+"' />"+
-                                        "</slot>"+
-                                        "</iq>").firstElementChild;
-
-                                    spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
-                                        const message = view.model.messages.at(0);
-                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
-                                        message.set('progress', 0.5);
-                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5');
-                                        message.set('progress', 1);
-                                        expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
-                                        expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
-                                        done();
-                                    });
-                                    var sent_stanza;
-                                    spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
-                                        sent_stanza = stanza;
-                                    });
-                                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                                });
+                        let view, contact_jid;
+
+                        test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items')
+                        .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []))
+                        .then(() => {
+                            test_utils.createContacts(_converse, 'current');
+                            _converse.emit('rosterContactsFetched');
+
+                            contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
+                            return test_utils.openChatBoxFor(_converse, contact_jid);
+                        }).then(() => {
+                            view = _converse.chatboxviews.get(contact_jid);
+                            const file = {
+                                'type': 'image/jpeg',
+                                'size': '23456' ,
+                                'lastModifiedDate': "",
+                                'name': "my-juliet.jpg"
+                            };
+                            view.model.sendFiles([file]);
+                            return test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length)
+                        }).then(function () {
+                            const iq = IQ_stanzas.pop();
+                            expect(iq.toLocaleString()).toBe(
+                                "<iq from='dummy@localhost/resource' "+
+                                    "to='upload.montague.tld' "+
+                                    "type='get' "+
+                                    "xmlns='jabber:client' "+
+                                    "id='"+iq.nodeTree.getAttribute('id')+"'>"+
+                                "<request xmlns='urn:xmpp:http:upload:0' "+
+                                    "filename='my-juliet.jpg' "+
+                                    "size='23456' "+
+                                    "content-type='image/jpeg'/>"+
+                                "</iq>");
+
+                            const base_url = document.URL.split(window.location.pathname)[0];
+                            const message = base_url+"/logo/conversejs-filled.svg";
+                            const stanza = Strophe.xmlHtmlNode(
+                                "<iq from='upload.montague.tld'"+
+                                "    id='"+iq.nodeTree.getAttribute('id')+"'"+
+                                "    to='dummy@localhost/resource'"+
+                                "    type='result'>"+
+                                "<slot xmlns='urn:xmpp:http:upload:0'>"+
+                                "    <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
+                                "    <header name='Authorization'>Basic Base64String==</header>"+
+                                "    <header name='Cookie'>foo=bar; user=romeo</header>"+
+                                "    </put>"+
+                                "    <get url='"+message+"' />"+
+                                "</slot>"+
+                                "</iq>").firstElementChild;
+                            spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
+                                const message = view.model.messages.at(0);
+                                expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
+                                message.set('progress', 0.5);
+                                expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5');
+                                message.set('progress', 1);
+                                expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
+                                expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
+                                done();
                             });
+                            var sent_stanza;
+                            spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
+                                sent_stanza = stanza;
+                            });
+                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
                         });
                     });
                 }));
             });
-
         });
     });
 }));

文件差異過大導致無法顯示
+ 509 - 491
spec/messages.js


+ 43 - 36
spec/minchats.js

@@ -13,32 +13,37 @@
                 function (done, _converse) {
 
             test_utils.createContacts(_converse, 'current');
+            _converse.emit('rosterContactsFetched');
+
             test_utils.openControlBox();
             _converse.minimized_chats.toggleview.model.browserStorage._clear();
             _converse.minimized_chats.initToggle();
 
-            var contact_jid, chatview;
-            contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            test_utils.openChatBoxFor(_converse, contact_jid);
-            chatview = _converse.chatboxviews.get(contact_jid);
-            expect(chatview.model.get('minimized')).toBeFalsy();
-            expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
-            chatview.el.querySelector('.toggle-chatbox-button').click();
-            expect(chatview.model.get('minimized')).toBeTruthy();
-            expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
-            expect(_converse.minimized_chats.keys().length).toBe(1);
-            expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
-
-            contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-            test_utils.openChatBoxFor(_converse, contact_jid);
-            chatview = _converse.chatboxviews.get(contact_jid);
-            expect(chatview.model.get('minimized')).toBeFalsy();
-            chatview.el.querySelector('.toggle-chatbox-button').click();
-            expect(chatview.model.get('minimized')).toBeTruthy();
-            expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
-            expect(_converse.minimized_chats.keys().length).toBe(2);
-            expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
-            done();
+            let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            let chatview;
+            test_utils.openChatBoxFor(_converse, contact_jid)
+            .then(() => {
+                chatview = _converse.chatboxviews.get(contact_jid);
+                expect(chatview.model.get('minimized')).toBeFalsy();
+                expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
+                chatview.el.querySelector('.toggle-chatbox-button').click();
+                expect(chatview.model.get('minimized')).toBeTruthy();
+                expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
+                expect(_converse.minimized_chats.keys().length).toBe(1);
+                expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
+
+                contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
+                return test_utils.openChatBoxFor(_converse, contact_jid);
+            }).then(() => {
+                chatview = _converse.chatboxviews.get(contact_jid);
+                expect(chatview.model.get('minimized')).toBeFalsy();
+                chatview.el.querySelector('.toggle-chatbox-button').click();
+                expect(chatview.model.get('minimized')).toBeTruthy();
+                expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
+                expect(_converse.minimized_chats.keys().length).toBe(2);
+                expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
+                done();
+            });
         }));
 
         it("can be toggled to hide or show minimized chats",
@@ -47,24 +52,26 @@
                 function (done, _converse) {
 
             test_utils.createContacts(_converse, 'current');
+            _converse.emit('rosterContactsFetched');
+
             test_utils.openControlBox();
             _converse.minimized_chats.toggleview.model.browserStorage._clear();
             _converse.minimized_chats.initToggle();
 
-            var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            test_utils.openChatBoxFor(_converse, contact_jid);
-            var chatview = _converse.chatboxviews.get(contact_jid);
-            expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
-            chatview.model.set({'minimized': true});
-            expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
-            expect(_converse.minimized_chats.keys().length).toBe(1);
-            expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
-            expect($(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')).is(':visible')).toBeTruthy();
-            expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
-            _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
-
-            return test_utils.waitUntil(() => u.isVisible(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))))
-            .then(function () {
+            const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            test_utils.openChatBoxFor(_converse, contact_jid)
+            .then(() => {
+                const chatview = _converse.chatboxviews.get(contact_jid);
+                expect(u.isVisible(_converse.minimized_chats.el)).toBeFalsy();
+                chatview.model.set({'minimized': true});
+                expect(u.isVisible(_converse.minimized_chats.el)).toBeTruthy();
+                expect(_converse.minimized_chats.keys().length).toBe(1);
+                expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
+                expect(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))).toBeTruthy();
+                expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
+                _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
+                return test_utils.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
+            }).then(() => {
                 expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
                 done();
             });

+ 105 - 102
spec/roomslist.js

@@ -12,44 +12,46 @@
     describe("A list of open rooms", function () {
 
         it("is shown in the \"Rooms\" panel", mock.initConverseWithPromises(
-            null, ['rosterGroupsFetched'],
+            null, ['rosterGroupsFetched', 'chatBoxesFetched'],
             { allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
             },
             function (done, _converse) {
                 test_utils.openControlBox();
-                var controlbox = _converse.chatboxviews.get('controlbox');
-
-                var list = controlbox.el.querySelector('div.rooms-list-container');
+                const controlbox = _converse.chatboxviews.get('controlbox');
+                let list = controlbox.el.querySelector('div.rooms-list-container');
                 expect(_.includes(list.classList, 'hidden')).toBeTruthy();
 
-                test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC');
-
-                expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy();
-                var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-                expect(room_els.length).toBe(1);
-                expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
-
-                test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
-                room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-                expect(room_els.length).toBe(2);
-
-                var view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
-                view.close();
-                room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-                expect(room_els.length).toBe(1);
-                expect(room_els[0].innerText).toBe('lounge@localhost');
-                list = controlbox.el.querySelector('div.rooms-list-container');
-                expect(_.includes(list.classList, 'hidden')).toBeFalsy();
-
-                view = _converse.chatboxviews.get('lounge@localhost');
-                view.close();
-                room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-                expect(room_els.length).toBe(0);
-
-                list = controlbox.el.querySelector('div.rooms-list-container');
-                expect(_.includes(list.classList, 'hidden')).toBeTruthy();
-                done();
+                let room_els;
+
+                test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC')
+                .then(() => {
+                    expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy();
+                    room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                    expect(room_els.length).toBe(1);
+                    expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
+                    return test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
+                }).then(() => {
+                    room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                    expect(room_els.length).toBe(2);
+
+                    var view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
+                    view.close();
+                    room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                    expect(room_els.length).toBe(1);
+                    expect(room_els[0].innerText).toBe('lounge@localhost');
+                    list = controlbox.el.querySelector('div.rooms-list-container');
+                    expect(_.includes(list.classList, 'hidden')).toBeFalsy();
+
+                    view = _converse.chatboxviews.get('lounge@localhost');
+                    view.close();
+                    room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                    expect(room_els.length).toBe(0);
+
+                    list = controlbox.el.querySelector('div.rooms-list-container');
+                    expect(_.includes(list.classList, 'hidden')).toBeTruthy();
+                    done();
+                });
             }
         ));
     });
@@ -57,79 +59,81 @@
     describe("A groupchat shown in the groupchats list", function () {
 
         it("is highlighted if its currently open", mock.initConverseWithPromises(
-            null, ['rosterGroupsFetched'],
+            null, ['rosterGroupsFetched', 'chatBoxesFetched'],
             { whitelisted_plugins: ['converse-roomslist'],
               allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
             }, function (done, _converse) {
 
-            spyOn(_converse, 'isSingleton').and.callFake(function () {
-                return true;
-            });
+            spyOn(_converse, 'isSingleton').and.callFake(() => true);
 
+            let room_els, item;
             test_utils.openControlBox();
-            _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
-            let room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom");
-            expect(room_els.length).toBe(1);
-
-            let item = room_els[0];
-            expect(u.hasClass('open', item)).toBe(true);
-            expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
+            _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'})
+            .then(() => {
+                room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom");
+                expect(room_els.length).toBe(1);
 
-            _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
-            room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-            expect(room_els.length).toBe(2);
+                item = room_els[0];
+                expect(u.hasClass('open', item)).toBe(true);
+                expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
+                return _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
+            }).then(() => {
+                room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                expect(room_els.length).toBe(2);
 
-            room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom.open");
-            expect(room_els.length).toBe(1);
-            item = room_els[0];
-            expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit');
-            done();
+                room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom.open");
+                expect(room_els.length).toBe(1);
+                item = room_els[0];
+                expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit');
+                done();
+            });
         }));
 
         it("has an info icon which opens a details modal when clicked", mock.initConverseWithPromises(
-            null, ['rosterGroupsFetched'],
+            null, ['rosterGroupsFetched', 'chatBoxesFetched'],
             { whitelisted_plugins: ['converse-roomslist'],
               allow_bookmarks: false // Makes testing easier, otherwise we
                                      // have to mock stanza traffic.
             }, function (done, _converse) {
 
+            let view;
             test_utils.openControlBox();
-            _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
-            const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
-            const last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
-            const IQ_id = last_stanza.getAttribute('id');
-            const features_stanza = $iq({
-                    'from': 'coven@chat.shakespeare.lit',
-                    'id': IQ_id,
-                    'to': 'dummy@localhost/desktop',
-                    'type': 'result'
-                })
-                .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
-                    .c('identity', {
-                        'category': 'conference',
-                        'name': 'A Dark Cave',
-                        'type': 'text'
-                    }).up()
-                    .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
-                    .c('feature', {'var': 'muc_passwordprotected'}).up()
-                    .c('feature', {'var': 'muc_hidden'}).up()
-                    .c('feature', {'var': 'muc_temporary'}).up()
-                    .c('feature', {'var': 'muc_open'}).up()
-                    .c('feature', {'var': 'muc_unmoderated'}).up()
-                    .c('feature', {'var': 'muc_nonanonymous'}).up()
-                    .c('feature', {'var': 'urn:xmpp:mam:0'}).up()
-                    .c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
-                        .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
-                            .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
-                        .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
-                            .c('value').t('This is the description').up().up()
-                        .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));
-
-            test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
-            .then(function () {
+            _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'})
+            .then(() => {
+                view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
+                const last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
+                const IQ_id = last_stanza.getAttribute('id');
+                const features_stanza = $iq({
+                        'from': 'coven@chat.shakespeare.lit',
+                        'id': IQ_id,
+                        'to': 'dummy@localhost/desktop',
+                        'type': 'result'
+                    })
+                    .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
+                        .c('identity', {
+                            'category': 'conference',
+                            'name': 'A Dark Cave',
+                            'type': 'text'
+                        }).up()
+                        .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
+                        .c('feature', {'var': 'muc_passwordprotected'}).up()
+                        .c('feature', {'var': 'muc_hidden'}).up()
+                        .c('feature', {'var': 'muc_temporary'}).up()
+                        .c('feature', {'var': 'muc_open'}).up()
+                        .c('feature', {'var': 'muc_unmoderated'}).up()
+                        .c('feature', {'var': 'muc_nonanonymous'}).up()
+                        .c('feature', {'var': 'urn:xmpp:mam:0'}).up()
+                        .c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
+                            .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
+                                .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
+                            .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
+                                .c('value').t('This is the description').up().up()
+                            .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));
+                return test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
+            }).then(function () {
                 var presence = $pres({
                         to: _converse.connection.jid,
                         from: 'coven@chat.shakespeare.lit/some1',
@@ -201,23 +205,22 @@
             },
             function (done, _converse) {
 
-            spyOn(window, 'confirm').and.callFake(function () {
-                return true;
-            });
+            spyOn(window, 'confirm').and.callFake(() => true);
             expect(_converse.chatboxes.length).toBe(1);
-            test_utils.openChatRoom(
-                _converse, 'lounge', 'conference.shakespeare.lit', 'JC');
-            expect(_converse.chatboxes.length).toBe(2);
-            var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-            expect(room_els.length).toBe(1);
-            var close_el = _converse.rooms_list_view.el.querySelector(".close-room");
-            close_el.click();
-            expect(window.confirm).toHaveBeenCalledWith(
-                'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?');
-            room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
-            expect(room_els.length).toBe(0);
-            expect(_converse.chatboxes.length).toBe(1);
-            done();
+            test_utils.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC')
+            .then(() => {
+                expect(_converse.chatboxes.length).toBe(2);
+                var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                expect(room_els.length).toBe(1);
+                var close_el = _converse.rooms_list_view.el.querySelector(".close-room");
+                close_el.click();
+                expect(window.confirm).toHaveBeenCalledWith(
+                    'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?');
+                room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
+                expect(room_els.length).toBe(0);
+                expect(_converse.chatboxes.length).toBe(1);
+                done();
+            });
         }));
 
         it("shows unread messages directed at the user", mock.initConverseWithAsync(

+ 26 - 22
spec/spoilers.js

@@ -92,12 +92,14 @@
 
         it("can be sent without a hint",
             mock.initConverseWithPromises(
-                null, ['rosterGroupsFetched'], {},
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                 function (done, _converse) {
 
-            test_utils.createContacts(_converse, 'current');
+            test_utils.createContacts(_converse, 'current', 1);
+            _converse.emit('rosterContactsFetched');
+
             test_utils.openControlBox();
-            var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
 
             // XXX: We need to send a presence from the contact, so that we
             // have a resource, that resource is then queried to see
@@ -108,9 +110,9 @@
                 'to': 'dummy@localhost'
             });
             _converse.connection._dataRecv(test_utils.createRequest(presence));
-            test_utils.openChatBoxFor(_converse, contact_jid);
-
-            test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
+            test_utils.openChatBoxFor(_converse, contact_jid)
+            .then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
+            .then(() => {
                 var view = _converse.chatboxviews.get(contact_jid);
                 spyOn(view, 'onMessageSubmitted').and.callThrough();
                 spyOn(_converse.connection, 'send');
@@ -167,10 +169,12 @@
 
         it("can be sent with a hint",
             mock.initConverseWithPromises(
-                null, ['rosterGroupsFetched'], {},
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                 function (done, _converse) {
 
-            test_utils.createContacts(_converse, 'current');
+            test_utils.createContacts(_converse, 'current', 1);
+            _converse.emit('rosterContactsFetched');
+
             test_utils.openControlBox();
             var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
 
@@ -183,9 +187,9 @@
                 'to': 'dummy@localhost'
             });
             _converse.connection._dataRecv(test_utils.createRequest(presence));
-            test_utils.openChatBoxFor(_converse, contact_jid);
-
-            test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
+            test_utils.openChatBoxFor(_converse, contact_jid)
+            .then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
+            .then(() => {
                 var view = _converse.chatboxviews.get(contact_jid);
                 var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
                 spoiler_toggle.click();
@@ -206,17 +210,17 @@
                 expect(view.onMessageSubmitted).toHaveBeenCalled();
 
                 /* Test the XML stanza 
-                *
-                * <message from="dummy@localhost/resource"
-                *          to="max.frankfurter@localhost"
-                *          type="chat"
-                *          id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
-                *          xmlns="jabber:client">
-                *    <body>This is the spoiler</body>
-                *    <active xmlns="http://jabber.org/protocol/chatstates"/>
-                *    <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
-                * </message>"
-                */
+                 *
+                 * <message from="dummy@localhost/resource"
+                 *          to="max.frankfurter@localhost"
+                 *          type="chat"
+                 *          id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
+                 *          xmlns="jabber:client">
+                 *    <body>This is the spoiler</body>
+                 *    <active xmlns="http://jabber.org/protocol/chatstates"/>
+                 *    <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
+                 * </message>"
+                 */
                 var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
                 var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
 

+ 10 - 9
spec/user-details-modal.js

@@ -58,16 +58,17 @@
             test_utils.createContacts(_converse, 'current');
             _converse.emit('rosterContactsFetched');
 
+            let view, modal;
             const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            test_utils.openChatBoxFor(_converse, contact_jid);
-
-            const view = _converse.chatboxviews.get(contact_jid);
-            const show_modal_button = view.el.querySelector('.show-user-details-modal');
-            expect(u.isVisible(show_modal_button)).toBeTruthy();
-            show_modal_button.click();
-            const modal = view.user_details_modal;
-            test_utils.waitUntil(() => u.isVisible(modal.el), 2000)
-            .then(function () {
+            test_utils.openChatBoxFor(_converse, contact_jid)
+            .then(() => {
+                view = _converse.chatboxviews.get(contact_jid);
+                const show_modal_button = view.el.querySelector('.show-user-details-modal');
+                expect(u.isVisible(show_modal_button)).toBeTruthy();
+                show_modal_button.click();
+                modal = view.user_details_modal;
+                return test_utils.waitUntil(() => u.isVisible(modal.el), 2000);
+            }).then(function () {
                 spyOn(window, 'confirm').and.returnValue(true);
                 spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) {
                     errback();

+ 1 - 2
src/converse-muc-views.js

@@ -561,8 +561,7 @@
                         const handler = () => {
                             if (!u.isPersistableModel(this.model)) {
                                 // Happens during tests, nothing to do if this
-                                // is a hanging chatbox (i.e. not in the
-                                // collection anymore).
+                                // is a hanging chatbox (i.e. not in the collection anymore).
                                 return;
                             }
                             this.populateAndJoin();

+ 7 - 2
src/i18n.js

@@ -148,8 +148,13 @@
                 );
                 xhr.onload = function () {
                     if (xhr.status >= 200 && xhr.status < 400) {
-                        jed_instance = new Jed(window.JSON.parse(xhr.responseText));
-                        resolve();
+                        try {
+                            const data = window.JSON.parse(xhr.responseText);
+                            jed_instance = new Jed(data);
+                            resolve();
+                        } catch (e) {
+                            xhr.onerror(e);
+                        }
                     } else {
                         xhr.onerror();
                     }

+ 54 - 53
tests/utils.js

@@ -98,8 +98,9 @@
         return views;
     };
 
-    utils.openChatBoxFor = function (converse, jid) {
-        return converse.roster.get(jid).trigger("open");
+    utils.openChatBoxFor = function (_converse, jid) {
+        _converse.roster.get(jid).trigger("open");
+        return utils.waitUntil(() => _converse.chatboxviews.get(jid));
     };
 
     utils.openChatRoomViaModal = function (_converse, jid, nick) {
@@ -121,64 +122,64 @@
     };
 
     utils.openChatRoom = function (_converse, room, server, nick) {
-        _converse.api.rooms.open(`${room}@${server}`);
+        return _converse.api.rooms.open(`${room}@${server}`);
     };
 
     utils.openAndEnterChatRoom = function (_converse, room, server, nick) {
         let last_stanza;
 
-        return new Promise(function (resolve, reject) {
-            _converse.api.rooms.open(`${room}@${server}`);
-            const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
-            // We pretend this is a new room, so no disco info is returned.
-            let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
-            const IQ_id = last_stanza.getAttribute('id');
-            const features_stanza = $iq({
-                    'from': room+'@'+server,
-                    'id': IQ_id,
-                    'to': nick+'@'+server,
-                    'type': 'error'
-                }).c('error', {'type': 'cancel'})
-                    .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
-            _converse.connection._dataRecv(utils.createRequest(features_stanza));
-
-            utils.waitUntil(() => {
-                return _.filter(
-                    _converse.connection.IQ_stanzas, (node) => {
-                        const query = node.nodeTree.querySelector('query');
-                        if (query && query.getAttribute('node') === 'x-roomuser-item') {
-                            last_stanza = node.nodeTree;
-                            return true;
-                        }
-                    }).length
-            }).then(function () {
-                // The XMPP server returns the reserved nick for this user.
+        return new Promise((resolve, reject) => {
+            return _converse.api.rooms.open(`${room}@${server}`).then(() => {
+                const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
+                // We pretend this is a new room, so no disco info is returned.
+                let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
                 const IQ_id = last_stanza.getAttribute('id');
-                const stanza = $iq({
-                    'type': 'result',
-                    'id': IQ_id,
-                    'from': view.model.get('jid'),
-                    'to': _converse.connection.jid 
-                }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
-                    .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
-                _converse.connection._dataRecv(utils.createRequest(stanza));
-                // The user has just entered the room (because join was called)
-                // and receives their own presence from the server.
-                // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
-                var presence = $pres({
-                        to: _converse.connection.jid,
-                        from: room+'@'+server+'/'+nick,
-                        id: 'DC352437-C019-40EC-B590-AF29E879AF97'
-                }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
-                    .c('item').attrs({
-                        affiliation: 'member',
-                        jid: _converse.bare_jid,
-                        role: 'participant'
-                    }).up()
-                    .c('status').attrs({code:'110'});
-                _converse.connection._dataRecv(utils.createRequest(presence));
-                resolve();
+                const features_stanza = $iq({
+                        'from': room+'@'+server,
+                        'id': IQ_id,
+                        'to': nick+'@'+server,
+                        'type': 'error'
+                    }).c('error', {'type': 'cancel'})
+                        .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
+                _converse.connection._dataRecv(utils.createRequest(features_stanza));
 
+                utils.waitUntil(() => {
+                    return _.filter(
+                        _converse.connection.IQ_stanzas, (node) => {
+                            const query = node.nodeTree.querySelector('query');
+                            if (query && query.getAttribute('node') === 'x-roomuser-item') {
+                                last_stanza = node.nodeTree;
+                                return true;
+                            }
+                        }).length
+                }).then(function () {
+                    // The XMPP server returns the reserved nick for this user.
+                    const IQ_id = last_stanza.getAttribute('id');
+                    const stanza = $iq({
+                        'type': 'result',
+                        'id': IQ_id,
+                        'from': view.model.get('jid'),
+                        'to': _converse.connection.jid 
+                    }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
+                        .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
+                    _converse.connection._dataRecv(utils.createRequest(stanza));
+                    // The user has just entered the room (because join was called)
+                    // and receives their own presence from the server.
+                    // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
+                    var presence = $pres({
+                            to: _converse.connection.jid,
+                            from: room+'@'+server+'/'+nick,
+                            id: 'DC352437-C019-40EC-B590-AF29E879AF97'
+                    }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
+                        .c('item').attrs({
+                            affiliation: 'member',
+                            jid: _converse.bare_jid,
+                            role: 'participant'
+                        }).up()
+                        .c('status').attrs({code:'110'});
+                    _converse.connection._dataRecv(utils.createRequest(presence));
+                    resolve();
+                }).catch(_.partial(console.error, _));
             }).catch(_.partial(console.error, _));
         }).catch(_.partial(console.error, _));
     };

部分文件因文件數量過多而無法顯示