Correcting type of DatabaseSqlite::insertId()
[mediawiki.git] / resources / jquery / jquery.qunit.completenessTest.js
blob5abb8691ffce4f708a9230d1b5b056c3325b0375
1 /**
2  * jQuery QUnit CompletenessTest 0.3
3  *
4  * Tests the completeness of test suites for object oriented javascript
5  * libraries. Written to be used in environments with jQuery and QUnit.
6  * Requires jQuery 1.5.2 or higher.
7  *
8  * Globals: jQuery, QUnit, console.log
9  *
10  * Built for and tested with:
11  * - Safari 5
12  * - Firefox 4
13  *
14  * @author Timo Tijhof, 2011
15  */
16 ( function( $ ) {
18 /**
19  * CompletenessTest
20  * @constructor
21  *
22  * @example
23  *  var myTester = new CompletenessTest( myLib );
24  * @param masterVariable {Object} The root variable that contains all object
25  *  members. CompletenessTest will recursively traverse objects and keep track
26  *  of all methods.
27  * @param ignoreFn {Function} Optionally pass a function to filter out certain
28  *  methods. Example: You may want to filter out instances of jQuery or some
29  *  other constructor. Otherwise "missingTests" will include all methods that
30  *  were not called from that instance.
31  */
32 var CompletenessTest = function ( masterVariable, ignoreFn ) {
34         // Keep track in these objects. Keyed by strings with the
35         // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true.
36         this.methodCallTracker = {};
37         this.missingTests = {};
39         this.ignoreFn = undefined === ignoreFn ? function(){ return false; } : ignoreFn;
41         // Lazy limit in case something weird happends (like recurse (part of) ourself).
42         this.lazyLimit = 1000;
43         this.lazyCounter = 0;
45         var that = this;
47         // Bind begin and end to QUnit.
48         QUnit.begin = function(){
49                 that.checkTests( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_INJECT );
50         };
52         QUnit.done = function(){
53                 that.checkTests( null, masterVariable, masterVariable, [], CompletenessTest.ACTION_CHECK );
54                 console.log( 'CompletenessTest.ACTION_CHECK', that );
56                 // Build HTML representing the outcome from CompletenessTest
57                 // And insert it into the header.
59                 var makeList = function( blob, title, style ) {
60                         title = title || 'Values';
61                         var html = '<strong>' + mw.html.escape(title) + '</strong>';
62                         $.each( blob, function( key ) {
63                                 html += '<br />' + mw.html.escape(key);
64                         });
65                         html += '<br /><br /><em>&mdash; CompletenessTest</em>';
66                         var     $oldResult = $( '#qunit-completenesstest' ),
67                                 $result = $oldResult.length ? $oldResult : $( '<div id="qunit-completenesstest"></div>' );
68                         return $result.css( style ).html( html );
69                 };
71                 if ( $.isEmptyObject( that.missingTests ) ) {
72                         // Good
73                         var $testResults = makeList(
74                                 { 'No missing tests!': true },
75                                 'missingTests',
76                                 {
77                                         background: '#D2E0E6',
78                                         color: '#366097',
79                                         padding: '1em'
80                                 }
81                         );
82                 } else {
83                         // Bad
84                         var $testResults = makeList(
85                                 that.missingTests,
86                                 'missingTests',
87                                 {
88                                         background: '#EE5757',
89                                         color: 'black',
90                                         padding: '1em'
91                                 }
92                         );
93                 }
95                 $( '#qunit-testrunner-toolbar' ).prepend( $testResults );
96         };
98         return this;
101 /* Static members */
102 CompletenessTest.ACTION_INJECT = 500;
103 CompletenessTest.ACTION_CHECK = 501;
105 /* Public methods */
106 CompletenessTest.fn = CompletenessTest.prototype = {
108         /**
109          * CompletenessTest.fn.checkTests
110          *
111          * This function recursively walks through the given object, calling itself as it goes.
112          * Depending on the action it either injects our listener into the methods, or
113          * reads from our tracker and records which methods have not been called by the test suite.
114          *
115          * @param currName {String|Null} Name of the given object member (Initially this is null).
116          * @param currVar {mixed} The variable to check (initially an object,
117          *  further down it could be anything).
118          * @param masterVariable {Object} Throughout our interation, always keep track of the master/root.
119          *  Initially this is the same as currVar.
120          * @param parentPathArray {Array} Array of names that indicate our breadcrumb path starting at
121          *  masterVariable. Not including currName.
122          * @param action {Number} What is this function supposed to do (ACTION_INJECT or ACTION_CHECK)
123          */
124         checkTests: function( currName, currVar, masterVariable, parentPathArray, action ) {
126                 // Handle the lazy limit
127                 this.lazyCounter++;
128                 if ( this.lazyCounter > this.lazyLimit ) {
129                         console.log( 'CompletenessTest.fn.checkTests> Limit reached: ' + this.lazyCounter );
130                         return null;
131                 }
133                 var     type = $.type( currVar ),
134                         that = this;
136                 // Hard ignores
137                 if ( this.ignoreFn( currVar, that, parentPathArray ) ) {
138                         return null;
140                 // Functions
141                 } else  if ( type === 'function' ) {
143                         /* CHECK MODE */
145                         if ( action === CompletenessTest.ACTION_CHECK ) {
147                                 if ( !currVar.prototype || $.isEmptyObject( currVar.prototype ) ) {
149                                         that.hasTest( parentPathArray.join( '.' ) );
151                                 // We don't support checking object constructors yet...
152                                 } else {
154                                         // ...the prototypes are fine tho
155                                         $.each( currVar.prototype, function( key, value ) {
156                                                 if ( key === 'constructor' ) return;
158                                                 // Clone and break reference to parentPathArray
159                                                 var tmpPathArray = $.extend( [], parentPathArray );
160                                                 tmpPathArray.push( 'prototype' ); tmpPathArray.push( key );
162                                                 that.hasTest( tmpPathArray.join( '.' ) );
163                                         } );
164                                 }
166                         /* INJECT MODE */
168                         } else if ( action === CompletenessTest.ACTION_INJECT ) {
170                                 if ( !currVar.prototype || $.isEmptyObject( currVar.prototype ) ) {
172                                         // Inject check
173                                         that.injectCheck( masterVariable, parentPathArray, function() {
174                                                 that.methodCallTracker[ parentPathArray.join( '.' ) ] = true;
175                                         } );
177                                 // We don't support checking object constructors yet...
178                                 } else {
180                                         // ... the prototypes are fine tho
181                                         $.each( currVar.prototype, function( key, value ) {
182                                                 if ( key === 'constructor' ) return;
184                                                 // Clone and break reference to parentPathArray
185                                                 var tmpPathArray = $.extend( [], parentPathArray );
186                                                 tmpPathArray.push( 'prototype' ); tmpPathArray.push( key );
188                                                 that.checkTests( key, value, masterVariable, tmpPathArray, action );
189                                         } );
190                                 }
192                         }
194                 // Recursively. After all, this *is* the completeness test
195                 } else if ( type === 'object' ) {
197                         $.each( currVar, function( key, value ) {
199                                 // Clone and break reference to parentPathArray
200                                 var tmpPathArray = $.extend( [], parentPathArray );
201                                 tmpPathArray.push( key );
203                                 that.checkTests( key, value, masterVariable, tmpPathArray, action );
205                         } );
207                 }
208         },
210         /**
211          * CompletenessTest.fn.hasTest
212          *
213          * Checks if the given method name (ie. 'my.foo.bar')
214          * was called during the test suite (as far as the tracker knows).
215          * If not it adds it to missingTests.
216          *
217          * @param fnName {String}
218          * @return {Boolean}
219          */
220         hasTest: function( fnName ) {
221                 if ( !( fnName in this.methodCallTracker ) ) {
222                         this.missingTests[fnName] = true;
223                         return false;
224                 }
225                 return true;
226         },
228         /**
229          * CompletenessTest.fn.injectCheck
230          *
231          * Injects a function (such as a spy that updates methodCallTracker when
232          * it's called) inside another function.
233          *
234          * @param masterVariable {Object}
235          * @param objectPathArray {Array}
236          * @param injectFn {Function}
237          */
238         injectCheck: function( masterVariable, objectPathArray, injectFn ) {
239                 var     prev,
240                         curr = masterVariable,
241                         lastMember;
243                 $.each( objectPathArray, function( i, memberName ) {
244                         prev = curr;
245                         curr = prev[memberName];
246                         lastMember = memberName;
247                 });
249                 // Objects are by reference, members (unless objects) are not.
250                 prev[lastMember] = function() {
251                         injectFn();
252                         return curr.apply( this, arguments );
253                 };
254         }
257 window.CompletenessTest = CompletenessTest;
259 } )( jQuery );