瀏覽代碼

Changes from master

JC Brand 12 年之前
父節點
當前提交
9ee92f245c
共有 6 個文件被更改,包括 191 次插入141 次删除
  1. 15 7
      CONTRIBUTING.rst
  2. 9 3
      Libraries/jasmine-1.3.1/jasmine.js
  3. 13 10
      converse.js
  4. 50 54
      index.html
  5. 103 66
      spec/MainSpec.js
  6. 1 1
      tests_main.js

+ 15 - 7
CONTRIBUTING.rst

@@ -2,9 +2,10 @@
 Contributing to Converse.js
 ===========================
 
-Contributions to Converse.js are very welcome. Please follow the usual github
-workflow. Create your own local fork of this repository, make your changes and
-then submit a pull request.
+Thanks for contributing to Converse.js_! 
+
+Please follow the usual github workflow. Create your own local fork of this repository,
+make your changes and then submit a pull request.
 
 Before submitting a pull request
 ================================
@@ -12,11 +13,18 @@ Before submitting a pull request
 Add tests for your bugfix or feature
 ------------------------------------
 
-- Please try to add a test for any bug fixed or feature added. We use Jasmine
-  for testing.
+Add a test for any bug fixed or feature added. We use Jasmine
+for testing. 
+
+Take a look at ``tests.html`` and ``spec/MainSpec.js`` to see how
+the tests are implemented.
 
 Check that the tests run
 ------------------------
 
-- Check that the Jasmine BDD tests complete sucessfully. Open test.html in your
-  browser, and the tests will run automatically.
+Check that the Jasmine BDD tests complete sucessfully. Open tests.html in your
+browser, and the tests will run automatically.
+
+You can see the current test output online, here: http://conversejs.org/tests.html
+
+.. _Converse.js: http://conversejs.org

+ 9 - 3
Libraries/jasmine-1.3.1/jasmine.js

@@ -2080,11 +2080,17 @@ jasmine.Queue.prototype.next_ = function() {
         self.index++;
 
         var now = new Date().getTime();
-        if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
-          self.env.lastUpdate = now;
+        if (self.env.updateInterval) {
+            var timeout;
+            if (now - self.env.lastUpdate > self.env.updateInterval) {
+                timeout = 0;
+            } else {
+                timeout = self.env.updateInterval;
+            }
           self.env.setTimeout(function() {
+            self.env.lastUpdate = now;
             self.next_();
-          }, 0);
+          }, timeout);
         } else {
           if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
             goAgain = true;

+ 13 - 10
converse.js

@@ -489,7 +489,7 @@
         initialize: function (){
             this.model.messages.on('add', this.showMessage, this);
             this.model.on('show', this.show, this);
-            this.model.on('destroy', function (model, response, options) { this.$el.hide('fast'); }, this);
+            this.model.on('destroy', this.hide, this);
             this.model.on('change', this.onChange, this);
 
             this.$el.appendTo(xmppchat.chatboxesview.$el);
@@ -539,6 +539,14 @@
             return this;
         },
 
+        hide: function () {
+            if (xmppchat.animate) {
+                this.$el.hide('fast');
+            } else {
+                this.$el.hide();
+            }
+        },
+
         show: function () {
             if (this.$el.is(':visible') && this.$el.css('opacity') == "1") {
                 return this.focus();
@@ -750,13 +758,8 @@
             }, this));
 
             this.model.on('show', this.show, this);
-            this.model.on('destroy', $.proxy(function (model, response, options) {
-                this.$el.hide('fast');
-            }, this));
-            this.model.on('hide', $.proxy(function (model, response, options) {
-                this.$el.hide('fast');
-            }, this));
-
+            this.model.on('destroy', this.hide, this);
+            this.model.on('hide', this.hide, this);
             if (this.model.get('visible')) {
                 this.show();
             } 
@@ -1064,7 +1067,7 @@
                 xmppchat.getVCard(
                     partner_jid, 
                     $.proxy(function (jid, fullname, image, image_type, url) {
-                        chatbox = this.create({
+                        var chatbox = this.create({
                             'id': jid,
                             'jid': jid,
                             'fullname': fullname,
@@ -1093,7 +1096,7 @@
         initialize: function () {
             // boxesviewinit
             this.views = {};
-            this.options.model.on("add", function (item) {
+            this.model.on("add", function (item) {
                 var view = this.views[item.get('id')];
                 if (!view) {
                     if (item.get('chatroom')) {

+ 50 - 54
index.html

@@ -3,8 +3,10 @@
 <head>
     <meta charset='utf-8' />
     <meta http-equiv="X-UA-Compatible" content="chrome=1" />
-    <meta name="description" content="Converse.js : Browser-based Instant Messaging with Strophe.js and Backbone.js" />
+    <meta name="description" content="Converse.js: Open Source Browser-Based Instant Messaging" />
     <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/stylesheet.css">
+    <link rel="stylesheet" type="text/css" media="screen" href="converse.css">
+    <script data-main="main" src="Libraries/require-jquery.js"></script>
     <title>Converse.js</title>
 </head>
 
@@ -26,53 +28,52 @@
 
     <!-- MAIN CONTENT -->
     <div id="main_content_wrap" class="outer">
-      <section id="main_content" class="inner">
-        <p><strong>Converse.js</strong> implements an <a href="http://xmpp.org">XMPP</a> based instant messaging client in the browser.</p>
+    <section id="main_content" class="inner">
+    <p><strong>Converse.js</strong> implements an <a href="http://xmpp.org">XMPP</a> based instant messaging client in the browser.</p>
+    <p>It is used by <a href="http://github.com/collective/collective.xmpp.chat">collective.xmpp.chat</a>, which is a <a href="http://plone.org">Plone</a> instant messaging add-on.</p>
+    <p>The ultimate goal is to enable anyone to add chat functionality to their websites, regardless of the backend.</p>
+    <p>This is currently possible, except for adding new contacts, which still makes an XHR call to the (Plone) backend to fetch user info.</p>
 
-<p>It is used by <a href="http://github.com/collective/collective.xmpp.chat">collective.xmpp.chat</a>, which is a <a href="http://plone.org">Plone</a> instant messaging add-on.</p>
+    <h2>Features</h2>
+    <ul>
+        <li>Manually or automically subscribe to other users.</li>
+        <li>Accept or decline contact requests</li>
+        <li>Chat status (online, busy, away, offline)</li>
+        <li>Custom status messages</li>
+        <li>Typing notifications</li>
+        <li>Third person messages (/me )</li>
+        <li>Multi-user chat in chatrooms</li>
+        <li>Chatroom Topics</li>
+        <li>vCard support</li>
+    </ul>
 
-<p>The ultimate goal is to enable anyone to add chat functionality to their websites, regardless of the backend.</p>
-<p>This is currently possible, except for adding new contacts, which still makes an XHR call to the (Plone) backend to fetch user info.</p>
+    <h2>Screencasts</h2>
+    <ul>
+        <li><a href="http://opkode.com/media/blog/instant-messaging-for-plone-with-javascript-and-xmpp">Screencast 1</a>:
+            Integrated into a Plone site via <strong>collective.xmpp.chat</strong>.
+        </li>
+        <li><a href="http://opkode.com/media/blog/2013/04/02/converse.js-xmpp-instant-messaging-with-javascript">Screencast 2</a>:
+            A static HTML page with <em>Converse.js</em>. Here we chat to external XMPP accounts on Jabber.org and Gmail.
+        </li>
+    </ul>
 
-<h2>Features</h2>
-<ul>
-    <li>Manually or automically subscribe to other users.</li>
-    <li>Accept or decline contact requests</li>
-    <li>Chat status (online, busy, away, offline)</li>
-    <li>Custom status messages</li>
-    <li>Typing notifications</li>
-    <li>Third person messages (/me )</li>
-    <li>Multi-user chat in chatrooms</li>
-    <li>Chatroom Topics</li>
-    <li>vCard support</li>
-</ul>
+    <h2>Dependencies</h2>
+    <p><strong>Converse.js</strong> depends on a few third party libraries, including: 
+    <ul>
+        <li><a href="http://strophe.im/strophejs">strophe.js</a></li>
+        <li><a href="http:/backbonejs.org">backbone.js</a></li>
+        <li><a href="http:/requirejs.org">require.js</a></li>
+    </ul>
+    </p>
 
-<h2>Screencasts</h2>
-
-
-<ul>
-<li><a href="http://opkode.com/media/blog/instant-messaging-for-plone-with-javascript-and-xmpp">Screencast 1</a>:
-    Integrated into a Plone site via <strong>collective.xmpp.chat</strong>.
-    
-</li>
-<li><a href="http://opkode.com/media/blog/2013/04/02/converse.js-xmpp-instant-messaging-with-javascript">Screencast 2</a>:
-    A static HTML page with <em>Converse.js</em>. Here we chat to external XMPP accounts on Jabber.org and Gmail.
-</li>
-</ul>
-
-<h2>Dependencies</h2>
-<p><strong>Converse.js</strong> depends on a few third party libraries, including: 
-<ul>
-    <li><a href="http://strophe.im/strophejs">strophe.js</a></li>
-    <li><a href="http:/backbonejs.org">backbone.js</a></li>
-    <li><a href="http:/requirejs.org">require.js</a></li>
-</ul>
-</p>
-
-<h2>Licence</h2>
-
-<p><strong>Converse.js</strong> is released under both the <a href="http://opensource.org/licenses/mit-license.php">MIT</a> and <a href="http://opensource.org/licenses/gpl-license.php">GPL</a> licenses.</p>
-      </section>
+    <h2>Licence</h2>
+    <div id="chatpanel">
+        <div id="collective-xmpp-chat-data"></div>
+        <div id="toggle-controlbox">
+            <a href="#" class="chat" id="toggle-online-users">
+                <span class="conn-feedback">Click here to chat</span> <strong style="display: none" id="online-count">(0)</strong>
+            </a>
+        </div>
     </div>
 
     <!-- FOOTER  -->
@@ -83,15 +84,10 @@
       </footer>
     </div>
 
-              <script type="text/javascript">
-            var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
-            document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
-          </script>
-          <script type="text/javascript">
-            try {
-              var pageTracker = _gat._getTracker("UA-2128260-8");
-            pageTracker._trackPageview();
-            } catch(err) {}
-          </script>
+    <script type="text/javascript">
+        var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+        document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+    </script>
+    <script type="text/javascript">try { var pageTracker = _gat._getTracker("UA-2128260-8"); pageTracker._trackPageview(); } catch(err) {}</script>
   </body>
 </html>

+ 103 - 66
spec/MainSpec.js

@@ -10,22 +10,15 @@
     return describe("Converse.js", $.proxy(function() {
         // Names from http://www.fakenamegenerator.com/
         var req_names = [
-            'Louw Spekman', 'Mohamad Stet', 'Dominik Beyer', 'Dirk Eichel', 'Marco Duerr', 'Ute Schiffer',
-            'Billie Westerhuis', 'Sarah Kuester', 'Sabrina Loewe', 'Laura Duerr', 'Mathias Meyer',
-            'Tijm Keller', 'Lea Gerste', 'Martin Pfeffer', 'Ulrike Abt', 'Zoubida van Rooij',
-            'Maylin Hettema', 'Ruwan Bechan', 'Marco Beich', 'Karin Busch', 'Mathias Müller'
+            'Louw Spekman', 'Mohamad Stet', 'Dominik Beyer'
         ];
         var pend_names = [
-            'Suleyman van Beusichem', 'Nicole Diederich', 'Nanja van Yperen', 'Delany Bloemendaal',
-            'Jannah Hofmeester', 'Christine Trommler', 'Martin Bumgarner', 'Emil Baeten', 'Farshad Brasser',
-            'Gabriele Fisher', 'Sofiane Schopman', 'Sky Wismans', 'Jeffery Stoelwinder', 'Ganesh Waaijenberg',
-            'Dani Boldewijn', 'Katrin Propst', 'Martina Kaiser', 'Philipp Kappel', 'Meeke Grootendorst'
+            'Suleyman van Beusichem', 'Nicole Diederich', 'Nanja van Yperen'
         ];
         var cur_names = [
             'Max Frankfurter', 'Candice van der Knijff', 'Irini Vlastuin', 'Rinse Sommer', 'Annegreet Gomez',
             'Robin Schook', 'Marcel Eberhardt', 'Simone Brauer', 'Asmaa Haakman', 'Felix Amsel',
-            'Lena Grunewald', 'Laura Grunewald', 'Mandy Seiler', 'Sven Bosch', 'Nuriye Cuypers', 'Ben Zomer',
-            'Leah Weiss', 'Francesca Disseldorp', 'Sven Bumgarner', 'Benjamin Zweig'
+            'Lena Grunewald', 'Laura Grunewald', 'Mandy Seiler', 'Sven Bosch', 'Nuriye Cuypers'
         ];
         var num_contacts = req_names.length + pend_names.length + cur_names.length;
         this.bare_jid = 'dummy@localhost';
@@ -44,6 +37,16 @@
                 'get': function () {},
                 'subscribe': function () {},
                 'registerCallback': function () {}
+            },
+            'vcard': { 
+                'get': function (callback, jid) {
+                    var name = jid.split('@')[0].replace('.', ' ').split(' ');
+                    var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1);
+                    var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1);
+                    var fullname = firstname+' '+lastname;
+                    var vcard = $iq().c('vCard').c('FN').t(fullname);
+                    callback(vcard.tree());
+                } 
             }
         };
 
@@ -54,24 +57,13 @@
             hex_sha1('converse.chatboxes-'+this.bare_jid));
         window.localStorage.removeItem(
             hex_sha1('converse.xmppstatus-'+this.bare_jid));
+        window.localStorage.removeItem(
+            hex_sha1('converse.messages'+cur_names[0].replace(' ','.').toLowerCase() + '@localhost'));
 
         this.prebind = true;
         this.onConnected(mock_connection);
         this.animate = false; // don't use animations
 
-        // The timeout is used to slow down the tests so that one can see
-        // visually what is happening in the page.
-        var timeout = 0;
-        var sleep = function (delay) {
-            // Yes this is blocking and stupid, but these are tests and this is
-            // the easiest way to delay execution without having to use
-            // callbacks.
-            var start = new Date().getTime();
-            while (new Date().getTime() < start + delay) {
-                continue;
-            }
-        };
-
         describe("The Contacts Roster", $.proxy(function () {
             it("is not shown by default", $.proxy(function () {
                 expect(this.rosterview.$el.is(':visible')).toEqual(false);
@@ -112,7 +104,6 @@
                         t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').text();
                         expect(t).toEqual(pend_names.slice(0,i+1).sort().join(''));
                     }
-                    sleep(timeout);
                 }, xmppchat));
 
                 it("will have their own heading once they have been added", $.proxy(function () {
@@ -141,7 +132,6 @@
                         t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
                         expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
                     }
-                    sleep(timeout);
                 }, xmppchat));
 
                 it("will have their own heading once they have been added", $.proxy(function () {
@@ -151,7 +141,7 @@
                 it("can change their status to online and be sorted alphabetically", $.proxy(function () {
                     var item, view, jid, t;
                     spyOn(this.rosterview, 'render').andCallThrough();
-                    for (i=0; i<5; i++) {
+                    for (i=0; i<3; i++) {
                         jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
                         view = this.rosterview.rosteritemviews[jid];
                         spyOn(view, 'render').andCallThrough();
@@ -163,14 +153,13 @@
                         // Check that they are sorted alphabetically
                         t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
                         expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
-                        sleep(timeout);
                     }
                 }, xmppchat));
 
                 it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
                     var item, view, jid, t;
                     spyOn(this.rosterview, 'render').andCallThrough();
-                    for (i=5; i<10; i++) {
+                    for (i=3; i<6; i++) {
                         jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
                         view = this.rosterview.rosteritemviews[jid];
                         spyOn(view, 'render').andCallThrough();
@@ -180,15 +169,14 @@
                         expect(this.rosterview.render).toHaveBeenCalled();
                         // Check that they are sorted alphabetically
                         t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
-                        expect(t).toEqual(cur_names.slice(5,i+1).sort().join(''));
-                        sleep(timeout);
+                        expect(t).toEqual(cur_names.slice(3,i+1).sort().join(''));
                     }
                 }, xmppchat));
 
                 it("can change their status to away and be sorted alphabetically", $.proxy(function () {
                     var item, view, jid, t;
                     spyOn(this.rosterview, 'render').andCallThrough();
-                    for (i=10; i<15; i++) {
+                    for (i=6; i<9; i++) {
                         jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
                         view = this.rosterview.rosteritemviews[jid];
                         spyOn(view, 'render').andCallThrough();
@@ -199,15 +187,14 @@
 
                         // Check that they are sorted alphabetically
                         t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
-                        expect(t).toEqual(cur_names.slice(10,i+1).sort().join(''));
-                        sleep(timeout);
+                        expect(t).toEqual(cur_names.slice(6,i+1).sort().join(''));
                     }
                 }, xmppchat));
 
                 it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
                     var item, view, jid, t;
                     spyOn(this.rosterview, 'render').andCallThrough();
-                    for (i=15; i<20; i++) {
+                    for (i=9; i<12; i++) {
                         jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
                         view = this.rosterview.rosteritemviews[jid];
                         spyOn(view, 'render').andCallThrough();
@@ -218,8 +205,7 @@
 
                         // Check that they are sorted alphabetically
                         t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
-                        expect(t).toEqual(cur_names.slice(15, i+1).sort().join(''));
-                        sleep(timeout);
+                        expect(t).toEqual(cur_names.slice(9, i+1).sort().join(''));
                     }
                 }, xmppchat));
 
@@ -227,23 +213,23 @@
                     var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
                     var i;
                     // The first five contacts are online.
-                    for (i=0; i<5; i++) {
+                    for (i=0; i<3; i++) {
                         expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('online');
                     }
                     // The next five are busy
-                    for (i=5; i<10; i++) {
+                    for (i=3; i<6; i++) {
                         expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('dnd');
                     }
                     // The next five are away
-                    for (i=10; i<15; i++) {
+                    for (i=6; i<9; i++) {
                         expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('away');
                     }
                     // The next five are unavailable
-                    for (i=15; i<20; i++) {
+                    for (i=9; i<12; i++) {
                         expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('unavailable');
                     }
                     // The next 20 are offline
-                    for (i=20; i<cur_names.length; i++) {
+                    for (i=12; i<cur_names.length; i++) {
                         expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
                     }
                 }, xmppchat));
@@ -278,7 +264,6 @@
                         // be opened.
                         expect(this.showControlBox).toHaveBeenCalled();
                     }
-                    sleep(timeout);
                 }, xmppchat));
 
                 it("will have their own heading once they have been added", $.proxy(function () {
@@ -298,7 +283,6 @@
                     accept_button.click();
                     expect(view.acceptRequest).toHaveBeenCalled();
                     expect(this.connection.roster.authorize).toHaveBeenCalled();
-                    sleep(timeout);
                 }, xmppchat));
 
                 it("can have their requests denied by the user", $.proxy(function () {
@@ -315,7 +299,6 @@
                     expect(this.connection.roster.unauthorize).toHaveBeenCalled();
                     // There should now be one less contact
                     expect(this.roster.length).toEqual(num_contacts-1); 
-                    sleep(timeout);
                 }, xmppchat));
             }, xmppchat));
 
@@ -366,9 +349,9 @@
             }, xmppchat));
         }, xmppchat));
 
-        describe("Chatboxes", $.proxy(function () {
+        describe("A Chatbox", $.proxy(function () {
 
-            it("are created when you click on a roster item", $.proxy(function () {
+            it("is created when you click on a roster item", $.proxy(function () {
                 var i, $el, click, jid, view;
                 // showControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
@@ -384,52 +367,106 @@
                     $el.click();
                     expect(view.openChat).toHaveBeenCalled();
                     expect(this.chatboxes.length).toEqual(i+2);
-                    sleep(timeout);
                 }
             }, xmppchat));
 
             it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
-                var old_chatboxes = this.chatboxes;
-                expect(this.chatboxes.length).toEqual(6);
-                this.chatboxes = new this.ChatBoxes();
-                expect(this.chatboxes.length).toEqual(0);
-
-                this.chatboxes.onConnected();
-                expect(this.chatboxes.length).toEqual(6);
-
+                // We instantiate a new ChatBoxes collection, which by default
+                // will be empty.
+                this.newchatboxes = new this.ChatBoxes();
+                expect(this.newchatboxes.length).toEqual(0);
+                // The chatboxes will then be fetched from localStorage inside the
+                // onConnected method
+                this.newchatboxes.onConnected();
+                expect(this.newchatboxes.length).toEqual(6);
                 // Check that the roster items retrieved from localStorage
                 // have the same attributes values as the original ones.
                 attrs = ['id', 'box_id', 'visible'];
                 for (i=0; i<attrs.length; i++) {
-                    new_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
-                    old_attrs = _.pluck(_.pluck(old_chatboxes.models, 'attributes'), attrs[i]);
+                    new_attrs = _.pluck(_.pluck(this.newchatboxes.models, 'attributes'), attrs[i]);
+                    old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
                     expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
                 }
                 this.rosterview.render();
             }, xmppchat));
 
             it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
-                var chatbox, view, $el;
-                for (i=0; i<this.chatboxes.length; i++) {
-                    chatbox = this.chatboxes.models[i];
+                var chatbox, view, $el,
+                    num_open_chats = this.chatboxes.length;
+                for (i=0; i<num_open_chats; i++) {
+                    chatbox = this.chatboxes.models[0];
                     view = this.chatboxesview.views[chatbox.get('id')];
                     spyOn(view, 'closeChat').andCallThrough();
                     view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     view.$el.find('.close-chatbox-button').click();
                     expect(view.closeChat).toHaveBeenCalled();
-                    sleep(timeout);
                 }
             }, xmppchat));
 
             it("will be removed from localStorage when closed", $.proxy(function () {
-                var old_chatboxes = this.chatboxes;
-                expect(this.chatboxes.length).toEqual(6);
-                this.chatboxes = new this.ChatBoxes();
-                expect(this.chatboxes.length).toEqual(0);
-
+                this.newchatboxes = new this.ChatBoxes();
+                expect(this.newchatboxes.length).toEqual(0);
+                // onConnected will fetch chatboxes in localStorage, but
+                // because there aren't any open chatboxes, there won't be any
+                // in localStorage either.
                 this.chatboxes.onConnected();
                 expect(this.chatboxes.length).toEqual(0);
             }, xmppchat));
+
+            describe("A Chat Message", $.proxy(function () {
+                it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
+                    var message = 'This is a received message';
+                    var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
+                        msg = $msg({
+                            from: sender_jid,
+                            to: this.bare_jid, 
+                            type: 'chat', 
+                            id: (new Date()).getTime()
+                        }).c('body').t(message).up()
+                          .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
+
+                    spyOn(this, 'getVCard').andCallThrough();
+
+                    // We don't already have an open chatbox for this user
+                    expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
+
+                    runs($.proxy(function () {
+                        // messageReceived is a handler for received XMPP
+                        // messages
+                        this.chatboxes.messageReceived(msg);
+                    }, xmppchat));
+                    waits(500);
+                    runs($.proxy(function () {
+                        // Since we didn't already have an open chatbox, one
+                        // will asynchronously created inside a callback to
+                        // getVCard
+                        expect(this.getVCard).toHaveBeenCalled();
+                        // Check that the chatbox and its view now exist
+                        var chatbox = this.chatboxes.get(sender_jid);
+                        var chatboxview = this.chatboxesview.views[sender_jid];
+                        expect(chatbox).toBeDefined();
+                        expect(chatboxview).toBeDefined();
+                        // Check that the message was received and check the
+                        // message parameters
+                        expect(chatbox.messages.length).toEqual(1);
+                        var msg_obj = chatbox.messages.models[0];
+                        expect(msg_obj.get('message')).toEqual(message);
+                        // XXX: This is stupid, fullname is actually only the
+                        // users first name
+                        expect(msg_obj.get('fullname')).toEqual(cur_names[0].split(' ')[0]);
+                        expect(msg_obj.get('sender')).toEqual('them');
+                        expect(msg_obj.get('delayed')).toEqual(false);
+                        // Now check that the message appears inside the
+                        // chatbox in the DOM
+                        var txt = chatboxview.$el.find('.chat-content').find('.chat-message').find('.chat-message-content').text();
+                        expect(txt).toEqual(message);
+                    }, xmppchat));
+                }, xmppchat));
+
+                it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
+                    // TODO
+                }, xmppchat));
+            }, xmppchat));
         }, xmppchat));
 
     }, xmppchat));

+ 1 - 1
tests_main.js

@@ -2,7 +2,7 @@ require(["jquery", "spec/MainSpec"], function($) {
 
     $(function($) {
         var jasmineEnv = jasmine.getEnv();
-        jasmineEnv.updateInterval = 1000;
+        jasmineEnv.updateInterval = 500;
 
         var htmlReporter = new jasmine.HtmlReporter();