qunit.js 35 KB

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