qunit.js 32 KB

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