*
* @author Timo Tijhof, 2011-2012
*/
-( function ( $ ) {
+( function ( mw, $ ) {
'use strict';
var util,
hasOwn = Object.prototype.hasOwnProperty,
- log = (window.console && window.console.log)
- ? function () { return window.console.log.apply(window.console, arguments); }
+ log = ( window.console && window.console.log )
+ ? function () { return window.console.log.apply( window.console, arguments ); }
: function () {};
// Simplified version of a few jQuery methods, except that they don't
* were not called from that instance.
*/
function CompletenessTest( masterVariable, ignoreFn ) {
+ var warn,
+ that = this;
// Keep track in these objects. Keyed by strings with the
// method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true.
this.lazyLimit = 2000;
this.lazyCounter = 0;
- var that = this;
-
// Bind begin and end to QUnit.
QUnit.begin( function () {
- that.walkTheObject( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT );
- log( 'CompletenessTest/walkTheObject/ACTION_INJECT', that );
- });
+ // Suppress warnings (e.g. deprecation notices for accessing the properties)
+ warn = mw.log.warn;
+ mw.log.warn = $.noop;
+
+ that.walkTheObject( masterVariable, null, masterVariable, [] );
+ log( 'CompletenessTest/walkTheObject', that );
+
+ // Restore warnings
+ mw.log.warn = warn;
+ warn = undefined;
+ } );
QUnit.done( function () {
that.populateMissingTests();
var elItem = document.createElement( 'li' );
elItem.textContent = key;
elList.appendChild( elItem );
- });
+ } );
elFoot = document.createElement( 'p' );
elFoot.innerHTML = '<em>— CompletenessTest</em>';
util.each( style, function ( key, value ) {
elOutputWrapper.style[key] = value;
- });
+ } );
return elOutputWrapper;
}
if ( toolbar ) {
toolbar.insertBefore( testResults, toolbar.firstChild );
}
- });
+ } );
return this;
}
- /* Static members */
- CompletenessTest.ACTION_INJECT = 500;
- CompletenessTest.ACTION_CHECK = 501;
-
/* Public methods */
CompletenessTest.fn = CompletenessTest.prototype = {
* 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, currPathArray,
- type = util.type( currVar ),
- that = this;
+ walkTheObject: function ( currObj, currName, masterVariable, parentPathArray ) {
+ var key, currVal, type,
+ ct = this,
+ currPathArray = parentPathArray;
- currPathArray = parentPathArray;
if ( currName ) {
currPathArray.push( currName );
+ currVal = currObj[currName];
+ } else {
+ currName = '(root)';
+ currVal = currObj;
}
+ type = util.type( currVal );
+
// Hard ignores
- if ( this.ignoreFn( currVar, that, currPathArray ) ) {
+ if ( this.ignoreFn( currVal, this, currPathArray ) ) {
return null;
}
// Functions
if ( type === 'function' ) {
-
- if ( !currVar.prototype || util.isEmptyObject( currVar.prototype ) ) {
-
- if ( action === CompletenessTest.ACTION_INJECT ) {
-
- that.injectionTracker[ currPathArray.join( '.' ) ] = true;
- that.injectCheck( masterVariable, currPathArray, function () {
- that.methodCallTracker[ currPathArray.join( '.' ) ] = true;
- } );
- }
-
- // We don't support checking object constructors yet...
- // ...we can check the prototypes fine, though.
- } else {
- if ( action === CompletenessTest.ACTION_INJECT ) {
-
- for ( key in currVar.prototype ) {
- if ( hasOwn.call( currVar.prototype, key ) ) {
- value = currVar.prototype[key];
- if ( key === 'constructor' ) {
- continue;
- }
-
- that.walkTheObject( key, value, masterVariable, currPathArray.concat( 'prototype' ), action );
- }
- }
-
- }
+ // Don't put a spy in constructor functions as it messes with
+ // instanceof etc.
+ if ( !currVal.prototype || util.isEmptyObject( currVal.prototype ) ) {
+ this.injectionTracker[ currPathArray.join( '.' ) ] = true;
+ this.injectCheck( currObj, currName, function () {
+ ct.methodCallTracker[ currPathArray.join( '.' ) ] = true;
+ } );
}
-
}
// 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];
-
- that.walkTheObject( key, value, masterVariable, currPathArray.slice(), action );
+ // This also traverses static properties and the prototype of a constructor
+ if ( type === 'object' || type === 'function' ) {
+ for ( key in currVal ) {
+ if ( hasOwn.call( currVal, key ) ) {
+ this.walkTheObject( currVal, key, masterVariable, currPathArray.slice() );
}
}
}
var ct = this;
util.each( ct.injectionTracker, function ( key ) {
ct.hasTest( key );
- });
+ } );
},
/**
* @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;
- }
+ injectCheck: function ( obj, key, injectFn ) {
+ var spy,
+ val = obj[ key ];
- // Objects are by reference, members (unless objects) are not.
- prev[lastMember] = function () {
+ spy = function () {
injectFn();
- return curr.apply( this, arguments );
+ return val.apply( this, arguments );
};
+
+ // Make the spy inherit from the original so that its static methods are also
+ // visible in the spy (e.g. when we inject a check into mw.log, mw.log.warn
+ // must remain accessible).
+ /*jshint proto:true */
+ spy.__proto__ = val;
+
+ // Objects are by reference, members (unless objects) are not.
+ obj[ key ] = spy;
}
};
/* Expose */
window.CompletenessTest = CompletenessTest;
-}( jQuery ) );
+}( mediaWiki, jQuery ) );