qunit.js 27 KB

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