testrunner.js 37 KB

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