testrunner.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /**
  2. * Allow the test suite to run with other libs or jQuery's.
  3. */
  4. jQuery.noConflict();
  5. // For checking globals pollution despite auto-created globals in various environments
  6. jQuery.each( [ jQuery.expando, "getInterface", "Packages", "java", "netscape" ], function( i, name ) {
  7. window[ name ] = window[ name ];
  8. });
  9. // Expose Sizzle for Sizzle's selector tests
  10. // We remove Sizzle's globalization in jQuery
  11. var Sizzle = Sizzle || jQuery.find,
  12. // Allow subprojects to test against their own fixtures
  13. qunitModule = QUnit.module,
  14. qunitTest = QUnit.test;
  15. this.testSubproject = function( label, url, risTests ) {
  16. var sub, fixture, fixtureHTML,
  17. fixtureReplaced = false;
  18. // Don't let subproject tests jump the gun
  19. QUnit.config.reorder = false;
  20. // Create module
  21. module( label );
  22. // Duckpunch QUnit
  23. // TODO restore parent fixture on teardown to support reordering
  24. module = QUnit.module = function( name ) {
  25. var args = arguments;
  26. // Remember subproject-scoped module name
  27. sub = name;
  28. // Override
  29. args[0] = label;
  30. return qunitModule.apply( this, args );
  31. };
  32. test = function( name ) {
  33. var args = arguments,
  34. i = args.length - 1;
  35. // Prepend subproject-scoped module name to test name
  36. args[0] = sub + ": " + name;
  37. // Find test function and wrap to require subproject fixture
  38. for ( ; i >= 0; i-- ) {
  39. if ( originaljQuery.isFunction( args[i] ) ) {
  40. args[i] = requireFixture( args[i] );
  41. break;
  42. }
  43. }
  44. return qunitTest.apply( this, args );
  45. };
  46. // Load tests and fixture from subproject
  47. // Test order matters, so we must be synchronous and throw an error on load failure
  48. originaljQuery.ajax( url, {
  49. async: false,
  50. dataType: "html",
  51. error: function( jqXHR, status ) {
  52. throw new Error( "Could not load: " + url + " (" + status + ")" );
  53. },
  54. success: function( data, status, jqXHR ) {
  55. var page = originaljQuery.parseHTML(
  56. // replace html/head with dummy elements so they are represented in the DOM
  57. ( data || "" ).replace( /<\/?((!DOCTYPE|html|head)\b.*?)>/gi, "[$1]" ),
  58. document,
  59. true
  60. );
  61. if ( !page || !page.length ) {
  62. this.error( jqXHR, "no data" );
  63. }
  64. page = originaljQuery( page );
  65. // Include subproject tests
  66. page.filter("script[src]").add( page.find("script[src]") ).each(function() {
  67. var src = originaljQuery( this ).attr("src"),
  68. html = "<script src='" + url + src + "'></script>";
  69. if ( risTests.test( src ) ) {
  70. if ( originaljQuery.isReady ) {
  71. originaljQuery("head").first().append( html );
  72. } else {
  73. document.write( html );
  74. }
  75. }
  76. });
  77. // Get the fixture, including content outside of #qunit-fixture
  78. fixture = page.find("[id='qunit-fixture']");
  79. fixtureHTML = fixture.html();
  80. fixture.empty();
  81. while ( fixture.length && !fixture.prevAll("[id='qunit']").length ) {
  82. fixture = fixture.parent();
  83. }
  84. fixture = fixture.add( fixture.nextAll() );
  85. }
  86. });
  87. function requireFixture( fn ) {
  88. return function() {
  89. if ( !fixtureReplaced ) {
  90. // Make sure that we retrieved a fixture for the subproject
  91. if ( !fixture.length ) {
  92. ok( false, "Found subproject fixture" );
  93. return;
  94. }
  95. // Replace the current fixture, including content outside of #qunit-fixture
  96. var oldFixture = originaljQuery("#qunit-fixture");
  97. while ( oldFixture.length && !oldFixture.prevAll("[id='qunit']").length ) {
  98. oldFixture = oldFixture.parent();
  99. }
  100. oldFixture.nextAll().remove();
  101. oldFixture.replaceWith( fixture );
  102. // WARNING: UNDOCUMENTED INTERFACE
  103. QUnit.config.fixture = fixtureHTML;
  104. QUnit.reset();
  105. if ( originaljQuery("#qunit-fixture").html() !== fixtureHTML ) {
  106. ok( false, "Copied subproject fixture" );
  107. return;
  108. }
  109. fixtureReplaced = true;
  110. }
  111. fn.apply( this, arguments );
  112. };
  113. }
  114. };
  115. // Register globals for cleanup and the cleanup code itself
  116. // Explanation at http://perfectionkills.com/understanding-delete/#ie_bugs
  117. this.Globals = (function() {
  118. var globals = {};
  119. return {
  120. register: function( name ) {
  121. globals[ name ] = true;
  122. jQuery.globalEval( "var " + name + " = undefined;" );
  123. },
  124. cleanup: function() {
  125. var name,
  126. current = globals;
  127. globals = {};
  128. for ( name in current ) {
  129. jQuery.globalEval( "try { " +
  130. "delete " + ( jQuery.support.deleteExpando ? "window['" + name + "']" : name ) +
  131. "; } catch( x ) {}" );
  132. }
  133. }
  134. };
  135. })();
  136. // Sandbox start for great justice
  137. (function() {
  138. var oldStart = window.start;
  139. window.start = function() {
  140. oldStart();
  141. };
  142. })();
  143. /**
  144. * QUnit hooks
  145. */
  146. (function() {
  147. // Store the old counts so that we only assert on tests that have actually leaked,
  148. // instead of asserting every time a test has leaked sometime in the past
  149. var oldCacheLength = 0,
  150. oldFragmentsLength = 0,
  151. oldActive = 0,
  152. expectedDataKeys = {},
  153. splice = [].splice,
  154. reset = QUnit.reset,
  155. ajaxSettings = jQuery.ajaxSettings;
  156. function keys(o) {
  157. var ret, key;
  158. if ( Object.keys ) {
  159. ret = Object.keys( o );
  160. } else {
  161. ret = [];
  162. for ( key in o ) {
  163. ret.push( key );
  164. }
  165. }
  166. ret.sort();
  167. return ret;
  168. }
  169. /**
  170. * @param {jQuery|HTMLElement|Object|Array} elems Target (or array of targets) for jQuery.data.
  171. * @param {string} key
  172. */
  173. QUnit.expectJqData = function( elems, key ) {
  174. var i, elem, expando;
  175. // As of jQuery 2.0, there will be no "cache"-data is
  176. // stored and managed completely below the API surface
  177. if ( jQuery.cache ) {
  178. QUnit.current_testEnvironment.checkJqData = true;
  179. if ( elems.jquery && elems.toArray ) {
  180. elems = elems.toArray();
  181. }
  182. if ( !jQuery.isArray( elems ) ) {
  183. elems = [ elems ];
  184. }
  185. for ( i = 0; i < elems.length; i++ ) {
  186. elem = elems[i];
  187. // jQuery.data only stores data for nodes in jQuery.cache,
  188. // for other data targets the data is stored in the object itself,
  189. // in that case we can't test that target for memory leaks.
  190. // But we don't have to since in that case the data will/must will
  191. // be available as long as the object is not garbage collected by
  192. // the js engine, and when it is, the data will be removed with it.
  193. if ( !elem.nodeType ) {
  194. // Fixes false positives for dataTests(window), dataTests({}).
  195. continue;
  196. }
  197. expando = elem[ jQuery.expando ];
  198. if ( expando === undefined ) {
  199. // In this case the element exists fine, but
  200. // jQuery.data (or internal data) was never (in)directly
  201. // called.
  202. // Since this method was called it means some data was
  203. // expected to be found, but since there is nothing, fail early
  204. // (instead of in teardown).
  205. notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." );
  206. } else {
  207. if ( expectedDataKeys[expando] ) {
  208. expectedDataKeys[expando].push( key );
  209. } else {
  210. expectedDataKeys[expando] = [ key ];
  211. }
  212. }
  213. }
  214. }
  215. };
  216. QUnit.config.urlConfig.push( {
  217. id: "jqdata",
  218. label: "Always check jQuery.data",
  219. tooltip: "Trigger QUnit.expectJqData detection for all tests instead of just the ones that call it"
  220. } );
  221. /**
  222. * Ensures that tests have cleaned up properly after themselves. Should be passed as the
  223. * teardown function on all modules' lifecycle object.
  224. */
  225. this.moduleTeardown = function() {
  226. var i,
  227. expectedKeys, actualKeys,
  228. fragmentsLength = 0,
  229. cacheLength = 0;
  230. // Only look for jQuery data problems if this test actually
  231. // provided some information to compare against.
  232. if ( QUnit.urlParams.jqdata || this.checkJqData ) {
  233. for ( i in jQuery.cache ) {
  234. expectedKeys = expectedDataKeys[i];
  235. actualKeys = jQuery.cache[i] ? keys( jQuery.cache[i] ) : jQuery.cache[i];
  236. if ( !QUnit.equiv( expectedKeys, actualKeys ) ) {
  237. deepEqual( actualKeys, expectedKeys, "Expected keys exist in jQuery.cache" );
  238. }
  239. delete jQuery.cache[i];
  240. delete expectedDataKeys[i];
  241. }
  242. // In case it was removed from cache before (or never there in the first place)
  243. for ( i in expectedDataKeys ) {
  244. deepEqual( expectedDataKeys[i], undefined, "No unexpected keys were left in jQuery.cache (#" + i + ")" );
  245. delete expectedDataKeys[i];
  246. }
  247. }
  248. // Reset data register
  249. expectedDataKeys = {};
  250. // Check for (and clean up, if possible) incomplete animations/requests/etc.
  251. if ( jQuery.timers && jQuery.timers.length !== 0 ) {
  252. equal( jQuery.timers.length, 0, "No timers are still running" );
  253. splice.call( jQuery.timers, 0, jQuery.timers.length );
  254. jQuery.fx.stop();
  255. }
  256. if ( jQuery.active !== undefined && jQuery.active !== oldActive ) {
  257. equal( jQuery.active, oldActive, "No AJAX requests are still active" );
  258. if ( ajaxTest.abort ) {
  259. ajaxTest.abort("active requests");
  260. }
  261. oldActive = jQuery.active;
  262. }
  263. // Allow QUnit.reset to clean up any attached elements before checking for leaks
  264. QUnit.reset();
  265. for ( i in jQuery.cache ) {
  266. ++cacheLength;
  267. }
  268. jQuery.fragments = {};
  269. for ( i in jQuery.fragments ) {
  270. ++fragmentsLength;
  271. }
  272. // Because QUnit doesn't have a mechanism for retrieving the number of expected assertions for a test,
  273. // if we unconditionally assert any of these, the test will fail with too many assertions :|
  274. if ( cacheLength !== oldCacheLength ) {
  275. equal( cacheLength, oldCacheLength, "No unit tests leak memory in jQuery.cache" );
  276. oldCacheLength = cacheLength;
  277. }
  278. if ( fragmentsLength !== oldFragmentsLength ) {
  279. equal( fragmentsLength, oldFragmentsLength, "No unit tests leak memory in jQuery.fragments" );
  280. oldFragmentsLength = fragmentsLength;
  281. }
  282. };
  283. QUnit.done(function() {
  284. // Remove our own fixtures outside #qunit-fixture
  285. jQuery("#qunit ~ *").remove();
  286. });
  287. // jQuery-specific QUnit.reset
  288. QUnit.reset = function() {
  289. // Ensure jQuery events and data on the fixture are properly removed
  290. jQuery("#qunit-fixture").empty();
  291. // Reset internal jQuery state
  292. jQuery.event.global = {};
  293. if ( ajaxSettings ) {
  294. jQuery.ajaxSettings = jQuery.extend( true, {}, ajaxSettings );
  295. } else {
  296. delete jQuery.ajaxSettings;
  297. }
  298. // Cleanup globals
  299. Globals.cleanup();
  300. // Let QUnit reset the fixture
  301. reset.apply( this, arguments );
  302. };
  303. })();
  304. /**
  305. * QUnit configuration
  306. */
  307. // Max time for stop() and asyncTest() until it aborts test
  308. // and start()'s the next test.
  309. QUnit.config.testTimeout = 20 * 1000; // 20 seconds
  310. // Enforce an "expect" argument or expect() call in all test bodies.
  311. QUnit.config.requireExpects = true;
  312. /**
  313. * Load the TestSwarm listener if swarmURL is in the address.
  314. */
  315. (function() {
  316. var url = window.location.search;
  317. url = decodeURIComponent( url.slice( url.indexOf("swarmURL=") + "swarmURL=".length ) );
  318. if ( !url || url.indexOf("http") !== 0 ) {
  319. return;
  320. }
  321. document.write("<scr" + "ipt src='http://swarm.jquery.org/js/inject.js?" + (new Date()).getTime() + "'></scr" + "ipt>");
  322. })();