Переглянути джерело

Big refactor. The RosterView is no longer an overview.

This is because we can no longer assume a one to one mapping between roster
contacts and their views. Roster contacts can belong to more than one group,
each group needs to show the contact, which means we need a view for each group
the contact belongs to.

updates #83
JC Brand 11 роки тому
батько
коміт
2b927f21be
4 змінених файлів з 157 додано та 131 видалено
  1. 28 28
      converse.js
  2. 3 12
      spec/chatbox.js
  3. 116 89
      spec/controlbox.js
  4. 10 2
      tests/utils.js

+ 28 - 28
converse.js

@@ -2724,9 +2724,7 @@
             },
 
             toggle: function (ev) {
-                if (ev && ev.preventDefault) {
-                    ev.preventDefault();
-                }
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
                 this.toggleview.model.save({'collapsed': !this.toggleview.model.get('collapsed')});
                 this.$('.minimized-chats-flyout').toggle();
             },
@@ -2830,6 +2828,9 @@
 
             initialize: function () {
                 this.model.on("change", this.onChange, this);
+                this.model.on("remove", this.remove, this);
+                this.model.on("destroy", this.remove, this);
+                this.model.on("open", this.openChat, this);
             },
 
             onChange: function () {
@@ -2845,7 +2846,7 @@
             },
 
             openChat: function (ev) {
-                ev.preventDefault();
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
                 return converse.chatboxviews.showChat({
                     'id': this.model.get('jid'),
                     'jid': this.model.get('jid'),
@@ -2858,7 +2859,7 @@
             },
 
             removeContact: function (ev) {
-                ev.preventDefault();
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
                 var result = confirm(__("Are you sure you want to remove this contact?"));
                 if (result === true) {
                     var bare_jid = this.model.get('jid');
@@ -3250,7 +3251,7 @@
             }
         });
 
-        this.RosterView = Backbone.Overview.extend({
+        this.RosterView = Backbone.View.extend({
             tagName: 'dl',
             id: 'converse-roster',
             events: {
@@ -3294,6 +3295,7 @@
                 }
                 this.$fragment = $('<span>');
                 this.$fragment.append($(roster_markup));
+                return this;
             },
 
             update: function (item) {
@@ -3301,8 +3303,8 @@
             },
 
             reset: function () {
-                this.removeAll();
-                this.update();
+                this.$el.find('span').empty();
+                this.render().update();
                 return this;
             },
 
@@ -3396,34 +3398,34 @@
                 return $group;
             },
 
-            addCurrentContact: function (view) {
-                var item = view.model;
+            addCurrentContact: function (item) {
                 if (converse.roster_groups) {
                     if (item.get('groups').length === 0) {
-                        this.getRoster().find('.roster-group[data-group="'+HEADER_UNGROUPED+'"]').after(view.el);
+                        this.getRoster().find('.roster-group[data-group="'+HEADER_UNGROUPED+'"]')
+                            .after((new converse.RosterItemView({model: item})).render().el);
                     } else {
                         _.each(item.get('groups'), $.proxy(function (group) {
-                            this.getGroup(group).after(view.el);
+                            // We need a separate view per group
+                            this.getGroup(group).after((new converse.RosterItemView({model: item})).render().el);
                         },this));
                     }
                 } else {
-                    this.getRoster().find('.roster-group[data-group="'+HEADER_CURRENT_CONTACTS+'"]').after(view.el);
+                    this.getRoster().find('.roster-group[data-group="'+HEADER_CURRENT_CONTACTS+'"]')
+                        .after((new converse.RosterItemView({model: item})).render().el);
                 }
             },
 
             addRosterItem: function (item) {
-                var view = new converse.RosterItemView({model: item});
-                this.add(item.id, view);
-                if ((converse.show_only_online_users) && (item.get('chat_status') !== 'online')) {
-                    return this;
-                }
-                view.render()
-                if (view.$el.hasClass('current-xmpp-contact')) {
-                    this.addCurrentContact(view);
-                } else if (view.$el.hasClass('pending-xmpp-contact')) {
-                    this.getRoster().find('#pending-xmpp-contacts').after(view.el);
-                } else if (view.$el.hasClass('requesting-xmpp-contact')) {
-                    this.getRoster().find('#xmpp-contact-requests').after(view.render().el);
+                var view;
+                if (item.get('subscription') === 'both' || item.get('subscription') === 'to') {
+                    this.addCurrentContact(item);
+                } else {
+                    view = (new converse.RosterItemView({model: item})).render();
+                    if ((item.get('ask') === 'subscribe') || (item.get('subscription') === 'from')) {
+                        this.getRoster().find('#pending-xmpp-contacts').after(view.el);
+                    } else if (item.get('requesting') === true) {
+                        this.getRoster().find('#xmpp-contact-requests').after(view.el);
+                    }
                 }
                 return this;
             },
@@ -3496,9 +3498,7 @@
             },
 
             sortRoster: function (chat_status) {
-                /* XXX
-                 * 1). See if the jquery detach method can be somehow used to avoid DOM reflows.
-                 * 2). Likewise see if documentFragment can be used.
+                /* 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) {

+ 3 - 12
spec/chatbox.js

@@ -22,7 +22,7 @@
             });
 
             it("is created when you click on a roster item", $.proxy(function () {
-                var i, $el, click, jid, view, chatboxview;
+                var i, $el, click, jid, chatboxview;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
@@ -33,12 +33,8 @@
                 for (i=0; i<online_contacts.length; i++) {
                     $el = $(online_contacts[i]);
                     jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'openChat').andCallThrough();
-                    view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     $el.click();
                     chatboxview = this.chatboxviews.get(jid);
-                    expect(view.openChat).toHaveBeenCalled();
                     expect(this.chatboxes.length).toEqual(i+2);
                     expect(this.chatboxviews.trimChats).toHaveBeenCalled();
                     // Check that new chat boxes are created to the left of the
@@ -49,7 +45,7 @@
             }, converse));
 
             it("can be trimmed to conserve space", $.proxy(function () {
-                var i, $el, click, jid, key, view, chatbox, chatboxview;
+                var i, $el, click, jid, key, chatbox, chatboxview;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 var trimmed_chatboxes = converse.minimized_chats;
@@ -64,7 +60,6 @@
                 for (i=0; i<online_contacts.length; i++) {
                     $el = $(online_contacts[i]);
                     jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
                     $el.click();
                     expect(this.chatboxviews.trimChats).toHaveBeenCalled();
 
@@ -96,7 +91,7 @@
 
             it("is focused if its already open and you click on its corresponding roster item", $.proxy(function () {
                 var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var i, $el, click, jid, view, chatboxview, chatbox;
+                var i, $el, click, jid, chatboxview, chatbox;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
@@ -105,11 +100,7 @@
                 spyOn(chatboxview, 'focus');
                 $el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
                 jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
-                view = this.rosterview.get(jid);
-                spyOn(view, 'openChat').andCallThrough();
-                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 $el.click();
-                expect(view.openChat).toHaveBeenCalled();
                 expect(this.chatboxes.length).toEqual(2);
                 expect(chatboxview.focus).toHaveBeenCalled();
             }, converse));

+ 116 - 89
spec/controlbox.js

@@ -220,12 +220,6 @@
                 utils.createContacts('pending').openControlBox().openContactsPanel();
             };
 
-            it("do not have a header if there aren't any", $.proxy(function () {
-                _addContacts();
-                this.rosterview.model.reset();
-                expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
-            }, converse));
-
             it("can be collapsed under their own header", $.proxy(function () {
                 _addContacts();
                 checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt#pending-xmpp-contacts')]);
@@ -253,35 +247,51 @@
 
             it("can be removed by the user", $.proxy(function () {
                 _addContacts();
-                var jid = mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var view = this.rosterview.get(jid);
+                var name = mock.pend_names[0];
+                var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
                 spyOn(window, 'confirm').andReturn(true);
                 spyOn(converse, 'emit');
                 spyOn(this.connection.roster, 'remove').andCallThrough();
                 spyOn(this.connection.roster, 'unauthorize');
                 spyOn(this.rosterview.model, 'remove').andCallThrough();
-                spyOn(view, 'removeContact').andCallThrough();
-                spyOn(view, 'remove').andCallThrough();
-                view.delegateEvents();
 
-                view.$el.find('.remove-xmpp-contact').click();
+                converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
+                    .siblings('.remove-xmpp-contact').click();
+
                 expect(window.confirm).toHaveBeenCalled();
                 expect(this.connection.roster.remove).toHaveBeenCalled();
                 expect(this.connection.roster.unauthorize).toHaveBeenCalled();
                 expect(this.rosterview.model.remove).toHaveBeenCalled();
-                expect(view.removeContact).toHaveBeenCalled();
-                expect(view.remove).toHaveBeenCalled();
-                // The element must now be detached from the DOM.
-                expect(view.$el.closest('html').length).toBeFalsy();
+                expect(converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length).toEqual(0);
+            }, converse));
+
+            it("do not have a header if there aren't any", $.proxy(function () {
+                var name = mock.pend_names[0];
+                _clearContacts();
+                spyOn(window, 'confirm').andReturn(true);
+                this.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');
+                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');
             }, converse));
 
+
             it("will lose their own header once the last one has been removed", $.proxy(function () {
                 _addContacts();
-                var view;
+                var name;
                 spyOn(window, 'confirm').andReturn(true);
                 for (i=0; i<mock.pend_names.length; i++) {
-                    view = this.rosterview.get(mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@localhost');
-                    view.$el.find('.remove-xmpp-contact').click();
+                    name = mock.pend_names[i];
+                    converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
+                        .siblings('.remove-xmpp-contact').click();
                 }
                 expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').is(':visible')).toBeFalsy();
             }, converse));
@@ -321,13 +331,6 @@
                 utils.createContacts().openControlBox().openContactsPanel();
             };
 
-            it("do not have a header if there aren't any", $.proxy(function () {
-                // To properly tests, we add contacts and then remove them all again.
-                _addContacts();
-                this.rosterview.model.reset();
-                expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
-            }, converse));
-
             it("can be collapsed under their own header", $.proxy(function () {
                 _addContacts();
                 checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt.roster-group')]);
@@ -353,18 +356,53 @@
                 expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
             }, converse));
 
+            it("can be removed by the user", $.proxy(function () {
+                _addContacts();
+                var name = mock.cur_names[0];
+                var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
+                spyOn(window, 'confirm').andReturn(true);
+                spyOn(converse, 'emit');
+                spyOn(this.connection.roster, 'remove').andCallThrough();
+                spyOn(this.connection.roster, 'unauthorize');
+                spyOn(this.rosterview.model, 'remove').andCallThrough();
+
+                converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
+                    .siblings('.remove-xmpp-contact').click();
+
+                expect(window.confirm).toHaveBeenCalled();
+                expect(this.connection.roster.remove).toHaveBeenCalled();
+                expect(this.connection.roster.unauthorize).toHaveBeenCalled();
+                expect(this.rosterview.model.remove).toHaveBeenCalled();
+                expect(converse.rosterview.$el.find(".open-chat:contains('"+name+"')").length).toEqual(0);
+            }, converse));
+
+
+            it("do not have a header if there aren't any", $.proxy(function () {
+                var name = mock.cur_names[0];
+                _clearContacts();
+                spyOn(window, 'confirm').andReturn(true);
+                this.roster.create({
+                    jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
+                    subscription: 'both',
+                    ask: null,
+                    fullname: name,
+                    is_last: true
+                });
+                expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('block');
+                converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
+                    .siblings('.remove-xmpp-contact').click();
+                expect(window.confirm).toHaveBeenCalled();
+                expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
+            }, converse));
+
             it("can change their status to online and be sorted alphabetically", $.proxy(function () {
                 _addContacts();
-                var item, view, jid, t;
+                var jid, t;
                 spyOn(converse, 'emit');
                 spyOn(this.rosterview, 'updateCount').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'render').andCallThrough();
-                    item = view.model;
-                    item.set('chat_status', 'online');
-                    expect(view.render).toHaveBeenCalled();
+                    this.roster.get(jid).set('chat_status', 'online');
                     expect(this.rosterview.updateCount).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();
@@ -374,16 +412,12 @@
 
             it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
                 _addContacts();
-                var item, view, jid, t;
+                var jid, t;
                 spyOn(converse, 'emit');
                 spyOn(this.rosterview, 'updateCount').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'render').andCallThrough();
-                    item = view.model;
-                    item.set('chat_status', 'dnd');
-                    expect(view.render).toHaveBeenCalled();
+                    this.roster.get(jid).set('chat_status', 'dnd');
                     expect(this.rosterview.updateCount).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();
@@ -393,16 +427,12 @@
 
             it("can change their status to away and be sorted alphabetically", $.proxy(function () {
                 _addContacts();
-                var item, view, jid, t;
+                var jid, t;
                 spyOn(converse, 'emit');
                 spyOn(this.rosterview, 'updateCount').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'render').andCallThrough();
-                    item = view.model;
-                    item.set('chat_status', 'away');
-                    expect(view.render).toHaveBeenCalled();
+                    this.roster.get(jid).set('chat_status', 'away');
                     expect(this.rosterview.updateCount).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();
@@ -412,16 +442,12 @@
 
             it("can change their status to xa and be sorted alphabetically", $.proxy(function () {
                 _addContacts();
-                var item, view, jid, t;
+                var jid, t;
                 spyOn(converse, 'emit');
                 spyOn(this.rosterview, 'updateCount').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'render').andCallThrough();
-                    item = view.model;
-                    item.set('chat_status', 'xa');
-                    expect(view.render).toHaveBeenCalled();
+                    this.roster.get(jid).set('chat_status', 'xa');
                     expect(this.rosterview.updateCount).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();
@@ -431,16 +457,12 @@
 
             it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
                 _addContacts();
-                var item, view, jid, t;
+                var jid, t;
                 spyOn(converse, 'emit');
                 spyOn(this.rosterview, 'updateCount').andCallThrough();
                 for (i=0; i<mock.cur_names.length; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    spyOn(view, 'render').andCallThrough();
-                    item = view.model;
-                    item.set('chat_status', 'unavailable');
-                    expect(view.render).toHaveBeenCalled();
+                    this.roster.get(jid).set('chat_status', 'unavailable');
                     expect(this.rosterview.updateCount).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();
@@ -453,28 +475,23 @@
                 var i;
                 for (i=0; i<3; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'online');
+                    this.roster.get(jid).set('chat_status', 'online');
                 }
                 for (i=3; i<6; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'dnd');
+                    this.roster.get(jid).set('chat_status', 'dnd');
                 }
                 for (i=6; i<9; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'away');
+                    this.roster.get(jid).set('chat_status', 'away');
                 }
                 for (i=9; i<12; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'xa');
+                    this.roster.get(jid).set('chat_status', 'xa');
                 }
                 for (i=12; i<15; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'unavailable');
+                    this.roster.get(jid).set('chat_status', 'unavailable');
                 }
 
                 var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
@@ -512,13 +529,6 @@
                 });
             }, converse));
 
-            it("do not have a header if there aren't any", $.proxy(function () {
-                // by default the dts are hidden from css class and only later they will be hidden
-                // by jQuery therefore for the first check we will see if visible instead of none
-                converse.rosterview.model.reset();
-                expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
-            }, converse));
-
             it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
                 converse.rosterview.model.reset(); // We want to manually create users so that we can spy
                 var i, children;
@@ -552,6 +562,26 @@
                 expect(names.join('')).toEqual(mock.req_names.slice(0,i+1).sort().join(''));
             }, converse));
 
+            it("do not have a header if there aren't any", $.proxy(function () {
+                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({
+                    jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
+                    subscription: 'none',
+                    ask: null,
+                    requesting: true,
+                    fullname: name,
+                    is_last: true
+                });
+                expect(this.rosterview.$('dt#xmpp-contact-requests').css('display')).toEqual('block');
+                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);
+            }, converse));
+
             it("can be collapsed under their own header", $.proxy(function () {
                 checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt#xmpp-contact-requests')]);
             }, converse));
@@ -560,14 +590,14 @@
                 // TODO: Testing can be more thorough here, the user is
                 // actually not accepted/authorized because of
                 // mock_connection.
-                var jid = mock.req_names.sort()[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var view = this.rosterview.get(jid);
+                var name = mock.req_names.sort()[0];
+                var jid =  name.replace(/ /g,'.').toLowerCase() + '@localhost';
                 spyOn(this.connection.roster, 'authorize');
-                spyOn(view, 'acceptRequest').andCallThrough();
-                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
-                var accept_button = view.$el.find('.accept-xmpp-request');
-                accept_button.click();
-                expect(view.acceptRequest).toHaveBeenCalled();
+
+                converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
+                    .siblings('.request-actions')
+                    .find('.accept-xmpp-request').click();
+
                 expect(this.connection.roster.authorize).toHaveBeenCalled();
             }, converse));
 
@@ -580,14 +610,12 @@
                 this.rosterview.initialize(); // Must be initialized only after the spy has been called
                 utils.createContacts('requesting').openControlBox();
 
-                var jid = mock.req_names.sort()[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var view = this.rosterview.get(jid);
-                spyOn(view, 'declineRequest').andCallThrough();
-                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                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();
 
-                var accept_button = view.$el.find('.decline-xmpp-request');
-                accept_button.click();
-                expect(view.declineRequest).toHaveBeenCalled();
                 expect(window.confirm).toHaveBeenCalled();
                 expect(this.rosterview.update).toHaveBeenCalled();
                 expect(this.connection.roster.unauthorize).toHaveBeenCalled();
@@ -643,8 +671,7 @@
                 // we make some online now
                 for (i=0; i<5; i++) {
                     jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    view = this.rosterview.get(jid);
-                    view.model.set('chat_status', 'online');
+                    this.roster.get(jid).set('chat_status', 'online');
                 }
             }, converse));
         }, converse));

+ 10 - 2
tests/utils.js

@@ -80,13 +80,21 @@
         var i = 0, jid, views = [];
         for (i; i<amount; i++) {
             jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-            views[i] = converse.rosterview.get(jid).openChat(mock.event);
+            views[i] = converse.roster.get(jid).trigger("open");
         }
         return views;
     };
 
     utils.openChatBoxFor = function (jid) {
-        return converse.rosterview.get(jid).openChat(mock.event);
+        return converse.roster.get(jid).trigger("open");
+    };
+
+    utils.removeRosterContacts = function () {
+        var model;
+        while (converse.rosterview.model.length) {
+            model = converse.rosterview.model.pop();
+            converse.rosterview.model.remove(model);
+        }
     };
 
     utils.clearBrowserStorage = function () {