Browse Source

Upgrade to Jasmine 2.5.3

JC Brand 8 years ago
parent
commit
0c43e4b87c
28 changed files with 1782 additions and 1824 deletions
  1. 1 1
      .eslintrc.json
  2. 3 14
      Makefile
  3. 1 1
      docs/CHANGES.md
  4. 5 4
      package.json
  5. 84 99
      spec/bookmarks.js
  6. 366 369
      spec/chatbox.js
  7. 345 371
      spec/chatroom.js
  8. 343 371
      spec/controlbox.js
  9. 20 23
      spec/converse.js
  10. 4 4
      spec/disco.js
  11. 9 9
      spec/eventemitter.js
  12. 45 60
      spec/headline.js
  13. 15 15
      spec/mam.js
  14. 1 1
      spec/minchats.js
  15. 42 52
      spec/notification.js
  16. 2 2
      spec/otr.js
  17. 6 6
      spec/ping.js
  18. 1 1
      spec/presence.js
  19. 258 270
      spec/protocol.js
  20. 45 49
      spec/register.js
  21. 2 2
      spec/utils.js
  22. 3 3
      spec/xmppstatus.js
  23. 1 0
      src/config.js
  24. 8 3
      tests.html
  25. 26 18
      tests/mock.js
  26. 97 0
      tests/run-jasmine2.js
  27. 38 36
      tests/runner.js
  28. 11 40
      tests/utils.js

+ 1 - 1
.eslintrc.json

@@ -255,7 +255,7 @@
             "error",
             "never"
         ],
-        "valid-jsdoc": "error",
+        "valid-jsdoc": "off",
         "vars-on-top": "off",
         "wrap-iife": [
             "error",

+ 3 - 14
Makefile

@@ -3,7 +3,6 @@ BUILDDIR        = ./docs
 BUNDLE          ?= ./.bundle/bin/bundle
 GRUNT           ?= ./node_modules/.bin/grunt
 HTTPSERVE       ?= ./node_modules/.bin/http-server
-JSHINT          ?= ./node_modules/.bin/jshint
 ESLINT          ?= ./node_modules/.bin/eslint
 PAPER           =
 PHANTOMJS       ?= ./node_modules/.bin/phantomjs
@@ -16,13 +15,6 @@ SPHINXOPTS      =
 
 # Internal variables.
 ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ./docs/source
-SOURCES    = $(wildcard *.js) $(wildcard spec/*.js) $(wildcard src/*.js)
-JSHINTEXCEPTIONS = $(GENERATED) \
-           src/build-mobile.js \
-           src/build-no-jquery.js \
-           src/build-no-dependencies.js \
-           src/build.js \
-CHECKSOURCES    = $(filter-out $(JSHINTEXCEPTIONS),$(SOURCES))
 
 .PHONY: all
 all: dev dist
@@ -184,18 +176,15 @@ build:: dev css
 ########################################################################
 ## Tests
 
-.PHONY: jshint
-jshint: stamp-npm
-	$(JSHINT) --config jshintrc $(CHECKSOURCES)
-
 .PHONY: eslint
 eslint: stamp-npm
 	$(ESLINT) src/
 	$(ESLINT) spec/
 
 .PHONY: check
-check: jshint eslint
-	$(PHANTOMJS) node_modules/phantom-jasmine/lib/run_jasmine_test.coffee tests.html
+check: eslint
+	$(PHANTOMJS) tests/run-jasmine2.js tests.html
+
 
 ########################################################################
 ## Documentation

+ 1 - 1
docs/CHANGES.md

@@ -2,7 +2,7 @@
 
 ## 3.0.2 (Unreleased)
 
-- No changes yet.
+- Update Jasmine from 1.3.1 to 2.5.3 and Phantomjs from 1.9.7-1 to 2.1.14 [jcbrand]
 
 ## 3.0.1 (2017-04-04)
 

+ 5 - 4
package.json

@@ -40,6 +40,7 @@
     "bootstrap": "^3.3.7",
     "bourbon": "^4.3.2",
     "clean-css": "^3.4.19",
+    "es6-promise": "^4.1.0",
     "eslint": "^3.16.1",
     "eslint-plugin-lodash": "^2.3.3",
     "font-awesome": "^4.7.0",
@@ -49,7 +50,7 @@
     "grunt-json": "^0.2.0",
     "http-server": "^0.9.0",
     "install": "^0.8.5",
-    "jasmine": "https://github.com/jcbrand/jasmine.git#439a7f805eeaec0cabe18a8ecf7e47da1a0afa33",
+    "jasmine": "2.5.3",
     "jed": "0.5.4",
     "jquery": "2.2.3",
     "jquery-easing": "0.0.1",
@@ -60,8 +61,7 @@
     "moment": "~2.13.0",
     "npm": "^4.1.1",
     "otr": "0.2.16",
-    "phantom-jasmine": "0.1.8",
-    "phantomjs": "~1.9.7-1",
+    "phantomjs-prebuilt": "~2.1.14",
     "pluggable.js": "1.0.0",
     "po2json": "^0.4.4",
     "requirejs": "2.3.3",
@@ -73,7 +73,8 @@
     "strophejs-plugin-register": "0.0.1",
     "strophejs-plugin-rsm": "0.0.1",
     "strophejs-plugin-vcard": "0.0.1",
-    "text": "requirejs/text#2.0.15"
+    "text": "requirejs/text#2.0.15",
+    "wait-until-promise": "^1.0.0"
   },
   "dependencies": {}
 }

+ 84 - 99
spec/bookmarks.js

@@ -1,10 +1,12 @@
+/*global waitUntilPromise */
+
 (function (root, factory) {
     define([
         "jquery",
         "converse-core",
         "utils",
         "mock",
-        "test_utils"
+        "test-utils"
         ], factory);
 } (this, function ($, converse, utils, mock, test_utils) {
     "use strict";
@@ -17,17 +19,17 @@
         it("can be bookmarked", mock.initConverse(function (_converse) {
             var sent_stanza, IQ_id;
             var sendIQ = _converse.connection.sendIQ;
-            spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+            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').andCallThrough();
+            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').andCallThrough();
-            spyOn(view, 'cancelConfiguration').andCallThrough();
+            spyOn(view, 'renderBookmarkForm').and.callThrough();
+            spyOn(view, 'cancelConfiguration').and.callThrough();
 
             var $bookmark = view.$el.find('.icon-pushpin');
             $bookmark.click();
@@ -146,85 +148,73 @@
         describe("when bookmarked", function () {
 
             it("displays that it's bookmarked through its bookmark icon", mock.initConverse(function (_converse) {
-                runs(function () {
-                    test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
-                });
-                waits(100);
-                runs(function () {
-                    var view = _converse.chatboxviews.get('lounge@localhost');
-                    var $bookmark_icon = view.$('.icon-pushpin');
-                    expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
-                    view.model.set('bookmarked', true);
-                    expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
-                    view.model.set('bookmarked', false);
-                    expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
-                });
+                test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
+                var view = _converse.chatboxviews.get('lounge@localhost');
+                var $bookmark_icon = view.$('.icon-pushpin');
+                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
+                view.model.set('bookmarked', true);
+                expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
+                view.model.set('bookmarked', false);
+                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
             }));
 
             it("can be unbookmarked", mock.initConverse(function (_converse) {
-                var view, sent_stanza, IQ_id;
+                var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
+                var jid = 'theplay@conference.shakespeare.lit';
+                var view = _converse.chatboxviews.get(jid);
+                spyOn(view, 'toggleBookmark').and.callThrough();
+                spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
+                view.delegateEvents();
+                _converse.bookmarks.create({
+                    'jid': view.model.get('jid'),
+                    'autojoin': false,
+                    'name':  'The Play',
+                    'nick': ' Othello'
+                });
+                expect(_converse.bookmarks.length).toBe(1);
+                expect(view.model.get('bookmarked')).toBeTruthy();
+                var $bookmark_icon = view.$('.icon-pushpin');
+                expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
+
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
-                spyOn(_converse.connection, 'getUniqueId').andCallThrough();
+                spyOn(_converse.connection, 'getUniqueId').and.callThrough();
+                $bookmark_icon.click();
+                expect(view.toggleBookmark).toHaveBeenCalled();
+                expect($bookmark_icon.hasClass('button-on')).toBeFalsy();
+                expect(_converse.bookmarks.length).toBe(0);
 
-                runs(function () {
-                    test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
-                });
-                waits(100);
-                runs(function () {
-                    var jid = 'theplay@conference.shakespeare.lit';
-                    view = _converse.chatboxviews.get(jid);
-                    spyOn(view, 'toggleBookmark').andCallThrough();
-                    spyOn(_converse.bookmarks, 'sendBookmarkStanza').andCallThrough();
-                    view.delegateEvents();
-                    _converse.bookmarks.create({
-                        'jid': view.model.get('jid'),
-                        'autojoin': false,
-                        'name':  'The Play',
-                        'nick': ' Othello'
-                    });
-                    expect(_converse.bookmarks.length).toBe(1);
-                });
-                waits(100);
-                runs(function () {
-                    expect(view.model.get('bookmarked')).toBeTruthy();
-                    var $bookmark_icon = view.$('.icon-pushpin');
-                    expect($bookmark_icon.hasClass('button-on')).toBeTruthy();
-                    $bookmark_icon.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>"
-                    );
-                });
+                // 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>"
+                );
             }));
         });
 
@@ -255,7 +245,7 @@
 
     describe("Bookmarks", function () {
 
-        it("can be pushed from the XMPP server", mock.initConverse(function (_converse) {
+        xit("can be pushed from the XMPP server", mock.initConverse(function (_converse) {
             // TODO
             /* The stored data is automatically pushed to all of the user's
              * connected resources.
@@ -305,7 +295,7 @@
         it("can be retrieved from the XMPP server", mock.initConverse(function (_converse) {
             var sent_stanza, IQ_id,
                 sendIQ = _converse.connection.sendIQ;
-            spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+            spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 sent_stanza = iq;
                 IQ_id = sendIQ.bind(this)(iq, callback, errback);
             });
@@ -376,7 +366,7 @@
             it("shows a list of bookmarks", mock.initConverse(function (_converse) {
                 var IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 _converse.chatboxviews.get('controlbox').$('#chatrooms dl.bookmarks').html('');
@@ -406,27 +396,22 @@
             }));
 
             it("remembers the toggle state of the bookmarks list", mock.initConverse(function (_converse) {
-                runs(function () {
-                    _converse.bookmarks.create({
-                        'jid': 'theplay@conference.shakespeare.lit',
-                        'autojoin': false,
-                        'name':  'The Play',
-                        'nick': ''
-                    });
-                    _converse.emit('chatBoxesFetched');
-                    test_utils.openControlBox().openRoomsPanel(_converse);
-                });
-                waits(100);
-                runs(function () {
-                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
-                    $('#chatrooms .bookmarks-toggle').click();
-                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(0);
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
-                    $('#chatrooms .bookmarks-toggle').click();
-                    expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
-                    expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                _converse.bookmarks.create({
+                    'jid': 'theplay@conference.shakespeare.lit',
+                    'autojoin': false,
+                    'name':  'The Play',
+                    'nick': ''
                 });
+                _converse.emit('chatBoxesFetched');
+                test_utils.openControlBox().openRoomsPanel(_converse);
+                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
+                $('#chatrooms .bookmarks-toggle').click();
+                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(0);
+                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
+                $('#chatrooms .bookmarks-toggle').click();
+                expect($('#chatrooms dl.bookmarks dd:visible').length).toBe(1);
+                expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
             }));
         });
     });

File diff suppressed because it is too large
+ 366 - 369
spec/chatbox.js


File diff suppressed because it is too large
+ 345 - 371
spec/chatroom.js


File diff suppressed because it is too large
+ 343 - 371
spec/controlbox.js


+ 20 - 23
spec/converse.js

@@ -2,7 +2,7 @@
     define([
         "converse-core",
         "mock",
-        "test_utils"], factory);
+        "test-utils"], factory);
 } (this, function (converse, mock, test_utils) {
     var b64_sha1 = converse.env.b64_sha1;
     var _ = converse.env._;
@@ -52,9 +52,9 @@
         describe("A chat state indication", function () {
 
             it("are sent out when the client becomes or stops being idle", mock.initConverse(function (_converse) {
-                spyOn(_converse, 'sendCSI').andCallThrough();
+                spyOn(_converse, 'sendCSI').and.callThrough();
                 var sent_stanza;
-                spyOn(_converse.connection, 'send').andCallFake(function (stanza) {
+                spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
                     sent_stanza = stanza;
                 });
                 var i = 0;
@@ -298,26 +298,23 @@
                 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
-                runs(function () {
-                    // Test on chat that doesn't exist.
-                    expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
-                    var box = _converse.api.chats.open(jid);
-                    expect(box instanceof Object).toBeTruthy();
-                    expect(box.model.get('box_id')).toBe(b64_sha1(jid));
-                    expect(
-                        _.keys(box),
-                        ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
-                    );
-                    chatboxview = _converse.chatboxviews.get(jid);
-                    expect(chatboxview.$el.is(':visible')).toBeTruthy();
-                    // Test for multiple JIDs
-                    var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    var list = _converse.api.chats.open([jid, jid2]);
-                    expect(_.isArray(list)).toBeTruthy();
-                    expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
-                    expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
-                });
+                // Test on chat that doesn't exist.
+                expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
+                var box = _converse.api.chats.open(jid);
+                expect(box instanceof Object).toBeTruthy();
+                expect(box.model.get('box_id')).toBe(b64_sha1(jid));
+                expect(
+                    _.keys(box),
+                    ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
+                );
+                chatboxview = _converse.chatboxviews.get(jid);
+                expect(chatboxview.$el.is(':visible')).toBeTruthy();
+                // Test for multiple JIDs
+                var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
+                var list = _converse.api.chats.open([jid, jid2]);
+                expect(_.isArray(list)).toBeTruthy();
+                expect(list[0].model.get('box_id')).toBe(b64_sha1(jid));
+                expect(list[1].model.get('box_id')).toBe(b64_sha1(jid2));
             }));
         });
 

+ 4 - 4
spec/disco.js

@@ -3,7 +3,7 @@
         "jquery",
         "converse-core",
         "mock",
-        "test_utils"], factory);
+        "test-utils"], factory);
 } (this, function ($, converse, mock, test_utils) {
     "use strict";
     var Strophe = converse.env.Strophe;
@@ -11,10 +11,10 @@
     describe("Service Discovery", function () {
         describe("Whenever converse.js discovers a new server feature", function () {
            it("emits the serviceDiscovered event", mock.initConverse(function (_converse) {
-                spyOn(_converse, 'emit');
+                sinon.spy(_converse, 'emit');
                 _converse.features.create({'var': Strophe.NS.MAM});
-                expect(_converse.emit).toHaveBeenCalled();
-                expect(_converse.emit.argsForCall[0][1].get('var')).toBe(Strophe.NS.MAM);
+                expect(_converse.emit.called).toBe(true);
+                expect(_converse.emit.args[0][1].get('var')).toBe(Strophe.NS.MAM);
             }));
         });
     });

+ 9 - 9
spec/eventemitter.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
 
     return describe("The _converse Event Emitter", function() {
@@ -11,9 +11,9 @@
             _converse.emit('connected');
             expect(this.callback).toHaveBeenCalled();
             _converse.emit('connected');
-            expect(this.callback.callCount, 2);
+            expect(this.callback.calls.count(), 2);
             _converse.emit('connected');
-            expect(this.callback.callCount, 3);
+            expect(this.callback.calls.count(), 3);
         }));
 
         it("allows you to listen once for an emitted event", mock.initConverse(function (_converse) {
@@ -23,9 +23,9 @@
             _converse.emit('connected');
             expect(this.callback).toHaveBeenCalled();
             _converse.emit('connected');
-            expect(this.callback.callCount, 1);
+            expect(this.callback.calls.count(), 1);
             _converse.emit('connected');
-            expect(this.callback.callCount, 1);
+            expect(this.callback.calls.count(), 1);
         }));
 
         it("allows you to stop listening or subscribing to an event", mock.initConverse(function (_converse) {
@@ -46,15 +46,15 @@
             _converse.off('connected', this.callback);
 
             _converse.emit('connected');
-            expect(this.callback.callCount, 1);
-            expect(this.anotherCallback.callCount, 2);
+            expect(this.callback.calls.count(), 1);
+            expect(this.anotherCallback.calls.count(), 2);
 
             _converse.once('connected', this.neverCalled);
             _converse.off('connected', this.neverCalled);
 
             _converse.emit('connected');
-            expect(this.callback.callCount, 1);
-            expect(this.anotherCallback.callCount, 3);
+            expect(this.callback.calls.count(), 1);
+            expect(this.anotherCallback.calls.count(), 3);
             expect(this.neverCalled).not.toHaveBeenCalled();
         }));
     });

+ 45 - 60
spec/headline.js

@@ -4,7 +4,7 @@
         "converse-core",
         "utils",
         "mock",
-        "test_utils"
+        "test-utils"
         ], factory);
 } (this, function ($, converse, utils, mock, test_utils) {
     "use strict";
@@ -25,23 +25,18 @@
              *  </message
              */
             sinon.spy(utils, 'isHeadlineMessage');
-            runs(function () {
-                var stanza = $msg({
-                        'xmlns': 'jabber:client',
-                        'to': 'dummy@localhost',
-                        'type': 'chat',
-                        'from': 'gapowa20102106@rds-rostov.ru/Adium',
-                    })
-                    .c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
-                    .c('body').t('SORRY FOR THIS ADVERT');
-                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            });
-            waits(250);
-            runs(function () {
-                expect(utils.isHeadlineMessage.called).toBeTruthy();
-                expect(utils.isHeadlineMessage.returned(false)).toBeTruthy();
-                utils.isHeadlineMessage.restore();
-            });
+            var stanza = $msg({
+                    'xmlns': 'jabber:client',
+                    'to': 'dummy@localhost',
+                    'type': 'chat',
+                    'from': 'gapowa20102106@rds-rostov.ru/Adium',
+                })
+                .c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
+                .c('body').t('SORRY FOR THIS ADVERT');
+            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+            expect(utils.isHeadlineMessage.called).toBeTruthy();
+            expect(utils.isHeadlineMessage.returned(false)).toBeTruthy();
+            utils.isHeadlineMessage.restore();
         }));
 
         it("will open and display headline messages", mock.initConverse(function (_converse) {
@@ -59,53 +54,43 @@
              *  </message>
              */
             sinon.spy(utils, 'isHeadlineMessage');
-            runs(function () {
-                var stanza = $msg({
-                        'type': 'headline',
-                        'from': 'notify.example.com',
-                        'to': 'dummy@localhost',
-                        'xml:lang': 'en'
-                    })
-                    .c('subject').t('SIEVE').up()
-                    .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
-                    .c('x', {'xmlns': 'jabber:x:oob'})
-                    .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
-                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            });
-            waits(250);
-            runs(function () {
-                expect(
-                    _.includes(
-                        _converse.chatboxviews.keys(),
-                        'notify.example.com')
-                    ).toBeTruthy();
-                expect(utils.isHeadlineMessage.called).toBeTruthy();
-                expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
-                utils.isHeadlineMessage.restore(); // unwraps
-            });
+            var stanza = $msg({
+                    'type': 'headline',
+                    'from': 'notify.example.com',
+                    'to': 'dummy@localhost',
+                    'xml:lang': 'en'
+                })
+                .c('subject').t('SIEVE').up()
+                .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
+                .c('x', {'xmlns': 'jabber:x:oob'})
+                .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
+            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+            expect(
+                _.includes(
+                    _converse.chatboxviews.keys(),
+                    'notify.example.com')
+                ).toBeTruthy();
+            expect(utils.isHeadlineMessage.called).toBeTruthy();
+            expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
+            utils.isHeadlineMessage.restore(); // unwraps
         }));
 
         it("will not show a headline messages from a full JID if allow_non_roster_messaging is false", mock.initConverse(function (_converse) {
             _converse.allow_non_roster_messaging = false;
             sinon.spy(utils, 'isHeadlineMessage');
-            runs(function () {
-                var stanza = $msg({
-                        'type': 'headline',
-                        'from': 'andre5114@jabber.snc.ru/Spark',
-                        'to': 'dummy@localhost',
-                        'xml:lang': 'en'
-                    })
-                    .c('nick').t('gpocy').up()
-                    .c('body').t('Здравствуйте друзья');
-                _converse.connection._dataRecv(test_utils.createRequest(stanza));
-            });
-            waits(250);
-            runs(function () {
-                expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
-                expect(utils.isHeadlineMessage.called).toBeTruthy();
-                expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
-                utils.isHeadlineMessage.restore(); // unwraps
-            });
+            var stanza = $msg({
+                    'type': 'headline',
+                    'from': 'andre5114@jabber.snc.ru/Spark',
+                    'to': 'dummy@localhost',
+                    'xml:lang': 'en'
+                })
+                .c('nick').t('gpocy').up()
+                .c('body').t('Здравствуйте друзья');
+            _converse.connection._dataRecv(test_utils.createRequest(stanza));
+            expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
+            expect(utils.isHeadlineMessage.called).toBeTruthy();
+            expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
+            utils.isHeadlineMessage.restore(); // unwraps
         }));
     });
 }));

+ 15 - 15
spec/mam.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
     "use strict";
     var _ = converse.env._;
@@ -18,7 +18,7 @@
            it("can be used to query for all archived messages", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -34,7 +34,7 @@
            it("can be used to query for all messages to/from a particular JID", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -62,7 +62,7 @@
            it("can be used to query for all messages in a certain timespan", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -108,7 +108,7 @@
            it("can be used to query for all messages after a certain time", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -137,7 +137,7 @@
            it("can be used to query for a limited set of results", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -169,7 +169,7 @@
            it("can be used to page through results", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -206,7 +206,7 @@
            it("accepts \"before\" with an empty string as value to reverse the order", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -242,7 +242,7 @@
                 }
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -280,7 +280,7 @@
                 }
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
@@ -352,7 +352,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
                 expect(callback).toHaveBeenCalled();
-                var args = callback.argsForCall[0];
+                var args = callback.calls.argsFor(0);
                 expect(args[0].length).toBe(2);
                 expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML);
                 expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML);
@@ -370,17 +370,17 @@
             it("is set once server support for MAM has been confirmed", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
-                spyOn(_converse, 'onMAMPreferences').andCallThrough();
+                spyOn(_converse, 'onMAMPreferences').and.callThrough();
                 _converse.message_archiving = 'never';
 
                 var feature = new _converse.Feature({
                     'var': Strophe.NS.MAM
                 });
-                spyOn(feature, 'save').andCallFake(feature.set); // Save will complain about a url not being set
+                spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
                 _converse.features.onFeatureAdded(feature);
 
                 expect(_converse.connection.sendIQ).toHaveBeenCalled();
@@ -407,7 +407,7 @@
 
                 expect(_converse.onMAMPreferences).toHaveBeenCalled();
 
-                expect(_converse.connection.sendIQ.callCount).toBe(2);
+                expect(_converse.connection.sendIQ.calls.count()).toBe(2);
                 expect(sent_stanza.toString()).toBe(
                     "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
                         "<prefs xmlns='urn:xmpp:mam:0' default='never'>"+

+ 1 - 1
spec/minchats.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
     var _ = converse.env._;
     var $msg = converse.env.$msg;

+ 42 - 52
spec/notification.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils", "utils"], factory);
+    define(["mock", "converse-core", "test-utils", "utils"], factory);
 } (this, function (mock, converse, test_utils, utils) {
     "use strict";
     var _ = converse.env._;
@@ -16,7 +16,7 @@
                         // TODO: not yet testing show_desktop_notifications setting
                         test_utils.createContacts(_converse, 'current');
                         spyOn(_converse, 'showMessageNotification');
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
+                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                         
                         var message = 'This message will show a desktop notification';
                         var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
@@ -46,8 +46,8 @@
                                 };
                             };
                         }
-                        spyOn(_converse, 'showMessageNotification').andCallThrough();
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
+                        spyOn(_converse, 'showMessageNotification').and.callThrough();
+                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                         
                         var message = 'dummy: This message will show a desktop notification';
                         var nick = mock.chatroom_names[0],
@@ -66,56 +66,46 @@
                     }));
 
                     it("is shown for headline messages", mock.initConverse(function (_converse) {
-                        spyOn(_converse, 'showMessageNotification').andCallThrough();
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
-                        runs(function () {
-                            var stanza = $msg({
-                                    'type': 'headline',
-                                    'from': 'notify.example.com',
-                                    'to': 'dummy@localhost',
-                                    'xml:lang': 'en'
-                                })
-                                .c('subject').t('SIEVE').up()
-                                .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
-                                .c('x', {'xmlns': 'jabber:x:oob'})
-                                .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
-                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                        });
-                        waits(250);
-                        runs(function () {
-                            expect(
-                                _.includes(_converse.chatboxviews.keys(),
-                                    'notify.example.com')
-                                ).toBeTruthy();
-                            expect(_converse.showMessageNotification).toHaveBeenCalled();
-                        });
+                        spyOn(_converse, 'showMessageNotification').and.callThrough();
+                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
+                        var stanza = $msg({
+                                'type': 'headline',
+                                'from': 'notify.example.com',
+                                'to': 'dummy@localhost',
+                                'xml:lang': 'en'
+                            })
+                            .c('subject').t('SIEVE').up()
+                            .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
+                            .c('x', {'xmlns': 'jabber:x:oob'})
+                            .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
+                        _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                        expect(
+                            _.includes(_converse.chatboxviews.keys(),
+                                'notify.example.com')
+                            ).toBeTruthy();
+                        expect(_converse.showMessageNotification).toHaveBeenCalled();
                     }));
 
                     it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse(function (_converse) {
                         _converse.allow_non_roster_messaging = false;
-                        spyOn(_converse, 'showMessageNotification').andCallThrough();
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
-                        runs(function () {
-                            var stanza = $msg({
-                                    'type': 'headline',
-                                    'from': 'someone@notify.example.com',
-                                    'to': 'dummy@localhost',
-                                    'xml:lang': 'en'
-                                })
-                                .c('subject').t('SIEVE').up()
-                                .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
-                                .c('x', {'xmlns': 'jabber:x:oob'})
-                                .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
-                            _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                        });
-                        waits(250);
-                        runs(function () {
-                            expect(
-                                _.includes(_converse.chatboxviews.keys(),
-                                    'someone@notify.example.com')
-                                ).toBeFalsy();
-                            expect(_converse.showMessageNotification).not.toHaveBeenCalled();
-                        });
+                        spyOn(_converse, 'showMessageNotification').and.callThrough();
+                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
+                        var stanza = $msg({
+                                'type': 'headline',
+                                'from': 'someone@notify.example.com',
+                                'to': 'dummy@localhost',
+                                'xml:lang': 'en'
+                            })
+                            .c('subject').t('SIEVE').up()
+                            .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
+                            .c('x', {'xmlns': 'jabber:x:oob'})
+                            .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
+                        _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                        expect(
+                            _.includes(_converse.chatboxviews.keys(),
+                                'someone@notify.example.com')
+                            ).toBeFalsy();
+                        expect(_converse.showMessageNotification).not.toHaveBeenCalled();
                     }));
 
                     it("is shown when a user changes their chat state (if show_chatstate_notifications is true)", mock.initConverse(function (_converse) {
@@ -123,7 +113,7 @@
                         _converse.show_chatstate_notifications = true;
 
                         test_utils.createContacts(_converse, 'current');
-                        spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
+                        spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                         spyOn(_converse, 'showChatStateNotification');
                         var jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
                         _converse.roster.get(jid).set('chat_status', 'busy'); // This will emit 'contactStatusChanged'
@@ -135,7 +125,7 @@
 
             describe("When a new contact request is received", function () {
                 it("an HTML5 Notification is received", mock.initConverse(function (_converse) {
-                    spyOn(_converse, 'areDesktopNotificationsEnabled').andReturn(true);
+                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                     spyOn(_converse, 'showContactRequestNotification');
                     _converse.emit('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
                     expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();

+ 2 - 2
spec/otr.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
     var $ = converse.env.jQuery;
     var Strophe = converse.env.Strophe;
@@ -41,7 +41,7 @@
                 spyOn(_converse.connection, 'send');
                 chatbox.set('otr_status', 1); // Set OTR status to UNVERIFIED, to mock an encrypted session
                 chatbox.trigger('sendMessage', new _converse.Message({ message: msgtext }));
-                var $sent = $(_converse.connection.send.argsForCall[0][0].tree());
+                var $sent = $(_converse.connection.send.calls.argsFor(0)[0].tree());
                 expect($sent.find('body').siblings('private').length).toBe(1);
                 expect($sent.find('private').length).toBe(1);
                 expect($sent.find('private').attr('xmlns')).toBe('urn:xmpp:carbons:2');

+ 6 - 6
spec/ping.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils", "converse-ping"], factory);
+    define(["mock", "converse-core", "test-utils", "converse-ping"], factory);
 } (this, function (mock, test_utils) {
     "use strict";
 
@@ -7,16 +7,16 @@
         describe("Ping and pong handlers", function () {
 
             it("are registered when _converse.js is connected", mock.initConverse(function (_converse) {
-                spyOn(_converse, 'registerPingHandler').andCallThrough();
-                spyOn(_converse, 'registerPongHandler').andCallThrough();
+                spyOn(_converse, 'registerPingHandler').and.callThrough();
+                spyOn(_converse, 'registerPongHandler').and.callThrough();
                 _converse.emit('connected');
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
             }));
 
             it("are registered when _converse.js reconnected", mock.initConverse(function (_converse) {
-                spyOn(_converse, 'registerPingHandler').andCallThrough();
-                spyOn(_converse, 'registerPongHandler').andCallThrough();
+                spyOn(_converse, 'registerPingHandler').and.callThrough();
+                spyOn(_converse, 'registerPongHandler').and.callThrough();
                 _converse.emit('reconnected');
                 expect(_converse.registerPingHandler).toHaveBeenCalled();
                 expect(_converse.registerPongHandler).toHaveBeenCalled();
@@ -28,7 +28,7 @@
             it("is sent out when _converse.js pings a server", mock.initConverse(function (_converse) {
                 var sent_stanza, IQ_id;
                 var sendIQ = _converse.connection.sendIQ;
-                spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_stanza = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });

+ 1 - 1
spec/presence.js

@@ -5,7 +5,7 @@
         "jquery",
         "converse-core",
         "mock",
-        "test_utils",
+        "test-utils",
         "lodash"], factory);
 } (this, function ($, converse, mock, test_utils, _) {
     "use strict";

+ 258 - 270
spec/protocol.js

@@ -3,7 +3,7 @@
         "jquery",
         "converse-core",
         "mock",
-        "test_utils"], factory);
+        "test-utils"], factory);
 } (this, function ($, converse, mock, test_utils) {
     "use strict";
     var Strophe = converse.env.Strophe;
@@ -46,175 +46,171 @@
              * that session. A client MUST acknowledge each roster push with an IQ
              * stanza of type "result".
              */
-            it("Subscribe to contact, contact accepts and subscribes back", mock.initConverse(function (_converse) {
+            it("Subscribe to contact, contact accepts and subscribes back", mock.initConverseWithAsync(function (done, _converse) {
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 */
                 _converse.roster_groups = false;
                 var contact, stanza, sent_stanza, IQ_id;
-                runs(function () {
-                    test_utils.openControlBox(_converse);
+                test_utils.openControlBox(_converse);
+                var panel = _converse.chatboxviews.get('controlbox').contactspanel;
+                spyOn(panel, "addContactFromForm").and.callThrough();
+                spyOn(_converse.roster, "addAndSubscribe").and.callThrough();
+                spyOn(_converse.roster, "addContact").and.callThrough();
+                spyOn(_converse.roster, "sendContactAddIQ").and.callThrough();
+                spyOn(_converse, "getVCard").and.callThrough();
+                var sendIQ = _converse.connection.sendIQ;
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
+                    sent_stanza = iq;
+                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
-                waits(100);
-                runs(function () {
-                    var panel = _converse.chatboxviews.get('controlbox').contactspanel;
-                    spyOn(panel, "addContactFromForm").andCallThrough();
-                    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);
-                    });
-                    panel.delegateEvents(); // Rebind all events so that our spy gets called
+                panel.delegateEvents(); // Rebind all events so that our spy gets called
 
-                    /* Add a new contact through the UI */
-                    var $form = panel.$('form.add-xmpp-contact');
-                    expect($form.is(":visible")).toBeFalsy();
-                    // Click the "Add a contact" link.
-                    panel.$('.toggle-xmpp-contact-form').click();
-                    // Check that the $form appears
-                    expect($form.is(":visible")).toBeTruthy();
-                    // Fill in the form and submit
-                    $form.find('input').val('contact@example.org');
-                    $form.submit();
+                /* Add a new contact through the UI */
+                var $form = panel.$('form.add-xmpp-contact');
+                expect($form.is(":visible")).toBeFalsy();
+                // Click the "Add a contact" link.
+                panel.$('.toggle-xmpp-contact-form').click();
+                // Check that the $form appears
+                expect($form.is(":visible")).toBeTruthy();
+                // Fill in the form and submit
+                $form.find('input').val('contact@example.org');
+                $form.submit();
 
-                    /* In preparation for being able to render the contact in the
-                    * user's client interface and for the server to keep track of the
-                    * subscription, the user's client SHOULD perform a "roster set"
-                    * for the new roster item.
-                    */
-                    expect(panel.addContactFromForm).toHaveBeenCalled();
-                    expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
-                    expect(_converse.roster.addContact).toHaveBeenCalled();
-                    // The form should not be visible anymore.
-                    expect($form.is(":visible")).toBeFalsy();
+                /* In preparation for being able to render the contact in the
+                * user's client interface and for the server to keep track of the
+                * subscription, the user's client SHOULD perform a "roster set"
+                * for the new roster item.
+                */
+                expect(panel.addContactFromForm).toHaveBeenCalled();
+                expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
+                expect(_converse.roster.addContact).toHaveBeenCalled();
+                // The form should not be visible anymore.
+                expect($form.is(":visible")).toBeFalsy();
 
-                    /* _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/>
-                    * element MUST possess a 'jid' attribute, MAY possess a 'name'
-                    * attribute, MUST NOT possess a 'subscription' attribute, and MAY
-                    * contain one or more <group/> child elements:
-                    *
-                    *   <iq type='set' id='set1'>
-                    *   <query xmlns='jabber:iq:roster'>
-                    *       <item
-                    *           jid='contact@example.org'
-                    *           name='MyContact'>
-                    *       <group>MyBuddies</group>
-                    *       </item>
-                    *   </query>
-                    *   </iq>
-                    */
-                    expect(_converse.roster.sendContactAddIQ).toHaveBeenCalled();
-                    expect(sent_stanza.toLocaleString()).toBe(
-                        "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
-                            "<query xmlns='jabber:iq:roster'>"+
-                                "<item jid='contact@example.org' name='contact@example.org'/>"+
-                            "</query>"+
-                        "</iq>"
-                    );
-                    /* As a result, the user's server (1) MUST initiate a roster push
-                    * for the new roster item to all available resources associated
-                    * 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:
-                    *
-                    * <iq type='set'>
-                    *     <query xmlns='jabber:iq:roster'>
-                    *         <item
-                    *             jid='contact@example.org'
-                    *             subscription='none'
-                    *             name='MyContact'>
-                    *         <group>MyBuddies</group>
-                    *         </item>
-                    *     </query>
-                    * </iq>
-                    */
-                    var create = _converse.roster.create;
-                    spyOn(_converse.connection, 'send').andCallFake(function (stanza) {
-                        sent_stanza = stanza;
-                    });
-                    spyOn(_converse.roster, 'create').andCallFake(function () {
-                        contact = create.apply(_converse.roster, arguments);
-                        spyOn(contact, 'subscribe').andCallThrough();
-                        return contact;
-                    });
-                    stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
-                        .c('item', {
-                            'jid': 'contact@example.org',
-                            'subscription': 'none',
-                            'name': 'contact@example.org'});
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    /*
-                    * <iq type='result' id='set1'/>
-                    */
-                    stanza = $iq({'type': 'result', 'id':IQ_id});
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                /* _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/>
+                * element MUST possess a 'jid' attribute, MAY possess a 'name'
+                * attribute, MUST NOT possess a 'subscription' attribute, and MAY
+                * contain one or more <group/> child elements:
+                *
+                *   <iq type='set' id='set1'>
+                *   <query xmlns='jabber:iq:roster'>
+                *       <item
+                *           jid='contact@example.org'
+                *           name='MyContact'>
+                *       <group>MyBuddies</group>
+                *       </item>
+                *   </query>
+                *   </iq>
+                */
+                expect(_converse.roster.sendContactAddIQ).toHaveBeenCalled();
+                expect(sent_stanza.toLocaleString()).toBe(
+                    "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                        "<query xmlns='jabber:iq:roster'>"+
+                            "<item jid='contact@example.org' name='contact@example.org'/>"+
+                        "</query>"+
+                    "</iq>"
+                );
+                /* As a result, the user's server (1) MUST initiate a roster push
+                * for the new roster item to all available resources associated
+                * 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:
+                *
+                * <iq type='set'>
+                *     <query xmlns='jabber:iq:roster'>
+                *         <item
+                *             jid='contact@example.org'
+                *             subscription='none'
+                *             name='MyContact'>
+                *         <group>MyBuddies</group>
+                *         </item>
+                *     </query>
+                * </iq>
+                */
+                var create = _converse.roster.create;
+                spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
+                    sent_stanza = stanza;
+                });
+                spyOn(_converse.roster, 'create').and.callFake(function () {
+                    contact = create.apply(_converse.roster, arguments);
+                    spyOn(contact, 'subscribe').and.callThrough();
+                    return contact;
+                });
+                stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
+                    .c('item', {
+                        'jid': 'contact@example.org',
+                        'subscription': 'none',
+                        'name': 'contact@example.org'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                /*
+                * <iq type='result' id='set1'/>
+                */
+                stanza = $iq({'type': 'result', 'id':IQ_id});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
 
-                    // A contact should now have been created
-                    expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
-                    expect(contact.get('jid')).toBe('contact@example.org');
-                    expect(_converse.getVCard).toHaveBeenCalled();
+                // A contact should now have been created
+                expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
+                expect(contact.get('jid')).toBe('contact@example.org');
+                expect(_converse.getVCard).toHaveBeenCalled();
 
-                    /* To subscribe to the contact's presence information,
-                    * the user's client MUST send a presence stanza of
-                    * type='subscribe' to the contact:
-                    *
-                    *  <presence to='contact@example.org' type='subscribe'/>
-                    */
-                    expect(contact.subscribe).toHaveBeenCalled();
-                    expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
-                        "<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'>"+
-                            "<nick xmlns='http://jabber.org/protocol/nick'>Max Mustermann</nick>"+
-                        "</presence>"
-                    );
-                    /* 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; _converse pending
-                    * sub-state is denoted by the inclusion of the ask='subscribe'
-                    * attribute in the roster item:
-                    *
-                    *  <iq type='set'>
-                    *    <query xmlns='jabber:iq:roster'>
-                    *      <item
-                    *          jid='contact@example.org'
-                    *          subscription='none'
-                    *          ask='subscribe'
-                    *          name='MyContact'>
-                    *      <group>MyBuddies</group>
-                    *      </item>
-                    *    </query>
-                    *  </iq>
-                    */
-                    spyOn(_converse.roster, "updateContact").andCallThrough();
-                    stanza = $iq({'type': 'set', 'from': 'dummy@localhost'})
-                        .c('query', {'xmlns': 'jabber:iq:roster'})
-                        .c('item', {
-                            'jid': 'contact@example.org',
-                            'subscription': 'none',
-                            'ask': 'subscribe',
-                            'name': 'contact@example.org'});
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    expect(_converse.roster.updateContact).toHaveBeenCalled();
-                });
-                waits(50);
-                runs(function () {
-                    // Check that the user is now properly shown as a pending
-                    // contact in the roster.
+                /* To subscribe to the contact's presence information,
+                * the user's client MUST send a presence stanza of
+                * type='subscribe' to the contact:
+                *
+                *  <presence to='contact@example.org' type='subscribe'/>
+                */
+                expect(contact.subscribe).toHaveBeenCalled();
+                expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
+                    "<presence to='contact@example.org' type='subscribe' xmlns='jabber:client'>"+
+                        "<nick xmlns='http://jabber.org/protocol/nick'>Max Mustermann</nick>"+
+                    "</presence>"
+                );
+                /* 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; _converse pending
+                * sub-state is denoted by the inclusion of the ask='subscribe'
+                * attribute in the roster item:
+                *
+                *  <iq type='set'>
+                *    <query xmlns='jabber:iq:roster'>
+                *      <item
+                *          jid='contact@example.org'
+                *          subscription='none'
+                *          ask='subscribe'
+                *          name='MyContact'>
+                *      <group>MyBuddies</group>
+                *      </item>
+                *    </query>
+                *  </iq>
+                */
+                spyOn(_converse.roster, "updateContact").and.callThrough();
+                stanza = $iq({'type': 'set', 'from': 'dummy@localhost'})
+                    .c('query', {'xmlns': 'jabber:iq:roster'})
+                    .c('item', {
+                        'jid': 'contact@example.org',
+                        'subscription': 'none',
+                        'ask': 'subscribe',
+                        'name': 'contact@example.org'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                expect(_converse.roster.updateContact).toHaveBeenCalled();
+                // Check that the user is now properly shown as a pending
+                // contact in the roster.
+                test_utils.waitUntil(function () {
+                    return $('a:contains("Pending contacts")').length;
+                }).then(function () {
                     var $header = $('a:contains("Pending contacts")');
                     expect($header.length).toBe(1);
                     expect($header.is(":visible")).toBeTruthy();
                     var $contacts = $header.parent().nextUntil('dt', 'dd');
                     expect($contacts.length).toBe(1);
 
-                    spyOn(contact, "ackSubscribe").andCallThrough();
+                    spyOn(contact, "ackSubscribe").and.callThrough();
                     /* Here we assume the "happy path" that the contact
                     * approves the subscription request
                     *
@@ -301,8 +297,8 @@
                     *
                     * <presence from='contact@example.org' to='user@example.com' type='subscribe'/>
                     */
-                    spyOn(contact, 'authorize').andCallThrough();
-                    spyOn(_converse.roster, 'handleIncomingSubscription').andCallThrough();
+                    spyOn(contact, 'authorize').and.callThrough();
+                    spyOn(_converse.roster, 'handleIncomingSubscription').and.callThrough();
                     stanza = $pres({
                         'to': _converse.bare_jid,
                         'from': 'contact@example.org/resource',
@@ -348,6 +344,7 @@
                     // The class on the contact will now have switched.
                     expect($contacts.hasClass('to')).toBeFalsy();
                     expect($contacts.hasClass('both')).toBeTruthy();
+                    done();
                 });
             }));
 
@@ -356,110 +353,101 @@
                 * the interaction between roster items and subscription states.
                 */
                 var contact, stanza, sent_stanza, sent_IQ;
-                runs(function () {
-                    test_utils.openControlBox(_converse);
+                test_utils.openControlBox(_converse);
+                // Add a new roster contact via roster push
+                stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
+                    .c('item', {
+                        'jid': 'contact@example.org',
+                        'subscription': 'none',
+                        'ask': 'subscribe',
+                        'name': 'contact@example.org'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                // A pending contact should now exist.
+                contact = _converse.roster.get('contact@example.org');
+                expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
+                spyOn(contact, "ackUnsubscribe").and.callThrough();
+
+                spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
+                    sent_stanza = stanza;
                 });
-                waits(100);
-                runs(function () {
-                    // Add a new roster contact via roster push
-                    stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
-                        .c('item', {
-                            'jid': 'contact@example.org',
-                            'subscription': 'none',
-                            'ask': 'subscribe',
-                            'name': 'contact@example.org'});
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
+                    sent_IQ = iq;
                 });
-                waits(50);
-                runs(function () {
-                    // A pending contact should now exist.
-                    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(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
-                        sent_IQ = iq;
-                    });
-                    /* We now assume the contact declines the subscription
-                    * 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>
-                    */
-                    // FIXME: also add the <iq>
-                    stanza = $pres({
-                        'to': _converse.bare_jid,
-                        'from': 'contact@example.org',
-                        'type': 'unsubscribed'
-                    });
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                /* We now assume the contact declines the subscription
+                * 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>
+                */
+                // FIXME: also add the <iq>
+                stanza = $pres({
+                    'to': _converse.bare_jid,
+                    'from': 'contact@example.org',
+                    'type': 'unsubscribed'
+                });
+                _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
-                    */
-                    expect(contact.ackUnsubscribe).toHaveBeenCalled();
-                    expect(sent_stanza.toLocaleString()).toBe(
-                        "<presence type='unsubscribe' to='contact@example.org' xmlns='jabber:client'/>"
-                    );
+                /* 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
+                */
+                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.
-                    */
-                    expect(sent_IQ.toLocaleString()).toBe(
-                        "<iq type='set' xmlns='jabber:client'>"+
-                            "<query xmlns='jabber:iq:roster'>"+
-                                "<item jid='contact@example.org' subscription='remove'/>"+
-                            "</query>"+
-                        "</iq>"
-                    );
-                });
+                /* _converse.js will then also automatically remove the
+                * contact from the user's roster.
+                */
+                expect(sent_IQ.toLocaleString()).toBe(
+                    "<iq type='set' xmlns='jabber:client'>"+
+                        "<query xmlns='jabber:iq:roster'>"+
+                            "<item jid='contact@example.org' subscription='remove'/>"+
+                        "</query>"+
+                    "</iq>"
+                );
             }));
 
-            it("Unsubscribe to a contact when subscription is mutual", mock.initConverse(function (_converse) {
+            it("Unsubscribe to a contact when subscription is mutual", mock.initConverseWithAsync(function (done, _converse) {
                 var sent_IQ, IQ_id, jid = 'annegreet.gomez@localhost';
                 _converse.roster_groups = false;
-                runs(function () {
-                    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(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy();
+                test_utils.openControlBox(_converse);
+                test_utils.createContacts(_converse, 'current');
+                spyOn(window, 'confirm').and.returnValue(true);
+                // We now have a contact we want to remove
+                expect(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy();
 
-                    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);
-                    });
+                var sendIQ = _converse.connection.sendIQ;
+                spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
+                    sent_IQ = iq;
+                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                });
 
+                test_utils.waitUntil(function () {
+                    return $('a:contains("My contacts")').length;
+                }).then(function () {
                     var $header = $('a:contains("My contacts")');
                     // remove the first user
                     $($header.parent().nextUntil('dt', 'dd').find('.remove-xmpp-contact').get(0)).click();
@@ -497,31 +485,31 @@
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
                     // Our contact has now been removed
                     expect(typeof _converse.roster.get(jid) === "undefined").toBeTruthy();
+                    done();
                 });
             }));
 
             it("Receiving a subscription request", mock.initConverse(function (_converse) {
-                runs(function () {
-                    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
-                     *     from='user@example.com'
-                     *     to='contact@example.org'
-                     *     type='subscribe'/>
-                     */
-                    var stanza = $pres({
-                        'to': _converse.bare_jid,
-                        'from': 'contact@example.org',
-                        'type': 'subscribe'
-                    }).c('nick', {
-                        'xmlns': Strophe.NS.NICK,
-                    }).t('Clint Contact');
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
+                test_utils.openControlBox(_converse);
+                test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
+                spyOn(_converse, "emit");
+                /* <presence
+                 *     from='user@example.com'
+                 *     to='contact@example.org'
+                 *     type='subscribe'/>
+                 */
+                var stanza = $pres({
+                    'to': _converse.bare_jid,
+                    'from': 'contact@example.org',
+                    'type': 'subscribe'
+                }).c('nick', {
+                    'xmlns': Strophe.NS.NICK,
+                }).t('Clint Contact');
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
+                test_utils.waitUntil(function () {
+                    return $('a:contains("Contact requests")').length;
+                }).then(function () {
                     var $header = $('a:contains("Contact requests")');
                     expect($header.length).toBe(1);
                     expect($header.is(":visible")).toBeTruthy();

+ 45 - 49
spec/register.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
     var $ = converse.env.jQuery;
     var Strophe = converse.env.Strophe;
@@ -8,44 +8,40 @@
     describe("The Registration Panel", function () {
 
         it("is not available unless allow_registration=true",  mock.initConverse(function (_converse) {
-            runs(test_utils.openControlBox);
-            waits(50);
-            runs(function () {
-                var cbview = _converse.chatboxviews.get('controlbox');
-                expect(cbview.$('#controlbox-tabs li').length).toBe(1);
-                expect(cbview.$('#controlbox-tabs li').text().trim()).toBe("Sign in");
-            });
-        }, { auto_login: false,
-              allow_registration: false,
-            }));
+            test_utils.openControlBox();
+            var cbview = _converse.chatboxviews.get('controlbox');
+            expect(cbview.$('#controlbox-tabs li').length).toBe(1);
+            expect(cbview.$('#controlbox-tabs li').text().trim()).toBe("Sign in");
+
+            }, { auto_login: false,
+                 allow_registration: false,
+        }));
 
         it("can be opened by clicking on the registration tab", mock.initConverse(function (_converse) {
             var cbview = _converse.chatboxviews.get('controlbox');
-            runs(test_utils.openControlBox);
-            waits(50);
-            runs(function () {
-                var $tabs = cbview.$('#controlbox-tabs');
-                var $panels = cbview.$('.controlbox-panes');
-                var $login = $panels.children().first();
-                var $registration = $panels.children().last();
-                expect($tabs.find('li').first().text()).toBe('Sign in');
-                expect($tabs.find('li').last().text()).toBe('Register');
-
-                spyOn(cbview, 'switchTab').andCallThrough();
-                cbview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
-                $tabs.find('li').last().find('a').click(); // Click the Register tab
-                expect($login.is(':visible')).toBe(false);
-                expect($registration.is(':visible')).toBe(true);
-                expect(cbview.switchTab).toHaveBeenCalled();
-            });
-        }, { auto_login: false,
-              allow_registration: true,
-            }));
+            test_utils.openControlBox();
+            var $tabs = cbview.$('#controlbox-tabs');
+            var $panels = cbview.$('.controlbox-panes');
+            var $login = $panels.children().first();
+            var $registration = $panels.children().last();
+            expect($tabs.find('li').first().text()).toBe('Sign in');
+            expect($tabs.find('li').last().text()).toBe('Register');
+
+            spyOn(cbview, 'switchTab').and.callThrough();
+            cbview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+            $tabs.find('li').last().find('a').click(); // Click the Register tab
+            expect($login.is(':visible')).toBe(false);
+            expect($registration.is(':visible')).toBe(true);
+            expect(cbview.switchTab).toHaveBeenCalled();
+
+            }, { auto_login: false,
+                 allow_registration: true,
+        }));
 
         it("allows the user to choose an XMPP provider's domain", mock.initConverse(function (_converse) {
             var cbview = _converse.chatboxviews.get('controlbox');
             var registerview = cbview.registerpanel;
-            spyOn(registerview, 'onProviderChosen').andCallThrough();
+            spyOn(registerview, 'onProviderChosen').and.callThrough();
             registerview.delegateEvents();  // We need to rebind all events otherwise our spy won't be called
             spyOn(_converse.connection, 'connect');
             var $tabs = cbview.$('#controlbox-tabs');
@@ -72,12 +68,12 @@
             var cbview = _converse.chatboxviews.get('controlbox');
             cbview.$('#controlbox-tabs').find('li').last().find('a').click(); // Click the Register tab
             var registerview = _converse.chatboxviews.get('controlbox').registerpanel;
-            spyOn(registerview, 'onProviderChosen').andCallThrough();
-            spyOn(registerview, 'getRegistrationFields').andCallThrough();
-            spyOn(registerview, 'onRegistrationFields').andCallThrough();
-            spyOn(registerview, 'renderRegistrationForm').andCallThrough();
+            spyOn(registerview, 'onProviderChosen').and.callThrough();
+            spyOn(registerview, 'getRegistrationFields').and.callThrough();
+            spyOn(registerview, 'onRegistrationFields').and.callThrough();
+            spyOn(registerview, 'renderRegistrationForm').and.callThrough();
             registerview.delegateEvents();  // We need to rebind all events otherwise our spy won't be called
-            spyOn(_converse.connection, 'connect').andCallThrough();
+            spyOn(_converse.connection, 'connect').and.callThrough();
 
             expect(registerview._registering).toBeFalsy();
             expect(_converse.connection.connected).toBeFalsy();
@@ -120,12 +116,12 @@
             var cbview = _converse.chatboxviews.get('controlbox');
             cbview.$('#controlbox-tabs').find('li').last().find('a').click(); // Click the Register tab
             var registerview = _converse.chatboxviews.get('controlbox').registerpanel;
-            spyOn(registerview, 'onProviderChosen').andCallThrough();
-            spyOn(registerview, 'getRegistrationFields').andCallThrough();
-            spyOn(registerview, 'onRegistrationFields').andCallThrough();
-            spyOn(registerview, 'renderRegistrationForm').andCallThrough();
+            spyOn(registerview, 'onProviderChosen').and.callThrough();
+            spyOn(registerview, 'getRegistrationFields').and.callThrough();
+            spyOn(registerview, 'onRegistrationFields').and.callThrough();
+            spyOn(registerview, 'renderRegistrationForm').and.callThrough();
             registerview.delegateEvents();  // We need to rebind all events otherwise our spy won't be called
-            spyOn(_converse.connection, 'connect').andCallThrough();
+            spyOn(_converse.connection, 'connect').and.callThrough();
 
             registerview.$('input[name=domain]').val('conversejs.org');
             registerview.$('input[type=submit]').click();
@@ -158,7 +154,7 @@
             registerview.$('input[type=submit]').click();
 
             expect(_converse.connection.send).toHaveBeenCalled();
-            var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
+            var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree());
             expect($stanza.children('query').children().length).toBe(3);
             expect($stanza.children('query').children()[0].tagName).toBe('username');
         }, { auto_login: false,
@@ -169,12 +165,12 @@
             var cbview = _converse.chatboxviews.get('controlbox');
             cbview.$('#controlbox-tabs').find('li').last().find('a').click(); // Click the Register tab
             var registerview = _converse.chatboxviews.get('controlbox').registerpanel;
-            spyOn(registerview, 'onProviderChosen').andCallThrough();
-            spyOn(registerview, 'getRegistrationFields').andCallThrough();
-            spyOn(registerview, 'onRegistrationFields').andCallThrough();
-            spyOn(registerview, 'renderRegistrationForm').andCallThrough();
+            spyOn(registerview, 'onProviderChosen').and.callThrough();
+            spyOn(registerview, 'getRegistrationFields').and.callThrough();
+            spyOn(registerview, 'onRegistrationFields').and.callThrough();
+            spyOn(registerview, 'renderRegistrationForm').and.callThrough();
             registerview.delegateEvents();  // We need to rebind all events otherwise our spy won't be called
-            spyOn(_converse.connection, 'connect').andCallThrough();
+            spyOn(_converse.connection, 'connect').and.callThrough();
 
             registerview.$('input[name=domain]').val('conversejs.org');
             registerview.$('input[type=submit]').click();
@@ -209,7 +205,7 @@
             registerview.$('input[type=submit]').click();
 
             expect(_converse.connection.send).toHaveBeenCalled();
-            var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
+            var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree());
             expect($stanza.children('query').children().length).toBe(1);
             expect($stanza.children('query').children().children().length).toBe(3);
             expect($stanza.children('query').children().children()[0].tagName).toBe('field');

+ 2 - 2
spec/utils.js

@@ -1,6 +1,6 @@
 (function (root, factory) {
-    define(["converse-core"], factory);
-} (this, function (converse) {
+    define(["jasmine-html", "converse-core"], factory);
+} (this, function (jasmine, converse) {
     var utils = converse.env.utils,
         _ = converse.env._;
 

+ 3 - 3
spec/xmppstatus.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define(["mock", "converse-core", "test_utils"], factory);
+    define(["mock", "converse-core", "test-utils"], factory);
 } (this, function (mock, converse, test_utils) {
     var $ = converse.env.jQuery;
 
@@ -7,11 +7,11 @@
 
         it("won't send <show>online</show> when setting a custom status message", mock.initConverse(function (_converse) {
             _converse.xmppstatus.save({'status': 'online'});
-            spyOn(_converse.xmppstatus, 'setStatusMessage').andCallThrough();
+            spyOn(_converse.xmppstatus, 'setStatusMessage').and.callThrough();
             spyOn(_converse.connection, 'send');
             _converse.xmppstatus.setStatusMessage("I'm also happy!");
             expect(_converse.connection.send).toHaveBeenCalled();
-            var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
+            var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree());
             expect($stanza.children().length).toBe(2);
             expect($stanza.children('status').length).toBe(1);
             expect($stanza.children('status').text()).toBe("I'm also happy!");

+ 1 - 0
src/config.js

@@ -21,6 +21,7 @@ require.config({
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
         "eventemitter":             "node_modules/otr/build/dep/eventemitter",
+        "es6-promise":              "node_modules/es6-promise/dist/es6-promise",
         "jquery":                   "node_modules/jquery/dist/jquery",
         "jquery-private":           "src/jquery-private",
         "jquery.browser":           "node_modules/jquery.browser/dist/jquery.browser",

+ 8 - 3
tests.html

@@ -4,12 +4,16 @@
 <head>
     <title>Converse.js Tests</title>
     <meta name="description" content="Converse.js: A chat client for your website" />
-    <link rel="shortcut icon" type="image/png" href="components/jasmine/images/jasmine_favicon.png">
+    <link rel="shortcut icon" type="image/png" href="node_modules/jasmine-core/images/jasmine_favicon.png">
+
+    <link rel="stylesheet" type="text/css" media="screen" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
     <link rel="stylesheet" type="text/css" media="screen" href="css/jasmine.css">
     <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
+
     <script src="src/config.js"></script>
-    <script data-main="tests/main" src="node_modules/requirejs/require.js"></script>
+    <script data-main="tests/runner" src="node_modules/requirejs/require.js"></script>
+
     <style>
         .tests-brand-heading {
             margin-top: 1em;
@@ -21,7 +25,8 @@
 <body>
     <div id="header_wrap" class="outer">
         <header class="inner">
-          <h1 class="brand-heading tests-brand-heading"><i class="icon-conversejs"></i> Converse.js</h1>
+          <h1 class="brand-heading tests-brand-heading">
+              <i class="icon-conversejs"></i> Converse.js</h1>
           <h2 id="project_tagline">Tests</h2>
         </header>
     </div>

+ 26 - 18
tests/mock.js

@@ -44,7 +44,7 @@
         'preventDefault': function () {}
     };
 
-    mock.mock_connection = function ()  {
+    mock.mock_connection = function ()  {  // eslint-disable-line wrap-iife
         return function () {
             Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
             var c = new Strophe.Connection('jasmine tests');
@@ -76,25 +76,33 @@
         };
     }();
 
+    function initConverse (settings) {
+        window.localStorage.clear();
+        window.sessionStorage.clear();
+        var converse = converse_api.initialize(_.extend({
+            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: false
+        }, settings || {}));
+        converse.ChatBoxViews.prototype.trimChat = function () {};
+        return converse;
+    }
+    mock.initConverseWithAsync = function (func, settings) {
+        return function (done) {
+            return func(done, initConverse(settings));
+        };
+    };
     mock.initConverse = function (func, settings) {
         return function () {
-            window.localStorage.clear();
-            window.sessionStorage.clear();
-
-            var converse = converse_api.initialize(_.extend({
-                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: false
-            }, settings || {}));
-            converse.ChatBoxViews.prototype.trimChat = function () {};
-            return func(converse);
+            initConverse(settings);
+            return func(initConverse(settings));
         };
     };
     return mock;

+ 97 - 0
tests/run-jasmine2.js

@@ -0,0 +1,97 @@
+/*global document, require, setInterval, clearInterval, eval, phantom, console */
+
+var system = require('system');
+
+function waitFor(testFx, onReady, timeOutMillis) {
+    /**
+     * Wait until the test condition is true or a timeout occurs. Useful for waiting
+     * on a server response or for a ui change (fadeIn, etc.) to occur.
+     *
+     * @param testFx javascript condition that evaluates to a boolean,
+     * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
+     * as a callback function.
+     * @param onReady what to do when testFx condition is fulfilled,
+     * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
+     * as a callback function.
+     * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
+     */
+    "use strict";
+    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 40001, //< Default Max Timeout is 10s
+        start = new Date().getTime(),
+        condition = false,
+        interval = setInterval(function() {
+            if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
+                // If not time-out yet and condition not yet fulfilled
+                condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); // eslint-disable-line no-eval
+            } else {
+                if(!condition) {
+                    // If condition still not fulfilled (timeout but condition is 'false')
+                    console.log("'waitFor()' timeout");
+                    phantom.exit(1);
+                } else {
+                    // Condition fulfilled (timeout and/or condition is 'true')
+                    console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
+                    // Do what it's supposed to do once the condition is fulfilled
+                    typeof(onReady) === "string" ? eval(onReady) : onReady(); // eslint-disable-line no-eval
+                    clearInterval(interval); //< Stop this interval
+                }
+            }
+        }, 100); //< repeat check every 100ms
+}
+
+if (system.args.length !== 2) {
+    console.log('Usage: run-jasmine2.js URL');
+    phantom.exit(1);
+}
+
+var page = require('webpage').create();
+
+page.onConsoleMessage = function(msg) {
+    // Route "console.log()" calls from within the Page context to the main
+    // Phantom context (i.e. current "this")
+    console.log(msg);
+};
+
+page.open(system.args[1], function(status){
+    "use strict";
+    if (status !== "success") {
+        console.log("Unable to access network");
+        phantom.exit();
+    } else {
+        waitFor(function(){
+            return page.evaluate(function(){
+                return (document.body.querySelector('.jasmine-symbol-summary .pending') === null &&
+                        document.body.querySelector('.jasmine-duration') !== null);
+            });
+        }, function(){
+            var exitCode = page.evaluate(function(){
+                console.log('');
+                var title = 'Jasmine';
+                var version = document.body.querySelector('.jasmine-version').innerText;
+                var duration = document.body.querySelector('.jasmine-duration').innerText;
+                var banner = title + ' ' + version + ' ' + duration;
+                console.log(banner);
+
+                var list = document.body.querySelectorAll('.jasmine-results > .jasmine-failures > .jasmine-spec-detail.jasmine-failed');
+                if (list && list.length > 0) {
+                    console.log('');
+                    console.log(list.length + ' test(s) FAILED:');
+                    for (var i = 0; i < list.length; ++i) {
+                        var el = list[i],
+                            desc = el.querySelector('.jasmine-description'),
+                            msg = el.querySelector('.jasmine-messages > .jasmine-result-message');
+                        console.log('');
+                        console.log(desc.innerText);
+                        console.log(msg.innerText);
+                        console.log('');
+                    }
+                    return 1;
+                } else {
+                    console.log(document.body.querySelector('.jasmine-alert > .jasmine-bar.jasmine-passed, .jasmine-alert > .jasmine-bar.skipped').innerText);
+                    return 0;
+                }
+            });
+            phantom.exit(exitCode);
+        });
+    }
+});

+ 38 - 36
tests/main.js → tests/runner.js

@@ -1,18 +1,31 @@
+/*global config */
+
 // Extra test dependencies
 config.paths.mock = "tests/mock";
-config.paths.test_utils = "tests/utils";
+config.paths['wait-until-promise'] = "node_modules/wait-until-promise/index";
+config.paths['test-utils'] = "tests/utils";
 config.paths.sinon = "node_modules/sinon/lib/sinon";
-config.paths.jasmine = "node_modules/jasmine-core/lib/jasmine-core/jasmine";
 config.paths.transcripts = "converse-logs/converse-logs";
+config.paths.jasmine = "node_modules/jasmine-core/lib/jasmine-core/jasmine";
+config.paths.boot = "node_modules/jasmine-core/lib/jasmine-core/boot";
 config.paths["jasmine-html"] = "node_modules/jasmine-core/lib/jasmine-core/jasmine-html";
-config.paths["console-runner"] = "node_modules/phantom-jasmine/lib/console-runner";
+// config.paths["console-runner"] = "node_modules/phantom-jasmine/lib/console-runner";
+config.shim.jasmine = {
+    exports: 'window.jasmineRequire'
+};
 config.shim['jasmine-html'] = {
     deps: ['jasmine'],
-    exports: 'jasmine'
+    exports: 'window.jasmineRequire'
 };
+config.shim.boot = {
+    deps: ['jasmine', 'jasmine-html'],
+    exports: 'window.jasmineRequire'
+};
+/*
 config.shim['console-runner'] = {
     deps: ['jasmine']
 };
+*/
 require.config(config);
 
 // Polyfill 'bind' which is not available in phantomjs < 2.0
@@ -35,13 +48,9 @@ if (!Function.prototype.bind) {
     };
 }
 
-require([
-    "jquery",
-    "mock",
-    "jasmine-html",
-    "sinon",
-    "console-runner",
+var specs = [
     //"spec/transcripts",
+    // "spec/profiling",
     "spec/utils",
     "spec/converse",
     "spec/bookmarks",
@@ -49,38 +58,31 @@ require([
     "spec/disco",
     "spec/protocol",
     "spec/presence",
+    "spec/eventemitter",
+    "spec/ping",
+    "spec/xmppstatus",
     "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($, mock, jasmine, sinon) {
-        window.sinon = sinon;
-        window.localStorage.clear();
-        window.sessionStorage.clear();
-        // 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();
+    "spec/register"
+];
+
+require(['jquery', 'mock', 'boot', 'sinon', 'wait-until-promise'],
+        function($, mock, jasmine, sinon, waitUntilPromise) {
+    window.sinon = sinon;
+    window.waitUntilPromise = waitUntilPromise['default'];
+    window.localStorage.clear();
+    window.sessionStorage.clear();
+
+    // Load the specs
+    require(specs, function () {
+            // Initialize the HTML Reporter and execute the environment (setup by `boot.js`)
+            // http://stackoverflow.com/questions/19240302/does-jasmine-2-0-really-not-work-with-require-js
+            window.onload();
+        });
     }
 );

+ 11 - 40
tests/utils.js

@@ -1,12 +1,18 @@
 (function (root, factory) {
-    define("test_utils", ['converse', 'mock'], factory);
-}(this, function (converse_api, mock) {
+    define(['converse', 'es6-promise',  'mock', 'wait-until-promise'], factory);
+}(this, function (converse_api, Promise, mock, waitUntilPromise) {
+    var _ = converse_api.env._;
     var $ = converse_api.env.jQuery;
     var $pres = converse_api.env.$pres;
     var $iq = converse_api.env.$iq;
     var Strophe = converse_api.env.Strophe;
     var utils = {};
 
+    if (typeof window.Promise === 'undefined') {
+        waitUntilPromise.setPromiseImplementation(Promise);
+    }
+    utils.waitUntil = waitUntilPromise['default'];
+
     utils.createRequest = function (iq) {
         iq = typeof iq.tree == "function" ? iq.tree() : iq;
         var req = new Strophe.Request(iq, function() {});
@@ -27,28 +33,6 @@
         return this;
     };
 
-    utils.removeAllChatBoxes = function () {
-        var i, chatbox, num_chatboxes = converse.chatboxes.models.length;
-        for (i=num_chatboxes-1; i>-1; i--) {
-            chatbox = converse.chatboxes.models[i];
-            converse.chatboxviews.get(chatbox.get('id')).close();
-            converse.chatboxviews.get(chatbox.get('id')).$el.remove();
-        }
-        converse.chatboxviews.get('controlbox').close();
-        converse.chatboxviews.get('controlbox').$el.remove();
-        return this;
-    };
-
-    utils.initConverse = function () {
-        converse._tearDown();
-        converse._initialize();
-    };
-
-    utils.initRoster = function () {
-        converse.roster.browserStorage._clear();
-        converse.initRoster();
-    };
-
     utils.openControlBox = function () {
         var $toggle = $(".toggle-controlbox");
         if (!$("#controlbox").is(':visible')) {
@@ -69,11 +53,6 @@
         return this;
     };
 
-    utils.removeControlBox = function () {
-        converse.controlboxtoggle.show();
-        $('#controlbox').remove();
-    };
-
     utils.openContactsPanel = function (converse) {
         this.openControlBox(converse);
         var cbview = converse.chatboxviews.get('controlbox');
@@ -157,14 +136,6 @@
         converse.connection.sendIQ.restore();
     };
 
-    utils.removeRosterContacts = function () {
-        var model;
-        while (converse.rosterview.model.length) {
-            model = converse.rosterview.model.pop();
-            converse.rosterview.model.remove(model);
-        }
-    };
-
     utils.clearBrowserStorage = function () {
         window.localStorage.clear();
         window.sessionStorage.clear();
@@ -184,7 +155,7 @@
          *
          * These contacts are not grouped. See below.
          */
-        var names, jid;
+        var names, jid, subscription, requesting, ask;
         if (type === 'requesting') {
             names = mock.req_names;
             subscription = 'none';
@@ -206,13 +177,13 @@
                 .createContacts(converse, 'pending');
             return this;
         } else {
-            throw "Need to specify the type of contact to create";
+            throw Error("Need to specify the type of contact to create");
         }
 
         if (typeof length === 'undefined') {
             length = names.length;
         }
-        for (i=0; i<length; i++) {
+        for (var i=0; i<length; i++) {
             jid = names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
             if (!converse.roster.get(jid)) {
                 converse.roster.create({

Some files were not shown because too many files changed in this diff