| 1 |
/* |
| 2 |
* QUnit - jQuery unit testrunner |
| 3 |
* |
| 4 |
* http://docs.jquery.com/QUnit |
| 5 |
* |
| 6 |
* Copyright (c) 2008 John Resig, Jörn Zaefferer |
| 7 |
* Dual licensed under the MIT (MIT-LICENSE.txt) |
| 8 |
* and GPL (GPL-LICENSE.txt) licenses. |
| 9 |
* |
| 10 |
* $Id: testrunner.js 6173 2009-02-02 20:09:32Z jeresig $ |
| 11 |
*/ |
| 12 |
|
| 13 |
(function($) { |
| 14 |
|
| 15 |
// Tests for equality any JavaScript type and structure without unexpected results. |
| 16 |
// Discussions and reference: http://philrathe.com/articles/equiv |
| 17 |
// Test suites: http://philrathe.com/tests/equiv |
| 18 |
// Author: Philippe Rathé <[email protected]> |
| 19 |
var equiv = function () { |
| 20 |
|
| 21 |
var innerEquiv; // the real equiv function |
| 22 |
var callers = []; // stack to decide between skip/abort functions |
| 23 |
|
| 24 |
// Determine what is o. |
| 25 |
function hoozit(o) { |
| 26 |
if (typeof o === "string") { |
| 27 |
return "string"; |
| 28 |
|
| 29 |
} else if (typeof o === "boolean") { |
| 30 |
return "boolean"; |
| 31 |
|
| 32 |
} else if (typeof o === "number") { |
| 33 |
|
| 34 |
if (isNaN(o)) { |
| 35 |
return "nan"; |
| 36 |
} else { |
| 37 |
return "number"; |
| 38 |
} |
| 39 |
|
| 40 |
} else if (typeof o === "undefined") { |
| 41 |
return "undefined"; |
| 42 |
|
| 43 |
// consider: typeof null === object |
| 44 |
} else if (o === null) { |
| 45 |
return "null"; |
| 46 |
|
| 47 |
// consider: typeof [] === object |
| 48 |
} else if (o instanceof Array) { |
| 49 |
return "array"; |
| 50 |
|
| 51 |
// consider: typeof new Date() === object |
| 52 |
} else if (o instanceof Date) { |
| 53 |
return "date"; |
| 54 |
|
| 55 |
// consider: /./ instanceof Object; |
| 56 |
// /./ instanceof RegExp; |
| 57 |
// typeof /./ === "function"; // => false in IE and Opera, |
| 58 |
// true in FF and Safari |
| 59 |
} else if (o instanceof RegExp) { |
| 60 |
return "regexp"; |
| 61 |
|
| 62 |
} else if (typeof o === "object") { |
| 63 |
return "object"; |
| 64 |
|
| 65 |
} else if (o instanceof Function) { |
| 66 |
return "function"; |
| 67 |
} |
| 68 |
} |
| 69 |
|
| 70 |
// Call the o related callback with the given arguments. |
| 71 |
function bindCallbacks(o, callbacks, args) { |
| 72 |
var prop = hoozit(o); |
| 73 |
if (prop) { |
| 74 |
if (hoozit(callbacks[prop]) === "function") { |
| 75 |
return callbacks[prop].apply(callbacks, args); |
| 76 |
} else { |
| 77 |
return callbacks[prop]; // or undefined |
| 78 |
} |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
var callbacks = function () { |
| 83 |
|
| 84 |
// for string, boolean, number and null |
| 85 |
function useStrictEquality(b, a) { |
| 86 |
return a === b; |
| 87 |
} |
| 88 |
|
| 89 |
return { |
| 90 |
"string": useStrictEquality, |
| 91 |
"boolean": useStrictEquality, |
| 92 |
"number": useStrictEquality, |
| 93 |
"null": useStrictEquality, |
| 94 |
"undefined": useStrictEquality, |
| 95 |
|
| 96 |
"nan": function (b) { |
| 97 |
return isNaN(b); |
| 98 |
}, |
| 99 |
|
| 100 |
"date": function (b, a) { |
| 101 |
return hoozit(b) === "date" && a.valueOf() === b.valueOf(); |
| 102 |
}, |
| 103 |
|
| 104 |
"regexp": function (b, a) { |
| 105 |
return hoozit(b) === "regexp" && |
| 106 |
a.source === b.source && // the regex itself |
| 107 |
a.global === b.global && // and its modifers (gmi) ... |
| 108 |
a.ignoreCase === b.ignoreCase && |
| 109 |
a.multiline === b.multiline; |
| 110 |
}, |
| 111 |
|
| 112 |
// - skip when the property is a method of an instance (OOP) |
| 113 |
// - abort otherwise, |
| 114 |
// initial === would have catch identical references anyway |
| 115 |
"function": function () { |
| 116 |
var caller = callers[callers.length - 1]; |
| 117 |
return caller !== Object && |
| 118 |
typeof caller !== "undefined"; |
| 119 |
}, |
| 120 |
|
| 121 |
"array": function (b, a) { |
| 122 |
var i; |
| 123 |
var len; |
| 124 |
|
| 125 |
// b could be an object literal here |
| 126 |
if ( ! (hoozit(b) === "array")) { |
| 127 |
return false; |
| 128 |
} |
| 129 |
|
| 130 |
len = a.length; |
| 131 |
if (len !== b.length) { // safe and faster |
| 132 |
return false; |
| 133 |
} |
| 134 |
for (i = 0; i < len; i++) { |
| 135 |
if( ! innerEquiv(a[i], b[i])) { |
| 136 |
return false; |
| 137 |
} |
| 138 |
} |
| 139 |
return true; |
| 140 |
}, |
| 141 |
|
| 142 |
"object": function (b, a) { |
| 143 |
var i; |
| 144 |
var eq = true; // unless we can proove it |
| 145 |
var aProperties = [], bProperties = []; // collection of strings |
| 146 |
|
| 147 |
// comparing constructors is more strict than using instanceof |
| 148 |
if ( a.constructor !== b.constructor) { |
| 149 |
return false; |
| 150 |
} |
| 151 |
|
| 152 |
// stack constructor before traversing properties |
| 153 |
callers.push(a.constructor); |
| 154 |
|
| 155 |
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep |
| 156 |
|
| 157 |
aProperties.push(i); // collect a's properties |
| 158 |
|
| 159 |
if ( ! innerEquiv(a[i], b[i])) { |
| 160 |
eq = false; |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
callers.pop(); // unstack, we are done |
| 165 |
|
| 166 |
for (i in b) { |
| 167 |
bProperties.push(i); // collect b's properties |
| 168 |
} |
| 169 |
|
| 170 |
// Ensures identical properties name |
| 171 |
return eq && innerEquiv(aProperties.sort(), bProperties.sort()); |
| 172 |
} |
| 173 |
}; |
| 174 |
}(); |
| 175 |
|
| 176 |
innerEquiv = function () { // can take multiple arguments |
| 177 |
var args = Array.prototype.slice.apply(arguments); |
| 178 |
if (args.length < 2) { |
| 179 |
return true; // end transition |
| 180 |
} |
| 181 |
|
| 182 |
return (function (a, b) { |
| 183 |
if (a === b) { |
| 184 |
return true; // catch the most you can |
| 185 |
|
| 186 |
} else if (typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined") { |
| 187 |
return false; // don't lose time with error prone cases |
| 188 |
|
| 189 |
} else { |
| 190 |
return bindCallbacks(a, callbacks, [b, a]); |
| 191 |
} |
| 192 |
|
| 193 |
// apply transition with (1..n) arguments |
| 194 |
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); |
| 195 |
}; |
| 196 |
|
| 197 |
return innerEquiv; |
| 198 |
}(); // equiv |
| 199 |
|
| 200 |
var GETParams = $.map( location.search.slice(1).split('&'), decodeURIComponent ), |
| 201 |
ngindex = $.inArray("noglobals", GETParams), |
| 202 |
noglobals = ngindex !== -1; |
| 203 |
|
| 204 |
if( noglobals ) |
| 205 |
GETParams.splice( ngindex, 1 ); |
| 206 |
|
| 207 |
var config = { |
| 208 |
stats: { |
| 209 |
all: 0, |
| 210 |
bad: 0 |
| 211 |
}, |
| 212 |
queue: [], |
| 213 |
// block until document ready |
| 214 |
blocking: true, |
| 215 |
//restrict modules/tests by get parameters |
| 216 |
filters: GETParams, |
| 217 |
isLocal: !!(window.location.protocol == 'file:') |
| 218 |
}; |
| 219 |
|
| 220 |
// public API as global methods |
| 221 |
$.extend(window, { |
| 222 |
test: test, |
| 223 |
module: module, |
| 224 |
expect: expect, |
| 225 |
ok: ok, |
| 226 |
equals: equals, |
| 227 |
start: start, |
| 228 |
stop: stop, |
| 229 |
reset: reset, |
| 230 |
isLocal: config.isLocal, |
| 231 |
same: function(a, b, message) { |
| 232 |
push(equiv(a, b), a, b, message); |
| 233 |
}, |
| 234 |
QUnit: { |
| 235 |
equiv: equiv, |
| 236 |
ok: ok, |
| 237 |
done: function(failures, total){}, |
| 238 |
log: function(result, message){} |
| 239 |
}, |
| 240 |
// legacy methods below |
| 241 |
isSet: isSet, |
| 242 |
isObj: isObj, |
| 243 |
compare: function() { |
| 244 |
throw "compare is deprecated - use same() instead"; |
| 245 |
}, |
| 246 |
compare2: function() { |
| 247 |
throw "compare2 is deprecated - use same() instead"; |
| 248 |
}, |
| 249 |
serialArray: function() { |
| 250 |
throw "serialArray is deprecated - use jsDump.parse() instead"; |
| 251 |
}, |
| 252 |
q: q, |
| 253 |
t: t, |
| 254 |
url: url, |
| 255 |
triggerEvent: triggerEvent |
| 256 |
}); |
| 257 |
|
| 258 |
$(window).load(function() { |
| 259 |
$('#userAgent').html(navigator.userAgent); |
| 260 |
var head = $('<div class="testrunner-toolbar"><label for="filter-pass">Hide passed tests</label></div>').insertAfter("#userAgent"); |
| 261 |
$('<input type="checkbox" id="filter-pass" />').attr("disabled", true).prependTo(head).click(function() { |
| 262 |
$('li.pass')[this.checked ? 'hide' : 'show'](); |
| 263 |
}); |
| 264 |
$('<input type="checkbox" id="filter-missing">').attr("disabled", true).appendTo(head).click(function() { |
| 265 |
$("li.fail:contains('missing test - untested code is broken code')").parent('ol').parent('li.fail')[this.checked ? 'hide' : 'show'](); |
| 266 |
}); |
| 267 |
$("#filter-missing").after('<label for="filter-missing">Hide missing tests (untested code is broken code)</label>'); |
| 268 |
runTest(); |
| 269 |
}); |
| 270 |
|
| 271 |
function synchronize(callback) { |
| 272 |
config.queue.push(callback); |
| 273 |
if(!config.blocking) { |
| 274 |
process(); |
| 275 |
} |
| 276 |
} |
| 277 |
|
| 278 |
function process() { |
| 279 |
while(config.queue.length && !config.blocking) { |
| 280 |
config.queue.shift()(); |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
function stop(timeout) { |
| 285 |
config.blocking = true; |
| 286 |
if (timeout) |
| 287 |
config.timeout = setTimeout(function() { |
| 288 |
QUnit.ok( false, "Test timed out" ); |
| 289 |
start(); |
| 290 |
}, timeout); |
| 291 |
} |
| 292 |
function start() { |
| 293 |
// A slight delay, to avoid any current callbacks |
| 294 |
setTimeout(function() { |
| 295 |
if(config.timeout) |
| 296 |
clearTimeout(config.timeout); |
| 297 |
config.blocking = false; |
| 298 |
process(); |
| 299 |
}, 13); |
| 300 |
} |
| 301 |
|
| 302 |
function validTest( name ) { |
| 303 |
var i = config.filters.length, |
| 304 |
run = false; |
| 305 |
|
| 306 |
if( !i ) |
| 307 |
return true; |
| 308 |
|
| 309 |
while( i-- ){ |
| 310 |
var filter = config.filters[i], |
| 311 |
not = filter.charAt(0) == '!'; |
| 312 |
if( not ) |
| 313 |
filter = filter.slice(1); |
| 314 |
if( name.indexOf(filter) != -1 ) |
| 315 |
return !not; |
| 316 |
if( not ) |
| 317 |
run = true; |
| 318 |
} |
| 319 |
return run; |
| 320 |
} |
| 321 |
|
| 322 |
function runTest() { |
| 323 |
config.blocking = false; |
| 324 |
var started = +new Date; |
| 325 |
config.fixture = document.getElementById('main').innerHTML; |
| 326 |
config.ajaxSettings = $.ajaxSettings; |
| 327 |
synchronize(function() { |
| 328 |
$('<p id="testresult" class="result"/>').html(['Tests completed in ', |
| 329 |
+new Date - started, ' milliseconds.<br/>', |
| 330 |
'<span class="bad">', config.stats.bad, '</span> tests of <span class="all">', config.stats.all, '</span> failed.'] |
| 331 |
.join('')) |
| 332 |
.appendTo("body"); |
| 333 |
$("#banner").addClass(config.stats.bad ? "fail" : "pass"); |
| 334 |
QUnit.done( config.stats.bad, config.stats.all ); |
| 335 |
}); |
| 336 |
} |
| 337 |
|
| 338 |
var pollution; |
| 339 |
|
| 340 |
function saveGlobal(){ |
| 341 |
pollution = [ ]; |
| 342 |
|
| 343 |
if( noglobals ) |
| 344 |
for( var key in window ) |
| 345 |
pollution.push(key); |
| 346 |
} |
| 347 |
function checkPollution( name ){ |
| 348 |
var old = pollution; |
| 349 |
saveGlobal(); |
| 350 |
|
| 351 |
if( pollution.length > old.length ){ |
| 352 |
ok( false, "Introduced global variable(s): " + diff(old, pollution).join(", ") ); |
| 353 |
config.expected++; |
| 354 |
} |
| 355 |
} |
| 356 |
|
| 357 |
function diff( clean, dirty ){ |
| 358 |
return $.grep( dirty, function(name){ |
| 359 |
return $.inArray( name, clean ) == -1; |
| 360 |
}); |
| 361 |
} |
| 362 |
|
| 363 |
function test(name, callback) { |
| 364 |
if(config.currentModule) |
| 365 |
name = config.currentModule + " module: " + name; |
| 366 |
var lifecycle = $.extend({ |
| 367 |
setup: function() {}, |
| 368 |
teardown: function() {} |
| 369 |
}, config.moduleLifecycle); |
| 370 |
|
| 371 |
if ( !validTest(name) ) |
| 372 |
return; |
| 373 |
|
| 374 |
synchronize(function() { |
| 375 |
config.assertions = []; |
| 376 |
config.expected = null; |
| 377 |
try { |
| 378 |
if( !pollution ) |
| 379 |
saveGlobal(); |
| 380 |
lifecycle.setup(); |
| 381 |
} catch(e) { |
| 382 |
QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); |
| 383 |
} |
| 384 |
}) |
| 385 |
synchronize(function() { |
| 386 |
try { |
| 387 |
callback(); |
| 388 |
} catch(e) { |
| 389 |
if( typeof console != "undefined" && console.error && console.warn ) { |
| 390 |
console.error("Test " + name + " died, exception and test follows"); |
| 391 |
console.error(e); |
| 392 |
console.warn(callback.toString()); |
| 393 |
} |
| 394 |
QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); |
| 395 |
// else next test will carry the responsibility |
| 396 |
saveGlobal(); |
| 397 |
} |
| 398 |
}); |
| 399 |
synchronize(function() { |
| 400 |
try { |
| 401 |
checkPollution(); |
| 402 |
lifecycle.teardown(); |
| 403 |
} catch(e) { |
| 404 |
QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); |
| 405 |
} |
| 406 |
}) |
| 407 |
synchronize(function() { |
| 408 |
try { |
| 409 |
reset(); |
| 410 |
} catch(e) { |
| 411 |
if( typeof console != "undefined" && console.error && console.warn ) { |
| 412 |
console.error("reset() failed, following Test " + name + ", exception and reset fn follows"); |
| 413 |
console.error(e); |
| 414 |
console.warn(reset.toString()); |
| 415 |
} |
| 416 |
} |
| 417 |
|
| 418 |
if(config.expected && config.expected != config.assertions.length) { |
| 419 |
QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); |
| 420 |
} |
| 421 |
|
| 422 |
var good = 0, bad = 0; |
| 423 |
var ol = $("<ol/>").hide(); |
| 424 |
config.stats.all += config.assertions.length; |
| 425 |
for ( var i = 0; i < config.assertions.length; i++ ) { |
| 426 |
var assertion = config.assertions[i]; |
| 427 |
$("<li/>").addClass(assertion.result ? "pass" : "fail").text(assertion.message || "(no message)").appendTo(ol); |
| 428 |
assertion.result ? good++ : bad++; |
| 429 |
} |
| 430 |
config.stats.bad += bad; |
| 431 |
|
| 432 |
var b = $("<strong/>").html(name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>") |
| 433 |
.click(function(){ |
| 434 |
$(this).next().toggle(); |
| 435 |
}) |
| 436 |
.dblclick(function(event) { |
| 437 |
var target = $(event.target).filter("strong").clone(); |
| 438 |
if ( target.length ) { |
| 439 |
target.children().remove(); |
| 440 |
location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent($.trim(target.text())); |
| 441 |
} |
| 442 |
}); |
| 443 |
|
| 444 |
$("<li/>").addClass(bad ? "fail" : "pass").append(b).append(ol).appendTo("#tests"); |
| 445 |
|
| 446 |
if(bad) { |
| 447 |
$("#filter-pass").attr("disabled", null); |
| 448 |
$("#filter-missing").attr("disabled", null); |
| 449 |
} |
| 450 |
}); |
| 451 |
} |
| 452 |
|
| 453 |
// call on start of module test to prepend name to all tests |
| 454 |
function module(name, lifecycle) { |
| 455 |
config.currentModule = name; |
| 456 |
config.moduleLifecycle = lifecycle; |
| 457 |
} |
| 458 |
|
| 459 |
/** |
| 460 |
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. |
| 461 |
*/ |
| 462 |
function expect(asserts) { |
| 463 |
config.expected = asserts; |
| 464 |
} |
| 465 |
|
| 466 |
/** |
| 467 |
* Resets the test setup. Useful for tests that modify the DOM. |
| 468 |
*/ |
| 469 |
function reset() { |
| 470 |
$("#main").html( config.fixture ); |
| 471 |
$.event.global = {}; |
| 472 |
$.ajaxSettings = $.extend({}, config.ajaxSettings); |
| 473 |
} |
| 474 |
|
| 475 |
/** |
| 476 |
* Asserts true. |
| 477 |
* @example ok( $("a").size() > 5, "There must be at least 5 anchors" ); |
| 478 |
*/ |
| 479 |
function ok(a, msg) { |
| 480 |
QUnit.log(a, msg); |
| 481 |
|
| 482 |
config.assertions.push({ |
| 483 |
result: !!a, |
| 484 |
message: msg |
| 485 |
}); |
| 486 |
} |
| 487 |
|
| 488 |
/** |
| 489 |
* Asserts that two arrays are the same |
| 490 |
*/ |
| 491 |
function isSet(a, b, msg) { |
| 492 |
function serialArray( a ) { |
| 493 |
var r = []; |
| 494 |
|
| 495 |
if ( a && a.length ) |
| 496 |
for ( var i = 0; i < a.length; i++ ) { |
| 497 |
var str = a[i].nodeName; |
| 498 |
if ( str ) { |
| 499 |
str = str.toLowerCase(); |
| 500 |
if ( a[i].id ) |
| 501 |
str += "#" + a[i].id; |
| 502 |
} else |
| 503 |
str = a[i]; |
| 504 |
r.push( str ); |
| 505 |
} |
| 506 |
|
| 507 |
return "[ " + r.join(", ") + " ]"; |
| 508 |
} |
| 509 |
var ret = true; |
| 510 |
if ( a && b && a.length != undefined && a.length == b.length ) { |
| 511 |
for ( var i = 0; i < a.length; i++ ) |
| 512 |
if ( a[i] != b[i] ) |
| 513 |
ret = false; |
| 514 |
} else |
| 515 |
ret = false; |
| 516 |
QUnit.ok( ret, !ret ? (msg + " expected: " + serialArray(b) + " result: " + serialArray(a)) : msg ); |
| 517 |
} |
| 518 |
|
| 519 |
/** |
| 520 |
* Asserts that two objects are equivalent |
| 521 |
*/ |
| 522 |
function isObj(a, b, msg) { |
| 523 |
var ret = true; |
| 524 |
|
| 525 |
if ( a && b ) { |
| 526 |
for ( var i in a ) |
| 527 |
if ( a[i] != b[i] ) |
| 528 |
ret = false; |
| 529 |
|
| 530 |
for ( i in b ) |
| 531 |
if ( a[i] != b[i] ) |
| 532 |
ret = false; |
| 533 |
} else |
| 534 |
ret = false; |
| 535 |
|
| 536 |
QUnit.ok( ret, msg ); |
| 537 |
} |
| 538 |
|
| 539 |
/** |
| 540 |
* Returns an array of elements with the given IDs, eg. |
| 541 |
* @example q("main", "foo", "bar") |
| 542 |
* @result [<div id="main">, <span id="foo">, <input id="bar">] |
| 543 |
*/ |
| 544 |
function q() { |
| 545 |
var r = []; |
| 546 |
for ( var i = 0; i < arguments.length; i++ ) |
| 547 |
r.push( document.getElementById( arguments[i] ) ); |
| 548 |
return r; |
| 549 |
} |
| 550 |
|
| 551 |
/** |
| 552 |
* Asserts that a select matches the given IDs |
| 553 |
* @example t("Check for something", "//[a]", ["foo", "baar"]); |
| 554 |
* @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar' |
| 555 |
*/ |
| 556 |
function t(a,b,c) { |
| 557 |
var f = $(b); |
| 558 |
var s = ""; |
| 559 |
for ( var i = 0; i < f.length; i++ ) |
| 560 |
s += (s && ",") + '"' + f[i].id + '"'; |
| 561 |
isSet(f, q.apply(q,c), a + " (" + b + ")"); |
| 562 |
} |
| 563 |
|
| 564 |
/** |
| 565 |
* Add random number to url to stop IE from caching |
| 566 |
* |
| 567 |
* @example url("data/test.html") |
| 568 |
* @result "data/test.html?10538358428943" |
| 569 |
* |
| 570 |
* @example url("data/test.php?foo=bar") |
| 571 |
* @result "data/test.php?foo=bar&10538358345554" |
| 572 |
*/ |
| 573 |
function url(value) { |
| 574 |
return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); |
| 575 |
} |
| 576 |
|
| 577 |
/** |
| 578 |
* Checks that the first two arguments are equal, with an optional message. |
| 579 |
* Prints out both actual and expected values. |
| 580 |
* |
| 581 |
* Prefered to ok( actual == expected, message ) |
| 582 |
* |
| 583 |
* @example equals( $.format("Received {0} bytes.", 2), "Received 2 bytes." ); |
| 584 |
* |
| 585 |
* @param Object actual |
| 586 |
* @param Object expected |
| 587 |
* @param String message (optional) |
| 588 |
*/ |
| 589 |
function equals(actual, expected, message) { |
| 590 |
push(expected == actual, actual, expected, message); |
| 591 |
} |
| 592 |
|
| 593 |
function push(result, actual, expected, message) { |
| 594 |
message = message || (result ? "okay" : "failed"); |
| 595 |
QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + jsDump.parse(expected) + " result: " + jsDump.parse(actual) ); |
| 596 |
} |
| 597 |
|
| 598 |
/** |
| 599 |
* Trigger an event on an element. |
| 600 |
* |
| 601 |
* @example triggerEvent( document.body, "click" ); |
| 602 |
* |
| 603 |
* @param DOMElement elem |
| 604 |
* @param String type |
| 605 |
*/ |
| 606 |
function triggerEvent( elem, type, event ) { |
| 607 |
if ( $.browser.mozilla || $.browser.opera ) { |
| 608 |
event = document.createEvent("MouseEvents"); |
| 609 |
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, |
| 610 |
0, 0, 0, 0, 0, false, false, false, false, 0, null); |
| 611 |
elem.dispatchEvent( event ); |
| 612 |
} else if ( $.browser.msie ) { |
| 613 |
elem.fireEvent("on"+type); |
| 614 |
} |
| 615 |
} |
| 616 |
|
| 617 |
})(jQuery); |
| 618 |
|
| 619 |
/** |
| 620 |
* jsDump |
| 621 |
* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com |
| 622 |
* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) |
| 623 |
* Date: 5/15/2008 |
| 624 |
* @projectDescription Advanced and extensible data dumping for Javascript. |
| 625 |
* @version 1.0.0 |
| 626 |
* @author Ariel Flesler |
| 627 |
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} |
| 628 |
*/ |
| 629 |
(function(){ |
| 630 |
function quote( str ){ |
| 631 |
return '"' + str.toString().replace(/"/g, '\\"') + '"'; |
| 632 |
}; |
| 633 |
function literal( o ){ |
| 634 |
return o + ''; |
| 635 |
}; |
| 636 |
function join( pre, arr, post ){ |
| 637 |
var s = jsDump.separator(), |
| 638 |
base = jsDump.indent(); |
| 639 |
inner = jsDump.indent(1); |
| 640 |
if( arr.join ) |
| 641 |
arr = arr.join( ',' + s + inner ); |
| 642 |
if( !arr ) |
| 643 |
return pre + post; |
| 644 |
return [ pre, inner + arr, base + post ].join(s); |
| 645 |
}; |
| 646 |
function array( arr ){ |
| 647 |
var i = arr.length, ret = Array(i); |
| 648 |
this.up(); |
| 649 |
while( i-- ) |
| 650 |
ret[i] = this.parse( arr[i] ); |
| 651 |
this.down(); |
| 652 |
return join( '[', ret, ']' ); |
| 653 |
}; |
| 654 |
|
| 655 |
var reName = /^function (\w+)/; |
| 656 |
|
| 657 |
var jsDump = window.jsDump = { |
| 658 |
parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance |
| 659 |
var parser = this.parsers[ type || this.typeOf(obj) ]; |
| 660 |
type = typeof parser; |
| 661 |
|
| 662 |
return type == 'function' ? parser.call( this, obj ) : |
| 663 |
type == 'string' ? parser : |
| 664 |
this.parsers.error; |
| 665 |
}, |
| 666 |
typeOf:function( obj ){ |
| 667 |
var type = typeof obj, |
| 668 |
f = 'function';//we'll use it 3 times, save it |
| 669 |
return type != 'object' && type != f ? type : |
| 670 |
!obj ? 'null' : |
| 671 |
obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions |
| 672 |
obj.getHours ? 'date' : |
| 673 |
obj.scrollBy ? 'window' : |
| 674 |
obj.nodeName == '#document' ? 'document' : |
| 675 |
obj.nodeName ? 'node' : |
| 676 |
obj.item ? 'nodelist' : // Safari reports nodelists as functions |
| 677 |
obj.callee ? 'arguments' : |
| 678 |
obj.call || obj.constructor != Array && //an array would also fall on this hack |
| 679 |
(obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects |
| 680 |
'length' in obj ? 'array' : |
| 681 |
type; |
| 682 |
}, |
| 683 |
separator:function(){ |
| 684 |
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; |
| 685 |
}, |
| 686 |
indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing |
| 687 |
if( !this.multiline ) |
| 688 |
return ''; |
| 689 |
var chr = this.indentChar; |
| 690 |
if( this.HTML ) |
| 691 |
chr = chr.replace(/\t/g,' ').replace(/ /g,' '); |
| 692 |
return Array( this._depth_ + (extra||0) ).join(chr); |
| 693 |
}, |
| 694 |
up:function( a ){ |
| 695 |
this._depth_ += a || 1; |
| 696 |
}, |
| 697 |
down:function( a ){ |
| 698 |
this._depth_ -= a || 1; |
| 699 |
}, |
| 700 |
setParser:function( name, parser ){ |
| 701 |
this.parsers[name] = parser; |
| 702 |
}, |
| 703 |
// The next 3 are exposed so you can use them |
| 704 |
quote:quote, |
| 705 |
literal:literal, |
| 706 |
join:join, |
| 707 |
// |
| 708 |
_depth_: 1, |
| 709 |
// This is the list of parsers, to modify them, use jsDump.setParser |
| 710 |
parsers:{ |
| 711 |
window: '[Window]', |
| 712 |
document: '[Document]', |
| 713 |
error:'[ERROR]', //when no parser is found, shouldn't happen |
| 714 |
unknown: '[Unknown]', |
| 715 |
'null':'null', |
| 716 |
undefined:'undefined', |
| 717 |
'function':function( fn ){ |
| 718 |
var ret = 'function', |
| 719 |
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE |
| 720 |
if( name ) |
| 721 |
ret += ' ' + name; |
| 722 |
ret += '('; |
| 723 |
|
| 724 |
ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); |
| 725 |
return join( ret, this.parse(fn,'functionCode'), '}' ); |
| 726 |
}, |
| 727 |
array: array, |
| 728 |
nodelist: array, |
| 729 |
arguments: array, |
| 730 |
object:function( map ){ |
| 731 |
var ret = [ ]; |
| 732 |
this.up(); |
| 733 |
for( var key in map ) |
| 734 |
ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); |
| 735 |
this.down(); |
| 736 |
return join( '{', ret, '}' ); |
| 737 |
}, |
| 738 |
node:function( node ){ |
| 739 |
var open = this.HTML ? '<' : '<', |
| 740 |
close = this.HTML ? '>' : '>'; |
| 741 |
|
| 742 |
var tag = node.nodeName.toLowerCase(), |
| 743 |
ret = open + tag; |
| 744 |
|
| 745 |
for( var a in this.DOMAttrs ){ |
| 746 |
var val = node[this.DOMAttrs[a]]; |
| 747 |
if( val ) |
| 748 |
ret += ' ' + a + '=' + this.parse( val, 'attribute' ); |
| 749 |
} |
| 750 |
return ret + close + open + '/' + tag + close; |
| 751 |
}, |
| 752 |
functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function |
| 753 |
var l = fn.length; |
| 754 |
if( !l ) return ''; |
| 755 |
|
| 756 |
var args = Array(l); |
| 757 |
while( l-- ) |
| 758 |
args[l] = String.fromCharCode(97+l);//97 is 'a' |
| 759 |
return ' ' + args.join(', ') + ' '; |
| 760 |
}, |
| 761 |
key:quote, //object calls it internally, the key part of an item in a map |
| 762 |
functionCode:'[code]', //function calls it internally, it's the content of the function |
| 763 |
attribute:quote, //node calls it internally, it's an html attribute value |
| 764 |
string:quote, |
| 765 |
date:quote, |
| 766 |
regexp:literal, //regex |
| 767 |
number:literal, |
| 768 |
'boolean':literal |
| 769 |
}, |
| 770 |
DOMAttrs:{//attributes to dump from nodes, name=>realName |
| 771 |
id:'id', |
| 772 |
name:'name', |
| 773 |
'class':'className' |
| 774 |
}, |
| 775 |
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) |
| 776 |
indentChar:' ',//indentation unit |
| 777 |
multiline:true //if true, items in a collection, are separated by a \n, else just a space. |
| 778 |
}; |
| 779 |
|
| 780 |
})(); |
| 781 |
|