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