/*global jQuery, QUnit */
/*jshint eqeqeq:false, eqnull:false, forin:false */
( function ( $ ) {
- "use strict";
+ 'use strict';
var util,
hasOwn = Object.prototype.hasOwnProperty,
length = arguments.length;
for ( ; i < length; i++ ) {
+ options = arguments[ i ];
// Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
+ if ( options !== null && options !== undefined ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
};
-/**
- * CompletenessTest
- * @constructor
- *
- * @example
- * var myTester = new CompletenessTest( myLib );
- * @param masterVariable {Object} The root variable that contains all object
- * members. CompletenessTest will recursively traverse objects and keep track
- * of all methods.
- * @param ignoreFn {Function} Optionally pass a function to filter out certain
- * methods. Example: You may want to filter out instances of jQuery or some
- * other constructor. Otherwise "missingTests" will include all methods that
- * were not called from that instance.
- */
-var CompletenessTest = function ( masterVariable, ignoreFn ) {
-
- // Keep track in these objects. Keyed by strings with the
- // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true.
- this.injectionTracker = {};
- this.methodCallTracker = {};
- this.missingTests = {};
-
- this.ignoreFn = undefined === ignoreFn ? function () { return false; } : ignoreFn;
+ /**
+ * CompletenessTest
+ * @constructor
+ *
+ * @example
+ * var myTester = new CompletenessTest( myLib );
+ * @param masterVariable {Object} The root variable that contains all object
+ * members. CompletenessTest will recursively traverse objects and keep track
+ * of all methods.
+ * @param ignoreFn {Function} Optionally pass a function to filter out certain
+ * methods. Example: You may want to filter out instances of jQuery or some
+ * other constructor. Otherwise "missingTests" will include all methods that
+ * were not called from that instance.
+ */
+ function CompletenessTest( masterVariable, ignoreFn ) {
- // Lazy limit in case something weird happends (like recurse (part of) ourself).
- this.lazyLimit = 2000;
- this.lazyCounter = 0;
+ // Keep track in these objects. Keyed by strings with the
+ // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true.
+ this.injectionTracker = {};
+ this.methodCallTracker = {};
+ this.missingTests = {};
- var that = this;
+ this.ignoreFn = undefined === ignoreFn ? function () { return false; } : ignoreFn;
- // Bind begin and end to QUnit.
- QUnit.begin( function () {
- that.walkTheObject( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT );
- log( 'CompletenessTest/walkTheObject/ACTION_INJECT', that );
- });
+ // Lazy limit in case something weird happends (like recurse (part of) ourself).
+ this.lazyLimit = 2000;
+ this.lazyCounter = 0;
- QUnit.done( function () {
- that.populateMissingTests();
- log( 'CompletenessTest/populateMissingTests', that );
+ var that = this;
- var toolbar, testResults, cntTotal, cntCalled, cntMissing;
+ // Bind begin and end to QUnit.
+ QUnit.begin( function () {
+ that.walkTheObject( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT );
+ log( 'CompletenessTest/walkTheObject/ACTION_INJECT', that );
+ });
- cntTotal = util.keys( that.injectionTracker ).length;
- cntCalled = util.keys( that.methodCallTracker ).length;
- cntMissing = util.keys( that.missingTests ).length;
+ QUnit.done( function () {
+ that.populateMissingTests();
+ log( 'CompletenessTest/populateMissingTests', that );
- function makeTestResults( blob, title, style ) {
- var elOutputWrapper, elTitle, elContainer, elList, elFoot;
+ var toolbar, testResults, cntTotal, cntCalled, cntMissing;
- elTitle = document.createElement( 'strong' );
- elTitle.textContent = title || 'Values';
+ cntTotal = util.keys( that.injectionTracker ).length;
+ cntCalled = util.keys( that.methodCallTracker ).length;
+ cntMissing = util.keys( that.missingTests ).length;
- elList = document.createElement( 'ul' );
- util.each( blob, function ( key ) {
- var elItem = document.createElement( 'li' );
- elItem.textContent = key;
- elList.appendChild( elItem );
- });
+ function makeTestResults( blob, title, style ) {
+ var elOutputWrapper, elTitle, elContainer, elList, elFoot;
- elFoot = document.createElement( 'p' );
- elFoot.innerHTML = '<em>— CompletenessTest</em>';
+ elTitle = document.createElement( 'strong' );
+ elTitle.textContent = title || 'Values';
- elContainer = document.createElement( 'div' );
- elContainer.appendChild( elTitle );
- elContainer.appendChild( elList );
- elContainer.appendChild( elFoot );
+ elList = document.createElement( 'ul' );
+ util.each( blob, function ( key ) {
+ var elItem = document.createElement( 'li' );
+ elItem.textContent = key;
+ elList.appendChild( elItem );
+ });
- elOutputWrapper = document.getElementById( 'qunit-completenesstest' );
- if ( !elOutputWrapper ) {
- elOutputWrapper = document.createElement( 'div' );
- elOutputWrapper.id = 'qunit-completenesstest';
- }
- elOutputWrapper.appendChild( elContainer );
+ elFoot = document.createElement( 'p' );
+ elFoot.innerHTML = '<em>— CompletenessTest</em>';
- util.each( style, function ( key, value ) {
- elOutputWrapper.style[key] = value;
- });
- return elOutputWrapper;
- }
+ elContainer = document.createElement( 'div' );
+ elContainer.appendChild( elTitle );
+ elContainer.appendChild( elList );
+ elContainer.appendChild( elFoot );
- if ( cntMissing === 0 ) {
- // Good
- testResults = makeTestResults(
- {},
- 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. No missing tests!',
- {
- backgroundColor: '#D2E0E6',
- color: '#366097',
- paddingTop: '1em',
- paddingRight: '1em',
- paddingBottom: '1em',
- paddingLeft: '1em'
- }
- );
- } else {
- // Bad
- testResults = makeTestResults(
- that.missingTests,
- 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. ' + cntMissing + ' methods not covered:',
- {
- backgroundColor: '#EE5757',
- color: 'black',
- paddingTop: '1em',
- paddingRight: '1em',
- paddingBottom: '1em',
- paddingLeft: '1em'
+ elOutputWrapper = document.getElementById( 'qunit-completenesstest' );
+ if ( !elOutputWrapper ) {
+ elOutputWrapper = document.createElement( 'div' );
+ elOutputWrapper.id = 'qunit-completenesstest';
}
- );
- }
+ elOutputWrapper.appendChild( elContainer );
- toolbar = document.getElementById( 'qunit-testrunner-toolbar' );
- if ( toolbar ) {
- toolbar.insertBefore( testResults, toolbar.firstChild );
- }
- });
+ util.each( style, function ( key, value ) {
+ elOutputWrapper.style[key] = value;
+ });
+ return elOutputWrapper;
+ }
- return this;
-};
+ if ( cntMissing === 0 ) {
+ // Good
+ testResults = makeTestResults(
+ {},
+ 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. No missing tests!',
+ {
+ backgroundColor: '#D2E0E6',
+ color: '#366097',
+ paddingTop: '1em',
+ paddingRight: '1em',
+ paddingBottom: '1em',
+ paddingLeft: '1em'
+ }
+ );
+ } else {
+ // Bad
+ testResults = makeTestResults(
+ that.missingTests,
+ 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. ' + cntMissing + ' methods not covered:',
+ {
+ backgroundColor: '#EE5757',
+ color: 'black',
+ paddingTop: '1em',
+ paddingRight: '1em',
+ paddingBottom: '1em',
+ paddingLeft: '1em'
+ }
+ );
+ }
-/* Static members */
-CompletenessTest.ACTION_INJECT = 500;
-CompletenessTest.ACTION_CHECK = 501;
+ toolbar = document.getElementById( 'qunit-testrunner-toolbar' );
+ if ( toolbar ) {
+ toolbar.insertBefore( testResults, toolbar.firstChild );
+ }
+ });
-/* Public methods */
-CompletenessTest.fn = CompletenessTest.prototype = {
+ return this;
+ }
- /**
- * CompletenessTest.fn.walkTheObject
- *
- * This function recursively walks through the given object, calling itself as it goes.
- * Depending on the action it either injects our listener into the methods, or
- * reads from our tracker and records which methods have not been called by the test suite.
- *
- * @param currName {String|Null} Name of the given object member (Initially this is null).
- * @param currVar {mixed} The variable to check (initially an object,
- * further down it could be anything).
- * @param masterVariable {Object} Throughout our interation, always keep track of the master/root.
- * Initially this is the same as currVar.
- * @param parentPathArray {Array} Array of names that indicate our breadcrumb path starting at
- * masterVariable. Not including currName.
- * @param action {Number} What is this function supposed to do (ACTION_INJECT or ACTION_CHECK)
- */
- walkTheObject: function ( currName, currVar, masterVariable, parentPathArray, action ) {
+ /* Static members */
+ CompletenessTest.ACTION_INJECT = 500;
+ CompletenessTest.ACTION_CHECK = 501;
+
+ /* Public methods */
+ CompletenessTest.fn = CompletenessTest.prototype = {
+
+ /**
+ * CompletenessTest.fn.walkTheObject
+ *
+ * This function recursively walks through the given object, calling itself as it goes.
+ * Depending on the action it either injects our listener into the methods, or
+ * reads from our tracker and records which methods have not been called by the test suite.
+ *
+ * @param currName {String|Null} Name of the given object member (Initially this is null).
+ * @param currVar {mixed} The variable to check (initially an object,
+ * further down it could be anything).
+ * @param masterVariable {Object} Throughout our interation, always keep track of the master/root.
+ * Initially this is the same as currVar.
+ * @param parentPathArray {Array} Array of names that indicate our breadcrumb path starting at
+ * masterVariable. Not including currName.
+ * @param action {Number} What is this function supposed to do (ACTION_INJECT or ACTION_CHECK)
+ */
+ walkTheObject: function ( currName, currVar, masterVariable, parentPathArray, action ) {
+
+ var key, value, tmpPathArray,
+ type = util.type( currVar ),
+ that = this;
+
+ // Hard ignores
+ if ( this.ignoreFn( currVar, that, parentPathArray ) ) {
+ return null;
+ }
- var key, value, tmpPathArray,
- type = util.type( currVar ),
- that = this;
+ // Handle the lazy limit
+ this.lazyCounter++;
+ if ( this.lazyCounter > this.lazyLimit ) {
+ log( 'CompletenessTest.fn.walkTheObject> Limit reached: ' + this.lazyCounter, parentPathArray );
+ return null;
+ }
- // Hard ignores
- if ( this.ignoreFn( currVar, that, parentPathArray ) ) {
- return null;
- }
+ // Functions
+ if ( type === 'function' ) {
- // Handle the lazy limit
- this.lazyCounter++;
- if ( this.lazyCounter > this.lazyLimit ) {
- log( 'CompletenessTest.fn.walkTheObject> Limit reached: ' + this.lazyCounter, parentPathArray );
- return null;
- }
+ if ( !currVar.prototype || util.isEmptyObject( currVar.prototype ) ) {
- // Functions
- if ( type === 'function' ) {
+ if ( action === CompletenessTest.ACTION_INJECT ) {
- if ( !currVar.prototype || util.isEmptyObject( currVar.prototype ) ) {
+ that.injectionTracker[ parentPathArray.join( '.' ) ] = true;
+ that.injectCheck( masterVariable, parentPathArray, function () {
+ that.methodCallTracker[ parentPathArray.join( '.' ) ] = true;
+ } );
+ }
- if ( action === CompletenessTest.ACTION_INJECT ) {
+ // We don't support checking object constructors yet...
+ // ...we can check the prototypes fine, though.
+ } else {
+ if ( action === CompletenessTest.ACTION_INJECT ) {
- that.injectionTracker[ parentPathArray.join( '.' ) ] = true;
- that.injectCheck( masterVariable, parentPathArray, function () {
- that.methodCallTracker[ parentPathArray.join( '.' ) ] = true;
- } );
- }
+ for ( key in currVar.prototype ) {
+ if ( hasOwn.call( currVar.prototype, key ) ) {
+ value = currVar.prototype[key];
+ if ( key === 'constructor' ) {
+ continue;
+ }
- // We don't support checking object constructors yet...
- // ...we can check the prototypes fine, though.
- } else {
- if ( action === CompletenessTest.ACTION_INJECT ) {
+ // Clone and break reference to parentPathArray
+ tmpPathArray = util.extend( [], parentPathArray );
+ tmpPathArray.push( 'prototype' );
+ tmpPathArray.push( key );
- for ( key in currVar.prototype ) {
- if ( hasOwn.call( currVar.prototype, key ) ) {
- value = currVar.prototype[key];
- if ( key === 'constructor' ) {
- continue;
+ that.walkTheObject( key, value, masterVariable, tmpPathArray, action );
}
-
- // Clone and break reference to parentPathArray
- tmpPathArray = util.extend( [], parentPathArray );
- tmpPathArray.push( 'prototype' );
- tmpPathArray.push( key );
-
- that.walkTheObject( key, value, masterVariable, tmpPathArray, action );
}
- }
+ }
}
- }
- }
+ }
- // Recursively. After all, this is the *completeness* test
- if ( type === 'function' || type === 'object' ) {
- for ( key in currVar ) {
- if ( hasOwn.call( currVar, key ) ) {
- value = currVar[key];
+ // Recursively. After all, this is the *completeness* test
+ if ( type === 'function' || type === 'object' ) {
+ for ( key in currVar ) {
+ if ( hasOwn.call( currVar, key ) ) {
+ value = currVar[key];
- // Clone and break reference to parentPathArray
- tmpPathArray = util.extend( [], parentPathArray );
- tmpPathArray.push( key );
+ // Clone and break reference to parentPathArray
+ tmpPathArray = util.extend( [], parentPathArray );
+ tmpPathArray.push( key );
- that.walkTheObject( key, value, masterVariable, tmpPathArray, action );
+ that.walkTheObject( key, value, masterVariable, tmpPathArray, action );
+ }
}
}
- }
- },
+ },
- populateMissingTests: function () {
- var ct = this;
- util.each( ct.injectionTracker, function ( key ) {
- ct.hasTest( key );
- });
- },
+ populateMissingTests: function () {
+ var ct = this;
+ util.each( ct.injectionTracker, function ( key ) {
+ ct.hasTest( key );
+ });
+ },
- /**
- * CompletenessTest.fn.hasTest
- *
- * Checks if the given method name (ie. 'my.foo.bar')
- * was called during the test suite (as far as the tracker knows).
- * If not it adds it to missingTests.
- *
- * @param fnName {String}
- * @return {Boolean}
- */
- hasTest: function ( fnName ) {
- if ( !( fnName in this.methodCallTracker ) ) {
- this.missingTests[fnName] = true;
- return false;
- }
- return true;
- },
+ /**
+ * CompletenessTest.fn.hasTest
+ *
+ * Checks if the given method name (ie. 'my.foo.bar')
+ * was called during the test suite (as far as the tracker knows).
+ * If not it adds it to missingTests.
+ *
+ * @param fnName {String}
+ * @return {Boolean}
+ */
+ hasTest: function ( fnName ) {
+ if ( !( fnName in this.methodCallTracker ) ) {
+ this.missingTests[fnName] = true;
+ return false;
+ }
+ return true;
+ },
- /**
- * CompletenessTest.fn.injectCheck
- *
- * Injects a function (such as a spy that updates methodCallTracker when
- * it's called) inside another function.
- *
- * @param masterVariable {Object}
- * @param objectPathArray {Array}
- * @param injectFn {Function}
- */
- injectCheck: function ( masterVariable, objectPathArray, injectFn ) {
- var i, len, prev, memberName, lastMember,
- curr = masterVariable;
-
- // Get the object in question through the path from the master variable,
- // We can't pass the value directly because we need to re-define the object
- // member and keep references to the parent object, member name and member
- // value at all times.
- for ( i = 0, len = objectPathArray.length; i < len; i++ ) {
- memberName = objectPathArray[i];
-
- prev = curr;
- curr = prev[memberName];
- lastMember = memberName;
- }
+ /**
+ * CompletenessTest.fn.injectCheck
+ *
+ * Injects a function (such as a spy that updates methodCallTracker when
+ * it's called) inside another function.
+ *
+ * @param masterVariable {Object}
+ * @param objectPathArray {Array}
+ * @param injectFn {Function}
+ */
+ injectCheck: function ( masterVariable, objectPathArray, injectFn ) {
+ var i, len, prev, memberName, lastMember,
+ curr = masterVariable;
+
+ // Get the object in question through the path from the master variable,
+ // We can't pass the value directly because we need to re-define the object
+ // member and keep references to the parent object, member name and member
+ // value at all times.
+ for ( i = 0, len = objectPathArray.length; i < len; i++ ) {
+ memberName = objectPathArray[i];
+
+ prev = curr;
+ curr = prev[memberName];
+ lastMember = memberName;
+ }
- // Objects are by reference, members (unless objects) are not.
- prev[lastMember] = function () {
- injectFn();
- return curr.apply( this, arguments );
- };
- }
-};
+ // Objects are by reference, members (unless objects) are not.
+ prev[lastMember] = function () {
+ injectFn();
+ return curr.apply( this, arguments );
+ };
+ }
+ };
-window.CompletenessTest = CompletenessTest;
+ /* Expose */
+ window.CompletenessTest = CompletenessTest;
-} )( jQuery );
+}( jQuery ) );