Jelajahi Sumber

Merge branch 'live-filter'

Conflicts:
	builds/converse-no-locales-no-otr.min.js
	builds/converse-no-otr.min.js
	builds/converse.min.js
	builds/converse.website-no-otr.min.js
	builds/converse.website.min.js
JC Brand 11 tahun lalu
induk
melakukan
bf837a921e

File diff ditekan karena terlalu besar
+ 0 - 0
builds/converse-no-locales-no-otr.min.js


File diff ditekan karena terlalu besar
+ 0 - 0
builds/converse-no-otr.min.js


File diff ditekan karena terlalu besar
+ 0 - 0
builds/converse.min.js


File diff ditekan karena terlalu besar
+ 0 - 0
builds/converse.website-no-otr.min.js


File diff ditekan karena terlalu besar
+ 0 - 0
builds/converse.website.min.js


+ 145 - 13
converse.js

@@ -292,6 +292,9 @@
         var HEADER_REQUESTING_CONTACTS = __('Contact requests');
         var HEADER_UNGROUPED = __('Ungrouped');
 
+        var LABEL_CONTACTS = __('Contacts');
+        var LABEL_GROUPS = __('Groups');
+
         var HEADER_WEIGHTS = {};
         HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS]    = 0;
         HEADER_WEIGHTS[HEADER_UNGROUPED]           = 1;
@@ -307,8 +310,7 @@
         // Module-level functions
         // ----------------------
         this.giveFeedback = function (message, klass) {
-            $('.conn-feedback').text(message);
-            $('.conn-feedback').attr('class', 'conn-feedback');
+            $('.conn-feedback').attr('class', 'conn-feedback').text(message);
             if (klass) {
                 $('.conn-feedback').addClass(klass);
             }
@@ -1497,7 +1499,7 @@
                     label_away: __('Away'),
                     label_offline: __('Offline')
                 });
-                this.$tabs.append(converse.templates.contacts_tab({label_contacts: __('Contacts')}));
+                this.$tabs.append(converse.templates.contacts_tab({label_contacts: LABEL_CONTACTS}));
                 if (converse.xhr_user_search) {
                     markup = converse.templates.search_contact({
                         label_contact_name: __('Contact name'),
@@ -1517,9 +1519,9 @@
                     });
                 }
                 this.$el.html(widgets);
-
                 this.$el.find('.search-xmpp ul').append(markup);
                 this.$el.append(converse.rosterview.$el);
+                converse.rosterview.update(); // Will render live filter if needed.
                 return this;
             },
 
@@ -3245,7 +3247,8 @@
             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 might be optimized by instead of first sorting,
+                    // finding the correct position in positionContact
                     this.model.contacts.sort();
                     this.positionContact(contact).render();
                 }, this);
@@ -3282,23 +3285,73 @@
                 return view;
             },
 
+            hide: function () {
+                this.$el.nextUntil('dt').addBack().hide();
+            },
+
+            filter: function (q) {
+                /* Filter the group's contacts based on the query "q".
+                 * The query is matched against the contact's full name.
+                 * If all contacts are filtered out (i.e. hidden), then the
+                 * group must be filtered out as well.
+                 */
+                var matches, rejects;
+                var predicate = function (item) {
+                    return item.get('fullname').toLowerCase().indexOf(q) === -1;
+                };
+                if (q.length === 0) {
+                    if (this.model.get('state') === OPENED) {
+                        this.model.contacts.each($.proxy(function (item) {
+                            if (!(converse.show_only_online_users && item.get('chat_status') === 'online')) {
+                                this.get(item.get('id')).$el.show();
+                            }
+                        }, this));
+                    }
+                    this.showIfInvisible();
+                } else {
+                    q = q.toLowerCase();
+                    matches = this.model.contacts.filter(predicate);
+                    if (matches.length === this.model.contacts.length) { // hide the whole group
+                        this.hide();
+                    } else {
+                        _.each(matches, $.proxy(function (item) {
+                            this.get(item.get('id')).$el.hide();
+                        }, this));
+                        _.each(this.model.contacts.reject(predicate), $.proxy(function (item) {
+                            this.get(item.get('id')).$el.show();
+                        }, this));
+                        this.showIfInvisible();
+                    }
+                }
+            },
+
+            showIfInvisible: function () {
+                if (!this.$el.is(':visible')) {
+                    this.$el.show();
+                }
+            },
+
             toggle: function (ev) {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
                 var $el = $(ev.target);
-                this.$el.nextUntil('dt').slideToggle();
                 if ($el.hasClass("icon-opened")) {
+                    this.$el.nextUntil('dt').slideUp();
                     this.model.save({state: CLOSED});
                     $el.removeClass("icon-opened").addClass("icon-closed");
                 } else {
                     $el.removeClass("icon-closed").addClass("icon-opened");
                     this.model.save({state: OPENED});
+                    this.filter(
+                        converse.rosterview.$('.roster-filter').val(),
+                        converse.rosterview.$('.filter-type').val()
+                    );
                 }
             },
 
             addContact: function (contact) {
                 var view = new converse.RosterContactView({model: contact});
                 this.add(contact.get('id'), view);
-                var view = this.positionContact(contact).render();
+                view = this.positionContact(contact).render();
                 if (this.model.get('state') === CLOSED) {
                     view.$el.hide();
                 }
@@ -3347,8 +3400,14 @@
         });
 
         this.RosterView = Backbone.Overview.extend({
-            tagName: 'dl',
+            tagName: 'div',
             id: 'converse-roster',
+            events: {
+                "keydown .roster-filter": "liveFilter",
+                "click .onX": "clearFilter",
+                "mousemove .x": "togglePointer",
+                "change .filter-type": "changeFilterType"
+            },
 
             initialize: function () {
                 this.registerRosterHandler();
@@ -3369,7 +3428,80 @@
             },
 
             render: function () {
-                this.$el.empty();
+                this.$el.html(converse.templates.roster({
+                    placeholder: __('Type to filter'),
+                    label_contacts: LABEL_CONTACTS,
+                    label_groups: LABEL_GROUPS
+                }));
+                return this;
+            },
+
+            changeFilterType: function (ev) {
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                this.clearFilter();
+                this.filter(
+                    this.$('.roster-filter').val(),
+                    ev.target.value
+                );
+            },
+
+            tog: function (v) {
+                return v?'addClass':'removeClass';
+            },
+
+            togglePointer: function (ev) {
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                var el = ev.target;
+                $(el)[this.tog(el.offsetWidth-18 < ev.clientX-el.getBoundingClientRect().left)]('onX');
+            },
+
+            filter: function (query, type) {
+                var matches;
+                query = query.toLowerCase();
+                if (type === 'groups') {
+                    matches = _.filter(this.getAll(), function (view) {
+                        return view.model.get('name').toLowerCase().indexOf(query) === -1;
+                    });
+                    _.each(matches, function (view) {
+                        view.hide();
+                    });
+                } else {
+                    _.each(this.getAll(), function (view) {
+                        view.filter(query, type);
+                    });
+                }
+            },
+
+            liveFilter: _.debounce(function (ev) {
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                var q = ev.target.value;
+                var t = this.$('.filter-type').val();
+                $(ev.target)[this.tog(q)]('x');
+                this.filter(q, t);
+            }, 500),
+
+            clearFilter: function (ev) {
+                if (ev && ev.preventDefault) {
+                    ev.preventDefault();
+                    $(ev.target).removeClass('x onX').val('');
+                }
+                this.filter('');
+            },
+
+            showHideFilter: function () {
+                var $filter = this.$('.roster-filter');
+                var visible = $filter.is(':visible');
+                if (visible && $filter.val().length > 0) {
+                    // Don't hide if user is currently filtering.
+                    return;
+                }
+                if (this.$('.roster-contacts').hasScrollBar()) {
+                    if (!visible) {
+                        $filter.show();
+                    }
+                } else {
+                    $filter.hide();
+                }
                 return this;
             },
 
@@ -3380,7 +3512,7 @@
                 if (!$count.is(':visible')) {
                     $count.show();
                 }
-                return this;
+                return this.showHideFilter();
             },
 
             reset: function () {
@@ -3457,13 +3589,13 @@
                     */
                 model.sort();
                 model.each($.proxy(function (group, idx) {
-                    var view = this.get(group.get('name'))
+                    var view = this.get(group.get('name'));
                     if (!view) {
                         view = new converse.RosterGroupView({model: group});
                         this.add(group.get('name'), view.render());
                     }
                     if (idx === 0) {
-                        this.$el.append(view.$el);
+                        this.$('.roster-contacts').append(view.$el);
                     } else {
                         this.appendGroup(view);
                     }
@@ -3476,7 +3608,7 @@
                  */
                 var index = this.model.indexOf(view.model);
                 if (index === 0) {
-                    this.$el.prepend(view.$el);
+                    this.$('.roster-contacts').prepend(view.$el);
                 } else if (index == (this.model.length-1)) {
                     this.appendGroup(view);
                 } else {

+ 42 - 14
css/converse.css

@@ -754,7 +754,7 @@ dl.add-converse-contact {
   clear: right;
   height: 22px;
   width: 12px;
-  padding: 0px 5px 0 0;
+  padding: 0px 15px 0 0;
   color: #4f4f4f;
 }
 #conversejs ul#found-users {
@@ -817,7 +817,7 @@ dl.add-converse-contact {
   top: 1px;
 }
 #conversejs .controlbox-pane dt {
-  padding: 3px;
+  padding-bottom: 3px;
 }
 #conversejs .chatroom-form-container {
   height: 100%;
@@ -872,24 +872,52 @@ dl.add-converse-contact {
   white-space: nowrap;
   text-overflow: ellipsis;
 }
-#conversejs #converse-roster span.req-contact-name {
+#converse-roster span.req-contact-name {
   width: 65%;
 }
-#conversejs #converse-roster span.pending-contact-name,
-#conversejs #converse-roster a.open-chat {
+#converse-roster span.pending-contact-name,
+#converse-roster a.open-chat {
   width: 80%;
 }
 #converse-roster dd span {
   padding: 0 5px 0 0;
 }
 #converse-roster {
-  overflow-y: auto;
-  overflow-x: hidden;
   width: 100%;
   position: relative;
   margin: 0.5em 0 0 0;
-  height: 254px;
-  height: calc(100% - 70px);
+  height: 194px;
+  height: calc(100% - 25px);
+  overflow-x: hidden;
+}
+#converse-roster .roster-filter {
+  padding: 0;
+  margin: 0 0 5px 0.5em;
+  width: 111px;
+  height: 20px;
+  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAABNSURBVHjaXI7BDcAwCAMvyQjMyQ6dAbZiKfqoUK34g2zJh1dENIC7M8pMAPYdzAVY3d0ajNz9aypS/b5R6o+ZPdqoKgCq6h80KH3xDgBqNR97p8oAGQAAAABJRU5ErkJggg== ) no-repeat right -20px center;
+  border: 1px solid #999;
+  display: inline-block;
+}
+#converse-roster .filter-type {
+  height: 20px;
+  padding: 0;
+  margin: 0 0 0 -5px;
+}
+/* (jQ addClass:) if input has value: */
+#converse-roster .roster-filter.x {
+  background-position: right 3px center;
+}
+/* (jQ addClass:) if mouse is over the 'x' input area*/
+#converse-roster .roster-filter.onX {
+  cursor: pointer;
+}
+#converse-roster .roster-contacts {
+  margin: 0;
+  overflow-y: auto;
+  overflow-x: hidden;
+  max-height: 195px;
+  max-height: calc(100% - 67px);
 }
 #converse-roster .group-toggle {
   color: #666;
@@ -961,7 +989,7 @@ dl.add-converse-contact {
   text-shadow: 0 1px 0 #fafafa;
   clear: both;
 }
-#conversejs #converse-roster dd {
+#converse-roster dd {
   line-height: 16px;
 }
 #conversejs .group-toggle {
@@ -970,20 +998,20 @@ dl.add-converse-contact {
 }
 #conversejs .roster-group:hover,
 #conversejs dd.available-chatroom:hover,
-#conversejs #converse-roster dd:hover {
+#converse-roster dd:hover {
   background-color: #eee;
 }
-#conversejs #converse-roster dd a.decline-xmpp-request {
+#converse-roster dd a.decline-xmpp-request {
   margin-left: 5px;
 }
-#conversejs #converse-roster dd a.remove-xmpp-contact {
+#converse-roster dd a.remove-xmpp-contact {
   float: right;
   width: 22px;
   margin: 0;
   display: none;
   color: #4f4f4f;
 }
-#conversejs #converse-roster dd:hover a.remove-xmpp-contact {
+#converse-roster dd:hover a.remove-xmpp-contact {
   display: inline-block;
 }
 #conversejs .chatbox,

File diff ditekan karena terlalu besar
+ 0 - 0
css/converse.min.css


+ 8 - 2
docs/CHANGES.rst

@@ -1,8 +1,14 @@
 Changelog
 =========
 
-0.8 (2014-08-04)
-----------------
+0.8.1 (Unreleased)
+------------------
+
+* #212 Provide a live filter of the roster contacts. [jcbrand]
+
+
+0.8.0 (2014-08-04)
+------------------
 
 .. note::
     1. Converse.js is now relicensed under the `Mozilla Public License <http://www.mozilla.org/MPL/2.0/>`_.

+ 2 - 8
index.html

@@ -11,14 +11,8 @@
     <link type="text/css" rel="stylesheet" media="screen" href="components/bootstrap/dist/css/bootstrap.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="components/fontawesome/css/font-awesome.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
-    <link type="text/css" rel="stylesheet" media="screen" href="css/converse.min.css" />
-    <!-- Only for development: <script data-main="main" src="components/requirejs/require.js"></script> -->
-    <![if gte IE 9]>
-        <script src="builds/converse.website.min.js"></script>
-    <![endif]>
-    <!--[if lt IE 9]>
-        <script src="builds/converse.website-no-otr.min.js"></script>
-    <![endif]-->
+    <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
+    <script data-main="main" src="components/requirejs/require.js"></script>
 </head>
 
 <body id="page-top" data-spy="scroll" data-target=".navbar-custom">

+ 47 - 14
less/converse.less

@@ -831,7 +831,7 @@ dl.add-converse-contact {
     clear: right;
     height: 22px;
     width: 12px;
-    padding: 0px 5px 0 0;
+    padding: 0px 15px 0 0;
     color: rgb(79, 79, 79);
 }
 
@@ -902,7 +902,7 @@ dl.add-converse-contact {
 }
 
 #conversejs .controlbox-pane dt {
-    padding: 3px;
+    padding-bottom: 3px;
 }
 
 #conversejs .chatroom-form-container {
@@ -967,12 +967,12 @@ dl.add-converse-contact {
     text-overflow: ellipsis;
 }
 
-#conversejs #converse-roster span.req-contact-name {
+#converse-roster span.req-contact-name {
     width: 65%;
 }
 
-#conversejs #converse-roster span.pending-contact-name,
-#conversejs #converse-roster a.open-chat {
+#converse-roster span.pending-contact-name,
+#converse-roster a.open-chat {
     width: 80%;
 }
 
@@ -981,13 +981,46 @@ dl.add-converse-contact {
 }
 
 #converse-roster {
-    overflow-y: auto;
-    overflow-x: hidden;
     width: 100%;
     position: relative;
     margin: 0.5em 0 0 0;
-    height: 254px;
-    height: ~"calc(100% - 70px)";
+    height: 194px;
+    height: ~"calc(100% - 25px)";
+    overflow-x: hidden;
+}
+
+#converse-roster .roster-filter {
+    padding: 0;
+    margin: 0 0 5px 0.5em;
+    width: 111px;
+    height: 20px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAABNSURBVHjaXI7BDcAwCAMvyQjMyQ6dAbZiKfqoUK34g2zJh1dENIC7M8pMAPYdzAVY3d0ajNz9aypS/b5R6o+ZPdqoKgCq6h80KH3xDgBqNR97p8oAGQAAAABJRU5ErkJggg== ) no-repeat right -20px center;
+    border: 1px solid #999;
+    display: inline-block;
+}
+
+#converse-roster .filter-type {
+    height: 20px;
+    padding: 0;
+    margin: 0 0 0 -5px;
+}
+
+/* (jQ addClass:) if input has value: */
+#converse-roster .roster-filter.x {
+    background-position: right 3px center;
+}
+
+/* (jQ addClass:) if mouse is over the 'x' input area*/
+#converse-roster .roster-filter.onX{
+    cursor:pointer;
+}
+
+#converse-roster .roster-contacts {
+    margin: 0;
+    overflow-y: auto;
+    overflow-x: hidden;
+    max-height: 195px;
+    max-height: ~"calc(100% - 67px)";
 }
 
 #converse-roster .group-toggle {
@@ -1073,7 +1106,7 @@ dl.add-converse-contact {
     clear: both;
 }
 
-#conversejs #converse-roster dd {
+#converse-roster dd {
     line-height: 16px;
 }
 
@@ -1084,15 +1117,15 @@ dl.add-converse-contact {
 
 #conversejs .roster-group:hover,
 #conversejs dd.available-chatroom:hover,
-#conversejs #converse-roster dd:hover {
+#converse-roster dd:hover {
     background-color: #eee;
 }
 
-#conversejs #converse-roster dd a.decline-xmpp-request {
+#converse-roster dd a.decline-xmpp-request {
     margin-left: 5px;
 }
 
-#conversejs #converse-roster dd a.remove-xmpp-contact {
+#converse-roster dd a.remove-xmpp-contact {
     float: right;
     width: 22px;
     margin: 0;
@@ -1100,7 +1133,7 @@ dl.add-converse-contact {
     color: rgb(79, 79, 79);
 }
 
-#conversejs #converse-roster  dd:hover a.remove-xmpp-contact {
+#converse-roster  dd:hover a.remove-xmpp-contact {
     display: inline-block;
 }
 

+ 2 - 0
main.js

@@ -4,6 +4,7 @@ config = {
         "jquery": "components/jquery/dist/jquery",
         "jquery.browser": "components/jquery.browser/dist/jquery.browser",
         "jquery.easing": "components/jquery-easing-original/jquery.easing.1.3", // XXX: Only required for https://conversejs.org website
+        "utils": "src/utils",
         "bootstrap": "components/bootstrap/dist/js/bootstrap",                  // XXX: Only required for https://conversejs.org website
         "locales": "locale/locales",
         "underscore": "components/underscore/underscore",
@@ -74,6 +75,7 @@ config = {
         'bigint':               { deps: ['crypto'] },
         'jquery.browser':       { deps: ['jquery'] },
         'jquery.easing':        { deps: ['jquery'] },
+        'utils':                { deps: ['jquery'] },
         'strophe':              { deps: ['jquery'] },
         'strophe.disco':        { deps: ['strophe'] },
         'strophe.muc':          { deps: ['strophe', 'jquery'] },

+ 135 - 128
mockup/controlbox.html

@@ -114,139 +114,146 @@
                         </ul>
                     </dd>
                 </dl>
-                <dl id="converse-roster" style="display: block;">
-                    <dt class="roster-group" style="display: block;">
-                        <a href="#" data-group="Colleagues" class="group-toggle icon-opened" title="Click to hide these contacts">Colleagues</a>
-                    </dt>
-                    <dd class="online current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-online" title="This contact is online"></span>
-                            Victor Matfield
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="away current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-away" title="this contact is away"></span>
-                            William Winterbottom
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="dnd current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-dnd" title="This contact is busy"></span>
-                            Gary Teichmann
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
+                <div id="converse-roster">
+                    <input class="roster-filter" placeholder="Type to filter">
+                    <select class="filter-type">
+                        <option value="contacts">Contacts</option>
+                        <option value="groups">Groups</option>
+                    </select>
+                    <dl class="roster-contacts" style="display: block;">
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Colleagues" class="group-toggle icon-opened" title="Click to hide these contacts">Colleagues</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                Victor Matfield
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="away current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-away" title="this contact is away"></span>
+                                William Winterbottom
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="dnd current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-dnd" title="This contact is busy"></span>
+                                Gary Teichmann
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt class="roster-group" style="display: block;">
-                        <a href="#" data-group="Family" class="group-toggle icon-opened" title="Click to hide these contacts">Family</a>
-                    </dt>
-                    <dd class="away current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-away" title="this contact is away"></span>
-                            Allan Donald 
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="offline current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-offline" title="This contact is offline"></span>
-                            Corné Krige
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Family" class="group-toggle icon-opened" title="Click to hide these contacts">Family</a>
+                        </dt>
+                        <dd class="away current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-away" title="this contact is away"></span>
+                                Allan Donald 
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="offline current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-offline" title="This contact is offline"></span>
+                                Corné Krige
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt class="roster-group" style="display: block;">
-                        <a href="#" data-group="Friends" class="group-toggle icon-opened" title="Click to hide these contacts">Friends</a>
-                    </dt>
-                    <dd class="online current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-online" title="This contact is online"></span>
-                            John Smit
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="online current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-online" title="This contact is online"></span>
-                            Bakkies Botha
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Friends" class="group-toggle icon-opened" title="Click to hide these contacts">Friends</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                John Smit
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                Bakkies Botha
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt class="roster-group" style="display: block;">
-                        <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Ungrouped</a>
-                    </dt>
-                    <dd class="online current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                            <span class="icon-online" title="This contact is online"></span>
-                            James Small 
-                        </a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Ungrouped</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                James Small 
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt id="xmpp-contact-requests" style="display: block;">
-                        <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Contact Requests</a>
-                    </dt>
-                    <dd class="offline requesting-xmpp-contact">
-                        <span class="req-contact-name">Bob Skinstad</span>
-                        <span class="request-actions">
-                            <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
-                            <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
-                        </span>
-                    </dd>
-                    <dd class="offline requesting-xmpp-contact">
-                        <span class="req-contact-name">André Vos</span>
-                        <span class="request-actions">
-                            <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
-                            <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
-                        </span>
-                    </dd>
+                        <dt id="xmpp-contact-requests" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Contact Requests</a>
+                        </dt>
+                        <dd class="offline requesting-xmpp-contact">
+                            <span class="req-contact-name">Bob Skinstad</span>
+                            <span class="request-actions">
+                                <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
+                                <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
+                            </span>
+                        </dd>
+                        <dd class="offline requesting-xmpp-contact">
+                            <span class="req-contact-name">André Vos</span>
+                            <span class="request-actions">
+                                <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
+                                <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
+                            </span>
+                        </dd>
 
-                    <dt id="pending-xmpp-contacts" style="display: block;">
-                        <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Pending Contacts</a>
-                    </dt>
-                    <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Rassie Erasmus</span>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Victor Matfield</span>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                </dl>
-            </div>
-            <div id="chatrooms" style="display: none;">
-                <form class="add-chatroom" action="" method="post">
-                    <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
-                    <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
-                    <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
-                    <input type="submit" name="join" value="Join">
-                    <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
-                </form>
-                <dl id="available-chatrooms">
-                    <dt>Rooms on conference.opkode.im</dt>
-                    <dd class="available-chatroom">
-                        <a class="open-room" 
-                            data-room-jid="converse.js@conference.opkode.im"
-                            title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
-                        <a class="room-info icon-room-info" 
-                            data-room-jid="converse.js@conference.opkode.im" 
-                            title="Show more information on this room" href="#">&nbsp;</a>
-                        <div class="room-info">
-                            <p class="room-info"><strong>Description:</strong></p>
-                            <p class="room-info"><strong>Occupants:</strong> 2</p>
-                            <p class="room-info"><strong>Features:</strong> </p>
-                            <ul>
-                                <li class="room-info">Moderated</li><li class="room-info">Open room</li>
-                                <li class="room-info">Permanent room</li><li class="room-info">Public</li>
-                                <li class="room-info">Semi-anonymous</li>
-                                <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
-                                <p></p>
-                            </ul>
-                        </div>
-                    </dd>
-                </dl>
+                        <dt id="pending-xmpp-contacts" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Pending Contacts</a>
+                        </dt>
+                        <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Rassie Erasmus</span>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Victor Matfield</span>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                    </dl>
+                </div>
+                <div id="chatrooms" style="display: none;">
+                    <form class="add-chatroom" action="" method="post">
+                        <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
+                        <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
+                        <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
+                        <input type="submit" name="join" value="Join">
+                        <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
+                    </form>
+                    <dl id="available-chatrooms">
+                        <dt>Rooms on conference.opkode.im</dt>
+                        <dd class="available-chatroom">
+                            <a class="open-room" 
+                                data-room-jid="converse.js@conference.opkode.im"
+                                title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
+                            <a class="room-info icon-room-info" 
+                                data-room-jid="converse.js@conference.opkode.im" 
+                                title="Show more information on this room" href="#">&nbsp;</a>
+                            <div class="room-info">
+                                <p class="room-info"><strong>Description:</strong></p>
+                                <p class="room-info"><strong>Occupants:</strong> 2</p>
+                                <p class="room-info"><strong>Features:</strong> </p>
+                                <ul>
+                                    <li class="room-info">Moderated</li><li class="room-info">Open room</li>
+                                    <li class="room-info">Permanent room</li><li class="room-info">Public</li>
+                                    <li class="room-info">Semi-anonymous</li>
+                                    <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
+                                    <p></p>
+                                </ul>
+                            </div>
+                        </dd>
+                    </dl>
+                </div>
             </div>
         </div>
     </div>

+ 138 - 81
mockup/index.html

@@ -114,89 +114,146 @@
                         </ul>
                     </dd>
                 </dl>
-                <dl id="converse-roster" style="display: block;">
-                    <dt id="xmpp-contacts" style="display: block;">My contacts</dt>
-                    <dd class="online current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                        <span class="icon-online" title="This contact is online"></span>
-                        John Smit</a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="away current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                        <span class="icon-away" title="this contact is away"></span>
-                        Francois Pienaar</a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="dnd current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                        <span class="icon-dnd" title="This contact is busy"></span>
-                        Gary Teichmann</a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="offline current-xmpp-contact">
-                        <a class="open-chat" title="Click to chat with this contact" href="#">
-                        <span class="icon-offline" title="This contact is offline"></span>
-                        Corné Krige</a>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
+                <div id="converse-roster">
+                    <input class="roster-filter" placeholder="Type to filter">
+                    <select class="filter-type">
+                        <option value="contacts">Contacts</option>
+                        <option value="groups">Groups</option>
+                    </select>
+                    <dl class="roster-contacts" style="display: block;">
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Colleagues" class="group-toggle icon-opened" title="Click to hide these contacts">Colleagues</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                Victor Matfield
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="away current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-away" title="this contact is away"></span>
+                                William Winterbottom
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="dnd current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-dnd" title="This contact is busy"></span>
+                                Gary Teichmann
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt id="xmpp-contact-requests" style="display: block;">Contact requests</dt>
-                    <dd class="offline requesting-xmpp-contact">
-                        <span>Bob Skinstad</span>
-                        <span class="request-actions">
-                            <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
-                            <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
-                        </span>
-                    </dd>
-                    <dd class="offline requesting-xmpp-contact">
-                        <span>André Vos</span>
-                        <span class="request-actions">
-                            <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
-                            <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
-                        </span>
-                    </dd>
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Family" class="group-toggle icon-opened" title="Click to hide these contacts">Family</a>
+                        </dt>
+                        <dd class="away current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-away" title="this contact is away"></span>
+                                Allan Donald 
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="offline current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-offline" title="This contact is offline"></span>
+                                Corné Krige
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
 
-                    <dt id="pending-xmpp-contacts" style="display: block;">Pending contacts</dt>
-                    <dd class="offline pending-xmpp-contact"><span>Rassie Erasmus</span>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                    <dd class="offline pending-xmpp-contact"><span>Victor Matfield</span>
-                        <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
-                    </dd>
-                </dl>
-            </div>
-            <div id="chatrooms" style="display: none;">
-                <form class="add-chatroom" action="" method="post">
-                    <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
-                    <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
-                    <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
-                    <input type="submit" name="join" value="Join">
-                    <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
-                </form>
-                <dl id="available-chatrooms">
-                    <dt>Rooms on conference.opkode.im</dt>
-                    <dd class="available-chatroom">
-                        <a class="open-room" 
-                            data-room-jid="converse.js@conference.opkode.im"
-                            title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
-                        <a class="room-info icon-room-info" 
-                            data-room-jid="converse.js@conference.opkode.im" 
-                            title="Show more information on this room" href="#">&nbsp;</a>
-                        <div class="room-info">
-                            <p class="room-info"><strong>Description:</strong></p>
-                            <p class="room-info"><strong>Occupants:</strong> 2</p>
-                            <p class="room-info"><strong>Features:</strong> </p>
-                            <ul>
-                                <li class="room-info">Moderated</li><li class="room-info">Open room</li>
-                                <li class="room-info">Permanent room</li><li class="room-info">Public</li>
-                                <li class="room-info">Semi-anonymous</li>
-                                <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
-                                <p></p>
-                            </ul>
-                        </div>
-                    </dd>
-                </dl>
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" data-group="Friends" class="group-toggle icon-opened" title="Click to hide these contacts">Friends</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                John Smit
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                Bakkies Botha
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+
+                        <dt class="roster-group" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Ungrouped</a>
+                        </dt>
+                        <dd class="online current-xmpp-contact">
+                            <a class="open-chat" title="Click to chat with this contact" href="#">
+                                <span class="icon-online" title="This contact is online"></span>
+                                James Small 
+                            </a>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+
+                        <dt id="xmpp-contact-requests" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Contact Requests</a>
+                        </dt>
+                        <dd class="offline requesting-xmpp-contact">
+                            <span class="req-contact-name">Bob Skinstad</span>
+                            <span class="request-actions">
+                                <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
+                                <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
+                            </span>
+                        </dd>
+                        <dd class="offline requesting-xmpp-contact">
+                            <span class="req-contact-name">André Vos</span>
+                            <span class="request-actions">
+                                <a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
+                                <a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
+                            </span>
+                        </dd>
+
+                        <dt id="pending-xmpp-contacts" style="display: block;">
+                            <a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Pending Contacts</a>
+                        </dt>
+                        <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Rassie Erasmus</span>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                        <dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Victor Matfield</span>
+                            <a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
+                        </dd>
+                    </dl>
+                </div>
+                <div id="chatrooms" style="display: none;">
+                    <form class="add-chatroom" action="" method="post">
+                        <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
+                        <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
+                        <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
+                        <input type="submit" name="join" value="Join">
+                        <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
+                    </form>
+                    <dl id="available-chatrooms">
+                        <dt>Rooms on conference.opkode.im</dt>
+                        <dd class="available-chatroom">
+                            <a class="open-room" 
+                                data-room-jid="converse.js@conference.opkode.im"
+                                title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
+                            <a class="room-info icon-room-info" 
+                                data-room-jid="converse.js@conference.opkode.im" 
+                                title="Show more information on this room" href="#">&nbsp;</a>
+                            <div class="room-info">
+                                <p class="room-info"><strong>Description:</strong></p>
+                                <p class="room-info"><strong>Occupants:</strong> 2</p>
+                                <p class="room-info"><strong>Features:</strong> </p>
+                                <ul>
+                                    <li class="room-info">Moderated</li><li class="room-info">Open room</li>
+                                    <li class="room-info">Permanent room</li><li class="room-info">Public</li>
+                                    <li class="room-info">Semi-anonymous</li>
+                                    <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
+                                    <p></p>
+                                </ul>
+                            </div>
+                        </dd>
+                    </dl>
+                </div>
             </div>
         </div>
     </div>

+ 54 - 54
spec/chatbox.js

@@ -1,23 +1,23 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("Chatboxes", $.proxy(function(mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("Chatboxes", $.proxy(function(mock, test_utils) {
         describe("A Chatbox", $.proxy(function () {
             beforeEach(function () {
                 runs(function () {
-                    utils.closeAllChatBoxes();
-                    utils.removeControlBox();
-                    utils.clearBrowserStorage();
-                    utils.initConverse();
-                    utils.createContacts();
-                    utils.openControlBox();
-                    utils.openContactsPanel();
+                    test_utils.closeAllChatBoxes();
+                    test_utils.removeControlBox();
+                    test_utils.clearBrowserStorage();
+                    test_utils.initConverse();
+                    test_utils.createContacts();
+                    test_utils.openControlBox();
+                    test_utils.openContactsPanel();
                 });
             });
 
@@ -95,7 +95,7 @@
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
-                chatbox = utils.openChatBoxFor(contact_jid);
+                chatbox = test_utils.openChatBoxFor(contact_jid);
                 chatboxview = this.chatboxviews.get(contact_jid);
                 spyOn(chatboxview, 'focus');
                 $el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
@@ -109,11 +109,11 @@
                 spyOn(converse, 'emit');
                 spyOn(this.chatboxviews, 'trimChats');
                 runs(function () {
-                    utils.openControlBox();
+                    test_utils.openControlBox();
                 });
                 waits(250);
                 runs(function () {
-                    utils.openChatBoxes(6);
+                    test_utils.openChatBoxes(6);
                     expect(this.chatboxviews.trimChats).toHaveBeenCalled();
                     // We instantiate a new ChatBoxes collection, which by default
                     // will be empty.
@@ -136,7 +136,7 @@
             }, converse));
 
             it("can be closed by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
-                var chatbox = utils.openChatBoxes(1)[0],
+                var chatbox = test_utils.openChatBoxes(1)[0],
                     controlview = this.chatboxviews.get('controlbox'), // The controlbox is currently open
                     chatview = this.chatboxviews.get(chatbox.get('jid'));
                 spyOn(chatview, 'close').andCallThrough();
@@ -166,7 +166,7 @@
             }, converse));
 
             it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", function () {
-                var chatbox = utils.openChatBoxes(1)[0],
+                var chatbox = test_utils.openChatBoxes(1)[0],
                     chatview = this.chatboxviews.get(chatbox.get('jid')),
                     trimmed_chatboxes = this.minimized_chats,
                     trimmedview;
@@ -207,17 +207,17 @@
                 spyOn(converse.chatboxviews, 'trimChats');
                 this.chatboxes.browserStorage._clear();
                 runs(function () {
-                    utils.closeControlBox();
+                    test_utils.closeControlBox();
                 });
                 waits(250);
                 runs(function () {
                     expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
                     expect(converse.chatboxes.length).toEqual(0);
-                    utils.openChatBoxes(6);
+                    test_utils.openChatBoxes(6);
                     expect(converse.chatboxviews.trimChats).toHaveBeenCalled();
                     expect(converse.chatboxes.length).toEqual(6);
                     expect(converse.emit).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object));
-                    utils.closeAllChatBoxes();
+                    test_utils.closeAllChatBoxes();
                 });
                 waits(250);
                 runs(function () {
@@ -237,7 +237,7 @@
             describe("A chat toolbar", $.proxy(function () {
                 it("can be found on each chat box", $.proxy(function () {
                     var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var chatbox = this.chatboxes.get(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     expect(chatbox).toBeDefined();
@@ -249,7 +249,7 @@
 
                 it("contains a button for inserting emoticons", $.proxy(function () {
                     var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     var $toolbar = view.$el.find('ul.chat-toolbar');
                     var $textarea = view.$el.find('textarea.chat-textarea');
@@ -308,7 +308,7 @@
                 it("contains a button for starting an encrypted chat session", $.proxy(function () {
                     // TODO: More tests can be added here...
                     var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     var $toolbar = view.$el.find('ul.chat-toolbar');
                     expect($toolbar.children('li.toggle-otr').length).toBe(1);
@@ -336,7 +336,7 @@
                     // First check that the button doesn't show if it's not enabled
                     // via "visible_toolbar_buttons"
                     converse.visible_toolbar_buttons.call = false;
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     view = this.chatboxviews.get(contact_jid);
                     $toolbar = view.$el.find('ul.chat-toolbar');
                     callButton = $toolbar.find('.toggle-call');
@@ -345,7 +345,7 @@
                     // Now check that it's shown if enabled and that it emits
                     // callButtonClicked
                     converse.visible_toolbar_buttons.call = true; // enable the button
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     view = this.chatboxviews.get(contact_jid);
                     $toolbar = view.$el.find('ul.chat-toolbar');
                     callButton = $toolbar.find('.toggle-call');
@@ -360,7 +360,7 @@
                     // First check that the button doesn't show if it's not enabled
                     // via "visible_toolbar_buttons"
                     converse.visible_toolbar_buttons.clear = false;
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     view = this.chatboxviews.get(contact_jid);
                     view = this.chatboxviews.get(contact_jid);
                     $toolbar = view.$el.find('ul.chat-toolbar');
@@ -370,7 +370,7 @@
                     // Now check that it's shown if enabled and that it calls
                     // clearMessages
                     converse.visible_toolbar_buttons.clear = true; // enable the button
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     view = this.chatboxviews.get(contact_jid);
                     $toolbar = view.$el.find('ul.chat-toolbar');
                     clearButton = $toolbar.find('.toggle-clear');
@@ -387,7 +387,7 @@
 
                 beforeEach(function () {
                     runs(function () {
-                        utils.closeAllChatBoxes();
+                        test_utils.closeAllChatBoxes();
                     });
                     waits(250);
                     runs(function () {});
@@ -445,7 +445,7 @@
                     var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
                     spyOn(this, 'emit');
                     runs(function () {
-                        utils.openChatBoxFor(contact_jid);
+                        test_utils.openChatBoxFor(contact_jid);
                         var chatview = converse.chatboxviews.get(contact_jid);
                         expect(chatview.model.get('minimized')).toBeFalsy();
                         chatview.$el.find('.toggle-chatbox-button').click();
@@ -505,8 +505,8 @@
                     spyOn(converse, 'emit');
                     var contact_name = mock.cur_names[1];
                     var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
-                    utils.clearChatBoxMessages(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
+                    test_utils.clearChatBoxMessages(contact_jid);
                     var one_day_ago = moment();
                     one_day_ago.subtract('days', 1);
                     var message = 'This is a day old message';
@@ -574,7 +574,7 @@
                     spyOn(converse, 'emit');
                     var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                     runs(function () {
-                        utils.openChatBoxFor(contact_jid);
+                        test_utils.openChatBoxFor(contact_jid);
                     });
                     waits(250);
                     runs(function () {
@@ -582,7 +582,7 @@
                         var view = this.chatboxviews.get(contact_jid);
                         var message = 'This message is sent from this chatbox';
                         spyOn(view, 'sendMessage').andCallThrough();
-                        utils.sendMessage(view, message);
+                        test_utils.sendMessage(view, message);
                         expect(view.sendMessage).toHaveBeenCalled();
                         expect(view.model.messages.length, 2);
                         expect(converse.emit.mostRecentCall.args, ['messageSend', message]);
@@ -592,11 +592,11 @@
 
                 it("is sanitized to prevent Javascript injection attacks", $.proxy(function () {
                     var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     var message = '<p>This message contains <em>some</em> <b>markup</b></p>';
                     spyOn(view, 'sendMessage').andCallThrough();
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
@@ -605,11 +605,11 @@
 
                 it("can contain hyperlinks, which will be clickable", $.proxy(function () {
                     var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     var message = 'This message contains a hyperlink: www.opkode.com';
                     spyOn(view, 'sendMessage').andCallThrough();
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
@@ -618,7 +618,7 @@
 
                 it("should display emoticons correctly", $.proxy(function () {
                     var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     var messages = [':)', ';)', ':D', ':P', '8)', '>:)', ':S', ':\\', '>:(', ':(', ':O', '(^.^)b', '<3'];
                     var emoticons = [
@@ -633,7 +633,7 @@
                     spyOn(view, 'sendMessage').andCallThrough();
                     for (var i = 0; i < messages.length; i++) {
                         var message = messages[i];
-                        utils.sendMessage(view, message);
+                        test_utils.sendMessage(view, message);
                         expect(view.sendMessage).toHaveBeenCalled();
                         var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                         expect(msg.html()).toEqual(emoticons[i]);
@@ -642,33 +642,33 @@
 
                 it("will have properly escaped URLs", $.proxy(function () {
                     var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    utils.openChatBoxFor(contact_jid);
+                    test_utils.openChatBoxFor(contact_jid);
                     var view = this.chatboxviews.get(contact_jid);
                     spyOn(view, 'sendMessage').andCallThrough();
 
                     var message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
                     expect(msg.html()).toEqual('<a target="_blank" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
 
                     message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
                     expect(msg.html()).toEqual('<a target="_blank" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
 
                     message = "https://en.wikipedia.org/wiki/Ender's_Game";
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
                     expect(msg.html()).toEqual('<a target="_blank" href="https://en.wikipedia.org/wiki/Ender%27s_Game">https://en.wikipedia.org/wiki/Ender\'s_Game</a>');
 
                     message = "https://en.wikipedia.org/wiki/Ender%27s_Game";
-                    utils.sendMessage(view, message);
+                    test_utils.sendMessage(view, message);
                     expect(view.sendMessage).toHaveBeenCalled();
                     msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
                     expect(msg.text()).toEqual(message);
@@ -680,24 +680,24 @@
 
         describe("Special Messages", $.proxy(function () {
             beforeEach(function () {
-                utils.closeAllChatBoxes();
-                utils.removeControlBox();
+                test_utils.closeAllChatBoxes();
+                test_utils.removeControlBox();
                 converse.roster.browserStorage._clear();
-                utils.initConverse();
-                utils.createContacts();
-                utils.openControlBox();
-                utils.openContactsPanel();
+                test_utils.initConverse();
+                test_utils.createContacts();
+                test_utils.openControlBox();
+                test_utils.openContactsPanel();
             });
 
             it("'/clear' can be used to clear messages in a conversation", $.proxy(function () {
                 spyOn(converse, 'emit');
                 var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                utils.openChatBoxFor(contact_jid);
+                test_utils.openChatBoxFor(contact_jid);
                 var view = this.chatboxviews.get(contact_jid);
                 var message = 'This message is another sent from this chatbox';
                 // Lets make sure there is at least one message already
                 // (e.g for when this test is run on its own).
-                utils.sendMessage(view, message);
+                test_utils.sendMessage(view, message);
                 expect(view.model.messages.length > 0).toBeTruthy();
                 expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
                 expect(converse.emit).toHaveBeenCalledWith('messageSend', message);
@@ -709,7 +709,7 @@
                 spyOn(window, 'confirm').andCallFake(function () {
                     return true;
                 });
-                utils.sendMessage(view, message);
+                test_utils.sendMessage(view, message);
                 expect(view.sendMessage).toHaveBeenCalled();
                 expect(view.clearMessages).toHaveBeenCalled();
                 expect(window.confirm).toHaveBeenCalled();
@@ -775,5 +775,5 @@
                 expect(this.msg_counter).toBe(0);
             }, converse));
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 11 - 11
spec/chatroom.js

@@ -1,25 +1,25 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("ChatRooms", $.proxy(function (mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("ChatRooms", $.proxy(function (mock, test_utils) {
         describe("A Chat Room", $.proxy(function () {
             beforeEach(function () {
                 runs(function () {
-                    utils.closeAllChatBoxes();
+                    test_utils.closeAllChatBoxes();
                 });
                 waits(250);
                 runs(function () {
-                    utils.openControlBox();
+                    test_utils.openControlBox();
                 });
                 waits(250);
                 runs(function () {
-                    utils.openRoomsPanel();
+                    test_utils.openRoomsPanel();
                 });
                 waits(501);
                 runs(function () {
@@ -35,7 +35,7 @@
                 });
                 waits(250);
                 runs(function () {
-                    utils.closeControlBox();
+                    test_utils.closeControlBox();
                 });
                 waits(250);
                 runs(function () {});
@@ -116,7 +116,7 @@
                 // We instantiate a new ChatBoxes collection, which by default
                 // will be empty.
                 spyOn(this.chatboxviews, 'trimChats');
-                utils.openControlBox();
+                test_utils.openControlBox();
                 var newchatboxes = new this.ChatBoxes();
                 expect(newchatboxes.length).toEqual(0);
                 // The chatboxes will then be fetched from browserStorage inside the
@@ -350,5 +350,5 @@
                 expect(view.$el.find('.chat-body p').text()).toBe("This room has reached it's maximum number of occupants");
             }, converse));
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 32 - 32
spec/controlbox.js

@@ -1,12 +1,12 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
+} (this, function (mock, test_utils) {
 
     var checkHeaderToggling = function ($header) {
         var $toggle = $header.find('a.group-toggle');
@@ -24,16 +24,16 @@
         expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
     };
 
-    describe("The Control Box", $.proxy(function (mock, utils) {
+    describe("The Control Box", $.proxy(function (mock, test_utils) {
         beforeEach(function () {
             runs(function () {
-                utils.openControlBox();
+                test_utils.openControlBox();
             });
         });
 
         it("can be opened by clicking a DOM element with class 'toggle-controlbox'", $.proxy(function () {
             runs(function () {
-                utils.closeControlBox();
+                test_utils.closeControlBox();
             });
             waits(50);
             runs(function () {
@@ -59,7 +59,7 @@
         describe("The Status Widget", $.proxy(function () {
 
             beforeEach(function () {
-                utils.openControlBox();
+                test_utils.openControlBox();
             });
 
             it("shows the user's chat status, which is online by default", $.proxy(function () {
@@ -120,9 +120,9 @@
                 });
             }, converse));
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 
-    describe("The Contacts Roster", $.proxy(function (mock, utils) {
+    describe("The Contacts Roster", $.proxy(function (mock, test_utils) {
 
         describe("A Roster Group", $.proxy(function () {
 
@@ -135,7 +135,7 @@
             });
 
             function _clearContacts () {
-                utils.clearBrowserStorage();
+                test_utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
             }
 
@@ -146,8 +146,8 @@
                 spyOn(this.rosterview, 'update').andCallThrough();
                 converse.rosterview.render();
 
-                utils.createContacts('pending');
-                utils.createContacts('requesting');
+                test_utils.createContacts('pending');
+                test_utils.createContacts('requesting');
                 var groups = {
                     'colleagues': 3,
                     'friends & acquaintences': 3,
@@ -243,14 +243,14 @@
 
         describe("Pending Contacts", $.proxy(function () {
             function _clearContacts () {
-                utils.clearBrowserStorage();
+                test_utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
             }
 
             function _addContacts () {
                 _clearContacts();
                 // Must be initialized, so that render is called and documentFragment set up.
-                utils.createContacts('pending').openControlBox().openContactsPanel();
+                test_utils.createContacts('pending').openControlBox().openContactsPanel();
             }
 
             it("can be collapsed under their own header", $.proxy(function () {
@@ -350,13 +350,13 @@
 
         describe("Existing Contacts", $.proxy(function () {
             function _clearContacts () {
-                utils.clearBrowserStorage();
+                test_utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
             }
 
             var _addContacts = function () {
                 _clearContacts();
-                utils.createContacts().openControlBox().openContactsPanel();
+                test_utils.createContacts().openControlBox().openContactsPanel();
             };
 
             it("can be collapsed under their own header", $.proxy(function () {
@@ -561,13 +561,13 @@
         describe("Requesting Contacts", $.proxy(function () {
             beforeEach($.proxy(function () {
                 runs(function () {
-                    utils.clearBrowserStorage();
+                    test_utils.clearBrowserStorage();
                     converse.rosterview.model.reset();
-                    utils.createContacts('requesting').openControlBox();
+                    test_utils.createContacts('requesting').openControlBox();
                 });
                 waits(50);
                 runs(function () {
-                    utils.openContactsPanel();
+                    test_utils.openContactsPanel();
                 });
             }, converse));
 
@@ -646,7 +646,7 @@
                 spyOn(converse, 'emit');
                 spyOn(this.connection.roster, 'unauthorize');
                 spyOn(window, 'confirm').andReturn(true);
-                utils.createContacts('requesting').openControlBox();
+                test_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+"')")
@@ -661,10 +661,10 @@
 
         describe("All Contacts", $.proxy(function () {
             beforeEach($.proxy(function () {
-                utils.clearBrowserStorage();
+                test_utils.clearBrowserStorage();
                 converse.rosterview.model.reset();
-                utils.createContacts('all').openControlBox();
-                utils.openContactsPanel();
+                test_utils.createContacts('all').openControlBox();
+                test_utils.openContactsPanel();
             }, converse));
 
             it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () {
@@ -700,9 +700,9 @@
                 }
             }, converse));
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 
-    describe("The 'Add Contact' widget", $.proxy(function (mock, utils) {
+    describe("The 'Add Contact' widget", $.proxy(function (mock, test_utils) {
         it("opens up an add form when you click on it", $.proxy(function () {
             var panel = this.chatboxviews.get('controlbox').contactspanel;
             spyOn(panel, 'toggleContactForm').andCallThrough();
@@ -713,16 +713,16 @@
             panel.$el.find('a.toggle-xmpp-contact-form').click();
         }, converse));
 
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 
     describe("The Controlbox Tabs", $.proxy(function () {
         beforeEach($.proxy(function () {
             runs(function () {
-                utils.closeAllChatBoxes();
+                test_utils.closeAllChatBoxes();
             });
             waits(50);
             runs(function () {
-                utils.openControlBox();
+                test_utils.openControlBox();
             });
         }, converse));
 
@@ -739,11 +739,11 @@
         describe("chatrooms panel", $.proxy(function () {
             beforeEach($.proxy(function () {
                 runs(function () {
-                    utils.closeAllChatBoxes();
+                    test_utils.closeAllChatBoxes();
                 });
                 waits(50);
                 runs(function () {
-                    utils.openControlBox();
+                    test_utils.openControlBox();
                 });
             }, converse));
 
@@ -792,5 +792,5 @@
                 }, converse));
             }, converse));
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 6 - 6
spec/converse.js

@@ -1,13 +1,13 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("Converse", $.proxy(function(mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("Converse", $.proxy(function(mock, test_utils) {
 
         beforeEach($.proxy(function () {
             window.localStorage.clear();
@@ -43,5 +43,5 @@
             // Restore the connection
             converse.connection = old_connection;
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 6 - 6
spec/eventemitter.js

@@ -1,13 +1,13 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("The Converse Event Emitter", $.proxy(function(mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("The Converse Event Emitter", $.proxy(function(mock, test_utils) {
         window.localStorage.clear();
         window.sessionStorage.clear();
 
@@ -64,5 +64,5 @@
             expect(this.anotherCallback.callCount, 3);
             expect(this.neverCalled).not.toHaveBeenCalled();
         });
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 16 - 16
spec/minchats.js

@@ -1,23 +1,23 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("The Minimized Chats Widget", $.proxy(function(mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("The Minimized Chats Widget", $.proxy(function(mock, test_utils) {
 
         beforeEach(function () {
             runs(function () {
-                utils.closeAllChatBoxes();
-                utils.removeControlBox();
+                test_utils.closeAllChatBoxes();
+                test_utils.removeControlBox();
                 converse.roster.browserStorage._clear();
-                utils.initConverse();
-                utils.createContacts();
-                utils.openControlBox();
-                utils.openContactsPanel();
+                test_utils.initConverse();
+                test_utils.createContacts();
+                test_utils.openControlBox();
+                test_utils.openContactsPanel();
                 converse.minimized_chats.toggleview.model.browserStorage._clear();
                 converse.minimized_chats.initToggle();
             });
@@ -26,7 +26,7 @@
         it("shows chats that have been minimized",  $.proxy(function () {
             var contact_jid, chatview;
             contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            utils.openChatBoxFor(contact_jid);
+            test_utils.openChatBoxFor(contact_jid);
             chatview = converse.chatboxviews.get(contact_jid);
             expect(chatview.model.get('minimized')).toBeFalsy();
             expect(this.minimized_chats.$el.is(':visible')).toBeFalsy();
@@ -37,7 +37,7 @@
             expect(this.minimized_chats.keys()[0]).toBe(contact_jid);
 
             contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
-            utils.openChatBoxFor(contact_jid);
+            test_utils.openChatBoxFor(contact_jid);
             chatview = converse.chatboxviews.get(contact_jid);
             expect(chatview.model.get('minimized')).toBeFalsy();
             chatview.$el.find('.toggle-chatbox-button').click();
@@ -49,7 +49,7 @@
 
         it("can be toggled to hide or show minimized chats",  $.proxy(function () {
             var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            utils.openChatBoxFor(contact_jid);
+            test_utils.openChatBoxFor(contact_jid);
             var chatview = converse.chatboxviews.get(contact_jid);
             expect(this.minimized_chats.$el.is(':visible')).toBeFalsy();
             chatview.model.set({'minimized': true});
@@ -70,7 +70,7 @@
             expect(this.minimized_chats.toggleview.$('.unread-message-count').is(':visible')).toBeFalsy();
             for (i=0; i<3; i++) {
                 contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
-                utils.openChatBoxFor(contact_jid);
+                test_utils.openChatBoxFor(contact_jid);
                 chatview = converse.chatboxviews.get(contact_jid);
                 chatview.model.set({'minimized': true});
                 msg = $msg({
@@ -86,5 +86,5 @@
             }
         }, converse));
 
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 6 - 6
spec/otr.js

@@ -1,13 +1,13 @@
 (function (root, factory) {
     define([
         "mock",
-        "utils"
-        ], function (mock, utils) {
-            return factory(mock, utils);
+        "test_utils"
+        ], function (mock, test_utils) {
+            return factory(mock, test_utils);
         }
     );
-} (this, function (mock, utils) {
-    return describe("The OTR module", $.proxy(function(mock, utils) {
+} (this, function (mock, test_utils) {
+    return describe("The OTR module", $.proxy(function(mock, test_utils) {
 
         beforeEach($.proxy(function () {
             window.localStorage.clear();
@@ -36,5 +36,5 @@
             // Clean up
             this.prebind = false;
         }, converse));
-    }, converse, mock, utils));
+    }, converse, mock, test_utils));
 }));

+ 1 - 0
src/build-no-locales-no-otr.js

@@ -25,6 +25,7 @@
         "strophe.disco": "components/strophe.disco/index",
         "converse-dependencies": "src/deps-no-otr",
         "jquery.browser": "components/jquery.browser/dist/jquery.browser",
+        "utils": "src/utils",
         "moment":"components/momentjs/moment",
         "converse-templates":"src/templates",
         "tpl": "components/requirejs-tpl-jcbrand/tpl",

+ 1 - 0
src/build-no-otr.js

@@ -29,6 +29,7 @@
         "ru": "locale/ru/LC_MESSAGES/ru",
         "zh": "locale/zh/LC_MESSAGES/zh",
         "jquery.browser": "components/jquery.browser/dist/jquery.browser",
+        "utils": "src/utils",
         "underscore": "components/underscore/underscore",
         "backbone": "components/backbone/backbone",
         "backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",

+ 1 - 0
src/build-website-no-otr.js

@@ -29,6 +29,7 @@
         "ru": "locale/ru/LC_MESSAGES/ru",
         "zh": "locale/zh/LC_MESSAGES/zh",
         "jquery.browser": "components/jquery.browser/dist/jquery.browser",
+        "utils": "src/utils",
         "underscore": "components/underscore/underscore",
         "backbone": "components/backbone/backbone",
         "backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",

+ 1 - 0
src/build-website.js

@@ -35,6 +35,7 @@
         "backbone.overview": "components/backbone.overview/backbone.overview",
         "bootstrap": "components/bootstrap/dist/js/bootstrap",                  // XXX: Only required for https://conversejs.org website
         "jquery.easing": "components/jquery-easing-original/jquery.easing.1.3", // XXX: Only required for https://conversejs.org website
+        "utils": "src/utils",
         "strophe": "components/strophe/strophe",
         "strophe.muc": "components/strophe.muc/index",
         "strophe.roster": "components/strophe.roster/index",

+ 1 - 0
src/build.js

@@ -29,6 +29,7 @@
         "ru": "locale/ru/LC_MESSAGES/ru",
         "zh": "locale/zh/LC_MESSAGES/zh",
         "jquery.browser": "components/jquery.browser/dist/jquery.browser",
+        "utils": "src/utils",
         "underscore": "components/underscore/underscore",
         "backbone": "components/backbone/backbone",
         "backbone.browserStorage": "components/backbone.browserStorage/backbone.browserStorage",

+ 1 - 0
src/deps-full.js

@@ -5,6 +5,7 @@ define("converse-dependencies", [
     "backbone.browserStorage",
     "backbone.overview",
     "jquery.browser",
+    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",

+ 1 - 0
src/deps-no-otr.js

@@ -4,6 +4,7 @@ define("converse-dependencies", [
     "backbone.browserStorage",
     "backbone.overview",
     "jquery.browser",
+    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",

+ 1 - 0
src/deps-website-no-otr.js

@@ -6,6 +6,7 @@ define("converse-dependencies", [
     "backbone.overview",
     "jquery.browser",
     "jquery.easing", // XXX: Can be removed, only for https://conversejs.org
+    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",

+ 3 - 2
src/deps-website.js

@@ -2,11 +2,12 @@ define("converse-dependencies", [
     "otr",
     "moment",
     "locales",
-    "bootstrap", // XXX: Can be removed, only for https://conversejs.org
+    "bootstrap", // XXX: Only for https://conversejs.org
     "backbone.browserStorage",
     "backbone.overview",
     "jquery.browser",
-    "jquery.easing", // XXX: Can be removed, only for https://conversejs.org
+    "jquery.easing", // XXX: Only for https://conversejs.org
+    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",

+ 8 - 6
src/templates.js

@@ -32,6 +32,7 @@ define("converse-templates", [
     "tpl!src/templates/room_description",
     "tpl!src/templates/room_item",
     "tpl!src/templates/room_panel",
+    "tpl!src/templates/roster",
     "tpl!src/templates/roster_item",
     "tpl!src/templates/select_option",
     "tpl!src/templates/status_option",
@@ -73,11 +74,12 @@ define("converse-templates", [
         room_description:       arguments[30],
         room_item:              arguments[31],
         room_panel:             arguments[32],
-        roster_item:            arguments[33],
-        select_option:          arguments[34],
-        status_option:          arguments[35],
-        toggle_chats:           arguments[36],
-        toolbar:                arguments[37],
-        trimmed_chat:           arguments[38]
+        roster:                 arguments[33],
+        roster_item:            arguments[34],
+        select_option:          arguments[35],
+        status_option:          arguments[36],
+        toggle_chats:           arguments[37],
+        toolbar:                arguments[38],
+        trimmed_chat:           arguments[39]
     };
 });

+ 6 - 0
src/templates/roster.html

@@ -0,0 +1,6 @@
+<input class="roster-filter" placeholder="{{placeholder}}">
+<select class="filter-type">
+    <option value="contacts">{{label_contacts}}</option>
+    <option value="groups">{{label_groups}}</option>
+</select>
+<dl class="roster-contacts" style="display: block;">

+ 10 - 0
src/utils.js

@@ -0,0 +1,10 @@
+jQuery.fn.hasScrollBar = function() {
+    if (!$.contains(document, this.get(0))) {
+        return false;
+    }
+    if(this.parent().height() < this.get(0).scrollHeight) {
+        return true;
+    }
+    return false;
+};
+

+ 1 - 1
tests/main.js

@@ -1,6 +1,6 @@
 // Extra test dependencies
 config.paths.mock = "tests/mock";
-config.paths.utils = "tests/utils";
+config.paths.test_utils = "tests/utils";
 config.paths.jasmine = "components/jasmine/lib/jasmine-core/jasmine";
 config.paths["jasmine-html"] = "components/jasmine/lib/jasmine-core/jasmine-html";
 config.paths["console-runner"] = "node_modules/phantom-jasmine/lib/console-runner";

+ 1 - 1
tests/utils.js

@@ -1,5 +1,5 @@
 (function (root, factory) {
-    define("utils", [
+    define("test_utils", [
         'jquery',
         'mock'
     ],

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini