qunit.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417
  1. /*
  2. * QUnit - A JavaScript Unit Testing Framework
  3. *
  4. * http://docs.jquery.com/QUnit
  5. *
  6. * Copyright (c) 2011 John Resig, Jörn Zaefferer
  7. * Dual licensed under the MIT (MIT-LICENSE.txt)
  8. * or GPL (GPL-LICENSE.txt) licenses.
  9. */
  10. (function(window) {
  11. var defined = {
  12. setTimeout: typeof window.setTimeout !== "undefined",
  13. sessionStorage: (function() {
  14. try {
  15. return !!sessionStorage.getItem;
  16. } catch(e){
  17. return false;
  18. }
  19. })()
  20. };
  21. var testId = 0;
  22. var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
  23. this.name = name;
  24. this.testName = testName;
  25. this.expected = expected;
  26. this.testEnvironmentArg = testEnvironmentArg;
  27. this.async = async;
  28. this.callback = callback;
  29. this.assertions = [];
  30. };
  31. Test.prototype = {
  32. init: function() {
  33. var tests = id("qunit-tests");
  34. if (tests) {
  35. var b = document.createElement("strong");
  36. b.innerHTML = "Running " + this.name;
  37. var li = document.createElement("li");
  38. li.appendChild( b );
  39. li.className = "running";
  40. li.id = this.id = "test-output" + testId++;
  41. tests.appendChild( li );
  42. }
  43. },
  44. setup: function() {
  45. if (this.module != config.previousModule) {
  46. if ( config.previousModule ) {
  47. QUnit.moduleDone( {
  48. name: config.previousModule,
  49. failed: config.moduleStats.bad,
  50. passed: config.moduleStats.all - config.moduleStats.bad,
  51. total: config.moduleStats.all
  52. } );
  53. }
  54. config.previousModule = this.module;
  55. config.moduleStats = { all: 0, bad: 0 };
  56. QUnit.moduleStart( {
  57. name: this.module
  58. } );
  59. }
  60. config.current = this;
  61. this.testEnvironment = extend({
  62. setup: function() {},
  63. teardown: function() {}
  64. }, this.moduleTestEnvironment);
  65. if (this.testEnvironmentArg) {
  66. extend(this.testEnvironment, this.testEnvironmentArg);
  67. }
  68. QUnit.testStart( {
  69. name: this.testName
  70. } );
  71. // allow utility functions to access the current test environment
  72. // TODO why??
  73. QUnit.current_testEnvironment = this.testEnvironment;
  74. try {
  75. if ( !config.pollution ) {
  76. saveGlobal();
  77. }
  78. this.testEnvironment.setup.call(this.testEnvironment);
  79. } catch(e) {
  80. QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
  81. }
  82. },
  83. run: function() {
  84. if ( this.async ) {
  85. QUnit.stop();
  86. }
  87. if ( config.notrycatch ) {
  88. this.callback.call(this.testEnvironment);
  89. return;
  90. }
  91. try {
  92. this.callback.call(this.testEnvironment);
  93. } catch(e) {
  94. fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
  95. QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
  96. // else next test will carry the responsibility
  97. saveGlobal();
  98. // Restart the tests if they're blocking
  99. if ( config.blocking ) {
  100. start();
  101. }
  102. }
  103. },
  104. teardown: function() {
  105. try {
  106. checkPollution();
  107. this.testEnvironment.teardown.call(this.testEnvironment);
  108. } catch(e) {
  109. QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
  110. }
  111. },
  112. finish: function() {
  113. if ( this.expected && this.expected != this.assertions.length ) {
  114. QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
  115. }
  116. var good = 0, bad = 0,
  117. tests = id("qunit-tests");
  118. config.stats.all += this.assertions.length;
  119. config.moduleStats.all += this.assertions.length;
  120. if ( tests ) {
  121. var ol = document.createElement("ol");
  122. for ( var i = 0; i < this.assertions.length; i++ ) {
  123. var assertion = this.assertions[i];
  124. var li = document.createElement("li");
  125. li.className = assertion.result ? "pass" : "fail";
  126. li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
  127. ol.appendChild( li );
  128. if ( assertion.result ) {
  129. good++;
  130. } else {
  131. bad++;
  132. config.stats.bad++;
  133. config.moduleStats.bad++;
  134. }
  135. }
  136. // store result when possible
  137. QUnit.config.reorder && defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad);
  138. if (bad == 0) {
  139. ol.style.display = "none";
  140. }
  141. var b = document.createElement("strong");
  142. b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
  143. addEvent(b, "click", function() {
  144. var next = b.nextSibling, display = next.style.display;
  145. next.style.display = display === "none" ? "block" : "none";
  146. });
  147. addEvent(b, "dblclick", function(e) {
  148. var target = e && e.target ? e.target : window.event.srcElement;
  149. if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
  150. target = target.parentNode;
  151. }
  152. if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
  153. window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
  154. }
  155. });
  156. var li = id(this.id);
  157. li.className = bad ? "fail" : "pass";
  158. li.removeChild( li.firstChild );
  159. li.appendChild( b );
  160. li.appendChild( ol );
  161. } else {
  162. for ( var i = 0; i < this.assertions.length; i++ ) {
  163. if ( !this.assertions[i].result ) {
  164. bad++;
  165. config.stats.bad++;
  166. config.moduleStats.bad++;
  167. }
  168. }
  169. }
  170. try {
  171. QUnit.reset();
  172. } catch(e) {
  173. fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
  174. }
  175. QUnit.testDone( {
  176. name: this.testName,
  177. failed: bad,
  178. passed: this.assertions.length - bad,
  179. total: this.assertions.length
  180. } );
  181. },
  182. queue: function() {
  183. var test = this;
  184. synchronize(function() {
  185. test.init();
  186. });
  187. function run() {
  188. // each of these can by async
  189. synchronize(function() {
  190. test.setup();
  191. });
  192. synchronize(function() {
  193. test.run();
  194. });
  195. synchronize(function() {
  196. test.teardown();
  197. });
  198. synchronize(function() {
  199. test.finish();
  200. });
  201. }
  202. // defer when previous test run passed, if storage is available
  203. var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName);
  204. if (bad) {
  205. run();
  206. } else {
  207. synchronize(run);
  208. };
  209. }
  210. };
  211. var QUnit = {
  212. // call on start of module test to prepend name to all tests
  213. module: function(name, testEnvironment) {
  214. config.currentModule = name;
  215. config.currentModuleTestEnviroment = testEnvironment;
  216. },
  217. asyncTest: function(testName, expected, callback) {
  218. if ( arguments.length === 2 ) {
  219. callback = expected;
  220. expected = 0;
  221. }
  222. QUnit.test(testName, expected, callback, true);
  223. },
  224. test: function(testName, expected, callback, async) {
  225. var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
  226. if ( arguments.length === 2 ) {
  227. callback = expected;
  228. expected = null;
  229. }
  230. // is 2nd argument a testEnvironment?
  231. if ( expected && typeof expected === 'object') {
  232. testEnvironmentArg = expected;
  233. expected = null;
  234. }
  235. if ( config.currentModule ) {
  236. name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
  237. }
  238. if ( !validTest(config.currentModule + ": " + testName) ) {
  239. return;
  240. }
  241. var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
  242. test.module = config.currentModule;
  243. test.moduleTestEnvironment = config.currentModuleTestEnviroment;
  244. test.queue();
  245. },
  246. /**
  247. * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
  248. */
  249. expect: function(asserts) {
  250. config.current.expected = asserts;
  251. },
  252. /**
  253. * Asserts true.
  254. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
  255. */
  256. ok: function(a, msg) {
  257. a = !!a;
  258. var details = {
  259. result: a,
  260. message: msg
  261. };
  262. msg = escapeHtml(msg);
  263. QUnit.log(details);
  264. config.current.assertions.push({
  265. result: a,
  266. message: msg
  267. });
  268. },
  269. /**
  270. * Checks that the first two arguments are equal, with an optional message.
  271. * Prints out both actual and expected values.
  272. *
  273. * Prefered to ok( actual == expected, message )
  274. *
  275. * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
  276. *
  277. * @param Object actual
  278. * @param Object expected
  279. * @param String message (optional)
  280. */
  281. equal: function(actual, expected, message) {
  282. QUnit.push(expected == actual, actual, expected, message);
  283. },
  284. notEqual: function(actual, expected, message) {
  285. QUnit.push(expected != actual, actual, expected, message);
  286. },
  287. deepEqual: function(actual, expected, message) {
  288. QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
  289. },
  290. notDeepEqual: function(actual, expected, message) {
  291. QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
  292. },
  293. strictEqual: function(actual, expected, message) {
  294. QUnit.push(expected === actual, actual, expected, message);
  295. },
  296. notStrictEqual: function(actual, expected, message) {
  297. QUnit.push(expected !== actual, actual, expected, message);
  298. },
  299. raises: function(block, expected, message) {
  300. var actual, ok = false;
  301. if (typeof expected === 'string') {
  302. message = expected;
  303. expected = null;
  304. }
  305. try {
  306. block();
  307. } catch (e) {
  308. actual = e;
  309. }
  310. if (actual) {
  311. // we don't want to validate thrown error
  312. if (!expected) {
  313. ok = true;
  314. // expected is a regexp
  315. } else if (QUnit.objectType(expected) === "regexp") {
  316. ok = expected.test(actual);
  317. // expected is a constructor
  318. } else if (actual instanceof expected) {
  319. ok = true;
  320. // expected is a validation function which returns true is validation passed
  321. } else if (expected.call({}, actual) === true) {
  322. ok = true;
  323. }
  324. }
  325. QUnit.ok(ok, message);
  326. },
  327. start: function() {
  328. config.semaphore--;
  329. if (config.semaphore > 0) {
  330. // don't start until equal number of stop-calls
  331. return;
  332. }
  333. if (config.semaphore < 0) {
  334. // ignore if start is called more often then stop
  335. config.semaphore = 0;
  336. }
  337. // A slight delay, to avoid any current callbacks
  338. if ( defined.setTimeout ) {
  339. window.setTimeout(function() {
  340. if ( config.timeout ) {
  341. clearTimeout(config.timeout);
  342. }
  343. config.blocking = false;
  344. process();
  345. }, 13);
  346. } else {
  347. config.blocking = false;
  348. process();
  349. }
  350. },
  351. stop: function(timeout) {
  352. config.semaphore++;
  353. config.blocking = true;
  354. if ( timeout && defined.setTimeout ) {
  355. clearTimeout(config.timeout);
  356. config.timeout = window.setTimeout(function() {
  357. QUnit.ok( false, "Test timed out" );
  358. QUnit.start();
  359. }, timeout);
  360. }
  361. }
  362. };
  363. // Backwards compatibility, deprecated
  364. QUnit.equals = QUnit.equal;
  365. QUnit.same = QUnit.deepEqual;
  366. // Maintain internal state
  367. var config = {
  368. // The queue of tests to run
  369. queue: [],
  370. // block until document ready
  371. blocking: true,
  372. // by default, run previously failed tests first
  373. // very useful in combination with "Hide passed tests" checked
  374. reorder: true
  375. };
  376. // Load paramaters
  377. (function() {
  378. var location = window.location || { search: "", protocol: "file:" },
  379. GETParams = location.search.slice(1).split('&');
  380. for ( var i = 0; i < GETParams.length; i++ ) {
  381. GETParams[i] = decodeURIComponent( GETParams[i] );
  382. if ( GETParams[i] === "noglobals" ) {
  383. GETParams.splice( i, 1 );
  384. i--;
  385. config.noglobals = true;
  386. } else if ( GETParams[i] === "notrycatch" ) {
  387. GETParams.splice( i, 1 );
  388. i--;
  389. config.notrycatch = true;
  390. } else if ( GETParams[i].search('=') > -1 ) {
  391. GETParams.splice( i, 1 );
  392. i--;
  393. }
  394. }
  395. // restrict modules/tests by get parameters
  396. config.filters = GETParams;
  397. // Figure out if we're running the tests from a server or not
  398. QUnit.isLocal = !!(location.protocol === 'file:');
  399. })();
  400. // Expose the API as global variables, unless an 'exports'
  401. // object exists, in that case we assume we're in CommonJS
  402. if ( typeof exports === "undefined" || typeof require === "undefined" ) {
  403. extend(window, QUnit);
  404. window.QUnit = QUnit;
  405. } else {
  406. extend(exports, QUnit);
  407. exports.QUnit = QUnit;
  408. }
  409. // define these after exposing globals to keep them in these QUnit namespace only
  410. extend(QUnit, {
  411. config: config,
  412. // Initialize the configuration options
  413. init: function() {
  414. extend(config, {
  415. stats: { all: 0, bad: 0 },
  416. moduleStats: { all: 0, bad: 0 },
  417. started: +new Date,
  418. updateRate: 1000,
  419. blocking: false,
  420. autostart: true,
  421. autorun: false,
  422. filters: [],
  423. queue: [],
  424. semaphore: 0
  425. });
  426. var tests = id( "qunit-tests" ),
  427. banner = id( "qunit-banner" ),
  428. result = id( "qunit-testresult" );
  429. if ( tests ) {
  430. tests.innerHTML = "";
  431. }
  432. if ( banner ) {
  433. banner.className = "";
  434. }
  435. if ( result ) {
  436. result.parentNode.removeChild( result );
  437. }
  438. if ( tests ) {
  439. result = document.createElement( "p" );
  440. result.id = "qunit-testresult";
  441. result.className = "result";
  442. tests.parentNode.insertBefore( result, tests );
  443. result.innerHTML = 'Running...<br/>&nbsp;';
  444. }
  445. },
  446. /**
  447. * Resets the test setup. Useful for tests that modify the DOM.
  448. *
  449. * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
  450. */
  451. reset: function() {
  452. if ( window.jQuery ) {
  453. jQuery( "#main, #qunit-fixture" ).html( config.fixture );
  454. } else {
  455. var main = id( 'main' ) || id( 'qunit-fixture' );
  456. if ( main ) {
  457. main.innerHTML = config.fixture;
  458. }
  459. }
  460. },
  461. /**
  462. * Trigger an event on an element.
  463. *
  464. * @example triggerEvent( document.body, "click" );
  465. *
  466. * @param DOMElement elem
  467. * @param String type
  468. */
  469. triggerEvent: function( elem, type, event ) {
  470. if ( document.createEvent ) {
  471. event = document.createEvent("MouseEvents");
  472. event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
  473. 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  474. elem.dispatchEvent( event );
  475. } else if ( elem.fireEvent ) {
  476. elem.fireEvent("on"+type);
  477. }
  478. },
  479. // Safe object type checking
  480. is: function( type, obj ) {
  481. return QUnit.objectType( obj ) == type;
  482. },
  483. objectType: function( obj ) {
  484. if (typeof obj === "undefined") {
  485. return "undefined";
  486. // consider: typeof null === object
  487. }
  488. if (obj === null) {
  489. return "null";
  490. }
  491. var type = Object.prototype.toString.call( obj )
  492. .match(/^\[object\s(.*)\]$/)[1] || '';
  493. switch (type) {
  494. case 'Number':
  495. if (isNaN(obj)) {
  496. return "nan";
  497. } else {
  498. return "number";
  499. }
  500. case 'String':
  501. case 'Boolean':
  502. case 'Array':
  503. case 'Date':
  504. case 'RegExp':
  505. case 'Function':
  506. return type.toLowerCase();
  507. }
  508. if (typeof obj === "object") {
  509. return "object";
  510. }
  511. return undefined;
  512. },
  513. push: function(result, actual, expected, message) {
  514. var details = {
  515. result: result,
  516. message: message,
  517. actual: actual,
  518. expected: expected
  519. };
  520. message = escapeHtml(message) || (result ? "okay" : "failed");
  521. message = '<span class="test-message">' + message + "</span>";
  522. expected = escapeHtml(QUnit.jsDump.parse(expected));
  523. actual = escapeHtml(QUnit.jsDump.parse(actual));
  524. var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
  525. if (actual != expected) {
  526. output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
  527. output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
  528. }
  529. if (!result) {
  530. var source = sourceFromStacktrace();
  531. if (source) {
  532. details.source = source;
  533. output += '<tr class="test-source"><th>Source: </th><td><pre>' + source +'</pre></td></tr>';
  534. }
  535. }
  536. output += "</table>";
  537. QUnit.log(details);
  538. config.current.assertions.push({
  539. result: !!result,
  540. message: output
  541. });
  542. },
  543. // Logging callbacks; all receive a single argument with the listed properties
  544. // run test/logs.html for any related changes
  545. begin: function() {},
  546. // done: { failed, passed, total, runtime }
  547. done: function() {},
  548. // log: { result, actual, expected, message }
  549. log: function() {},
  550. // testStart: { name }
  551. testStart: function() {},
  552. // testDone: { name, failed, passed, total }
  553. testDone: function() {},
  554. // moduleStart: { name }
  555. moduleStart: function() {},
  556. // moduleDone: { name, failed, passed, total }
  557. moduleDone: function() {}
  558. });
  559. if ( typeof document === "undefined" || document.readyState === "complete" ) {
  560. config.autorun = true;
  561. }
  562. addEvent(window, "load", function() {
  563. QUnit.begin({});
  564. // Initialize the config, saving the execution queue
  565. var oldconfig = extend({}, config);
  566. QUnit.init();
  567. extend(config, oldconfig);
  568. config.blocking = false;
  569. var userAgent = id("qunit-userAgent");
  570. if ( userAgent ) {
  571. userAgent.innerHTML = navigator.userAgent;
  572. }
  573. var banner = id("qunit-header");
  574. if ( banner ) {
  575. var paramsIndex = location.href.lastIndexOf(location.search);
  576. if ( paramsIndex > -1 ) {
  577. var mainPageLocation = location.href.slice(0, paramsIndex);
  578. if ( mainPageLocation == location.href ) {
  579. banner.innerHTML = '<a href=""> ' + banner.innerHTML + '</a> ';
  580. } else {
  581. var testName = decodeURIComponent(location.search.slice(1));
  582. banner.innerHTML = '<a href="' + mainPageLocation + '">' + banner.innerHTML + '</a> &#8250; <a href="">' + testName + '</a>';
  583. }
  584. }
  585. }
  586. var toolbar = id("qunit-testrunner-toolbar");
  587. if ( toolbar ) {
  588. var filter = document.createElement("input");
  589. filter.type = "checkbox";
  590. filter.id = "qunit-filter-pass";
  591. addEvent( filter, "click", function() {
  592. var ol = document.getElementById("qunit-tests");
  593. if ( filter.checked ) {
  594. ol.className = ol.className + " hidepass";
  595. } else {
  596. var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
  597. ol.className = tmp.replace(/ hidepass /, " ");
  598. }
  599. if ( defined.sessionStorage ) {
  600. sessionStorage.setItem("qunit-filter-passed-tests", filter.checked ? "true" : "");
  601. }
  602. });
  603. if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
  604. filter.checked = true;
  605. var ol = document.getElementById("qunit-tests");
  606. ol.className = ol.className + " hidepass";
  607. }
  608. toolbar.appendChild( filter );
  609. var label = document.createElement("label");
  610. label.setAttribute("for", "qunit-filter-pass");
  611. label.innerHTML = "Hide passed tests";
  612. toolbar.appendChild( label );
  613. }
  614. var main = id('main') || id('qunit-fixture');
  615. if ( main ) {
  616. config.fixture = main.innerHTML;
  617. }
  618. if (config.autostart) {
  619. QUnit.start();
  620. }
  621. });
  622. function done() {
  623. config.autorun = true;
  624. // Log the last module results
  625. if ( config.currentModule ) {
  626. QUnit.moduleDone( {
  627. name: config.currentModule,
  628. failed: config.moduleStats.bad,
  629. passed: config.moduleStats.all - config.moduleStats.bad,
  630. total: config.moduleStats.all
  631. } );
  632. }
  633. var banner = id("qunit-banner"),
  634. tests = id("qunit-tests"),
  635. runtime = +new Date - config.started,
  636. passed = config.stats.all - config.stats.bad,
  637. html = [
  638. 'Tests completed in ',
  639. runtime,
  640. ' milliseconds.<br/>',
  641. '<span class="passed">',
  642. passed,
  643. '</span> tests of <span class="total">',
  644. config.stats.all,
  645. '</span> passed, <span class="failed">',
  646. config.stats.bad,
  647. '</span> failed.'
  648. ].join('');
  649. if ( banner ) {
  650. banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
  651. }
  652. if ( tests ) {
  653. id( "qunit-testresult" ).innerHTML = html;
  654. }
  655. QUnit.done( {
  656. failed: config.stats.bad,
  657. passed: passed,
  658. total: config.stats.all,
  659. runtime: runtime
  660. } );
  661. }
  662. function validTest( name ) {
  663. var i = config.filters.length,
  664. run = false;
  665. if ( !i ) {
  666. return true;
  667. }
  668. while ( i-- ) {
  669. var filter = config.filters[i],
  670. not = filter.charAt(0) == '!';
  671. if ( not ) {
  672. filter = filter.slice(1);
  673. }
  674. if ( name.indexOf(filter) !== -1 ) {
  675. return !not;
  676. }
  677. if ( not ) {
  678. run = true;
  679. }
  680. }
  681. return run;
  682. }
  683. // so far supports only Firefox, Chrome and Opera (buggy)
  684. // could be extended in the future to use something like https://github.com/csnover/TraceKit
  685. function sourceFromStacktrace() {
  686. try {
  687. throw new Error();
  688. } catch ( e ) {
  689. if (e.stacktrace) {
  690. // Opera
  691. return e.stacktrace.split("\n")[6];
  692. } else if (e.stack) {
  693. // Firefox, Chrome
  694. return e.stack.split("\n")[4];
  695. }
  696. }
  697. }
  698. function escapeHtml(s) {
  699. if (!s) {
  700. return "";
  701. }
  702. s = s + "";
  703. return s.replace(/[\&"<>\\]/g, function(s) {
  704. switch(s) {
  705. case "&": return "&amp;";
  706. case "\\": return "\\\\";
  707. case '"': return '\"';
  708. case "<": return "&lt;";
  709. case ">": return "&gt;";
  710. default: return s;
  711. }
  712. });
  713. }
  714. function synchronize( callback ) {
  715. config.queue.push( callback );
  716. if ( config.autorun && !config.blocking ) {
  717. process();
  718. }
  719. }
  720. function process() {
  721. var start = (new Date()).getTime();
  722. while ( config.queue.length && !config.blocking ) {
  723. if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
  724. config.queue.shift()();
  725. } else {
  726. window.setTimeout( process, 13 );
  727. break;
  728. }
  729. }
  730. if (!config.blocking && !config.queue.length) {
  731. done();
  732. }
  733. }
  734. function saveGlobal() {
  735. config.pollution = [];
  736. if ( config.noglobals ) {
  737. for ( var key in window ) {
  738. config.pollution.push( key );
  739. }
  740. }
  741. }
  742. function checkPollution( name ) {
  743. var old = config.pollution;
  744. saveGlobal();
  745. var newGlobals = diff( old, config.pollution );
  746. if ( newGlobals.length > 0 ) {
  747. ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
  748. config.current.expected++;
  749. }
  750. var deletedGlobals = diff( config.pollution, old );
  751. if ( deletedGlobals.length > 0 ) {
  752. ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
  753. config.current.expected++;
  754. }
  755. }
  756. // returns a new Array with the elements that are in a but not in b
  757. function diff( a, b ) {
  758. var result = a.slice();
  759. for ( var i = 0; i < result.length; i++ ) {
  760. for ( var j = 0; j < b.length; j++ ) {
  761. if ( result[i] === b[j] ) {
  762. result.splice(i, 1);
  763. i--;
  764. break;
  765. }
  766. }
  767. }
  768. return result;
  769. }
  770. function fail(message, exception, callback) {
  771. if ( typeof console !== "undefined" && console.error && console.warn ) {
  772. console.error(message);
  773. console.error(exception);
  774. console.warn(callback.toString());
  775. } else if ( window.opera && opera.postError ) {
  776. opera.postError(message, exception, callback.toString);
  777. }
  778. }
  779. function extend(a, b) {
  780. for ( var prop in b ) {
  781. a[prop] = b[prop];
  782. }
  783. return a;
  784. }
  785. function addEvent(elem, type, fn) {
  786. if ( elem.addEventListener ) {
  787. elem.addEventListener( type, fn, false );
  788. } else if ( elem.attachEvent ) {
  789. elem.attachEvent( "on" + type, fn );
  790. } else {
  791. fn();
  792. }
  793. }
  794. function id(name) {
  795. return !!(typeof document !== "undefined" && document && document.getElementById) &&
  796. document.getElementById( name );
  797. }
  798. // Test for equality any JavaScript type.
  799. // Discussions and reference: http://philrathe.com/articles/equiv
  800. // Test suites: http://philrathe.com/tests/equiv
  801. // Author: Philippe Rathé <prathe@gmail.com>
  802. QUnit.equiv = function () {
  803. var innerEquiv; // the real equiv function
  804. var callers = []; // stack to decide between skip/abort functions
  805. var parents = []; // stack to avoiding loops from circular referencing
  806. // Call the o related callback with the given arguments.
  807. function bindCallbacks(o, callbacks, args) {
  808. var prop = QUnit.objectType(o);
  809. if (prop) {
  810. if (QUnit.objectType(callbacks[prop]) === "function") {
  811. return callbacks[prop].apply(callbacks, args);
  812. } else {
  813. return callbacks[prop]; // or undefined
  814. }
  815. }
  816. }
  817. var callbacks = function () {
  818. // for string, boolean, number and null
  819. function useStrictEquality(b, a) {
  820. if (b instanceof a.constructor || a instanceof b.constructor) {
  821. // to catch short annotaion VS 'new' annotation of a declaration
  822. // e.g. var i = 1;
  823. // var j = new Number(1);
  824. return a == b;
  825. } else {
  826. return a === b;
  827. }
  828. }
  829. return {
  830. "string": useStrictEquality,
  831. "boolean": useStrictEquality,
  832. "number": useStrictEquality,
  833. "null": useStrictEquality,
  834. "undefined": useStrictEquality,
  835. "nan": function (b) {
  836. return isNaN(b);
  837. },
  838. "date": function (b, a) {
  839. return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
  840. },
  841. "regexp": function (b, a) {
  842. return QUnit.objectType(b) === "regexp" &&
  843. a.source === b.source && // the regex itself
  844. a.global === b.global && // and its modifers (gmi) ...
  845. a.ignoreCase === b.ignoreCase &&
  846. a.multiline === b.multiline;
  847. },
  848. // - skip when the property is a method of an instance (OOP)
  849. // - abort otherwise,
  850. // initial === would have catch identical references anyway
  851. "function": function () {
  852. var caller = callers[callers.length - 1];
  853. return caller !== Object &&
  854. typeof caller !== "undefined";
  855. },
  856. "array": function (b, a) {
  857. var i, j, loop;
  858. var len;
  859. // b could be an object literal here
  860. if ( ! (QUnit.objectType(b) === "array")) {
  861. return false;
  862. }
  863. len = a.length;
  864. if (len !== b.length) { // safe and faster
  865. return false;
  866. }
  867. //track reference to avoid circular references
  868. parents.push(a);
  869. for (i = 0; i < len; i++) {
  870. loop = false;
  871. for(j=0;j<parents.length;j++){
  872. if(parents[j] === a[i]){
  873. loop = true;//dont rewalk array
  874. }
  875. }
  876. if (!loop && ! innerEquiv(a[i], b[i])) {
  877. parents.pop();
  878. return false;
  879. }
  880. }
  881. parents.pop();
  882. return true;
  883. },
  884. "object": function (b, a) {
  885. var i, j, loop;
  886. var eq = true; // unless we can proove it
  887. var aProperties = [], bProperties = []; // collection of strings
  888. // comparing constructors is more strict than using instanceof
  889. if ( a.constructor !== b.constructor) {
  890. return false;
  891. }
  892. // stack constructor before traversing properties
  893. callers.push(a.constructor);
  894. //track reference to avoid circular references
  895. parents.push(a);
  896. for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
  897. loop = false;
  898. for(j=0;j<parents.length;j++){
  899. if(parents[j] === a[i])
  900. loop = true; //don't go down the same path twice
  901. }
  902. aProperties.push(i); // collect a's properties
  903. if (!loop && ! innerEquiv(a[i], b[i])) {
  904. eq = false;
  905. break;
  906. }
  907. }
  908. callers.pop(); // unstack, we are done
  909. parents.pop();
  910. for (i in b) {
  911. bProperties.push(i); // collect b's properties
  912. }
  913. // Ensures identical properties name
  914. return eq && innerEquiv(aProperties.sort(), bProperties.sort());
  915. }
  916. };
  917. }();
  918. innerEquiv = function () { // can take multiple arguments
  919. var args = Array.prototype.slice.apply(arguments);
  920. if (args.length < 2) {
  921. return true; // end transition
  922. }
  923. return (function (a, b) {
  924. if (a === b) {
  925. return true; // catch the most you can
  926. } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
  927. return false; // don't lose time with error prone cases
  928. } else {
  929. return bindCallbacks(a, callbacks, [b, a]);
  930. }
  931. // apply transition with (1..n) arguments
  932. })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
  933. };
  934. return innerEquiv;
  935. }();
  936. /**
  937. * jsDump
  938. * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
  939. * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
  940. * Date: 5/15/2008
  941. * @projectDescription Advanced and extensible data dumping for Javascript.
  942. * @version 1.0.0
  943. * @author Ariel Flesler
  944. * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
  945. */
  946. QUnit.jsDump = (function() {
  947. function quote( str ) {
  948. return '"' + str.toString().replace(/"/g, '\\"') + '"';
  949. };
  950. function literal( o ) {
  951. return o + '';
  952. };
  953. function join( pre, arr, post ) {
  954. var s = jsDump.separator(),
  955. base = jsDump.indent(),
  956. inner = jsDump.indent(1);
  957. if ( arr.join )
  958. arr = arr.join( ',' + s + inner );
  959. if ( !arr )
  960. return pre + post;
  961. return [ pre, inner + arr, base + post ].join(s);
  962. };
  963. function array( arr ) {
  964. var i = arr.length, ret = Array(i);
  965. this.up();
  966. while ( i-- )
  967. ret[i] = this.parse( arr[i] );
  968. this.down();
  969. return join( '[', ret, ']' );
  970. };
  971. var reName = /^function (\w+)/;
  972. var jsDump = {
  973. parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
  974. var parser = this.parsers[ type || this.typeOf(obj) ];
  975. type = typeof parser;
  976. return type == 'function' ? parser.call( this, obj ) :
  977. type == 'string' ? parser :
  978. this.parsers.error;
  979. },
  980. typeOf:function( obj ) {
  981. var type;
  982. if ( obj === null ) {
  983. type = "null";
  984. } else if (typeof obj === "undefined") {
  985. type = "undefined";
  986. } else if (QUnit.is("RegExp", obj)) {
  987. type = "regexp";
  988. } else if (QUnit.is("Date", obj)) {
  989. type = "date";
  990. } else if (QUnit.is("Function", obj)) {
  991. type = "function";
  992. } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
  993. type = "window";
  994. } else if (obj.nodeType === 9) {
  995. type = "document";
  996. } else if (obj.nodeType) {
  997. type = "node";
  998. } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
  999. type = "array";
  1000. } else {
  1001. type = typeof obj;
  1002. }
  1003. return type;
  1004. },
  1005. separator:function() {
  1006. return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
  1007. },
  1008. indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
  1009. if ( !this.multiline )
  1010. return '';
  1011. var chr = this.indentChar;
  1012. if ( this.HTML )
  1013. chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
  1014. return Array( this._depth_ + (extra||0) ).join(chr);
  1015. },
  1016. up:function( a ) {
  1017. this._depth_ += a || 1;
  1018. },
  1019. down:function( a ) {
  1020. this._depth_ -= a || 1;
  1021. },
  1022. setParser:function( name, parser ) {
  1023. this.parsers[name] = parser;
  1024. },
  1025. // The next 3 are exposed so you can use them
  1026. quote:quote,
  1027. literal:literal,
  1028. join:join,
  1029. //
  1030. _depth_: 1,
  1031. // This is the list of parsers, to modify them, use jsDump.setParser
  1032. parsers:{
  1033. window: '[Window]',
  1034. document: '[Document]',
  1035. error:'[ERROR]', //when no parser is found, shouldn't happen
  1036. unknown: '[Unknown]',
  1037. 'null':'null',
  1038. 'undefined':'undefined',
  1039. 'function':function( fn ) {
  1040. var ret = 'function',
  1041. name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
  1042. if ( name )
  1043. ret += ' ' + name;
  1044. ret += '(';
  1045. ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
  1046. return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
  1047. },
  1048. array: array,
  1049. nodelist: array,
  1050. arguments: array,
  1051. object:function( map ) {
  1052. var ret = [ ];
  1053. QUnit.jsDump.up();
  1054. for ( var key in map )
  1055. ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
  1056. QUnit.jsDump.down();
  1057. return join( '{', ret, '}' );
  1058. },
  1059. node:function( node ) {
  1060. var open = QUnit.jsDump.HTML ? '&lt;' : '<',
  1061. close = QUnit.jsDump.HTML ? '&gt;' : '>';
  1062. var tag = node.nodeName.toLowerCase(),
  1063. ret = open + tag;
  1064. for ( var a in QUnit.jsDump.DOMAttrs ) {
  1065. var val = node[QUnit.jsDump.DOMAttrs[a]];
  1066. if ( val )
  1067. ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
  1068. }
  1069. return ret + close + open + '/' + tag + close;
  1070. },
  1071. functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
  1072. var l = fn.length;
  1073. if ( !l ) return '';
  1074. var args = Array(l);
  1075. while ( l-- )
  1076. args[l] = String.fromCharCode(97+l);//97 is 'a'
  1077. return ' ' + args.join(', ') + ' ';
  1078. },
  1079. key:quote, //object calls it internally, the key part of an item in a map
  1080. functionCode:'[code]', //function calls it internally, it's the content of the function
  1081. attribute:quote, //node calls it internally, it's an html attribute value
  1082. string:quote,
  1083. date:quote,
  1084. regexp:literal, //regex
  1085. number:literal,
  1086. 'boolean':literal
  1087. },
  1088. DOMAttrs:{//attributes to dump from nodes, name=>realName
  1089. id:'id',
  1090. name:'name',
  1091. 'class':'className'
  1092. },
  1093. HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
  1094. indentChar:' ',//indentation unit
  1095. multiline:true //if true, items in a collection, are separated by a \n, else just a space.
  1096. };
  1097. return jsDump;
  1098. })();
  1099. // from Sizzle.js
  1100. function getText( elems ) {
  1101. var ret = "", elem;
  1102. for ( var i = 0; elems[i]; i++ ) {
  1103. elem = elems[i];
  1104. // Get the text from text nodes and CDATA nodes
  1105. if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
  1106. ret += elem.nodeValue;
  1107. // Traverse everything else, except comment nodes
  1108. } else if ( elem.nodeType !== 8 ) {
  1109. ret += getText( elem.childNodes );
  1110. }
  1111. }
  1112. return ret;
  1113. };
  1114. /*
  1115. * Javascript Diff Algorithm
  1116. * By John Resig (http://ejohn.org/)
  1117. * Modified by Chu Alan "sprite"
  1118. *
  1119. * Released under the MIT license.
  1120. *
  1121. * More Info:
  1122. * http://ejohn.org/projects/javascript-diff-algorithm/
  1123. *
  1124. * Usage: QUnit.diff(expected, actual)
  1125. *
  1126. * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
  1127. */
  1128. QUnit.diff = (function() {
  1129. function diff(o, n){
  1130. var ns = new Object();
  1131. var os = new Object();
  1132. for (var i = 0; i < n.length; i++) {
  1133. if (ns[n[i]] == null)
  1134. ns[n[i]] = {
  1135. rows: new Array(),
  1136. o: null
  1137. };
  1138. ns[n[i]].rows.push(i);
  1139. }
  1140. for (var i = 0; i < o.length; i++) {
  1141. if (os[o[i]] == null)
  1142. os[o[i]] = {
  1143. rows: new Array(),
  1144. n: null
  1145. };
  1146. os[o[i]].rows.push(i);
  1147. }
  1148. for (var i in ns) {
  1149. if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
  1150. n[ns[i].rows[0]] = {
  1151. text: n[ns[i].rows[0]],
  1152. row: os[i].rows[0]
  1153. };
  1154. o[os[i].rows[0]] = {
  1155. text: o[os[i].rows[0]],
  1156. row: ns[i].rows[0]
  1157. };
  1158. }
  1159. }
  1160. for (var i = 0; i < n.length - 1; i++) {
  1161. if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
  1162. n[i + 1] == o[n[i].row + 1]) {
  1163. n[i + 1] = {
  1164. text: n[i + 1],
  1165. row: n[i].row + 1
  1166. };
  1167. o[n[i].row + 1] = {
  1168. text: o[n[i].row + 1],
  1169. row: i + 1
  1170. };
  1171. }
  1172. }
  1173. for (var i = n.length - 1; i > 0; i--) {
  1174. if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
  1175. n[i - 1] == o[n[i].row - 1]) {
  1176. n[i - 1] = {
  1177. text: n[i - 1],
  1178. row: n[i].row - 1
  1179. };
  1180. o[n[i].row - 1] = {
  1181. text: o[n[i].row - 1],
  1182. row: i - 1
  1183. };
  1184. }
  1185. }
  1186. return {
  1187. o: o,
  1188. n: n
  1189. };
  1190. }
  1191. return function(o, n){
  1192. o = o.replace(/\s+$/, '');
  1193. n = n.replace(/\s+$/, '');
  1194. var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
  1195. var str = "";
  1196. var oSpace = o.match(/\s+/g);
  1197. if (oSpace == null) {
  1198. oSpace = [" "];
  1199. }
  1200. else {
  1201. oSpace.push(" ");
  1202. }
  1203. var nSpace = n.match(/\s+/g);
  1204. if (nSpace == null) {
  1205. nSpace = [" "];
  1206. }
  1207. else {
  1208. nSpace.push(" ");
  1209. }
  1210. if (out.n.length == 0) {
  1211. for (var i = 0; i < out.o.length; i++) {
  1212. str += '<del>' + out.o[i] + oSpace[i] + "</del>";
  1213. }
  1214. }
  1215. else {
  1216. if (out.n[0].text == null) {
  1217. for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
  1218. str += '<del>' + out.o[n] + oSpace[n] + "</del>";
  1219. }
  1220. }
  1221. for (var i = 0; i < out.n.length; i++) {
  1222. if (out.n[i].text == null) {
  1223. str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
  1224. }
  1225. else {
  1226. var pre = "";
  1227. for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
  1228. pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
  1229. }
  1230. str += " " + out.n[i].text + nSpace[i] + pre;
  1231. }
  1232. }
  1233. }
  1234. return str;
  1235. };
  1236. })();
  1237. })(this);