Sfoglia il codice sorgente

Major refactor.

The RosterView view is now an overview of RosterGroup objects.

RosterGroup objects each have their own collection of contacts which fall under that group.
Additionally, the RosterView has a collection of all contacts.

The comparator of RosterContacts is now used to correctly position roster
contacts and we therefore no longer need to explicitly sort them afterwards.

updates #83
updates #151
JC Brand 11 anni fa
parent
commit
219d5c8a30
5 ha cambiato i file con 219 aggiunte e 272 eliminazioni
  1. 147 186
      converse.js
  2. 2 2
      spec/chatbox.js
  3. 65 79
      spec/controlbox.js
  4. 1 1
      spec/minchats.js
  5. 4 4
      tests/utils.js

+ 147 - 186
converse.js

@@ -338,7 +338,7 @@
                         img_type = $vcard.find('TYPE').text(),
                         url = $vcard.find('URL').text();
                     if (jid) {
-                        var contact = converse.roster.get(jid);
+                        var contact = converse.rosterview.roster.get(jid);
                         if (contact) {
                             fullname = _.isEmpty(fullname)? contact.get('fullname') || jid: fullname;
                             contact.save({
@@ -357,7 +357,7 @@
                 jid,
                 function (iq) {
                     // Error callback
-                    var contact = converse.roster.get(jid);
+                    var contact = converse.rosterview.roster.get(jid);
                     if (contact) {
                         contact.save({
                             'vcard_updated': moment().format()
@@ -479,40 +479,6 @@
             this.xmppstatus.fetch({success: callback, error: callback});
         };
 
-        this.registerRosterHandler = function () {
-            // Register handlers that depend on the roster
-            this.connection.roster.registerCallback(
-                $.proxy(this.roster.rosterHandler, this.roster),
-                null, 'presence', null);
-        };
-
-        this.registerRosterXHandler = function () {
-            this.connection.addHandler(
-                $.proxy(this.roster.subscribeToSuggestedItems, this.roster),
-                'http://jabber.org/protocol/rosterx', 'message', null);
-        };
-
-        this.registerPresenceHandler = function () {
-            this.connection.addHandler(
-                $.proxy(function (presence) {
-                    this.presenceHandler(presence);
-                    return true;
-                }, this.roster), null, 'presence', null);
-        };
-
-        this.initRoster = function () {
-            // Set up the roster
-            this.roster = new this.RosterContacts();
-            this.roster.browserStorage = new Backbone.BrowserStorage[converse.storage](
-                b64_sha1('converse.contacts-'+converse.bare_jid));
-            this.registerRosterHandler();
-            this.registerRosterXHandler();
-            this.registerPresenceHandler();
-            // Now create the view which will fetch roster items from
-            // browserStorage
-            this.rosterview = new this.RosterView({'model':this.roster});
-        };
-
         this.registerGlobalEventHandlers = function () {
             $(document).click(function() {
                 if ($('.toggle-otr ul').is(':visible')) {
@@ -599,7 +565,7 @@
             this.features = new this.Features();
             this.enableCarbons();
             this.initStatus($.proxy(function () {
-                this.initRoster();
+                this.rosterview = new this.RosterView({model: new this.RosterGroups()});
                 this.chatboxes.onConnected();
                 this.connection.roster.get(function () {});
                 this.giveFeedback(__('Online Contacts'));
@@ -1367,7 +1333,7 @@
 
             updateVCard: function () {
                 var jid = this.model.get('jid'),
-                    contact = converse.roster.get(jid);
+                    contact = converse.rosterview.roster.get(jid);
                 if ((contact) && (!contact.get('vcard_updated'))) {
                     converse.getVCard(
                         jid,
@@ -2492,7 +2458,7 @@
                     resource = Strophe.getResourceFromJid(message_from);
                 }
                 chatbox = this.get(buddy_jid);
-                roster_item = converse.roster.get(buddy_jid);
+                roster_item = converse.rosterview.roster.get(buddy_jid);
 
                 if (roster_item === undefined) {
                     // The buddy was likely removed
@@ -2513,7 +2479,7 @@
                     });
                 }
                 chatbox.receiveMessage($message);
-                converse.roster.addResource(buddy_jid, resource);
+                converse.rosterview.roster.addResource(buddy_jid, resource);
                 converse.emit('message', message);
                 return true;
             }
@@ -2878,6 +2844,7 @@
                     converse.connection.roster.remove(bare_jid, $.proxy(function (iq) {
                         converse.connection.roster.unauthorize(bare_jid);
                         converse.rosterview.model.remove(bare_jid);
+                        this.model.destroy();
                         this.remove();
                     }, this));
                 }
@@ -2966,30 +2933,16 @@
 
         this.RosterContacts = Backbone.Collection.extend({
             model: converse.RosterContact,
-            comparator : function (contact) {
-                var chat_status = contact.get('chat_status'),
-                    rank = 4;
-                switch(chat_status) {
-                    case 'offline':
-                        rank = 0;
-                        break;
-                    case 'unavailable':
-                        rank = 1;
-                        break;
-                    case 'xa':
-                        rank = 2;
-                        break;
-                    case 'away':
-                        rank = 3;
-                        break;
-                    case 'dnd':
-                        rank = 4;
-                        break;
-                    case 'online':
-                        rank = 5;
-                        break;
-                }
-                return rank;
+            comparator: function (contact1, contact2) {
+                var name1 = contact1.get('fullname').toLowerCase();
+                var status1 = contact1.get('chat_status') || 'offline';
+                var name2 = contact2.get('fullname').toLowerCase();
+                var status2 = contact2.get('chat_status') || 'offline';
+                if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
+                    return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
+                } else  {
+                    return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
+                };
             },
 
             subscribeToSuggestedItems: function (msg) {
@@ -3267,14 +3220,29 @@
             initialize: function (attributes, options) {
                 this.set(_.extend({
                     description: DESC_GROUP_TOGGLE,
-                    toggle_state: OPENED
+                    state: OPENED
                 }, attributes))
+
+                // Collection of contacts belonging to this group.
+                this.contacts = new converse.RosterContacts();
             }
         });
 
-        this.RosterGroupView = Backbone.View.extend({
+        this.RosterGroupView = Backbone.Overview.extend({
             events: {
-                "click a.group-toggle": "toggleGroup"
+                "click a.group-toggle": "toggle"
+            },
+
+            initialize: function () {
+                this.model.contacts.on("add", this.addContact, this);
+                this.model.contacts.on("change:chat_status", function (contact) {
+                    // This might be optimized by instead of first sorting, finding the correct position in positionContact
+                    this.model.contacts.sort();
+                    this.positionContact(contact);
+                }, this);
+                this.model.contacts.on("destroy", this.onRemove, this);
+                this.model.contacts.on("remove", this.onRemove, this);
+                converse.rosterview.roster.on('change:groups', this.onContactGroupChange, this);
             },
 
             render: function () {
@@ -3282,7 +3250,7 @@
                     $(converse.templates.group_header({
                         label_group: this.model.get('name'),
                         desc_group_toggle: this.model.get('description'),
-                        toggle_state: this.model.get('state') 
+                        toggle_state: this.model.get('state')
                     }))
                 );
                 return this;
@@ -3293,7 +3261,7 @@
                     var $el = $(converse.templates.group_header({
                         label_group: this.model.get('name'),
                         desc_group_toggle: this.model.get('description'),
-                        toggle_state: this.model.get('state') 
+                        toggle_state: this.model.get('state')
                     }));
                     this.setElement($el, false);
                 } else {
@@ -3301,15 +3269,54 @@
                 }
             },
 
-            toggleGroup: function (ev) {
+            positionContact: function (contact) {
+                /* Place the contact's DOM element in the correct alphabetical
+                 * position amongst the other contacts in this group.
+                 */
+                var view = this.get(contact.get('id'));
+                var index = this.model.contacts.indexOf(contact);
+                if (index == 0) {
+                    this.$el.after(view.render().el);
+                } else if (index == (this.model.contacts.length-1)) {
+                    this.$el.nextUntil('dt').last().after(view.$el);
+                } else {
+                    this.$el.nextUntil('dt').eq(index).before(view.$el);
+                }
+                return view;
+            },
+
+            toggle: function (ev) {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
                 var $el = $(ev.target);
-                $el.parent().nextUntil('dt').slideToggle();
+                this.$el.nextUntil('dt').slideToggle();
                 if ($el.hasClass("icon-opened")) {
                     $el.removeClass("icon-opened").addClass("icon-closed");
                 } else {
                     $el.removeClass("icon-closed").addClass("icon-opened");
                 }
+            },
+
+            addContact: function (contact) {
+                this.add(contact.get('id'), new converse.RosterContactView({model: contact}));
+                this.positionContact(contact);
+                this.$el.show();
+            },
+
+            onContactGroupChange: function (contact) {
+                var in_this_group = _.contains(contact.get('groups'), this.model.get('name'));
+                var cid = contact.get('id');
+                var in_this_overview = !this.get(cid);
+                if (in_this_group && !in_this_overview) {
+                    this.remove(cid); // Contact has been added to this group
+                } else if (!in_this_group && in_this_overview) {
+                    this.addContact(contact); // Contact has been removed from this group
+                }
+            },
+
+            onRemove: function (contact) {
+                if (this.model.contacts.length === 0) {
+                    this.$el.hide();
+                }
             }
         });
 
@@ -3344,54 +3351,80 @@
         this.GroupViews = Backbone.Overview.extend({
 
             initialize: function () {
-                this.model.on("add", this.onAdd, this);
+                this.model.on("add", this.onGroupAdd, this);
             },
 
             onGroupAdd: function (group) {
                 this.add(group.get('name'), group);
-            }
+            },
 
         });
 
-        this.RosterView = Backbone.View.extend({
+        this.RosterView = Backbone.Overview.extend({
             tagName: 'dl',
             id: 'converse-roster',
 
             initialize: function () {
-                /* If initialize ever gets called again, event listeners will
-                 * be registered twice. So we turn them off first.
-                 * Currently only an issue in tests.
-                 */
-                this.model.off(); 
-                this.model.on("add", this.onAdd, this);
-                this.model.on('change', this.onChange, this); 
-                this.model.on("remove", this.update, this);
-                this.model.on("destroy", this.update, this);
+                this.roster = new converse.RosterContacts();
+                this.roster.browserStorage = new Backbone.BrowserStorage[converse.storage](
+                    b64_sha1('converse.contacts-'+converse.bare_jid));
+                this.registerRosterHandler();
+                this.registerRosterXHandler();
+                this.registerPresenceHandler();
+
+                this.roster.on("add", this.onAdd, this);
+                this.roster.on('change', this.onChange, this);
+                this.roster.on("remove", this.update, this);
+                this.roster.on("destroy", this.update, this);
                 this.model.on("reset", this.reset, this);
                 this.render();
-
-                this.groupviews = new converse.GroupViews({
-                    model: new converse.RosterGroups()
-                });
-                this.groupviews.model.fetch({add: true});
                 this.model.fetch({add: true});
+                this.roster.fetch({add: true});
             },
 
             render: function () {
-                this.$fragment = $('<span>');
+                this.$el.empty();
                 return this;
             },
 
             update: function (item) {
-                this.updateCount().toggleHeaders();
+                // XXX: Is this still being used/valid?
+                var $count = $('#online-count');
+                $count.text('('+this.roster.getNumOnlineContacts()+')');
+                if (!$count.is(':visible')) {
+                    $count.show();
+                }
+                return this;
             },
 
             reset: function () {
-                this.$el.find('span').empty();
+                this.roster.reset();
+                this.removeAll();
                 this.render().update();
                 return this;
             },
 
+            registerRosterHandler: function () {
+                // Register handlers that depend on the roster
+                converse.connection.roster.registerCallback(
+                    $.proxy(this.roster.rosterHandler, this.roster),
+                    null, 'presence', null);
+            },
+
+            registerRosterXHandler: function () {
+                converse.connection.addHandler(
+                    $.proxy(this.roster.subscribeToSuggestedItems, this.roster),
+                    'http://jabber.org/protocol/rosterx', 'message', null);
+            },
+
+            registerPresenceHandler: function () {
+                converse.connection.addHandler(
+                    $.proxy(function (presence) {
+                        this.roster.presenceHandler(presence);
+                        return true;
+                    }, this), null, 'presence', null);
+            },
+
             insertRosterFragment: function () {
                 if (this.$fragment) {
                     this.$el.html(this.$fragment.contents())
@@ -3403,7 +3436,7 @@
             onAdd: function (item) {
                 this.addRosterContact(item);
                 if (item.get('is_last')) {
-                    this.toggleHeaders().sortRoster().insertRosterFragment().updateCount();
+                    this.update();
                 }
                 if (!item.get('vcard_updated')) {
                     // This will update the vcard, which triggers a change
@@ -3416,10 +3449,7 @@
                 if ((_.size(item.changed) === 1) && _.contains(_.keys(item.changed), 'sorted')) {
                     return;
                 }
-                this.updateChatBox(item).toggleHeaders().updateCount();
-                if (item.changed.chat_status) { // A changed chat status implies a new sort order
-                    this.sortRoster();
-                }
+                this.updateChatBox(item).update();
             },
 
             updateChatBox: function (item, changed) {
@@ -3439,7 +3469,7 @@
             },
 
             getRoster: function () {
-                // TODO: see is _ensureElement can be used.
+                // TODO: see if _ensureElement can be used.
                 // Return the document fragment if it exists.
                 var $el;
                 if (this.$fragment) {
@@ -3453,11 +3483,10 @@
                 /* Place the group's DOM element in the correct alphabetical
                  * position amongst the other groups in the roster.
                  */
-                var groups = this.groupviews.model;
-                var index = groups.indexOf(view.model);
+                var index = this.model.indexOf(view.model);
                 if (index == 0) {
                     this.getRoster().prepend(view.$el);
-                } else if (index == (groups.length-1)) {
+                } else if (index == (this.model.length-1)) {
                     this.getRoster().find('.roster-group').last().siblings('dd').last().after(view.$el);
                 } else {
                     $(this.getRoster().find('.roster-group').eq(index)).before(view.$el);
@@ -3466,32 +3495,38 @@
             },
 
             getGroup: function (name) {
-                /* Returns the group view for a group specified by name.
+                /* Returns the group view as specified by name.
                  * Creates the view if it doesn't exist.
                  */
-                var view =  this.groupviews.get(name);
+                var view =  this.get(name);
                 if (view) {
                     return view;
                 }
                 view = new converse.RosterGroupView({
-                    model: this.groupviews.model.create({name: name})
+                    model: this.model.create({name: name, id: b64_sha1(name)})
                 });
-                this.groupviews.add(name, view);
+                this.add(name, view);
                 return this.positionGroup(view)
             },
 
+            addContactToGroup: function (contact, name) {
+                var group = this.getGroup(name);
+                group.model.contacts.add(contact);
+            },
+
             addCurrentContact: function (item) {
                 var groups;
                 if (converse.roster_groups) {
-                    groups = item.get('groups') || [HEADER_UNGROUPED];
+                    groups = item.get('groups');
+                    if (groups.length === 0) {
+                        groups = [HEADER_UNGROUPED];
+                    }
                 } else {
                     groups = [HEADER_CURRENT_CONTACTS];
                 }
-                _.each(groups, $.proxy(function (group) {
-                    // We need a separate view per group
-                    this.getGroup(group).$el
-                        .after((new converse.RosterContactView({model: item})).render().el);
-                },this));
+                _.each(groups, $.proxy(function (name) {
+                    this.addContactToGroup(item, name);
+                }, this));
             },
 
             addRosterContact: function (item) {
@@ -3501,86 +3536,12 @@
                 } else {
                     view = (new converse.RosterContactView({model: item})).render();
                     if ((item.get('ask') === 'subscribe') || (item.get('subscription') === 'from')) {
-                        this.getGroup(HEADER_PENDING_CONTACTS).$el.after(view.el);
+                        this.addContactToGroup(item, HEADER_PENDING_CONTACTS)
                     } else if (item.get('requesting') === true) {
-                        this.getGroup(HEADER_REQUESTING_CONTACTS).$el.after(view.el);
+                        this.addContactToGroup(item, HEADER_REQUESTING_CONTACTS)
                     }
                 }
                 return this;
-            },
-
-            updateCount: function () {
-                // XXX: Is this still being used/valid?
-                var $count = $('#online-count');
-                $count.text('('+this.model.getNumOnlineContacts()+')');
-                if (!$count.is(':visible')) {
-                    $count.show();
-                }
-                return this;
-            },
-
-            toggleHeaders: function () {
-                var $el = this.getRoster();
-                var $groups = $el.find('.roster-group');
-                if (_.contains(this.model.pluck('ask'), 'subscribe')) {
-                    $el.find('#pending-xmpp-contacts').show();
-                } else {
-                    $el.find('#pending-xmpp-contacts').hide();
-                }
-
-                if (_.contains(this.model.pluck('requesting'), true)) {
-                    $el.find('#xmpp-contact-requests').show();
-                } else {
-                    $el.find('#xmpp-contact-requests').hide();
-                }
-
-                // Hide the group headers if there are no contacts under them
-                var show_or_hide = function ($groups) {
-                    if ($groups.nextUntil('dt').length) {
-                        if (!$groups.is(':visible')) {
-                            $groups.show();
-                        }
-                    }
-                    else if ($groups.is(':visible')) { $groups.hide(); }
-                };
-                if ($groups.length > 1) {
-                    $groups.each(function (idx, group) {
-                        show_or_hide($(group));
-                    });
-                } else {
-                    show_or_hide($groups);
-                }
-                return this;
-            },
-
-            sortFunction: function (a, b) {
-                var a_status = a.s[0],
-                    a_name =a.s[1],
-                    b_status = b.s[0],
-                    b_name =b.s[1];
-                if (STATUS_WEIGHTS[a_status] === STATUS_WEIGHTS[b_status]) {
-                    return a_name < b_name ? -1 : (a_name > b_name ? 1 : 0);
-                } else  {
-                    return STATUS_WEIGHTS[a_status] < STATUS_WEIGHTS[b_status] ? -1 : 1;
-                }
-            },
-
-            sortRoster: function (chat_status) {
-                /* XXX See if the jquery detach method can be somehow used to avoid DOM reflows.
-                 */
-                var $el = this.getRoster();
-                $el.find('.roster-group').each($.proxy(function (idx, group) {
-                    var $group = $(group);
-                    var $contacts = $group.nextUntil('dt', 'dd.current-xmpp-contact');
-                    $group.after($contacts.tsort({sortFunction: this.sortFunction, data: 'status'}, 'a'));
-                },this));
-                // Also sort pending and requesting contacts
-                var crit = {order:'asc'},
-                    $contact_requests = $el.find('#xmpp-contact-requests'),
-                    $pending_contacts = $el.find('#pending-xmpp-contacts');
-                $pending_contacts.after($pending_contacts.siblings('dd.pending-xmpp-contact').tsort(crit));
-                $contact_requests.after($contact_requests.siblings('dd.requesting-xmpp-contact').tsort(crit));
-                return this;
             }
         });
 

+ 2 - 2
spec/chatbox.js

@@ -13,7 +13,7 @@
                 runs(function () {
                     utils.closeAllChatBoxes();
                     utils.removeControlBox();
-                    converse.roster.browserStorage._clear();
+                    utils.clearBrowserStorage();
                     utils.initConverse();
                     utils.createContacts();
                     utils.openControlBox();
@@ -682,7 +682,7 @@
             beforeEach(function () {
                 utils.closeAllChatBoxes();
                 utils.removeControlBox();
-                converse.roster.browserStorage._clear();
+                converse.rosterview.roster.browserStorage._clear();
                 utils.initConverse();
                 utils.createContacts();
                 utils.openControlBox();

+ 65 - 79
spec/controlbox.js

@@ -12,7 +12,6 @@
         var $toggle = $header.find('a.group-toggle');
         expect($header.css('display')).toEqual('block');
         expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
-        this.rosterview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
         expect($toggle.hasClass('icon-closed')).toBeFalsy();
         expect($toggle.hasClass('icon-opened')).toBeTruthy();
         $toggle.click();
@@ -126,18 +125,25 @@
     describe("The Contacts Roster", $.proxy(function (mock, utils) {
 
         describe("Roster Groups", $.proxy(function () {
+
+            beforeEach(function () {
+                converse.roster_groups = true;
+            });
+
+            afterEach(function () {
+                converse.roster_groups = false;
+            });
+
             function _clearContacts () {
                 utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
-                converse.rosterview.initialize(); // Register new listeners and document fragment
             };
 
             it("can be used to organize existing contacts", $.proxy(function () {
                 _clearContacts();
                 var i=0, j=0, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
-                converse.roster_groups = true;
+                spyOn(this.rosterview, 'update').andCallThrough();
                 converse.rosterview.render();
 
                 utils.createContacts('pending');
@@ -152,7 +158,7 @@
                 _.each(_.keys(groups), $.proxy(function (name) {
                     j = i;
                     for (i=j; i<j+groups[name]; i++) {
-                        this.roster.create({
+                        this.rosterview.roster.create({
                             jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
                             subscription: 'both',
                             ask: null,
@@ -183,17 +189,14 @@
             }, converse));
 
             it("can share contacts among them (values aren't distinct)", $.proxy(function () {
-                // TODO: this test is not finished yet and the thing that it's
-                // testing not yet implemented.
                 _clearContacts();
                 var i=0, j=0, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
-                converse.roster_groups = true;
+                spyOn(this.rosterview, 'update').andCallThrough();
                 converse.rosterview.render();
                 var groups = ['colleagues', 'friends'];
                 for (i=0; i<mock.cur_names.length; i++) {
-                    this.roster.create({
+                    this.rosterview.roster.create({
                         jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'both',
                         ask: null,
@@ -217,7 +220,6 @@
             function _clearContacts () {
                 utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
-                converse.rosterview.initialize(); // Register new listeners and document fragment
             };
 
             function _addContacts () {
@@ -228,15 +230,15 @@
 
             it("can be collapsed under their own header", $.proxy(function () {
                 _addContacts();
-                checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt#pending-xmpp-contacts')]);
+                checkHeaderToggling.apply(this, [this.rosterview.get('Pending contacts').$el]);
             }, converse));
 
             it("can be added to the roster", $.proxy(function () {
                 _clearContacts();
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 runs($.proxy(function () {
-                    this.roster.create({
+                    this.rosterview.roster.create({
                         jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'none',
                         ask: 'subscribe',
@@ -247,7 +249,7 @@
                 waits(300);
                 runs($.proxy(function () {
                     expect(this.rosterview.$el.is(':visible')).toEqual(true);
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    expect(this.rosterview.update).toHaveBeenCalled();
                 }, converse));
             }, converse));
 
@@ -275,18 +277,18 @@
                 var name = mock.pend_names[0];
                 _clearContacts();
                 spyOn(window, 'confirm').andReturn(true);
-                this.roster.create({
+                this.rosterview.roster.create({
                     jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
                     subscription: 'none',
                     ask: 'subscribe',
                     fullname: name,
                     is_last: true
                 });
-                expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('block');
+                expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(true);
                 converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
                     .siblings('.remove-xmpp-contact').click();
                 expect(window.confirm).toHaveBeenCalled();
-                expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
+                expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(false);
             }, converse));
 
 
@@ -306,20 +308,20 @@
                 _clearContacts();
                 var i, t, is_last;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.pend_names.length; i++) {
                     is_last = i===(mock.pend_names.length-1);
-                    this.roster.create({
+                    this.rosterview.roster.create({
                         jid: mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'none',
                         ask: 'subscribe',
                         fullname: mock.pend_names[i],
                         is_last: is_last
                     });
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    expect(this.rosterview.update).toHaveBeenCalled();
                 }
                 // Check that they are sorted alphabetically
-                t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').find('span').text();
+                t = this.rosterview.get('Pending contacts').$el.siblings('dd.pending-xmpp-contact').find('span').text();
                 expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
             }, converse));
 
@@ -329,7 +331,6 @@
             function _clearContacts () {
                 utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
-                converse.rosterview.initialize(); // Register new listeners and document fragment
             };
 
             var _addContacts = function () {
@@ -346,16 +347,16 @@
                 _clearContacts();
                 var i, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
-                    this.roster.create({
+                    this.rosterview.roster.create({
                         jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'both',
                         ask: null,
                         fullname: mock.cur_names[i],
                         is_last: i===(mock.cur_names.length-1)
                     });
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    expect(this.rosterview.update).toHaveBeenCalled();
                 }
                 // Check that they are sorted alphabetically
                 t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
@@ -387,7 +388,7 @@
                 var name = mock.cur_names[0];
                 _clearContacts();
                 spyOn(window, 'confirm').andReturn(true);
-                this.roster.create({
+                this.rosterview.roster.create({
                     jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
                     subscription: 'both',
                     ask: null,
@@ -405,11 +406,11 @@
                 _addContacts();
                 var jid, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'online');
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    this.rosterview.roster.get(jid).set('chat_status', 'online');
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
                     t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
                     expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
@@ -420,11 +421,11 @@
                 _addContacts();
                 var jid, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'dnd');
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    this.rosterview.roster.get(jid).set('chat_status', 'dnd');
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
                     t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
                     expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
@@ -435,11 +436,11 @@
                 _addContacts();
                 var jid, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'away');
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    this.rosterview.roster.get(jid).set('chat_status', 'away');
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
                     t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
                     expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
@@ -450,11 +451,11 @@
                 _addContacts();
                 var jid, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'xa');
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    this.rosterview.roster.get(jid).set('chat_status', 'xa');
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
                     t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
                     expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
@@ -465,11 +466,11 @@
                 _addContacts();
                 var jid, t;
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'unavailable');
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    this.rosterview.roster.get(jid).set('chat_status', 'unavailable');
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
                     t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
                     expect(t).toEqual(mock.cur_names.slice(0, i+1).sort().join(''));
@@ -481,23 +482,23 @@
                 var i;
                 for (i=0; i<3; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'online');
+                    this.rosterview.roster.get(jid).set('chat_status', 'online');
                 }
                 for (i=3; i<6; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'dnd');
+                    this.rosterview.roster.get(jid).set('chat_status', 'dnd');
                 }
                 for (i=6; i<9; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'away');
+                    this.rosterview.roster.get(jid).set('chat_status', 'away');
                 }
                 for (i=9; i<12; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'xa');
+                    this.rosterview.roster.get(jid).set('chat_status', 'xa');
                 }
                 for (i=12; i<15; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'unavailable');
+                    this.rosterview.roster.get(jid).set('chat_status', 'unavailable');
                 }
 
                 var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
@@ -540,7 +541,7 @@
                 var i, children;
                 var names = [];
                 spyOn(converse, 'emit');
-                spyOn(this.rosterview, 'updateCount').andCallThrough();
+                spyOn(this.rosterview, 'update').andCallThrough();
                 spyOn(this.controlboxtoggle, 'showControlBox').andCallThrough();
                 var addName = function (idx, item) {
                     if (!$(item).hasClass('request-actions')) {
@@ -548,7 +549,7 @@
                     }
                 };
                 for (i=0; i<mock.req_names.length; i++) {
-                    this.roster.create({
+                    this.rosterview.roster.create({
                         jid: mock.req_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'none',
                         ask: null,
@@ -556,13 +557,13 @@
                         fullname: mock.req_names[i],
                         is_last: i===(mock.req_names.length-1)
                     });
-                    expect(this.rosterview.updateCount).toHaveBeenCalled();
+                    expect(this.rosterview.update).toHaveBeenCalled();
                     // When a requesting contact is added, the controlbox must
                     // be opened.
                     expect(this.controlboxtoggle.showControlBox).toHaveBeenCalled();
                 }
                 // Check that they are sorted alphabetically
-                children = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').children('span');
+                children = this.rosterview.get('Contact requests').$el.siblings('dd.requesting-xmpp-contact').children('span');
                 names = [];
                 children.each(addName);
                 expect(names.join('')).toEqual(mock.req_names.slice(0,i+1).sort().join(''));
@@ -572,7 +573,7 @@
                 converse.rosterview.model.reset(); // We want to manually create users so that we can spy
                 var name = mock.req_names[0];
                 spyOn(window, 'confirm').andReturn(true);
-                this.roster.create({
+                this.rosterview.roster.create({
                     jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
                     subscription: 'none',
                     ask: null,
@@ -580,16 +581,16 @@
                     fullname: name,
                     is_last: true
                 });
-                expect(this.rosterview.$('dt#xmpp-contact-requests').css('display')).toEqual('block');
+                expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(true);
                 converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
                     .siblings('.request-actions')
                     .find('.decline-xmpp-request').click();
                 expect(window.confirm).toHaveBeenCalled();
-                expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
+                expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(false);
             }, converse));
 
             it("can be collapsed under their own header", $.proxy(function () {
-                checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt#xmpp-contact-requests')]);
+                checkHeaderToggling.apply(this, [this.rosterview.get('Contact requests').$el]);
             }, converse));
 
             it("can have their requests accepted by the user", $.proxy(function () {
@@ -611,49 +612,35 @@
                 this.rosterview.model.reset();
                 spyOn(converse, 'emit');
                 spyOn(this.connection.roster, 'unauthorize');
-                spyOn(this.rosterview, 'update').andCallThrough();
                 spyOn(window, 'confirm').andReturn(true);
-                this.rosterview.initialize(); // Must be initialized only after the spy has been called
                 utils.createContacts('requesting').openControlBox();
-
                 var name = mock.req_names.sort()[1];
                 var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
                 converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
                     .siblings('.request-actions')
                     .find('.decline-xmpp-request').click();
-
                 expect(window.confirm).toHaveBeenCalled();
-                expect(this.rosterview.update).toHaveBeenCalled();
                 expect(this.connection.roster.unauthorize).toHaveBeenCalled();
                 // There should now be one less contact
-                expect(this.roster.length).toEqual(mock.req_names.length-1);
+                expect(this.rosterview.roster.length).toEqual(mock.req_names.length-1);
             }, converse));
         }, converse));
 
         describe("All Contacts", $.proxy(function () {
             beforeEach($.proxy(function () {
-                runs(function () {
-                    utils.clearBrowserStorage();
-                    converse.rosterview.model.reset();
-                    converse.rosterview.model.browserStorage._clear();
-                    utils.createContacts('all').openControlBox();
-                });
-                waits(50);
-                runs(function () {
-                    utils.openContactsPanel();
-                });
+                utils.clearBrowserStorage();
+                converse.rosterview.model.reset();
+                utils.createContacts('all').openControlBox();
+                utils.openContactsPanel();
             }, converse));
 
             it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () {
                 var new_attrs, old_attrs, attrs, old_roster;
-                var num_contacts = this.roster.length;
+                var num_contacts = this.rosterview.roster.length;
                 new_roster = new this.RosterContacts();
                 // Roster items are yet to be fetched from browserStorage
                 expect(new_roster.length).toEqual(0);
-
-                new_roster.browserStorage = new Backbone.BrowserStorage.session(
-                    b64_sha1('converse.contacts-dummy@localhost'));
-
+                new_roster.browserStorage = this.rosterview.roster.browserStorage;
                 new_roster.fetch();
                 expect(new_roster.length).toEqual(num_contacts);
                 // Check that the roster items retrieved from browserStorage
@@ -661,13 +648,12 @@
                 attrs = ['jid', 'fullname', 'subscription', 'ask'];
                 for (i=0; i<attrs.length; i++) {
                     new_attrs = _.pluck(_.pluck(new_roster.models, 'attributes'), attrs[i]);
-                    old_attrs = _.pluck(_.pluck(this.roster.models, 'attributes'), attrs[i]);
+                    old_attrs = _.pluck(_.pluck(this.rosterview.roster.models, 'attributes'), attrs[i]);
                     // Roster items in storage are not necessarily sorted,
                     // so we have to sort them here to do a proper
                     // comparison
                     expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
                 }
-                // XXX: this.rosterview.updateCount();
             }, converse));
 
             afterEach($.proxy(function () {
@@ -677,7 +663,7 @@
                 // we make some online now
                 for (i=0; i<5; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    this.roster.get(jid).set('chat_status', 'online');
+                    this.rosterview.roster.get(jid).set('chat_status', 'online');
                 }
             }, converse));
         }, converse));

+ 1 - 1
spec/minchats.js

@@ -13,7 +13,7 @@
             runs(function () {
                 utils.closeAllChatBoxes();
                 utils.removeControlBox();
-                converse.roster.browserStorage._clear();
+                converse.rosterview.roster.browserStorage._clear();
                 utils.initConverse();
                 utils.createContacts();
                 utils.openControlBox();

+ 4 - 4
tests/utils.js

@@ -36,7 +36,7 @@
     };
 
     utils.initRoster = function () {
-        converse.roster.browserStorage._clear();
+        converse.rosterview.roster.browserStorage._clear();
         converse.initRoster();
     };
 
@@ -80,13 +80,13 @@
         var i = 0, jid, views = [];
         for (i; i<amount; i++) {
             jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-            views[i] = converse.roster.get(jid).trigger("open");
+            views[i] = converse.rosterview.roster.get(jid).trigger("open");
         }
         return views;
     };
 
     utils.openChatBoxFor = function (jid) {
-        return converse.roster.get(jid).trigger("open");
+        return converse.rosterview.roster.get(jid).trigger("open");
     };
 
     utils.removeRosterContacts = function () {
@@ -134,7 +134,7 @@
             ask = null;
         }
         for (i=0; i<names.length; i++) {
-            converse.roster.create({
+            converse.rosterview.roster.create({
                 ask: ask,
                 fullname: names[i],
                 is_last: i===(names.length-1),