PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/syntaxhighlighter_3.0.83/tests/js/qunit.js

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