瀏覽代碼

Use $.when to keep track of all outgoing promises

Now that we are sending multiple IQ stanzas when setting affiliations.
We now again have a sendAffiliation method, so we use that.
JC Brand 8 年之前
父節點
當前提交
b2240cfe94
共有 2 個文件被更改,包括 123 次插入56 次删除
  1. 61 5
      spec/chatroom.js
  2. 62 51
      src/converse-muc.js

+ 61 - 5
spec/chatroom.js

@@ -1163,11 +1163,59 @@
 
             }));
 
+            it("to make a user an owner", mock.initConverse(function (converse) {
+                var sent_IQ, IQ_id;
+                var sendIQ = converse.connection.sendIQ;
+                spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    sent_IQ = iq;
+                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                });
+                test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
+                var view = converse.chatboxviews.get('lounge@localhost');
+                spyOn(view, 'onMessageSubmitted').andCallThrough();
+                spyOn(view, 'setAffiliation').andCallThrough();
+                spyOn(view, 'showStatusNotification').andCallThrough();
+                spyOn(view, 'validateRoleChangeCommand').andCallThrough();
+                view.$el.find('.chat-textarea').text('/owner');
+                view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
+                expect(view.onMessageSubmitted).toHaveBeenCalled();
+                expect(view.validateRoleChangeCommand).toHaveBeenCalled();
+                expect(view.showStatusNotification).toHaveBeenCalledWith(
+                    "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.",
+                    true
+                );
+                expect(view.setAffiliation).not.toHaveBeenCalled();
+
+                // Call now with the correct amount of arguments.
+                // XXX: Calling onMessageSubmitted directly, trying
+                // again via triggering Event doesn't work for some weird
+                // reason.
+                view.onMessageSubmitted('/owner annoyingGuy@localhost You\'re annoying');
+                expect(view.validateRoleChangeCommand.callCount).toBe(2);
+                expect(view.showStatusNotification.callCount).toBe(1);
+                expect(view.setAffiliation).toHaveBeenCalled();
+                // Check that the member list now gets updated
+                expect(sent_IQ.toLocaleString()).toBe(
+                    "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                        "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
+                            "<item affiliation='owner' jid='annoyingGuy@localhost'>"+
+                                "<reason>You&apos;re annoying</reason>"+
+                            "</item>"+
+                        "</query>"+
+                    "</iq>");
+            }));
+
             it("to ban a user", mock.initConverse(function (converse) {
+                var sent_IQ, IQ_id;
+                var sendIQ = converse.connection.sendIQ;
+                spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    sent_IQ = iq;
+                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                });
                 test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
                 var view = converse.chatboxviews.get('lounge@localhost');
                 spyOn(view, 'onMessageSubmitted').andCallThrough();
-                spyOn(view, 'setAffiliations').andCallThrough();
+                spyOn(view, 'setAffiliation').andCallThrough();
                 spyOn(view, 'showStatusNotification').andCallThrough();
                 spyOn(view, 'validateRoleChangeCommand').andCallThrough();
                 view.$el.find('.chat-textarea').text('/ban');
@@ -1178,16 +1226,24 @@
                     "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.",
                     true
                 );
-                expect(view.setAffiliations).not.toHaveBeenCalled();
-
+                expect(view.setAffiliation).not.toHaveBeenCalled();
                 // Call now with the correct amount of arguments.
                 // XXX: Calling onMessageSubmitted directly, trying
                 // again via triggering Event doesn't work for some weird
                 // reason.
-                view.onMessageSubmitted('/ban jid This is the reason');
+                view.onMessageSubmitted('/ban annoyingGuy@localhost You\'re annoying');
                 expect(view.validateRoleChangeCommand.callCount).toBe(2);
                 expect(view.showStatusNotification.callCount).toBe(1);
-                expect(view.setAffiliations).toHaveBeenCalled();
+                expect(view.setAffiliation).toHaveBeenCalled();
+                // Check that the member list now gets updated
+                expect(sent_IQ.toLocaleString()).toBe(
+                    "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                        "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
+                            "<item affiliation='outcast' jid='annoyingGuy@localhost'>"+
+                                "<reason>You&apos;re annoying</reason>"+
+                            "</item>"+
+                        "</query>"+
+                    "</iq>");
             }));
         });
 

+ 62 - 51
src/converse-muc.js

@@ -586,14 +586,52 @@
                     return delta;
                 },
 
+                setAffiliation: function (affiliation, members) {
+                    /* Send an IQ stanzas to the server to modify one particular
+                     * affiliation for certain members
+                     *
+                     * See: http://xmpp.org/extensions/xep-0045.html#modifymember
+                     *
+                     * Parameters:
+                     *  (Object) members: A map of jids, affiliations and
+                     *      optionally reasons. Only those entries with the
+                     *      same affiliation as being currently set will be
+                     *      considered.
+                     *
+                     * Returns:
+                     *  A promise which resolves and fails depending on the
+                     *  XMPP server response.
+                     */
+                    var deferred = new $.Deferred();
+                    var iq = $iq({to: this.model.get('jid'), type: "set"})
+                        .c("query", {xmlns: Strophe.NS.MUC_ADMIN});
+
+                    _.each(members, function (member) {
+                        if (!_.isUndefined(member.affiliation) &&
+                                member.affiliation !== affiliation) {
+                            return;
+                        }
+                        iq.c("item", {
+                            'affiliation': member.affiliation || affiliation,
+                            'jid': member.jid
+                        });
+                        if (!_.isUndefined(member.reason)) {
+                            iq.c("reason", member.reason).up();
+                        }
+                        iq.up();
+                    });
+                    converse.connection.sendIQ(iq, deferred.resolve, deferred.reject);
+                    return deferred;
+                },
+
                 setAffiliations: function (members, onSuccess, onError) {
-                    /* Send an IQ stanza to the server to modify the
+                    /* Send IQ stanzas to the server to modify the
                      * affiliations in this room.
                      *
                      * See: http://xmpp.org/extensions/xep-0045.html#modifymember
                      *
                      * Parameters:
-                     *  (Object) members: A map of jids and affiliations
+                     *  (Object) members: A map of jids, affiliations and optionally reasons
                      *  (Function) onSuccess: callback for a succesful response
                      *  (Function) onError: callback for an error response
                      */
@@ -602,26 +640,9 @@
                         onSuccess(null);
                         return;
                     }
-                    var room_jid = this.model.get('jid');
                     var affiliations = _.uniq(_.pluck(members, 'affiliation'));
-                    _.each(affiliations, function (affiliation) {
-                        var iq = $iq({to: room_jid, type: "set"})
-                            .c("query", {xmlns: Strophe.NS.MUC_ADMIN});
-                        _.each(members, function (member) {
-                            if (member.affiliation !== affiliation) {
-                                return;
-                            }
-                            iq.c("item", {
-                                'affiliation': member.affiliation,
-                                'jid': member.jid
-                            });
-                            if (!_.isUndefined(member.reason)) {
-                                iq.c("reason", member.reason).up();
-                            }
-                            iq.up();
-                        });
-                        converse.connection.sendIQ(iq, onSuccess, onError);
-                    });
+                    var promises = _.map(affiliations, _.partial(this.setAffiliation, _, members), this);
+                    $.when.apply($, promises).done(onSuccess).fail(onError);
                 },
 
                 marshallAffiliationIQs: function () {
@@ -836,21 +857,17 @@
                     switch (match[1]) {
                         case 'admin':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
-                            this.setAffiliations(
-                                [{'jid': args[0],
-                                'affiliation': 'admin',
-                                'reason': args[1]
-                                }], undefined, this.onCommandError.bind(this)
-                            );
+                            this.setAffiliation('admin',
+                                    [{ 'jid': args[0],
+                                       'reason': args[1]
+                                    }]).fail(this.onCommandError.bind(this));
                             break;
                         case 'ban':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
-                            this.setAffiliations(
-                                [{'jid': args[0],
-                                'affiliation': 'outcast',
-                                'reason': args[1]
-                                }], undefined, this.onCommandError.bind(this)
-                            );
+                            this.setAffiliation('outcast',
+                                    [{ 'jid': args[0],
+                                       'reason': args[1]
+                                    }]).fail(this.onCommandError.bind(this));
                             break;
                         case 'clear':
                             this.clearChatRoomMessages();
@@ -894,12 +911,10 @@
                             break;
                         case 'member':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
-                            this.setAffiliations(
-                                [{'jid': args[0],
-                                'affiliation': 'member',
-                                'reason': args[1]
-                                }], undefined, this.onCommandError.bind(this)
-                            );
+                            this.setAffiliation('member',
+                                    [{ 'jid': args[0],
+                                       'reason': args[1]
+                                    }]).fail(this.onCommandError.bind(this));
                             break;
                         case 'nick':
                             converse.connection.send($pres({
@@ -910,12 +925,10 @@
                             break;
                         case 'owner':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
-                            this.setAffiliations(
-                                [{'jid': args[0],
-                                'affiliation': 'owner',
-                                'reason': args[1]
-                                }], undefined, this.onCommandError.bind(this)
-                            );
+                            this.setAffiliation('owner',
+                                    [{ 'jid': args[0],
+                                       'reason': args[1]
+                                    }]).fail(this.onCommandError.bind(this));
                             break;
                         case 'op':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
@@ -925,12 +938,10 @@
                             break;
                         case 'revoke':
                             if (!this.validateRoleChangeCommand(match[1], args)) { break; }
-                            this.setAffiliations(
-                                [{'jid': args[0],
-                                'affiliation': 'none',
-                                'reason': args[1]
-                                }], undefined, this.onCommandError.bind(this)
-                            );
+                            this.setAffiliation('none',
+                                    [{ 'jid': args[0],
+                                       'reason': args[1]
+                                    }]).fail(this.onCommandError.bind(this));
                             break;
                         case 'topic':
                             converse.connection.send(