qunit.js 43 KB

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