qunit.js 50 KB

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