Prechádzať zdrojové kódy

Add jshint checking and fix errors.

JC Brand 9 rokov pred
rodič
commit
e835a25184

+ 3 - 22
Gruntfile.js

@@ -1,6 +1,6 @@
+/*global __dirname, process */
 module.exports = function(grunt) {
     var path = require('path');
-    var cfg = require('./package.json');
     grunt.initConfig({
         jst: {
             compile: {
@@ -36,20 +36,6 @@ module.exports = function(grunt) {
             }
         },
 
-        jshint: {
-            options: {
-                trailing: true
-            },
-            target: {
-                src : [
-                    'converse.js',
-                    'mock.js',
-                    'main.js',
-                    'tests_main.js',
-                    'spec/*.js'
-                ]
-            }
-        },
         cssmin: {
             options: {
                 banner: "/*"+
@@ -70,7 +56,6 @@ module.exports = function(grunt) {
         }
     });
     grunt.loadNpmTasks('grunt-contrib-cssmin');
-    grunt.loadNpmTasks('grunt-contrib-jshint');
     grunt.loadNpmTasks('grunt-contrib-jst');
     grunt.loadNpmTasks('grunt-json');
     grunt.loadNpmTasks('grunt-contrib-requirejs');
@@ -131,13 +116,9 @@ module.exports = function(grunt) {
              rjs + ' -o src/build-no-locales-no-otr.js optimize=none out=builds/converse-no-locales-no-otr.js && ' +
              rjs + ' -o src/build-no-otr.js &&' +
              rjs + ' -o src/build-no-otr.js optimize=none out=builds/converse-no-otr.js', callback);
-        // XXX: It might be possible to not have separate build config files. For example:
-        // 'r.js -o src/build.js paths.converse-dependencies=src/deps-no-otr paths.locales=locale/nolocales out=builds/converse-no-locales-no-otr.min.js'
+            // XXX: It might be possible to not have separate build config files. For example:
+            // 'r.js -o src/build.js paths.converse-dependencies=src/deps-no-otr paths.locales=locale/nolocales out=builds/converse-no-locales-no-otr.min.js'
     });
 
     grunt.registerTask('minify', 'Create a new minified builds', ['cssmin', 'jsmin']);
-
-    grunt.registerTask('check', 'Perform all checks (e.g. before releasing)', function () {
-        grunt.task.run('jshint', 'test');
-    });
 };

+ 21 - 7
Makefile

@@ -1,18 +1,29 @@
 # You can set these variables from the command line.
 BOWER           ?= node_modules/.bin/bower
 BUILDDIR        = ./docs
+BUNDLE          ?= ./.bundle/bin/bundle
+GRUNT           ?= ./node_modules/.bin/grunt
+HTTPSERVE		?= ./node_modules/.bin/http-server
+JSHINT 			?= ./node_modules/.bin/jshint
 PAPER           =
 PHANTOMJS       ?= ./node_modules/.bin/phantomjs
-SPHINXBUILD     ?= ./bin/sphinx-build
-SPHINXOPTS      =
 PO2JSON         ?= ./node_modules/.bin/po2json
 SASS            ?= ./.bundle/bin/sass
-BUNDLE          ?= ./.bundle/bin/bundle
-GRUNT           ?= ./node_modules/.bin/grunt
-HTTPSERVE		?= ./node_modules/.bin/http-server
+SPHINXBUILD     ?= ./bin/sphinx-build
+SPHINXOPTS      =
 
 # Internal variables.
 ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ./docs/source
+SOURCES	= $(wildcard *.js) $(wildcard spec/*.js) $(wildcard src/*.js)
+JSHINTEXCEPTIONS = $(GENERATED) \
+		   src/otr.js \
+		   src/crypto.js \
+		   src/build-no-jquery.js \
+		   src/build-no-locales-no-otr.js \
+		   src/build-no-otr.js \
+		   src/build.js \
+		   src/bigint.js
+CHECKSOURCES	= $(filter-out $(JSHINTEXCEPTIONS),$(SOURCES))
 
 .PHONY: help
 help:
@@ -135,9 +146,12 @@ build:: stamp-npm
 ########################################################################
 ## Tests
 
+.PHONY: jshint
+jshint: stamp-npm
+	$(JSHINT) --config jshintrc $(CHECKSOURCES)
+
 .PHONY: watch
-check: stamp-npm
-	$(GRUNT) jshint
+check: stamp-npm jshint
 	$(PHANTOMJS) node_modules/phantom-jasmine/lib/run_jasmine_test.coffee tests.html
 
 ########################################################################

+ 96 - 95
converse.js

@@ -3,6 +3,9 @@
 //
 // Copyright (c) 2012-2015, Jan-Carel Brand <jc@opkode.com>
 // Licensed under the Mozilla Public License (MPLv2)
+//
+/*global Backbone, CryptoJS, crypto, define, window, jQuery, setTimeout, clearTimeout, document, templates, _,
+  $iq, $msg, $pres, $build, DSA, OTR, Strophe, moment, utils, b64_sha1, locales */
 
 (function (root, factory) {
     if (typeof define === 'function' && define.amd) {
@@ -50,9 +53,6 @@
      * Cannot use this due to Safari bug.
      * See https://github.com/jcbrand/converse.js/issues/196
      */
-    if (typeof console === "undefined" || typeof console.log === "undefined") {
-        console = { log: function () {}, error: function () {} };
-    }
 
     // Use Mustache style syntax for variable interpolation
     /* Configuration of underscore templates (this config is distinct to the
@@ -228,7 +228,7 @@
         // Detect support for the user's locale
         // ------------------------------------
         this.isConverseLocale = function (locale) { return typeof locales[locale] !== "undefined"; };
-        this.isMomentLocale = function (locale) { return moment.locale() != moment.locale(locale); };
+        this.isMomentLocale = function (locale) { return moment.locale() !== moment.locale(locale); };
 
         this.isLocaleAvailable = function (locale, available) {
             /* Check whether the locale or sub locale (e.g. en-US, en) is supported.
@@ -240,7 +240,7 @@
                 return locale;
             } else {
                 var sublocale = locale.split("-")[0];
-                if (sublocale != locale && available(sublocale)) {
+                if (sublocale !== locale && available(sublocale)) {
                     return sublocale;
                 }
             }
@@ -517,11 +517,17 @@
         };
 
         this.log = function (txt, level) {
+            var logger;
+            if (typeof console === "undefined" || typeof console.log === "undefined") {
+                logger = { log: function () {}, error: function () {} };
+            } else {
+                logger = console;
+            }
             if (this.debug) {
-                if (level == 'error') {
-                    console.log('ERROR: '+txt);
+                if (level === 'error') {
+                    logger.log('ERROR: '+txt);
                 } else {
-                    console.log(txt);
+                    logger.log(txt);
                 }
             }
         };
@@ -626,7 +632,7 @@
                     converse.onConnected();
                 }
             } else if (status === Strophe.Status.DISCONNECTED) {
-                if (converse.disconnection_cause == Strophe.Status.CONNFAIL && converse.auto_reconnect) {
+                if (converse.disconnection_cause === Strophe.Status.CONNFAIL && converse.auto_reconnect) {
                     converse.reconnect(condition);
                 } else {
                     converse.renderLoginPanel();
@@ -674,14 +680,14 @@
 
         this.updateMsgCounter = function () {
             if (this.msg_counter > 0) {
-                if (document.title.search(/^Messages \(\d+\) /) == -1) {
+                if (document.title.search(/^Messages \(\d+\) /) === -1) {
                     document.title = "Messages (" + this.msg_counter + ") " + document.title;
                 } else {
                     document.title = document.title.replace(/^Messages \(\d+\) /, "Messages (" + this.msg_counter + ") ");
                 }
                 window.blur();
                 window.focus();
-            } else if (document.title.search(/^Messages \(\d+\) /) != -1) {
+            } else if (document.title.search(/^Messages \(\d+\) /) !== -1) {
                 document.title = document.title.replace(/^Messages \(\d+\) /, "");
             }
         };
@@ -766,7 +772,7 @@
             }.bind(this));
 
             $(window).on("blur focus", function (ev) {
-                if ((this.windowState != ev.type) && (ev.type == 'focus')) {
+                if ((this.windowState !== ev.type) && (ev.type === 'focus')) {
                     converse.clearMsgCounter();
                 }
                 this.windowState = ev.type;
@@ -918,7 +924,7 @@
                 }
             },
 
-            generatePrivateKey: function () {
+            generatePrivateKey: function (instance_tag) {
                 var key = new DSA();
                 var jid = converse.connection.jid;
                 if (converse.cache_otr_key) {
@@ -997,7 +1003,7 @@
 
             getSession: function (callback) {
                 var cipher = CryptoJS.lib.PasswordBasedCipher;
-                var result, pass, instance_tag, saved_key, pass_check;
+                var pass, instance_tag, saved_key, pass_check;
                 if (converse.cache_otr_key) {
                     pass = converse.otr.getSessionPassphrase();
                     if (typeof pass !== "undefined") {
@@ -1027,9 +1033,10 @@
                     true // show spinner
                 );
                 setTimeout(function () {
+                    var instance_tag = OTR.makeInstanceTag();
                     callback({
-                        'key': converse.otr.generatePrivateKey.apply(this),
-                        'instance_tag': OTR.makeInstanceTag()
+                        'key': converse.otr.generatePrivateKey.call(this, instance_tag),
+                        'instance_tag': instance_tag
                     });
                 }, 500);
             },
@@ -1084,7 +1091,7 @@
                 // query message from our contact. Otherwise, it is us who will
                 // send the query message to them.
                 this.save({'otr_status': UNENCRYPTED});
-                var session = this.getSession(function (session) {
+                this.getSession(function (session) {
                     this.otr = new OTR({
                         fragment_size: 140,
                         send_interval: 200,
@@ -1147,7 +1154,7 @@
                 } else {
                     time = moment().format();
                 }
-                if ((is_groupchat && from === this.get('nick')) || (!is_groupchat && from == converse.bare_jid)) {
+                if ((is_groupchat && from === this.get('nick')) || (!is_groupchat && from === converse.bare_jid)) {
                     sender = 'me';
                 } else {
                     sender = 'them';
@@ -1324,7 +1331,7 @@
                             }
                         }
                     }.bind(this),
-                    function (iq) {
+                    function () {
                         this.clearSpinner();
                         converse.log("Error while trying to fetch archived messages", "error");
                     }.bind(this)
@@ -1341,7 +1348,7 @@
                  * We need this information for the drag-resizing feature.
                  */
                 var $flyout = this.$el.find('.box-flyout');
-                if (typeof this.model.get('height') == 'undefined') {
+                if (typeof this.model.get('height') === 'undefined') {
                     var height = $flyout.height();
                     var width = $flyout.width();
                     this.model.set('height', height);
@@ -1531,7 +1538,7 @@
                 }
                 this.$content.find('div.chat-event').remove();
 
-                if (this.is_chatroom && attrs.sender == 'them' && (new RegExp("\\b"+this.model.get('nick')+"\\b")).test(text)) {
+                if (this.is_chatroom && attrs.sender === 'them' && (new RegExp("\\b"+this.model.get('nick')+"\\b")).test(text)) {
                     // Add special class to mark groupchat messages in which we
                     // are mentioned.
                     extra_classes += ' mentioned';
@@ -1585,7 +1592,7 @@
                 } else {
                     this.showMessage(_.clone(message.attributes));
                 }
-                if ((message.get('sender') != 'me') && (converse.windowState == 'blur')) {
+                if ((message.get('sender') !== 'me') && (converse.windowState === 'blur')) {
                     converse.incrementMsgCounter();
                 }
                 if (!this.model.get('minimized') && !this.$el.is(':visible')) {
@@ -1605,18 +1612,19 @@
                     .c('body').t(message.get('message')).up()
                     .c(ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
 
-                if (this.model.get('otr_status') != UNENCRYPTED) {
+                if (this.model.get('otr_status') !== UNENCRYPTED) {
                     // OTR messages aren't carbon copied
                     messageStanza.c('private', {'xmlns': Strophe.NS.CARBONS});
                 }
                 converse.connection.send(messageStanza);
                 if (converse.forward_messages) {
                     // Forward the message, so that other connected resources are also aware of it.
-                    var forwarded = $msg({ to: converse.bare_jid, type: 'chat', id: message.get('msgid') })
-                                    .c('forwarded', {xmlns:'urn:xmpp:forward:0'})
-                                    .c('delay', {xmns:'urn:xmpp:delay',stamp:timestamp}).up()
-                                    .cnode(messageStanza.tree());
-                    converse.connection.send(forwarded);
+                    converse.connection.send(
+                        $msg({ to: converse.bare_jid, type: 'chat', id: message.get('msgid') })
+                        .c('forwarded', {xmlns:'urn:xmpp:forward:0'})
+                        .c('delay', {xmns:'urn:xmpp:delay',stamp:(new Date()).getTime()}).up()
+                        .cnode(messageStanza.tree())
+                    );
                 }
             },
 
@@ -1701,7 +1709,7 @@
                     this.chat_state_timeout = setTimeout(
                             this.setChatState.bind(this), converse.TIMEOUTS.INACTIVE, INACTIVE);
                 }
-                if (!no_save && this.model.get('chat_state') != state) {
+                if (!no_save && this.model.get('chat_state') !== state) {
                     this.model.set('chat_state', state);
                 }
                 return this;
@@ -1711,7 +1719,7 @@
                 /* Event handler for when a key is pressed in a chat box textarea.
                  */
                 var $textarea = $(ev.target), message;
-                if (ev.keyCode == KEY.ENTER) {
+                if (ev.keyCode === KEY.ENTER) {
                     ev.preventDefault();
                     message = $textarea.val();
                     $textarea.val('').focus();
@@ -1727,7 +1735,7 @@
                 } else if (!this.model.get('chatroom')) { // chat state data is currently only for single user chat
                     // Set chat state to composing if keyCode is not a forward-slash
                     // (which would imply an internal command and not a message).
-                    this.setChatState(COMPOSING, ev.keyCode==KEY.FORWARD_SLASH);
+                    this.setChatState(COMPOSING, ev.keyCode === KEY.FORWARD_SLASH);
                 }
             },
 
@@ -1828,20 +1836,20 @@
             },
 
             showOTRError: function (msg) {
-                if (msg == 'Message cannot be sent at this time.') {
+                if (msg === 'Message cannot be sent at this time.') {
                     this.showHelpMessages(
                         [__('Your message could not be sent')], 'error');
-                } else if (msg == 'Received an unencrypted message.') {
+                } else if (msg === 'Received an unencrypted message.') {
                     this.showHelpMessages(
                         [__('We received an unencrypted message')], 'error');
-                } else if (msg == 'Received an unreadable encrypted message.') {
+                } else if (msg === 'Received an unreadable encrypted message.') {
                     this.showHelpMessages(
                         [__('We received an unreadable encrypted message')],
                         'error');
                 } else {
                     this.showHelpMessages(['Encryption error occured: '+msg], 'error');
                 }
-                console.log("OTR ERROR:"+msg);
+                converse.log("OTR ERROR:"+msg);
             },
 
             startOTRFromToolbar: function (ev) {
@@ -1917,7 +1925,7 @@
                 converse.emit('contactStatusMessageChanged', item.attributes, item.get('status'));
             },
 
-            onOTRStatusChanged: function (item) {
+            onOTRStatusChanged: function () {
                 this.renderToolbar().informOTRChange();
             },
 
@@ -1997,13 +2005,13 @@
             informOTRChange: function () {
                 var data = this.model.toJSON();
                 var msgs = [];
-                if (data.otr_status == UNENCRYPTED) {
+                if (data.otr_status === UNENCRYPTED) {
                     msgs.push(__("Your messages are not encrypted anymore"));
-                } else if (data.otr_status == UNVERIFIED) {
+                } else if (data.otr_status === UNVERIFIED) {
                     msgs.push(__("Your messages are now encrypted but your contact's identity has not been verified."));
-                } else if (data.otr_status == VERIFIED) {
+                } else if (data.otr_status === VERIFIED) {
                     msgs.push(__("Your contact's identify has been verified."));
-                } else if (data.otr_status == FINISHED) {
+                } else if (data.otr_status === FINISHED) {
                     msgs.push(__("Your contact has ended encryption on their end, you should do the same."));
                 }
                 return this.showHelpMessages(msgs, 'info', false);
@@ -2012,13 +2020,13 @@
             renderToolbar: function () {
                 if (converse.show_toolbar) {
                     var data = this.model.toJSON();
-                    if (data.otr_status == UNENCRYPTED) {
+                    if (data.otr_status === UNENCRYPTED) {
                         data.otr_tooltip = __('Your messages are not encrypted. Click here to enable OTR encryption.');
-                    } else if (data.otr_status == UNVERIFIED) {
+                    } else if (data.otr_status === UNVERIFIED) {
                         data.otr_tooltip = __('Your messages are encrypted, but your contact has not been verified.');
-                    } else if (data.otr_status == VERIFIED) {
+                    } else if (data.otr_status === VERIFIED) {
                         data.otr_tooltip = __('Your messages are encrypted and your contact verified.');
-                    } else if (data.otr_status == FINISHED) {
+                    } else if (data.otr_status === FINISHED) {
                         data.otr_tooltip = __('Your contact has closed their end of the private session, you should do the same');
                     }
                     this.$el.find('.chat-toolbar').html(
@@ -2080,7 +2088,7 @@
             },
 
             hide: function () {
-                if (this.$el.is(':visible') && this.$el.css('opacity') == "1") {
+                if (this.$el.is(':visible') && this.$el.css('opacity') === "1") {
                     this.$el.hide();
                     converse.refreshWebkit();
                 }
@@ -2088,12 +2096,12 @@
             },
 
             show: function (callback) {
-                if (this.$el.is(':visible') && this.$el.css('opacity') == "1") {
+                if (this.$el.is(':visible') && this.$el.css('opacity') === "1") {
                     return this.focus();
                 }
                 this.initDragResize();
                 this.$el.fadeIn(function () {
-                    if (typeof callback == "function") {
+                    if (typeof callback === "function") {
                         callback.apply(this, arguments);
                     }
                     if (converse.connection.connected) {
@@ -2288,7 +2296,6 @@
                  * all its public rooms.
                  */
                 var name, jid, i, fragment,
-                    that = this,
                     $available_chatrooms = this.$el.find('#available-chatrooms');
                 this.rooms = $(iq).find('query').find('item');
                 if (this.rooms.length) {
@@ -2330,7 +2337,7 @@
                 );
             },
 
-            showRooms: function (ev) {
+            showRooms: function () {
                 var $available_chatrooms = this.$el.find('#available-chatrooms');
                 var $server = this.$el.find('input.new-chatroom-server');
                 var server = $server.val();
@@ -2552,7 +2559,6 @@
             },
 
             renderContactsPanel: function () {
-                var model;
                 this.$el.html(converse.templates.controlbox(this.model.toJSON()));
                 this.contactspanel = new converse.ContactsPanel({'$parent': this.$el.find('.controlbox-panes')});
                 this.contactspanel.render();
@@ -2621,7 +2627,7 @@
             },
 
             featureAdded: function (feature) {
-                if ((feature.get('var') == Strophe.NS.MUC) && (converse.allow_muc)) {
+                if ((feature.get('var') === Strophe.NS.MUC) && (converse.allow_muc)) {
                     this.roomspanel.model.save({muc_domain: feature.get('from')});
                     var $server= this.$el.find('input.new-chatroom-server');
                     if (! $server.is(':focus')) {
@@ -2830,10 +2836,9 @@
                         this.maximize();
                     }
                 }, this);
-                this.model.on('destroy', function (model, response, options) {
+                this.model.on('destroy', function () {
                     this.hide().leave();
-                },
-                this);
+                }, this);
 
                 this.occupantsview = new converse.ChatRoomOccupantsView({
                     model: new converse.ChatRoomOccupants({nick: this.model.get('nick')})
@@ -3182,7 +3187,7 @@
                     instructions = $stanza.find('instructions').text();
                 $form.find('span.spinner').remove();
                 $form.append($('<legend>').text(title));
-                if (instructions && instructions != title) {
+                if (instructions && instructions !== title) {
                     $form.append($('<p class="instructions">').text(instructions));
                 }
                 _.each($fields, function (field) {
@@ -3422,7 +3427,7 @@
             showErrorMessage: function ($error) {
                 // We didn't enter the room, so we must remove it from the MUC
                 // add-on
-                if ($error.attr('type') == 'auth') {
+                if ($error.attr('type') === 'auth') {
                     if ($error.find('not-authorized').length) {
                         this.renderPasswordForm();
                     } else if ($error.find('registration-required').length) {
@@ -3430,11 +3435,11 @@
                     } else if ($error.find('forbidden').length) {
                         this.showDisconnectMessage(__('You have been banned from this room'));
                     }
-                } else if ($error.attr('type') == 'modify') {
+                } else if ($error.attr('type') === 'modify') {
                     if ($error.find('jid-malformed').length) {
                         this.showDisconnectMessage(__('No nickname was specified'));
                     }
-                } else if ($error.attr('type') == 'cancel') {
+                } else if ($error.attr('type') === 'cancel') {
                     if ($error.find('not-allowed').length) {
                         this.showDisconnectMessage(__('You are not allowed to create new rooms'));
                     } else if ($error.find('not-acceptable').length) {
@@ -3459,7 +3464,7 @@
                     this.showErrorMessage($presence.find('error'));
                 } else {
                     is_self = ($presence.find("status[code='110']").length) ||
-                        ($presence.attr('from') == this.model.get('id')+'/'+Strophe.escapeNode(nick));
+                        ($presence.attr('from') === this.model.get('id')+'/'+Strophe.escapeNode(nick));
                     if (this.model.get('connection_status') !== Strophe.Status.CONNECTED) {
                         this.model.set('connection_status', Strophe.Status.CONNECTED);
                     }
@@ -3611,7 +3616,7 @@
                  */
                 var $message = $(message),
                     contact_jid, $forwarded, $delay, from_bare_jid, from_resource, is_me, msgid,
-                    chatbox, resource, roster_item,
+                    chatbox, resource,
                     from_jid = $message.attr('from'),
                     to_jid = $message.attr('to'),
                     to_resource = Strophe.getResourceFromJid(to_jid),
@@ -3635,7 +3640,7 @@
                 }
                 from_bare_jid = Strophe.getBareJidFromJid(from_jid);
                 from_resource = Strophe.getResourceFromJid(from_jid);
-                is_me = from_bare_jid == converse.bare_jid;
+                is_me = from_bare_jid === converse.bare_jid;
                 msgid = $message.attr('id');
 
                 if (is_me) {
@@ -4291,7 +4296,6 @@
 
             subscribeToSuggestedItems: function (msg) {
                 $(msg).find('item').each(function (i, items) {
-                    var $this = $(this);
                     if (this.getAttribute('action') === 'add') {
                         converse.roster.addAndSubscribe(
                                 this.getAttribute('jid'), null, converse.xmppstatus.get('fullname'));
@@ -4380,7 +4384,7 @@
                 if (item) {
                     resources = item.get('resources');
                     if (resources) {
-                        if (_.indexOf(resources, resource) == -1) {
+                        if (_.indexOf(resources, resource) === -1) {
                             resources.push(resource);
                             item.set({'resources': resources});
                         }
@@ -4539,16 +4543,16 @@
                     converse.rejectPresenceSubscription(jid, __("This client does not allow presence subscriptions"));
                 }
                 if (converse.auto_subscribe) {
-                    if ((!contact) || (contact.get('subscription') != 'to')) {
+                    if ((!contact) || (contact.get('subscription') !== 'to')) {
                         this.subscribeBack(bare_jid);
                     } else {
                         contact.authorize();
                     }
                 } else {
                     if (contact) {
-                        if (contact.get('subscription') != 'none')  {
+                        if (contact.get('subscription') !== 'none')  {
                             contact.authorize();
-                        } else if (contact.get('ask') == "subscribe") {
+                        } else if (contact.get('ask') === "subscribe") {
                             contact.authorize();
                         }
                     } else if (!contact) {
@@ -4583,7 +4587,7 @@
                 } else if (($presence.find('x').attr('xmlns') || '').indexOf(Strophe.NS.MUC) === 0) {
                     return; // Ignore MUC
                 }
-                if (contact && (status_message.text() != contact.get('status'))) {
+                if (contact && (status_message.text() !== contact.get('status'))) {
                     contact.save({'status': status_message.text()});
                 }
                 if (presence_type === 'subscribed' && contact) {
@@ -4675,7 +4679,7 @@
                 view.$el.detach();
                 if (index === 0) {
                     this.$el.after(view.$el);
-                } else if (index == (this.model.contacts.length-1)) {
+                } else if (index === (this.model.contacts.length-1)) {
                     this.$el.nextUntil('dt').last().after(view.$el);
                 } else {
                     this.$el.nextUntil('dt').eq(index).before(view.$el);
@@ -5026,16 +5030,16 @@
             onContactChange: function (contact) {
                 this.updateChatBox(contact).update();
                 if (_.has(contact.changed, 'subscription')) {
-                    if (contact.changed.subscription == 'from') {
+                    if (contact.changed.subscription === 'from') {
                         this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
                     } else if (_.contains(['both', 'to'], contact.get('subscription'))) {
                         this.addExistingContact(contact);
                     }
                 }
-                if (_.has(contact.changed, 'ask') && contact.changed.ask == 'subscribe') {
+                if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') {
                     this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
                 }
-                if (_.has(contact.changed, 'subscription') && contact.changed.requesting == 'true') {
+                if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') {
                     this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS);
                 }
                 this.liveFilter();
@@ -5089,7 +5093,7 @@
                     index = $groups.length ? this.model.indexOf(view.model) : 0;
                 if (index === 0) {
                     this.$roster.prepend(view.$el);
-                } else if (index == (this.model.length-1)) {
+                } else if (index === (this.model.length-1)) {
                     this.appendGroup(view);
                 } else {
                     $($groups.eq(index)).before(view.$el);
@@ -5263,8 +5267,7 @@
                     chat_status = this.model.get('status') || 'offline',
                     options = $('option', $select),
                     $options_target,
-                    options_list = [],
-                    that = this;
+                    options_list = [];
                 this.$el.html(converse.templates.choose_status());
                 this.$el.find('#fancy-xmpp-status-select')
                         .html(converse.templates.chat_status({
@@ -5381,7 +5384,7 @@
             onFeatureAdded: function (feature) {
                 var prefs = feature.get('preferences') || {};
                 converse.emit('serviceDiscovered', feature);
-                if (feature.get('var') == Strophe.NS.MAM && prefs['default'] !== converse.message_archiving) {
+                if (feature.get('var') === Strophe.NS.MAM && prefs['default'] !== converse.message_archiving) {
                     // Ask the server for archiving preferences
                     converse.connection.sendIQ(
                         $iq({'type': 'get'}).c('prefs', {'xmlns': Strophe.NS.MAM}),
@@ -5614,8 +5617,7 @@
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
                 var $form = $(ev.target),
                     $domain_input = $form.find('input[name=domain]'),
-                    domain = $domain_input.val(),
-                    errors = false;
+                    domain = $domain_input.val();
                 if (!domain) {
                     $domain_input.addClass('error');
                     return;
@@ -5643,7 +5645,7 @@
 
             onRegistering: function (status, error) {
                 var that;
-                console.log('onRegistering');
+                converse.log('onRegistering');
                 if (_.contains([
                             Strophe.Status.DISCONNECTED,
                             Strophe.Status.CONNFAIL,
@@ -5662,7 +5664,7 @@
                                 this.domain
                             ), 'error');
                     }
-                } else if (status == Strophe.Status.REGISTERED) {
+                } else if (status === Strophe.Status.REGISTERED) {
                     converse.log("Registered successfully.");
                     converse.connection.reset();
                     that = this;
@@ -5703,7 +5705,7 @@
                     'title': this.title,
                     'instructions': this.instructions
                 }));
-                if (this.form_type == 'xform') {
+                if (this.form_type === 'xform') {
                     $fields = $stanza.find('field');
                     _.each($fields, function (field) {
                         $form.append(utils.xForm2webForm.bind(this, $(field), $stanza));
@@ -5711,7 +5713,7 @@
                 } else {
                     // Show fields
                     _.each(Object.keys(this.fields), function (key) {
-                        if (key == "username") {
+                        if (key === "username") {
                             $input = templates.form_username({
                                 domain: ' @'+this.domain,
                                 name: key,
@@ -5803,7 +5805,7 @@
                 var $inputs = $(ev.target).find(':input:not([type=button]):not([type=submit])'),
                     iq = $iq({type: "set"}).c("query", {xmlns:Strophe.NS.REGISTER});
 
-                if (this.form_type == 'xform') {
+                if (this.form_type === 'xform') {
                     iq.c("x", {xmlns: Strophe.NS.XFORM, type: 'submit'});
                     $inputs.each(function () {
                         iq.cnode(utils.webForm2xForm(this)).up();
@@ -5865,7 +5867,7 @@
                         this.fields[_var.toLowerCase()] = $(field).children('value').text();
                     } else {
                         // TODO: other option seems to be type="fixed"
-                        console.log("WARNING: Found field we couldn't parse");
+                        converse.log("WARNING: Found field we couldn't parse");
                     }
                 }.bind(this));
                 this.form_type = 'xform';
@@ -5879,7 +5881,7 @@
                  * Parameters:
                  *      (XMLElement) stanza - The IQ stanza.
                  */
-                var i, field, error = null, that,
+                var error = null,
                     query = stanza.getElementsByTagName("query");
                 if (query.length > 0) {
                     query = query[0];
@@ -5959,7 +5961,6 @@
                     jid = $jid_input.val(),
                     $pw_input = $form.find('input[name=password]'),
                     password = $pw_input.val(),
-                    $bsu_input = null,
                     errors = false;
 
                 if (! jid) {
@@ -6071,8 +6072,8 @@
 
         this.setUpXMLLogging = function () {
             if (this.debug) {
-                this.connection.xmlInput = function (body) { console.log(body); };
-                this.connection.xmlOutput = function (body) { console.log(body); };
+                this.connection.xmlInput = function (body) { converse.log(body); };
+                this.connection.xmlOutput = function (body) { converse.log(body); };
             }
         };
 
@@ -6261,7 +6262,7 @@
                      * models that are in the "overrides" namespace.
                      */
                     var override = plugin.overrides[key];
-                    if (typeof override == "object") {
+                    if (typeof override === "object") {
                         this._extendObject(converse[key], override);
                     } else {
                         this._overrideAttribute(key, plugin);
@@ -6332,7 +6333,7 @@
                     if (!_.contains(_.keys(STATUS_WEIGHTS), value)) {
                         throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1');
                     }
-                    if (typeof message == "string") {
+                    if (typeof message === "string") {
                         data.status_message = message;
                     }
                     converse.xmppstatus.save(data);
@@ -6433,7 +6434,7 @@
                  * get the next or previous page in the result set.
                  */
                 var date, messages = [];
-                if (typeof options == "function") {
+                if (typeof options === "function") {
                     callback = options;
                     errback = callback;
                 }
@@ -6442,14 +6443,14 @@
                 }
                 var queryid = converse.connection.getUniqueId();
                 var attrs = {'type':'set'};
-                if (typeof options != "undefined" && options.groupchat) {
+                if (typeof options !== "undefined" && options.groupchat) {
                     if (!options['with']) {
                         throw new Error('You need to specify a "with" value containing the chat room JID, when querying groupchat messages.');
                     }
                     attrs.to = options['with'];
                 }
                 var stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid});
-                if (typeof options != "undefined") {
+                if (typeof options !== "undefined") {
                     stanza.c('x', {'xmlns':Strophe.NS.XFORM, 'type': 'submit'})
                             .c('field', {'var':'FORM_TYPE', 'type': 'hidden'})
                             .c('value').t(Strophe.NS.MAM).up().up();
@@ -6475,8 +6476,8 @@
                     }
                 }
                 converse.connection.addHandler(function (message) {
-                    var $msg = $(message), $fin, rsm, i;
-                    if (typeof callback == "function") {
+                    var $msg = $(message), $fin, rsm;
+                    if (typeof callback === "function") {
                         $fin = $msg.find('fin[xmlns="'+Strophe.NS.MAM+'"]');
                         if ($fin.length) {
                             rsm = new Strophe.RSM({xml: $fin.find('set')[0]});
@@ -6484,7 +6485,7 @@
                             _.extend(rsm, _.pick(options, MAM_ATTRIBUTES));
                             callback(messages, rsm);
                             return false; // We've received all messages, decommission this handler
-                        } else if (queryid == $msg.find('result').attr('queryid')) {
+                        } else if (queryid === $msg.find('result').attr('queryid')) {
                             messages.push(message);
                         }
                         return true;

+ 30 - 0
jshintrc

@@ -0,0 +1,30 @@
+{
+    "browser": true,
+    "devel": true,
+    "eqeqeq": true,
+    "indent": 4,
+    "jquery": false,
+    "smarttabs": true,
+    "trailing": true,
+    "undef": true,
+    "unused": "vars",
+    "white": false,
+    "predef": [
+        "afterEach",
+        "beforeEach",
+        "converse_api",
+        "define",
+        "define",
+        "describe",
+        "expect",
+        "global",
+        "it",
+        "jasmine",
+        "module",
+        "require",
+        "runs",
+        "spyOn",
+        "xit",
+        "waits"
+    ]
+}

+ 1 - 1
main.js

@@ -4,7 +4,7 @@ if (typeof(require) === 'undefined') {
      * We want to save the configuration in a variable so that we can reuse it in
      * tests/main.js.
      */
-    require = {
+    require = { // jshint ignore:line
         config: function (c) {
             config = c;
         }

+ 2 - 2
package.json

@@ -24,7 +24,7 @@
     "webchat"
   ],
   "author": "JC Brand",
-  "license": "MPL",
+  "license": "MPL-2.0",
   "bugs": {
     "url": "https://github.com/jcbrand/converse.js/issues"
   },
@@ -36,7 +36,6 @@
     "grunt": "~0.4.4",
     "grunt-cli": "~0.1.13",
     "grunt-contrib-cssmin": "~0.9.0",
-    "grunt-contrib-jshint": "~0.10.0",
     "grunt-contrib-jst": "~0.6.0",
     "grunt-contrib-requirejs": "~0.4.3",
     "grunt-json": "^0.1.3",
@@ -48,6 +47,7 @@
     "po2json": "^0.3.0"
   },
   "dependencies": {
+    "jshint": "^2.8.0",
     "requirejs": "~2.1.15"
   }
 }

+ 19 - 17
spec/chatbox.js

@@ -1,13 +1,15 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     var $msg = converse_api.env.$msg;
     var Strophe = converse_api.env.Strophe;
     var moment = converse_api.env.moment;
@@ -27,7 +29,7 @@
             });
 
             it("is created when you click on a roster item", function () {
-                var i, $el, click, jid, chatboxview;
+                var i, $el, jid, chatboxview;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
@@ -50,7 +52,7 @@
             }.bind(converse));
 
             it("can be trimmed to conserve space", function () {
-                var i, $el, click, jid, key, chatbox, chatboxview;
+                var i, $el, jid, chatbox, chatboxview, trimmedview;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 var trimmed_chatboxes = converse.minimized_chats;
@@ -99,7 +101,7 @@
 
             it("is focused if its already open and you click on its corresponding roster item", function () {
                 var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                var i, $el, click, jid, chatboxview, chatbox;
+                var $el, jid, chatboxview, chatbox;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
@@ -141,8 +143,9 @@
                     expect(newchatboxes.length).toEqual(7);
                     // Check that the chatboxes items retrieved from browserStorage
                     // have the same attributes values as the original ones.
-                    attrs = ['id', 'box_id', 'visible'];
-                    for (i=0; i<attrs.length; i++) {
+                    var attrs = ['id', 'box_id', 'visible'];
+                    var new_attrs, old_attrs;
+                    for (var i=0; i<attrs.length; i++) {
                         new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
                         old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
                         expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
@@ -565,7 +568,7 @@
                     expect(chatview.model.get('minimized')).toBeTruthy();
                     var message = 'This message is sent to a minimized chatbox';
                     var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    msg = $msg({
+                    var msg = $msg({
                         from: sender_jid,
                         to: this.connection.jid,
                         type: 'chat',
@@ -945,7 +948,7 @@
                         spyOn(converse, 'emit');
                         var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                         // <paused> state
-                        msg = $msg({
+                        var msg = $msg({
                                 from: sender_jid,
                                 to: this.connection.jid,
                                 type: 'chat',
@@ -954,7 +957,7 @@
                         this.chatboxes.onMessage(msg);
                         expect(converse.emit).toHaveBeenCalledWith('message', msg);
                         var chatboxview = this.chatboxviews.get(sender_jid);
-                        $events = chatboxview.$el.find('.chat-event');
+                        var $events = chatboxview.$el.find('.chat-event');
                         expect($events.length).toBe(1);
                         expect($events.text()).toEqual(mock.cur_names[1].split(' ')[0] + ' has stopped typing');
                     }.bind(converse));
@@ -1030,7 +1033,7 @@
                         expect(view.$el.find('.chat-event').length).toBe(0);
                         view.showStatusNotification(sender_jid+' '+'is typing');
                         expect(view.$el.find('.chat-event').length).toBe(1);
-                        msg = $msg({
+                        var msg = $msg({
                                 from: sender_jid,
                                 to: this.connection.jid,
                                 type: 'chat',
@@ -1048,7 +1051,7 @@
                         spyOn(converse, 'emit');
                         var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
                         // <paused> state
-                        msg = $msg({
+                        var msg = $msg({
                                 from: sender_jid,
                                 to: this.connection.jid,
                                 type: 'chat',
@@ -1057,7 +1060,7 @@
                         this.chatboxes.onMessage(msg);
                         expect(converse.emit).toHaveBeenCalledWith('message', msg);
                         var chatboxview = this.chatboxviews.get(sender_jid);
-                        $events = chatboxview.$el.find('.chat-event');
+                        var $events = chatboxview.$el.find('.chat-event');
                         expect($events.length).toBe(1);
                         expect($events.text()).toEqual(mock.cur_names[1].split(' ')[0] + ' has gone away');
                     }.bind(converse));
@@ -1090,7 +1093,6 @@
                 expect(converse.emit).toHaveBeenCalledWith('messageSend', message);
 
                 message = '/clear';
-                var old_length = view.model.messages.length;
                 spyOn(view, 'onMessageSubmitted').andCallThrough();
                 spyOn(view, 'clearMessages').andCallThrough();
                 spyOn(window, 'confirm').andCallFake(function () {
@@ -1118,7 +1120,7 @@
                 spyOn(converse, 'incrementMsgCounter').andCallThrough();
                 $(window).trigger('blur');
                 var message = 'This message will increment the message counter';
-                var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
                     msg = $msg({
                         from: sender_jid,
                         to: this.connection.jid,
@@ -1149,7 +1151,7 @@
                 spyOn(converse, 'incrementMsgCounter').andCallThrough();
                 $(window).trigger('focus');
                 var message = 'This message will not increment the message counter';
-                var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
                     msg = $msg({
                         from: sender_jid,
                         to: this.connection.jid,

+ 17 - 22
spec/chatroom.js

@@ -1,14 +1,16 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils",
         "utils"
-        ], function ($, mock, test_utils, utils) {
-            return factory($, mock, test_utils, utils);
+        ], function ($, _, mock, test_utils, utils) {
+            return factory($, _, mock, test_utils, utils);
         }
     );
-} (this, function ($, mock, test_utils, utils) {
+} (this, function ($, _, mock, test_utils, utils) {
     var $pres = converse_api.env.$pres;
     var $msg = converse_api.env.$msg;
     var Strophe = converse_api.env.Strophe;
@@ -42,8 +44,8 @@
                 var view = this.chatboxviews.get('lounge@localhost'),
                     $participants = view.$('.participant-list');
                 spyOn(view, 'onChatRoomPresence').andCallThrough();
-                var presence, room = {}, i, role;
-                for (i=0; i<mock.chatroom_names.length; i++) {
+                var presence, role;
+                for (var i=0; i<mock.chatroom_names.length; i++) {
                     name = mock.chatroom_names[i];
                     role = mock.chatroom_roles[name].role;
                     // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
@@ -117,13 +119,13 @@
                 spyOn(window, 'prompt').andCallFake(function () {
                     return null;
                 });
-                var roster = {}, $input;
+                var $input;
                 var view = this.chatboxviews.get('lounge@localhost');
                 view.$el.find('.chat-area').remove();
                 view.renderChatArea(); // Will init the widget
                 test_utils.createContacts('current'); // We need roster contacts, so that we have someone to invite
                 $input = view.$el.find('input.invited-contact.tt-input');
-                $hint = view.$el.find('input.invited-contact.tt-hint');
+                var $hint = view.$el.find('input.invited-contact.tt-hint');
                 runs (function () {
                     expect($input.length).toBe(1);
                     expect($input.attr('placeholder')).toBe('Invite...');
@@ -198,7 +200,6 @@
                 spyOn(converse, 'playNotification');
                 var view = this.chatboxviews.get('lounge@localhost');
                 if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
-                var nick = mock.chatroom_names[0];
                 var text = 'This message will play a sound because it mentions dummy';
                 var message = $msg({
                     from: 'lounge@localhost/otheruser',
@@ -237,7 +238,6 @@
                 spyOn(converse, 'emit');
                 var view = this.chatboxviews.get('lounge@localhost');
                 if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
-                var nick = mock.chatroom_names[0];
                 var text = 'This is a sent message';
                 view.$el.find('.chat-textarea').text(text);
                 view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
@@ -318,7 +318,7 @@
 
                 this.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.onChatRoomPresence).toHaveBeenCalled();
-                $participants = view.$('.participant-list');
+                var $participants = view.$('.participant-list');
                 expect($participants.children().length).toBe(1);
                 expect($participants.children().first(0).text()).toBe("oldnick");
                 expect($chat_content.find('div.chat-info').length).toBe(1);
@@ -421,8 +421,9 @@
                 expect(newchatboxes.length).toEqual(2);
                 // Check that the chatrooms retrieved from browserStorage
                 // have the same attributes values as the original ones.
-                attrs = ['id', 'box_id', 'visible'];
-                for (i=0; i<attrs.length; i++) {
+                var attrs = ['id', 'box_id', 'visible'];
+                var new_attrs, old_attrs;
+                for (var i=0; i<attrs.length; i++) {
                     new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
                     old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
                     // FIXME: should have have to sort here? Order must
@@ -453,7 +454,7 @@
                     expect(view.$el.is(':visible')).toBeFalsy();
                     expect(view.model.get('minimized')).toBeTruthy();
                     expect(view.minimize).toHaveBeenCalled();
-                    trimmedview = trimmed_chatboxes.get(view.model.get('id'));
+                    var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
                     trimmedview.$("a.restore-chat").click();
                 });
                 waits(250);
@@ -469,7 +470,7 @@
 
             it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", function () {
                 test_utils.openChatRoom('lounge', 'localhost', 'dummy');
-                var view = this.chatboxviews.get('lounge@localhost'), chatroom = view.model, $el;
+                var view = this.chatboxviews.get('lounge@localhost');
                 spyOn(view, 'close').andCallThrough();
                 spyOn(converse, 'emit');
                 spyOn(view, 'leave');
@@ -497,10 +498,7 @@
 
             it("to clear messages", function () {
                 test_utils.openChatRoom('lounge', 'localhost', 'dummy');
-                var view = converse.chatboxviews.get('lounge@localhost'),
-                    chatroom = view.model,
-                    $chat_content = view.$el.find('.chat-content');
-
+                var view = converse.chatboxviews.get('lounge@localhost');
                 spyOn(view, 'onChatRoomMessageSubmitted').andCallThrough();
                 spyOn(view, 'clearChatRoomMessages');
                 view.$el.find('.chat-textarea').text('/clear');
@@ -512,10 +510,7 @@
 
             it("to ban a user", function () {
                 test_utils.openChatRoom('lounge', 'localhost', 'dummy');
-                var view = converse.chatboxviews.get('lounge@localhost'),
-                    chatroom = view.model,
-                    $chat_content = view.$el.find('.chat-content');
-
+                var view = converse.chatboxviews.get('lounge@localhost');
                 spyOn(view, 'onChatRoomMessageSubmitted').andCallThrough();
                 spyOn(view, 'setAffiliation').andCallThrough();
                 spyOn(view, 'showStatusNotification').andCallThrough();

+ 18 - 17
spec/controlbox.js

@@ -1,13 +1,15 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     var $pres = converse_api.env.$pres;
     var $iq = converse_api.env.$iq;
 
@@ -138,7 +140,7 @@
                 var names = mock.cur_names;
                 expect($filter.length).toBe(1);
                 expect($filter.is(':visible')).toBeFalsy();
-                for (i=0; i<names.length; i++) {
+                for (var i=0; i<names.length; i++) {
                     converse.roster.create({
                         ask: null,
                         fullname: names[i],
@@ -255,7 +257,6 @@
                 _clearContacts();
                 utils.createGroupedContacts();
                 var $filter = converse.rosterview.$('.roster-filter');
-                var $roster = converse.rosterview.$roster;
                 runs (function () {
                     $filter.val("xxx");
                     $filter.trigger('keydown');
@@ -316,7 +317,7 @@
                 var groups = ['colleagues', 'friends'];
                 runs($.proxy(function () {
                     _clearContacts();
-                    var i=0, j=0;
+                    var i=0;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
                     converse.rosterview.render();
@@ -484,7 +485,7 @@
                 }, this));
                 waits(50);
                 runs($.proxy(function () {
-                    contact = this.roster.create({
+                    this.roster.create({
                         jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
                         subscription: 'none',
                         ask: 'subscribe',
@@ -508,7 +509,7 @@
                 _addContacts();
                 var name;
                 spyOn(window, 'confirm').andReturn(true);
-                for (i=0; i<mock.pend_names.length; i++) {
+                for (var i=0; i<mock.pend_names.length; i++) {
                     name = mock.pend_names[i];
                     converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
                         .siblings('.remove-xmpp-contact').click();
@@ -661,7 +662,7 @@
                     var jid, t;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
-                    for (i=0; i<mock.cur_names.length; i++) {
+                    for (var i=0; i<mock.cur_names.length; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'online');
                         expect(this.rosterview.update).toHaveBeenCalled();
@@ -681,7 +682,7 @@
                     var jid, t;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
-                    for (i=0; i<mock.cur_names.length; i++) {
+                    for (var i=0; i<mock.cur_names.length; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'dnd');
                         expect(this.rosterview.update).toHaveBeenCalled();
@@ -701,7 +702,7 @@
                     var jid, t;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
-                    for (i=0; i<mock.cur_names.length; i++) {
+                    for (var i=0; i<mock.cur_names.length; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'away');
                         expect(this.rosterview.update).toHaveBeenCalled();
@@ -721,7 +722,7 @@
                     var jid, t;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
-                    for (i=0; i<mock.cur_names.length; i++) {
+                    for (var i=0; i<mock.cur_names.length; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'xa');
                         expect(this.rosterview.update).toHaveBeenCalled();
@@ -741,7 +742,7 @@
                     var jid, t;
                     spyOn(converse, 'emit');
                     spyOn(this.rosterview, 'update').andCallThrough();
-                    for (i=0; i<mock.cur_names.length; i++) {
+                    for (var i=0; i<mock.cur_names.length; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'unavailable');
                         expect(this.rosterview.update).toHaveBeenCalled();
@@ -758,7 +759,7 @@
                 });
                 waits(50);
                 runs($.proxy(function () {
-                    var i;
+                    var i, jid;
                     for (i=0; i<3; i++) {
                         jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
                         this.roster.get(jid).set('chat_status', 'online');
@@ -1010,7 +1011,7 @@
             it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () {
                 var new_attrs, old_attrs, attrs;
                 var num_contacts = this.roster.length;
-                new_roster = new this.RosterContacts();
+                var new_roster = new this.RosterContacts();
                 // Roster items are yet to be fetched from browserStorage
                 expect(new_roster.length).toEqual(0);
                 new_roster.browserStorage = this.roster.browserStorage;
@@ -1019,7 +1020,7 @@
                 // Check that the roster items retrieved from browserStorage
                 // have the same attributes values as the original ones.
                 attrs = ['jid', 'fullname', 'subscription', 'ask'];
-                for (i=0; i<attrs.length; i++) {
+                for (var i=0; i<attrs.length; i++) {
                     new_attrs = _.pluck(_.pluck(new_roster.models, 'attributes'), attrs[i]);
                     old_attrs = _.pluck(_.pluck(this.roster.models, 'attributes'), attrs[i]);
                     // Roster items in storage are not necessarily sorted,
@@ -1030,7 +1031,7 @@
             }, converse));
 
             it("will show fullname and jid properties on tooltip", $.proxy(function () {
-                var jid, name, i, t;
+                var jid, name, i;
                 for (i=0; i<mock.cur_names.length; i++) {
                     name = mock.cur_names[i];
                     jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';

+ 5 - 3
spec/converse.js

@@ -1,13 +1,15 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     var b64_sha1 = converse_api.env.b64_sha1;
 
     return describe("Converse", $.proxy(function(mock, test_utils) {

+ 1 - 0
spec/disco.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",

+ 1 - 0
spec/eventemitter.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",

+ 5 - 4
spec/mam.js

@@ -1,17 +1,18 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     "use strict";
     var Strophe = converse_api.env.Strophe;
     var $iq = converse_api.env.$iq;
-    var $pres = converse_api.env.$pres;
     var $msg = converse_api.env.$msg;
     var moment = converse_api.env.moment;
     // See: https://xmpp.org/rfcs/rfc3921.html

+ 5 - 4
spec/minchats.js

@@ -1,13 +1,15 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     var $msg = converse_api.env.$msg;
 
     return describe("The Minimized Chats Widget", $.proxy(function(mock, test_utils) {
@@ -67,7 +69,6 @@
 
         it("shows the number messages received to minimized chats",  $.proxy(function () {
             var i, contact_jid, chatview, msg;
-            var sender_jid = mock.cur_names[4].replace(/ /g,'.').toLowerCase() + '@localhost';
             this.minimized_chats.toggleview.model.set({'collapsed': true});
             expect(this.minimized_chats.toggleview.$('.unread-message-count').is(':visible')).toBeFalsy();
             for (i=0; i<3; i++) {

+ 1 - 0
spec/otr.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",

+ 5 - 3
spec/profiling.js

@@ -1,13 +1,15 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
+        "underscore",
         "mock",
         "test_utils"
-        ], function ($, mock, test_utils) {
-            return factory($, mock, test_utils);
+        ], function ($, _, mock, test_utils) {
+            return factory($, _, mock, test_utils);
         }
     );
-} (this, function ($, mock, test_utils) {
+} (this, function ($, _, mock, test_utils) {
     var Strophe = converse_api.env.Strophe;
     var $iq = converse_api.env.$iq;
 

+ 2 - 4
spec/protocol.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
@@ -9,7 +10,6 @@
     );
 } (this, function ($, mock, test_utils) {
     "use strict";
-    var Strophe = converse_api.env.Strophe;
     var $iq = converse_api.env.$iq;
     var $pres = converse_api.env.$pres;
     // See:
@@ -355,7 +355,7 @@
                 /* The process by which a user subscribes to a contact, including
                 * the interaction between roster items and subscription states.
                 */
-                var contact, stanza, sent_stanza, sent_IQ, IQ_id;
+                var contact, stanza, sent_stanza, sent_IQ;
                 runs($.proxy(function () {
                     // Add a new roster contact via roster push
                     stanza = $iq({'type': 'set'}).c('query', {'xmlns': 'jabber:iq:roster'})
@@ -373,11 +373,9 @@
                     expect(this.roster.get('contact@example.org') instanceof this.RosterContact).toBeTruthy();
                     spyOn(contact, "ackUnsubscribe").andCallThrough();
 
-                    var send = this.connection.send;
                     spyOn(converse.connection, 'send').andCallFake(function (stanza) {
                         sent_stanza = stanza;
                     });
-                    var sendIQ = this.connection.sendIQ;
                     spyOn(this.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
                         sent_IQ = iq;
                     });

+ 2 - 1
spec/register.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",
@@ -14,7 +15,7 @@
     describe("The Registration Panel", $.proxy(function (mock, test_utils) {
         beforeEach(function () {
             test_utils.closeControlBox();
-            connection = mock.mock_connection;
+            var connection = mock.mock_connection;
             connection.connected = false;
             converse._tearDown();
             converse.initialize({

+ 1 - 0
spec/xmppstatus.js

@@ -1,3 +1,4 @@
+/*global converse */
 (function (root, factory) {
     define([
         "jquery",

+ 2 - 1
src/deps-full.js

@@ -1,5 +1,6 @@
 define("converse-dependencies", [
     "jquery",
+    "underscore",
     "polyfill",
     "utils",
     "otr",
@@ -13,7 +14,7 @@ define("converse-dependencies", [
     "backbone.overview",
     "jquery.browser",
     "typeahead"
-], function($, dummy, utils, otr, moment, Strophe) {
+], function($, _, dummy, utils, otr, moment, Strophe) {
     return _.extend({
         'underscore': _,
         'jQuery': $,

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

@@ -1,5 +1,6 @@
 define("converse-dependencies", [
     "jquery",
+    "underscore",
     "utils",
     "polyfill",
     "moment_with_locales",
@@ -12,7 +13,7 @@ define("converse-dependencies", [
     "backbone.overview",
     "jquery.browser",
     "typeahead"
-], function($, dummy, utils, moment, Strophe) {
+], function($, _, dummy, utils, moment, Strophe) {
     return _.extend({
         'underscore': _,
         'jQuery': $,

+ 1 - 0
src/jquery-external.js

@@ -1,3 +1,4 @@
+/*global jQuery */
 define('jquery', [], function () {
     return jQuery;
 });

+ 1 - 3
src/jquery.eventemitter.js

@@ -1,7 +1,5 @@
+/*global $ */
 (function (root, factory) {
-    if (typeof console === "undefined" || typeof console.log === "undefined") {
-        console = { log: function () {}, error: function () {} };
-    }
     if (typeof define === 'function' && define.amd) {
         define("converse", ["jquery"], function($) {
             return factory($);

+ 9 - 8
src/utils.js

@@ -1,10 +1,11 @@
+/*global jQuery, templates, escape, Jed, _ */
 (function (root, factory) {
     if (typeof define === 'function' && define.amd) {
-        define(["jquery", "converse-templates", "locales"], factory);
+        define(["jquery", "underscore", "converse-templates", "locales"], factory);
     } else {
-        root.utils = factory(jQuery, templates);
+        root.utils = factory(jQuery, _, templates);
     }
-}(this, function ($, templates, locales) {
+}(this, function ($, _, templates, locales) {
     "use strict";
 
     var XFORM_TYPE_MAP = {
@@ -156,7 +157,7 @@
             // FIXME: take <required> into consideration
             var options = [], j, $options, $values, value, values;
 
-            if ($field.attr('type') == 'list-single' || $field.attr('type') == 'list-multi') {
+            if ($field.attr('type') === 'list-single' || $field.attr('type') === 'list-multi') {
                 values = [];
                 $values = $field.children('value');
                 for (j=0; j<$values.length; j++) {
@@ -176,19 +177,19 @@
                     name: $field.attr('var'),
                     label: $field.attr('label'),
                     options: options.join(''),
-                    multiple: ($field.attr('type') == 'list-multi'),
+                    multiple: ($field.attr('type') === 'list-multi'),
                     required: $field.find('required').length
                 });
-            } else if ($field.attr('type') == 'fixed') {
+            } else if ($field.attr('type') === 'fixed') {
                 return $('<p class="form-help">').text($field.find('value').text());
-            } else if ($field.attr('type') == 'jid-multi') {
+            } else if ($field.attr('type') === 'jid-multi') {
                 return templates.form_textarea({
                     name: $field.attr('var'),
                     label: $field.attr('label') || '',
                     value: $field.find('value').text(),
                     required: $field.find('required').length
                 });
-            } else if ($field.attr('type') == 'boolean') {
+            } else if ($field.attr('type') === 'boolean') {
                 return templates.form_checkbox({
                     name: $field.attr('var'),
                     type: XFORM_TYPE_MAP[$field.attr('type')],