test runner: the changes are:
[jquery.git] / test / data / testrunner.js
1 jQuery.noConflict(); // Allow the test to run with other libs or jQuery's.
2
3 var _config = {
4         fixture: null,
5         Test: [],
6         stats: {
7                 all: 0,
8                 bad: 0
9         },
10         queue: [],
11         blocking: true,
12         timeout: null,
13         expected: null,
14         currentModule: null,
15         asyncTimeout: 2 // seconds for async timeout
16 };
17
18 _config.filters = location.search.length > 1 && //restrict modules/tests by get parameters
19                 jQuery.map( location.search.slice(1).split('&'), decodeURIComponent );
20
21 var isLocal = !!(window.location.protocol == 'file:');
22
23 jQuery(function() {
24         jQuery('#userAgent').html(navigator.userAgent);
25         runTest();      
26 });
27
28 function synchronize(callback) {
29         _config.queue[_config.queue.length] = callback;
30         if(!_config.blocking) {
31                 process();
32         }
33 }
34
35 function process() {
36         while(_config.queue.length && !_config.blocking) {
37                 var call = _config.queue[0];
38                 _config.queue = _config.queue.slice(1);
39                 call();
40         }
41 }
42
43 function stop(allowFailure) {
44         _config.blocking = true;
45         var handler = allowFailure ? start : function() {
46                 ok( false, "Test timed out" );
47                 start();
48         };
49         // Disabled, caused too many random errors
50         //_config.timeout = setTimeout(handler, _config.asyncTimeout * 1000);
51 }
52 function start() {
53         // A slight delay, to avoid any current callbacks
54         setTimeout(function(){
55                 if(_config.timeout)
56                         clearTimeout(_config.timeout);
57                 _config.blocking = false;
58                 process();
59         }, 13);
60 }
61
62 function validTest( name ) {
63         var filters = _config.filters;
64         if( !filters )
65                 return true;
66
67         var i = filters.length,
68                 run = false;
69         while( i-- ){
70                 var filter = filters[i],
71                         not = filter.charAt(0) == '!';
72                 if( not ) 
73                         filter = filter.slice(1);
74                 if( name.indexOf(filter) != -1 )
75                         return !not;
76                 if( not )
77                         run = true;
78         }
79         return run;
80 }
81
82 function runTest() {
83         _config.blocking = false;
84         var time = new Date();
85         _config.fixture = document.getElementById('main').innerHTML;
86         _config.ajaxSettings = jQuery.ajaxSettings;
87         synchronize(function() {
88                 time = new Date() - time;
89                 jQuery("<div>").html(['<p class="result">Tests completed in ',
90                         time, ' milliseconds.<br/>',
91                         _config.stats.bad, ' tests of ', _config.stats.all, ' failed.</p>']
92                         .join(''))
93                         .appendTo("body");
94                 jQuery("#banner").addClass(_config.stats.bad ? "fail" : "pass");
95         });
96 }
97
98 function test(name, callback, nowait) {
99         if(_config.currentModule)
100                 name = _config.currentModule + " module: " + name;
101                 
102         if ( !validTest(name) )
103                 return;
104                 
105         synchronize(function() {
106                 _config.Test = [];
107                 try {
108                         callback();
109                 } catch(e) {
110                         if( typeof console != "undefined" && console.error && console.warn ) {
111                                 console.error("Test " + name + " died, exception and test follows");
112                                 console.error(e);
113                                 console.warn(callback.toString());
114                         }
115                         _config.Test.push( [ false, "Died on test #" + (_config.Test.length+1) + ": " + e.message ] );
116                 }
117         });
118         synchronize(function() {
119                 reset();
120                 
121                 // don't output pause tests
122                 if(nowait) return;
123                 
124                 if(_config.expected && _config.expected != _config.Test.length) {
125                         _config.Test.push( [ false, "Expected " + _config.expected + " assertions, but " + _config.Test.length + " were run" ] );
126                 }
127                 _config.expected = null;
128                 
129                 var good = 0, bad = 0;
130                 var ol = document.createElement("ol");
131                 ol.style.display = "none";
132                 var li = "", state = "pass";
133                 for ( var i = 0; i < _config.Test.length; i++ ) {
134                         var li = document.createElement("li");
135                         li.className = _config.Test[i][0] ? "pass" : "fail";
136                         li.innerHTML = _config.Test[i][1];
137                         ol.appendChild( li );
138                         
139                         _config.stats.all++;
140                         if ( !_config.Test[i][0] ) {
141                                 state = "fail";
142                                 bad++;
143                                 _config.stats.bad++;
144                         } else good++;
145                 }
146         
147                 var li = document.createElement("li");
148                 li.className = state;
149         
150                 var b = document.createElement("strong");
151                 b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + _config.Test.length + ")</b>";
152                 b.onclick = function(){
153                         var n = this.nextSibling;
154                         if ( jQuery.css( n, "display" ) == "none" )
155                                 n.style.display = "block";
156                         else
157                                 n.style.display = "none";
158                 };
159                 jQuery(b).dblclick(function(event) {
160                         var target = jQuery(event.target).filter("strong").clone();
161                         if ( target.length ) {
162                                 target.children().remove();
163                                 location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(jQuery.trim(target.text()));
164                         }
165                 });
166                 li.appendChild( b );
167                 li.appendChild( ol );
168         
169                 document.getElementById("tests").appendChild( li );             
170         });
171 }
172
173 // call on start of module test to prepend name to all tests
174 function module(moduleName) {
175         _config.currentModule = moduleName;
176 }
177
178 /**
179  * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
180  */
181 function expect(asserts) {
182         _config.expected = asserts;
183 }
184
185 /**
186  * Resets the test setup. Useful for tests that modify the DOM.
187  */
188 function reset() {
189         jQuery("#main").html( _config.fixture );
190         jQuery.event.global = {};
191         jQuery.ajaxSettings = jQuery.extend({}, _config.ajaxSettings);
192 }
193
194 /**
195  * Asserts true.
196  * @example ok( jQuery("a").size() > 5, "There must be at least 5 anchors" );
197  */
198 function ok(a, msg) {
199         _config.Test.push( [ !!a, msg ] );
200 }
201
202 /**
203  * Asserts that two arrays are the same
204  */
205 function isSet(a, b, msg) {
206         var ret = true;
207         if ( a && b && a.length != undefined && a.length == b.length ) {
208                 for ( var i = 0; i < a.length; i++ )
209                         if ( a[i] != b[i] )
210                                 ret = false;
211         } else
212                 ret = false;
213         if ( !ret )
214                 _config.Test.push( [ ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) ] );
215         else 
216                 _config.Test.push( [ ret, msg ] );
217 }
218
219 /**
220  * Asserts that two objects are equivalent
221  */
222 function isObj(a, b, msg) {
223         var ret = true;
224         
225         if ( a && b ) {
226                 for ( var i in a )
227                         if ( a[i] != b[i] )
228                                 ret = false;
229
230                 for ( i in b )
231                         if ( a[i] != b[i] )
232                                 ret = false;
233         } else
234                 ret = false;
235
236     _config.Test.push( [ ret, msg ] );
237 }
238
239 function serialArray( a ) {
240         var r = [];
241         
242         if ( a && a.length )
243         for ( var i = 0; i < a.length; i++ ) {
244             var str = a[i].nodeName;
245             if ( str ) {
246                 str = str.toLowerCase();
247                 if ( a[i].id )
248                     str += "#" + a[i].id;
249             } else
250                 str = a[i];
251             r.push( str );
252         }
253
254         return "[ " + r.join(", ") + " ]";
255 }
256
257 /**
258  * Returns an array of elements with the given IDs, eg.
259  * @example q("main", "foo", "bar")
260  * @result [<div id="main">, <span id="foo">, <input id="bar">]
261  */
262 function q() {
263         var r = [];
264         for ( var i = 0; i < arguments.length; i++ )
265                 r.push( document.getElementById( arguments[i] ) );
266         return r;
267 }
268
269 /**
270  * Asserts that a select matches the given IDs
271  * @example t("Check for something", "//[a]", ["foo", "baar"]);
272  * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
273  */
274 function t(a,b,c) {
275         var f = jQuery(b);
276         var s = "";
277         for ( var i = 0; i < f.length; i++ )
278                 s += (s && ",") + '"' + f[i].id + '"';
279         isSet(f, q.apply(q,c), a + " (" + b + ")");
280 }
281
282 /**
283  * Add random number to url to stop IE from caching
284  *
285  * @example url("data/test.html")
286  * @result "data/test.html?10538358428943"
287  *
288  * @example url("data/test.php?foo=bar")
289  * @result "data/test.php?foo=bar&10538358345554"
290  */
291 function url(value) {
292         return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
293 }
294
295 /**
296  * Checks that the first two arguments are equal, with an optional message.
297  * Prints out both expected and actual values on failure.
298  *
299  * Prefered to ok( expected == actual, message )
300  *
301  * @example equals( "Expected 2 characters.", v.formatMessage("Expected {0} characters.", 2) );
302  *
303  * @param Object actual
304  * @param Object expected
305  * @param String message (optional)
306  */
307 function equals(actual, expected, message) {
308         var result = expected == actual;
309         message = message || (result ? "okay" : "failed");
310         _config.Test.push( [ result, result ? message + ": " + expected : message + " expected: " + expected + " actual: " + actual ] );
311 }
312
313 /**
314  * Trigger an event on an element.
315  *
316  * @example triggerEvent( document.body, "click" );
317  *
318  * @param DOMElement elem
319  * @param String type
320  */
321 function triggerEvent( elem, type, event ) {
322         if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
323                 event = document.createEvent("MouseEvents");
324                 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
325                         0, 0, 0, 0, 0, false, false, false, false, 0, null);
326                 elem.dispatchEvent( event );
327         } else if ( jQuery.browser.msie ) {
328                 elem.fireEvent("on"+type);
329         }
330 }