Ver código fonte

Remove shared state between tests

Only a small subset of tests currently working with this.
JC Brand 8 anos atrás
pai
commit
50275cce64
6 arquivos alterados com 349 adições e 366 exclusões
  1. 59 80
      spec/converse.js
  2. 181 176
      spec/protocol.js
  3. 9 0
      src/converse-core.js
  4. 43 65
      tests/main.js
  5. 50 33
      tests/mock.js
  6. 7 12
      tests/utils.js

+ 59 - 80
spec/converse.js

@@ -1,4 +1,3 @@
-/*global converse */
 (function (root, factory) {
     define([
         "jquery",
@@ -12,10 +11,15 @@
 } (this, function ($, _, mock, test_utils) {
     var b64_sha1 = converse_api.env.b64_sha1;
 
-    return describe("Converse", $.proxy(function(mock, test_utils) {
+    describe("Converse", function() {
+        
+        afterEach(function () {
+            converse_api.user.logout();
+            test_utils.clearBrowserStorage();
+        });
 
         describe("Authentication", function () {
-            it("needs either a bosh_service_url a websocket_url or both", function () {
+            it("needs either a bosh_service_url a websocket_url or both", mock.initConverse(function (converse) {
                 var url = converse.bosh_service_url;
                 var connection = converse.connection;
                 delete converse.bosh_service_url;
@@ -24,10 +28,10 @@
                     new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both."));
                 converse.bosh_service_url = url;
                 converse.connection = connection;
-            });
+            }));
 
             describe("with prebind", function () {
-                it("needs a jid when also using keepalive", function () {
+                it("needs a jid when also using keepalive", mock.initConverse(function (converse) {
                     var authentication = converse.authentication;
                     var jid = converse.jid;
                     delete converse.jid;
@@ -38,9 +42,9 @@
                     converse.authentication= authentication;
                     converse.jid = jid;
                     converse.keepalive = false;
-                });
+                }));
 
-                it("needs jid, rid and sid values when not using keepalive", function () {
+                it("needs jid, rid and sid values when not using keepalive", mock.initConverse(function (converse) {
                     var authentication = converse.authentication;
                     var jid = converse.jid;
                     delete converse.jid;
@@ -50,13 +54,13 @@
                     converse.authentication= authentication;
                     converse.bosh_service_url = undefined;
                     converse.jid = jid;
-                });
+                }));
             });
         });
 
         describe("A chat state indication", function () {
 
-            it("are sent out when the client becomes or stops being idle", function () {
+            it("are sent out when the client becomes or stops being idle", mock.initConverse(function (converse) {
                 spyOn(converse, 'sendCSI').andCallThrough();
                 var sent_stanza;
                 spyOn(converse.connection, 'send').andCallFake(function (stanza) {
@@ -85,12 +89,12 @@
                 // Reset values
                 converse.csi_waiting_time = 0;
                 converse.features['urn:xmpp:csi:0'] = false;
-            });
+            }));
         });
 
         describe("Automatic status change", function () {
 
-            it("happens when the client is idle for long enough", function () {
+            it("happens when the client is idle for long enough", mock.initConverse(function (converse) {
                 var i = 0;
                 // Usually initialized by registerIntervalHandler
                 converse.idle_seconds = 0;
@@ -124,26 +128,21 @@
                 converse.auto_away = 0;
                 converse.auto_xa = 0;
                 converse.auto_changed_status = false;
-            });
+            }));
         });
 
         describe("The \"user\" grouping", function () {
 
             describe("The \"status\" API", function () {
-                beforeEach(function () {
-                    test_utils.closeAllChatBoxes();
-                    test_utils.clearBrowserStorage();
-                    converse.rosterview.model.reset();
-                });
 
-                it("has a method for getting the user's availability", function () {
+                it("has a method for getting the user's availability", mock.initConverse(function (converse) {
                     converse.xmppstatus.set('status', 'online');
                     expect(converse_api.user.status.get()).toBe('online');
                     converse.xmppstatus.set('status', 'dnd');
                     expect(converse_api.user.status.get()).toBe('dnd');
-                });
+                }));
 
-                it("has a method for setting the user's availability", function () {
+                it("has a method for setting the user's availability", mock.initConverse(function (converse) {
                     converse_api.user.status.set('away');
                     expect(converse.xmppstatus.get('status')).toBe('away');
                     converse_api.user.status.set('dnd');
@@ -155,38 +154,33 @@
                     expect(_.partial(converse_api.user.status.set, 'invalid')).toThrow(
                         new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1')
                     );
-                });
+                }));
 
-                it("allows setting the status message as well", function () {
+                it("allows setting the status message as well", mock.initConverse(function (converse) {
                     converse_api.user.status.set('away', "I'm in a meeting");
                     expect(converse.xmppstatus.get('status')).toBe('away');
                     expect(converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
-                });
+                }));
 
-                it("has a method for getting the user's status message", function () {
+                it("has a method for getting the user's status message", mock.initConverse(function (converse) {
                     converse.xmppstatus.set('status_message', undefined);
                     expect(converse_api.user.status.message.get()).toBe(undefined);
                     converse.xmppstatus.set('status_message', "I'm in a meeting");
                     expect(converse_api.user.status.message.get()).toBe("I'm in a meeting");
-                });
+                }));
 
-                it("has a method for setting the user's status message", function () {
+                it("has a method for setting the user's status message", mock.initConverse(function (converse) {
                     converse.xmppstatus.set('status_message', undefined);
                     converse_api.user.status.message.set("I'm in a meeting");
                     expect(converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
-                });
+                }));
             });
         });
 
-        describe("The \"tokens\" API", $.proxy(function () {
-            beforeEach(function () {
-                test_utils.closeAllChatBoxes();
-                test_utils.clearBrowserStorage();
-                converse.rosterview.model.reset();
-                test_utils.createContacts('current');
-            });
+        describe("The \"tokens\" API", function () {
 
-            it("has a method for retrieving the next RID", $.proxy(function () {
+            it("has a method for retrieving the next RID", mock.initConverse(function (converse) {
+                test_utils.createContacts(converse, 'current');
                 var old_connection = converse.connection;
                 converse.connection._proto.rid = '1234';
                 converse.expose_rid_and_sid = false;
@@ -197,9 +191,10 @@
                 expect(converse_api.tokens.get('rid')).toBe(null);
                 // Restore the connection
                 converse.connection = old_connection;
-            }, converse));
+            }));
 
-            it("has a method for retrieving the SID", $.proxy(function () {
+            it("has a method for retrieving the SID", mock.initConverse(function (converse) {
+                test_utils.createContacts(converse, 'current');
                 var old_connection = converse.connection;
                 converse.connection._proto.sid = '1234';
                 converse.expose_rid_and_sid = false;
@@ -210,19 +205,14 @@
                 expect(converse_api.tokens.get('sid')).toBe(null);
                 // Restore the connection
                 converse.connection = old_connection;
-            }, converse));
-        }, converse));
-
-        describe("The \"contacts\" API", $.proxy(function () {
-            beforeEach($.proxy(function () {
-                test_utils.closeAllChatBoxes();
-                test_utils.clearBrowserStorage();
-                converse.rosterview.model.reset();
-                test_utils.createContacts('current');
-            }, converse));
-
-            it("has a method 'get' which returns wrapped contacts", $.proxy(function () {
+            }));
+        });
+
+        describe("The \"contacts\" API", function () {
+
+            it("has a method 'get' which returns wrapped contacts", mock.initConverse(function (converse) {
                 // Check that it returns nothing if a non-existing JID is given
+                test_utils.createContacts(converse, 'current');
                 expect(converse_api.contacts.get('non-existing@jabber.org')).toBeFalsy();
                 // Check when a single jid is given
                 var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
@@ -239,28 +229,23 @@
                 // Check that all JIDs are returned if you call without any parameters
                 list = converse_api.contacts.get();
                 expect(list.length).toBe(mock.cur_names.length);
-            }, converse));
+            }));
 
-            it("has a method 'add' with which contacts can be added", $.proxy(function () {
+            it("has a method 'add' with which contacts can be added", mock.initConverse(function (converse) {
+                test_utils.createContacts(converse, 'current');
                 var error = new TypeError('contacts.add: invalid jid');
                 expect(converse_api.contacts.add).toThrow(error);
                 expect(converse_api.contacts.add.bind(converse_api, "invalid jid")).toThrow(error);
                 spyOn(converse.roster, 'addAndSubscribe');
                 converse_api.contacts.add("newcontact@example.org");
                 expect(converse.roster.addAndSubscribe).toHaveBeenCalled();
-            }, converse));
-
-        }, converse));
+            }));
+        });
 
-        describe("The \"chats\" API", $.proxy(function() {
-            beforeEach($.proxy(function () {
-                test_utils.closeAllChatBoxes();
-                test_utils.clearBrowserStorage();
-                converse.rosterview.model.reset();
-                test_utils.createContacts('current');
-            }, converse));
+        describe("The \"chats\" API", function() {
 
-            it("has a method 'get' which returns a wrapped chat box", function () {
+            it("has a method 'get' which returns a wrapped chat box", mock.initConverse(function (converse) {
+                test_utils.createContacts(converse, 'current');
                 // Test on chat that doesn't exist.
                 expect(converse_api.chats.get('non-existing@jabber.org')).toBeFalsy();
                 var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
@@ -271,7 +256,7 @@
 
                 var chatboxview = converse.chatboxviews.get(jid);
                 // Test for single JID
-                test_utils.openChatBoxFor(jid);
+                test_utils.openChatBoxFor(converse, jid);
                 box = converse_api.chats.get(jid);
                 expect(box instanceof Object).toBeTruthy();
                 expect(box.get('box_id')).toBe(b64_sha1(jid));
@@ -279,14 +264,15 @@
                 expect(chatboxview.$el.is(':visible')).toBeTruthy();
                 // Test for multiple JIDs
                 var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-                test_utils.openChatBoxFor(jid2);
+                test_utils.openChatBoxFor(converse, jid2);
                 var list = converse_api.chats.get([jid, jid2]);
                 expect(Array.isArray(list)).toBeTruthy();
                 expect(list[0].get('box_id')).toBe(b64_sha1(jid));
                 expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
-            });
+            }));
 
-            it("has a method 'open' which opens and returns a wrapped chat box", function () {
+            it("has a method 'open' which opens and returns a wrapped chat box", mock.initConverse(function (converse) {
+                test_utils.createContacts(converse, 'current');
                 var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                 var chatboxview;
                 waits('300'); // ChatBox.show() is debounced for 250ms
@@ -309,18 +295,11 @@
                     expect(list[0].get('box_id')).toBe(b64_sha1(jid));
                     expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
                 });
-            });
-        }, converse));
-
-        describe("The \"settings\" API", $.proxy(function() {
-            beforeEach($.proxy(function () {
-                test_utils.closeAllChatBoxes();
-                test_utils.clearBrowserStorage();
-                converse.rosterview.model.reset();
-                test_utils.createContacts('current');
-            }, converse));
+            }));
+        });
 
-            it("has methods 'get' and 'set' to set configuration settings", $.proxy(function () {
+        describe("The \"settings\" API", function() {
+            it("has methods 'get' and 'set' to set configuration settings", mock.initConverse(function (converse) {
                 expect(Object.keys(converse_api.settings)).toEqual(["get", "set"]);
                 expect(converse_api.settings.get("play_sounds")).toBe(false);
                 converse_api.settings.set("play_sounds", true);
@@ -331,7 +310,7 @@
                 expect(typeof converse_api.settings.get("non_existing")).toBe("undefined");
                 converse_api.settings.set("non_existing", true);
                 expect(typeof converse_api.settings.get("non_existing")).toBe("undefined");
-            }, converse));
-        }, converse));
-    }, converse, mock, test_utils));
+            }));
+        });
+    });
 }));

+ 181 - 176
spec/protocol.js

@@ -1,4 +1,3 @@
-/*global converse */
 (function (root, factory) {
     define([
         "jquery",
@@ -16,14 +15,17 @@
     // See:
     // https://xmpp.org/rfcs/rfc3921.html
 
-    describe("The Protocol", $.proxy(function (mock, test_utils) {
-        beforeEach(function () {
-            test_utils.removeControlBox();
-            converse.roster.browserStorage._clear();
-            test_utils.initConverse();
-        });
+    describe("The Protocol", function () {
+
+        describe("Integration of Roster Items and Presence Subscriptions", function () {
+            // Stub the trimChat method. It causes havoc when running with
+            // phantomJS.
+
+            afterEach(function () {
+                converse_api.user.logout();
+                test_utils.clearBrowserStorage();
+            });
 
-        describe("Integration of Roster Items and Presence Subscriptions", $.proxy(function (mock, test_utils) {
             /* Some level of integration between roster items and presence
             * subscriptions is normally expected by an instant messaging user
             * regarding the user's subscriptions to and from other contacts. This
@@ -52,26 +54,24 @@
             * that session. A client MUST acknowledge each roster push with an IQ
             * stanza of type "result".
             */
-            beforeEach(function () {
-                test_utils.closeAllChatBoxes();
-                test_utils.openControlBox();
-                test_utils.openContactsPanel();
-            });
-
-            it("Subscribe to contact, contact accepts and subscribes back", $.proxy(function () {
+            it("Subscribe to contact, contact accepts and subscribes back", mock.initConverse(function (converse) {
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 */
                 var contact, stanza, sent_stanza, IQ_id;
-                runs($.proxy(function () {
-                    var panel = this.chatboxviews.get('controlbox').contactspanel;
+                runs(function () {
+                    test_utils.openControlBox(converse);
+                });
+                waits(100);
+                runs(function () {
+                    var panel = converse.chatboxviews.get('controlbox').contactspanel;
                     spyOn(panel, "addContactFromForm").andCallThrough();
-                    spyOn(this.roster, "addAndSubscribe").andCallThrough();
-                    spyOn(this.roster, "addContact").andCallThrough();
-                    spyOn(this.roster, "sendContactAddIQ").andCallThrough();
-                    spyOn(this, "getVCard").andCallThrough();
-                    var sendIQ = this.connection.sendIQ;
-                    spyOn(this.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    spyOn(converse.roster, "addAndSubscribe").andCallThrough();
+                    spyOn(converse.roster, "addContact").andCallThrough();
+                    spyOn(converse.roster, "sendContactAddIQ").andCallThrough();
+                    spyOn(converse, "getVCard").andCallThrough();
+                    var sendIQ = converse.connection.sendIQ;
+                    spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
                         sent_stanza = iq;
                         IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     });
@@ -99,7 +99,7 @@
                     // The form should not be visible anymore.
                     expect($form.is(":visible")).toBeFalsy();
 
-                    /* This request consists of sending an IQ
+                    /* converse request consists of sending an IQ
                     * stanza of type='set' containing a <query/> element qualified by
                     * the 'jabber:iq:roster' namespace, which in turn contains an
                     * <item/> element that defines the new roster item; the <item/>
@@ -127,7 +127,7 @@
                     );
                     /* As a result, the user's server (1) MUST initiate a roster push
                     * for the new roster item to all available resources associated
-                    * with this user that have requested the roster, setting the
+                    * with converse user that have requested the roster, setting the
                     * 'subscription' attribute to a value of "none"; and (2) MUST
                     * reply to the sending resource with an IQ result indicating the
                     * success of the roster set:
@@ -157,17 +157,17 @@
                             'jid': 'contact@example.org',
                             'subscription': 'none',
                             'name': 'contact@example.org'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     /*
                     * <iq type='result' id='set1'/>
                     */
                     stanza = $iq({'type': 'result', 'id':IQ_id});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
 
                     // A contact should now have been created
-                    expect(this.roster.get('contact@example.org') instanceof this.RosterContact).toBeTruthy();
+                    expect(converse.roster.get('contact@example.org') instanceof converse.RosterContact).toBeTruthy();
                     expect(contact.get('jid')).toBe('contact@example.org');
-                    expect(this.getVCard).toHaveBeenCalled();
+                    expect(converse.getVCard).toHaveBeenCalled();
 
                     /* To subscribe to the contact's presence information,
                     * the user's client MUST send a presence stanza of
@@ -184,7 +184,7 @@
                     /* As a result, the user's server MUST initiate a second roster
                     * push to all of the user's available resources that have
                     * requested the roster, setting the contact to the pending
-                    * sub-state of the 'none' subscription state; this pending
+                    * sub-state of the 'none' subscription state; converse pending
                     * sub-state is denoted by the inclusion of the ask='subscribe'
                     * attribute in the roster item:
                     *
@@ -208,11 +208,11 @@
                             'subscription': 'none',
                             'ask': 'subscribe',
                             'name': 'contact@example.org'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     expect(converse.roster.updateContact).toHaveBeenCalled();
-                }, this));
+                });
                 waits(50);
-                runs($.proxy(function () {
+                runs(function () {
                     // Check that the user is now properly shown as a pending
                     // contact in the roster.
                     var $header = $('a:contains("Pending contacts")');
@@ -223,46 +223,46 @@
 
                     spyOn(contact, "ackSubscribe").andCallThrough();
                     /* Here we assume the "happy path" that the contact
-                     * approves the subscription request
-                     *
-                     *  <presence
-                     *      to='user@example.com'
-                     *      from='contact@example.org'
-                     *      type='subscribed'/>
-                     */
+                    * approves the subscription request
+                    *
+                    *  <presence
+                    *      to='user@example.com'
+                    *      from='contact@example.org'
+                    *      type='subscribed'/>
+                    */
                     stanza = $pres({
                         'to': converse.bare_jid,
                         'from': 'contact@example.org',
                         'type': 'subscribed'
                     });
                     sent_stanza = ""; // Reset
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     /* Upon receiving the presence stanza of type "subscribed",
-                     * the user SHOULD acknowledge receipt of that
-                     * subscription state notification by sending a presence
-                     * stanza of type "subscribe".
-                     */
+                    * the user SHOULD acknowledge receipt of that
+                    * subscription state notification by sending a presence
+                    * stanza of type "subscribe".
+                    */
                     expect(contact.ackSubscribe).toHaveBeenCalled();
                     expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
                         "<presence type='subscribe' to='contact@example.org' xmlns='jabber:client'/>"
                     );
 
                     /* The user's server MUST initiate a roster push to all of the user's
-                     * available resources that have requested the roster,
-                     * containing an updated roster item for the contact with
-                     * the 'subscription' attribute set to a value of "to";
-                     *
-                     *  <iq type='set'>
-                     *    <query xmlns='jabber:iq:roster'>
-                     *      <item
-                     *          jid='contact@example.org'
-                     *          subscription='to'
-                     *          name='MyContact'>
-                     *        <group>MyBuddies</group>
-                     *      </item>
-                     *    </query>
-                     *  </iq>
-                     */
+                    * available resources that have requested the roster,
+                    * containing an updated roster item for the contact with
+                    * the 'subscription' attribute set to a value of "to";
+                    *
+                    *  <iq type='set'>
+                    *    <query xmlns='jabber:iq:roster'>
+                    *      <item
+                    *          jid='contact@example.org'
+                    *          subscription='to'
+                    *          name='MyContact'>
+                    *        <group>MyBuddies</group>
+                    *      </item>
+                    *    </query>
+                    *  </iq>
+                    */
                     IQ_id = converse.connection.getUniqueId('roster');
                     stanza = $iq({'type': 'set', 'id': IQ_id})
                         .c('query', {'xmlns': 'jabber:iq:roster'})
@@ -270,7 +270,7 @@
                             'jid': 'contact@example.org',
                             'subscription': 'to',
                             'name': 'contact@example.org'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     // Check that the IQ set was acknowledged.
                     expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
                         "<iq type='result' id='"+IQ_id+"' from='dummy@localhost/resource' xmlns='jabber:client'/>"
@@ -292,78 +292,82 @@
                     expect(contact.get('chat_status')).toBe('offline');
 
                     /*  <presence
-                     *      from='contact@example.org/resource'
-                     *      to='user@example.com/resource'/>
-                     */
+                    *      from='contact@example.org/resource'
+                    *      to='user@example.com/resource'/>
+                    */
                     stanza = $pres({'to': converse.bare_jid, 'from': 'contact@example.org/resource'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     // Now the contact should also be online.
                     expect(contact.get('chat_status')).toBe('online');
 
                     /* Section 8.3.  Creating a Mutual Subscription
-                     *
-                     * If the contact wants to create a mutual subscription,
-                     * the contact MUST send a subscription request to the
-                     * user.
-                     *
-                     * <presence from='contact@example.org' to='user@example.com' type='subscribe'/>
-                     */
+                    *
+                    * If the contact wants to create a mutual subscription,
+                    * the contact MUST send a subscription request to the
+                    * user.
+                    *
+                    * <presence from='contact@example.org' to='user@example.com' type='subscribe'/>
+                    */
                     spyOn(contact, 'authorize').andCallThrough();
-                    spyOn(this.roster, 'handleIncomingSubscription').andCallThrough();
+                    spyOn(converse.roster, 'handleIncomingSubscription').andCallThrough();
                     stanza = $pres({
                         'to': converse.bare_jid,
                         'from': 'contact@example.org/resource',
                         'type': 'subscribe'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
-                    expect(this.roster.handleIncomingSubscription).toHaveBeenCalled();
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
+                    expect(converse.roster.handleIncomingSubscription).toHaveBeenCalled();
 
                     /* The user's client MUST send a presence stanza of type
-                     * "subscribed" to the contact in order to approve the
-                     * subscription request.
-                     *
-                     *  <presence to='contact@example.org' type='subscribed'/>
-                     */
+                    * "subscribed" to the contact in order to approve the
+                    * subscription request.
+                    *
+                    *  <presence to='contact@example.org' type='subscribed'/>
+                    */
                     expect(contact.authorize).toHaveBeenCalled();
                     expect(sent_stanza.toLocaleString()).toBe(
                         "<presence to='contact@example.org' type='subscribed' xmlns='jabber:client'/>"
                     );
 
                     /* As a result, the user's server MUST initiate a
-                     * roster push containing a roster item for the
-                     * contact with the 'subscription' attribute set to
-                     * a value of "both".
-                     *
-                     *  <iq type='set'>
-                     *    <query xmlns='jabber:iq:roster'>
-                     *      <item
-                     *          jid='contact@example.org'
-                     *          subscription='both'
-                     *          name='MyContact'>
-                     *      <group>MyBuddies</group>
-                     *      </item>
-                     *    </query>
-                     *  </iq>
-                     */
+                    * roster push containing a roster item for the
+                    * contact with the 'subscription' attribute set to
+                    * a value of "both".
+                    *
+                    *  <iq type='set'>
+                    *    <query xmlns='jabber:iq:roster'>
+                    *      <item
+                    *          jid='contact@example.org'
+                    *          subscription='both'
+                    *          name='MyContact'>
+                    *      <group>MyBuddies</group>
+                    *      </item>
+                    *    </query>
+                    *  </iq>
+                    */
                     stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
                         .c('item', {
                             'jid': 'contact@example.org',
                             'subscription': 'both',
                             'name': 'contact@example.org'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     expect(converse.roster.updateContact).toHaveBeenCalled();
 
                     // The class on the contact will now have switched.
                     expect($contacts.hasClass('to')).toBeFalsy();
                     expect($contacts.hasClass('both')).toBeTruthy();
-                }, this));
-            }, converse));
+                });
+            }));
 
-            it("Alternate Flow: Contact Declines Subscription Request", $.proxy(function () {
+            it("Alternate Flow: Contact Declines Subscription Request", mock.initConverse(function (converse) {
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 */
                 var contact, stanza, sent_stanza, sent_IQ;
-                runs($.proxy(function () {
+                runs(function () {
+                    test_utils.openControlBox(converse);
+                });
+                waits(100);
+                runs(function () {
                     // Add a new roster contact via roster push
                     stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
                         .c('item', {
@@ -371,69 +375,69 @@
                             'subscription': 'none',
                             'ask': 'subscribe',
                             'name': 'contact@example.org'});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
-                }, this));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
+                });
                 waits(50);
-                runs($.proxy(function () {
+                runs(function () {
                     // A pending contact should now exist.
-                    contact = this.roster.get('contact@example.org');
-                    expect(this.roster.get('contact@example.org') instanceof this.RosterContact).toBeTruthy();
+                    contact = converse.roster.get('contact@example.org');
+                    expect(converse.roster.get('contact@example.org') instanceof converse.RosterContact).toBeTruthy();
                     spyOn(contact, "ackUnsubscribe").andCallThrough();
 
                     spyOn(converse.connection, 'send').andCallFake(function (stanza) {
                         sent_stanza = stanza;
                     });
-                    spyOn(this.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
                         sent_IQ = iq;
                     });
                     /* We now assume the contact declines the subscription
-                     * requests.
-                     *
+                    * requests.
+                    *
                     /* Upon receiving the presence stanza of type "unsubscribed"
-                     * addressed to the user, the user's server (1) MUST deliver
-                     * that presence stanza to the user and (2) MUST initiate a
-                     * roster push to all of the user's available resources that
-                     * have requested the roster, containing an updated roster
-                     * item for the contact with the 'subscription' attribute
-                     * set to a value of "none" and with no 'ask' attribute:
-                     *
-                     *  <presence
-                     *      from='contact@example.org'
-                     *      to='user@example.com'
-                     *      type='unsubscribed'/>
-                     *
-                     *  <iq type='set'>
-                     *  <query xmlns='jabber:iq:roster'>
-                     *      <item
-                     *          jid='contact@example.org'
-                     *          subscription='none'
-                     *          name='MyContact'>
-                     *      <group>MyBuddies</group>
-                     *      </item>
-                     *  </query>
-                     *  </iq>
-                     */
+                    * addressed to the user, the user's server (1) MUST deliver
+                    * that presence stanza to the user and (2) MUST initiate a
+                    * roster push to all of the user's available resources that
+                    * have requested the roster, containing an updated roster
+                    * item for the contact with the 'subscription' attribute
+                    * set to a value of "none" and with no 'ask' attribute:
+                    *
+                    *  <presence
+                    *      from='contact@example.org'
+                    *      to='user@example.com'
+                    *      type='unsubscribed'/>
+                    *
+                    *  <iq type='set'>
+                    *  <query xmlns='jabber:iq:roster'>
+                    *      <item
+                    *          jid='contact@example.org'
+                    *          subscription='none'
+                    *          name='MyContact'>
+                    *      <group>MyBuddies</group>
+                    *      </item>
+                    *  </query>
+                    *  </iq>
+                    */
                     // FIXME: also add the <iq>
                     stanza = $pres({
                         'to': converse.bare_jid,
                         'from': 'contact@example.org',
                         'type': 'unsubscribed'
                     });
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
 
                     /* Upon receiving the presence stanza of type "unsubscribed",
-                     * the user SHOULD acknowledge receipt of that subscription
-                     * state notification through either "affirming" it by
-                     * sending a presence stanza of type "unsubscribe
-                     */
+                    * the user SHOULD acknowledge receipt of that subscription
+                    * state notification through either "affirming" it by
+                    * sending a presence stanza of type "unsubscribe
+                    */
                     expect(contact.ackUnsubscribe).toHaveBeenCalled();
                     expect(sent_stanza.toLocaleString()).toBe(
                         "<presence type='unsubscribe' to='contact@example.org' xmlns='jabber:client'/>"
                     );
 
                     /* Converse.js will then also automatically remove the
-                     * contact from the user's roster.
-                     */
+                    * contact from the user's roster.
+                    */
                     expect(sent_IQ.toLocaleString()).toBe(
                         "<iq type='set' xmlns='jabber:client'>"+
                             "<query xmlns='jabber:iq:roster'>"+
@@ -441,22 +445,23 @@
                             "</query>"+
                         "</iq>"
                     );
-                }, this));
-            }, converse));
+                });
+            }));
 
-            it("Unsubscribe to a contact when subscription is mutual", function () {
+            it("Unsubscribe to a contact when subscription is mutual", mock.initConverse(function (converse) {
                 var sent_IQ, IQ_id, jid = 'annegreet.gomez@localhost';
                 runs(function () {
-                    test_utils.createContacts('current');
+                    test_utils.openControlBox(converse);
+                    test_utils.createContacts(converse, 'current');
                 });
                 waits(50);
                 runs(function () {
                     spyOn(window, 'confirm').andReturn(true);
                     // We now have a contact we want to remove
-                    expect(this.roster.get(jid) instanceof this.RosterContact).toBeTruthy();
+                    expect(converse.roster.get(jid) instanceof converse.RosterContact).toBeTruthy();
 
-                    var sendIQ = this.connection.sendIQ;
-                    spyOn(this.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    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);
                     });
@@ -467,24 +472,24 @@
                     expect(window.confirm).toHaveBeenCalled();
 
                     /* Section 8.6 Removing a Roster Item and Cancelling All
-                     * Subscriptions
-                     *
-                     * First the user is removed from the roster
-                     * Because there may be many steps involved in completely
-                     * removing a roster item and cancelling subscriptions in
-                     * both directions, the roster management protocol includes
-                     * a "shortcut" method for doing so. The process may be
-                     * initiated no matter what the current subscription state
-                     * is by sending a roster set containing an item for the
-                     * contact with the 'subscription' attribute set to a value
-                     * of "remove":
-                     *
-                     * <iq type='set' id='remove1'>
-                     *   <query xmlns='jabber:iq:roster'>
-                     *       <item jid='contact@example.org' subscription='remove'/>
-                     *   </query>
-                     * </iq>
-                     */
+                    * Subscriptions
+                    *
+                    * First the user is removed from the roster
+                    * Because there may be many steps involved in completely
+                    * removing a roster item and cancelling subscriptions in
+                    * both directions, the roster management protocol includes
+                    * a "shortcut" method for doing so. The process may be
+                    * initiated no matter what the current subscription state
+                    * is by sending a roster set containing an item for the
+                    * contact with the 'subscription' attribute set to a value
+                    * of "remove":
+                    *
+                    * <iq type='set' id='remove1'>
+                    *   <query xmlns='jabber:iq:roster'>
+                    *       <item jid='contact@example.org' subscription='remove'/>
+                    *   </query>
+                    * </iq>
+                    */
                     expect(sent_IQ.toLocaleString()).toBe(
                         "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
                             "<query xmlns='jabber:iq:roster'>"+
@@ -495,21 +500,21 @@
                     // Receive confirmation from the contact's server
                     // <iq type='result' id='remove1'/>
                     var stanza = $iq({'type': 'result', 'id':IQ_id});
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     // Our contact has now been removed
-                    expect(typeof this.roster.get(jid) === "undefined").toBeTruthy();
-                }.bind(converse));
-            }.bind(converse));
+                    expect(typeof converse.roster.get(jid) === "undefined").toBeTruthy();
+                });
+            }));
 
-            it("Receiving a subscription request", function () {
+            it("Receiving a subscription request", mock.initConverse(function (converse) {
                 runs(function () {
-                    test_utils.createContacts('current'); // Create some contacts so that we can test positioning
+                    test_utils.openControlBox(converse);
+                    test_utils.createContacts(converse, 'current'); // Create some contacts so that we can test positioning
                 });
                 waits(50);
                 runs(function () {
                     spyOn(converse, "emit");
-                    /*
-                     * <presence
+                    /* <presence
                      *     from='user@example.com'
                      *     to='contact@example.org'
                      *     type='subscribe'/>
@@ -521,15 +526,15 @@
                     }).c('nick', {
                         'xmlns': Strophe.NS.NICK,
                     }).t('Clint Contact');
-                    this.connection._dataRecv(test_utils.createRequest(stanza));
+                    converse.connection._dataRecv(test_utils.createRequest(stanza));
                     expect(converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
                     var $header = $('a:contains("Contact requests")');
                     expect($header.length).toBe(1);
                     expect($header.is(":visible")).toBeTruthy();
                     var $contacts = $header.parent().nextUntil('dt', 'dd');
                     expect($contacts.length).toBe(1);
-                }.bind(converse));
-            }.bind(converse));
-        }, converse, mock, test_utils));
-    }, converse, mock, test_utils));
+                });
+            }));
+        });
+    });
 }));

+ 9 - 0
src/converse-core.js

@@ -1950,6 +1950,15 @@
             utils.merge(converse, settings);
             utils.applyUserSettings(converse, settings, converse.user_settings);
         };
+
+        // If initialize gets called a second time (e.g. during tests), then we
+        // need to re-apply all plugins (for a new converse instance), and we
+        // therefore need to clear this array that prevents plugins from being
+        // initialized twice.
+        // If initialize is called for the first time, then this array is empty
+        // in any case.
+        converse.pluggable.initialized_plugins = [];
+
         converse.pluggable.initializePlugins({
             'updateSettings': updateSettings,
             'converse': converse

+ 43 - 65
tests/main.js

@@ -45,71 +45,49 @@ require([
         window.localStorage.clear();
         window.sessionStorage.clear();
 
-        converse.initialize({
-            i18n: window.locales.en,
-            auto_subscribe: false,
-            bosh_service_url: 'localhost',
-            connection: mock.mock_connection,
-            animate: false,
-            no_trimming: true,
-            auto_login: true,
-            jid: 'dummy@localhost',
-            password: 'secret',
-            debug: true 
-        }).then(function (converse) {
-            window.converse = converse;
-            window.crypto = {
-                getRandomValues: function (buf) {
-                    var i;
-                    for (i=0, len=buf.length; i<len; i++) {
-                        buf[i] = Math.floor(Math.random()*256);
-                    }
-                }
-            };
-            require([
-                "console-runner",
-                //"spec/transcripts",
-                "spec/utils",
-                "spec/converse",
-                "spec/bookmarks",
-                "spec/headline",
-                "spec/disco",
-                "spec/protocol",
-                "spec/mam",
-                "spec/otr",
-                "spec/eventemitter",
-                "spec/controlbox",
-                "spec/chatbox",
-                "spec/chatroom",
-                "spec/minchats",
-                "spec/notification",
-                "spec/profiling",
-                "spec/ping",
-                "spec/register",
-                "spec/xmppstatus",
-            ], function () {
-                // Stub the trimChat method. It causes havoc when running with
-                // phantomJS.
-                converse.ChatBoxViews.prototype.trimChat = function () {};
-
-                // Jasmine stuff
-                var jasmineEnv = jasmine.getEnv();
-                var reporter;
-                if (/PhantomJS/.test(navigator.userAgent)) {
-                    reporter = new jasmine.ConsoleReporter();
-                    window.console_reporter = reporter;
-                    jasmineEnv.addReporter(reporter);
-                    jasmineEnv.updateInterval = 0;
-                } else {
-                    reporter = new jasmine.HtmlReporter();
-                    jasmineEnv.addReporter(reporter);
-                    jasmineEnv.specFilter = function(spec) {
-                        return reporter.specFilter(spec);
-                    };
-                    jasmineEnv.updateInterval = 0;
-                }
-                jasmineEnv.execute();
-            });
+        require([
+            "console-runner",
+            //"spec/transcripts",
+            "spec/utils",
+            "spec/converse",
+            /*
+            "spec/bookmarks",
+            "spec/headline",
+            "spec/disco",
+            */
+            "spec/protocol",
+            /*
+            "spec/mam",
+            "spec/otr",
+            "spec/eventemitter",
+            "spec/controlbox",
+            "spec/chatbox",
+            "spec/chatroom",
+            "spec/minchats",
+            "spec/notification",
+            "spec/profiling",
+            "spec/ping",
+            "spec/register",
+            "spec/xmppstatus",
+            */
+        ], function () {
+            // Jasmine stuff
+            var jasmineEnv = jasmine.getEnv();
+            var reporter;
+            if (/PhantomJS/.test(navigator.userAgent)) {
+                reporter = new jasmine.ConsoleReporter();
+                window.console_reporter = reporter;
+                jasmineEnv.addReporter(reporter);
+                jasmineEnv.updateInterval = 0;
+            } else {
+                reporter = new jasmine.HtmlReporter();
+                jasmineEnv.addReporter(reporter);
+                jasmineEnv.specFilter = function(spec) {
+                    return reporter.specFilter(spec);
+                };
+                jasmineEnv.updateInterval = 0;
+            }
+            jasmineEnv.execute();
         });
     }
 );

+ 50 - 33
tests/mock.js

@@ -1,12 +1,8 @@
 (function (root, factory) {
-    define("mock",
-        ['converse'],
-        function(converse) {
-            return factory(converse);
-        });
-}(this, function (converse) {
-    var Strophe = converse.env.Strophe;
-    var $iq = converse.env.$iq;
+    define("mock", ['converse'], factory);
+}(this, function (converse_api) {
+    var Strophe = converse_api.env.Strophe;
+    var $iq = converse_api.env.$iq;
     var mock = {};
     // Names from http://www.fakenamegenerator.com/
     mock.req_names = [
@@ -48,33 +44,54 @@
     };
 
     mock.mock_connection = function ()  {
-        Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
-        var c = new Strophe.Connection('jasmine tests');
-        c.vcard = {
-            'get': function (callback, jid) {
-                var fullname;
-                if (!jid) {
-                    jid = 'dummy@localhost';
-                    fullname = 'Max Mustermann' ;
-                } else {
-                    var name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
-                    var last = name.length-1;
-                    name[0] =  name[0].charAt(0).toUpperCase()+name[0].slice(1);
-                    name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
-                    fullname = name.join(' ');
+        return function () {
+            Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
+            var c = new Strophe.Connection('jasmine tests');
+            c.vcard = {
+                'get': function (callback, jid) {
+                    var fullname;
+                    if (!jid) {
+                        jid = 'dummy@localhost';
+                        fullname = 'Max Mustermann' ;
+                    } else {
+                        var name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
+                        var last = name.length-1;
+                        name[0] =  name[0].charAt(0).toUpperCase()+name[0].slice(1);
+                        name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
+                        fullname = name.join(' ');
+                    }
+                    var vcard = $iq().c('vCard').c('FN').t(fullname);
+                    callback(vcard.tree());
                 }
-                var vcard = $iq().c('vCard').c('FN').t(fullname);
-                callback(vcard.tree());
-            }
+            };
+            c._proto._connect = function () {
+                c.authenticated = true;
+                c.connected = true;
+                c.mock = true;
+                c.jid = 'dummy@localhost/resource';
+                c._changeConnectStatus(Strophe.Status.CONNECTED);
+            };
+            return c;
         };
-        c._proto._connect = function () {
-            c.authenticated = true;
-            c.connected = true;
-            c.mock = true;
-            c.jid = 'dummy@localhost/resource';
-            c._changeConnectStatus(Strophe.Status.CONNECTED);
-        };
-        return c;
     }();
+
+    mock.initConverse = function (func) {
+        return function () {
+            var converse = converse_api.initialize({
+                i18n: window.locales.en,
+                auto_subscribe: false,
+                bosh_service_url: 'localhost',
+                connection: mock.mock_connection(),
+                animate: false,
+                no_trimming: true,
+                auto_login: true,
+                jid: 'dummy@localhost',
+                password: 'secret',
+                debug: true
+            });
+            converse.ChatBoxViews.prototype.trimChat = function () {};
+            return func(converse);
+        };
+    };
     return mock;
 }));

+ 7 - 12
tests/utils.js

@@ -1,12 +1,7 @@
 (function (root, factory) {
-    define("test_utils", [
-        'jquery',
-        'mock'
-    ],
-        function($, mock) {
-            return factory($, mock);
-        });
-}(this, function ($, mock) {
+    define("test_utils", ['converse', 'mock'], factory);
+}(this, function (converse_api, mock) {
+    var $ = converse_api.env.jQuery;
     var $pres = converse_api.env.$pres;
     var $iq = converse_api.env.$iq;
     var Strophe = converse_api.env.Strophe;
@@ -23,7 +18,7 @@
         return req;
     };
 
-    utils.closeAllChatBoxes = function () {
+    utils.closeAllChatBoxes = function (converse) {
         var i, chatbox;
         for (i=converse.chatboxes.models.length-1; i>-1; i--) {
             chatbox = converse.chatboxes.models[i];
@@ -78,7 +73,7 @@
         $('#controlbox').remove();
     };
 
-    utils.openContactsPanel = function () {
+    utils.openContactsPanel = function (converse) {
         var cbview = converse.chatboxviews.get('controlbox');
         var $tabs = cbview.$el.find('#controlbox-tabs');
         $tabs.find('li').first().find('a').click();
@@ -100,7 +95,7 @@
         return views;
     };
 
-    utils.openChatBoxFor = function (jid) {
+    utils.openChatBoxFor = function (converse, jid) {
         return converse.roster.get(jid).trigger("open");
     };
 
@@ -172,7 +167,7 @@
         view.model.messages.browserStorage._clear();
     };
 
-    utils.createContacts = function (type, length) {
+    utils.createContacts = function (converse, type, length) {
         /* Create current (as opposed to requesting or pending) contacts
          * for the user's roster.
          *