moduleloader_test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Tests for goog.module.ModuleLoader.
  16. * @author nicksantos@google.com (Nick Santos)
  17. */
  18. goog.provide('goog.module.ModuleLoaderTest');
  19. goog.require('goog.Promise');
  20. goog.require('goog.array');
  21. goog.require('goog.dom');
  22. goog.require('goog.dom.TagName');
  23. goog.require('goog.events');
  24. goog.require('goog.functions');
  25. goog.require('goog.module.ModuleLoader');
  26. goog.require('goog.module.ModuleManager');
  27. goog.require('goog.net.BulkLoader');
  28. goog.require('goog.net.XmlHttp');
  29. goog.require('goog.object');
  30. goog.require('goog.testing.PropertyReplacer');
  31. goog.require('goog.testing.TestCase');
  32. goog.require('goog.testing.events.EventObserver');
  33. goog.require('goog.testing.jsunit');
  34. goog.require('goog.userAgent');
  35. goog.setTestOnly('goog.module.ModuleLoaderTest');
  36. var modA1Loaded = false;
  37. var modA2Loaded = false;
  38. var modB1Loaded = false;
  39. var moduleLoader = null;
  40. var moduleManager = null;
  41. var stubs = new goog.testing.PropertyReplacer();
  42. var EventType = goog.module.ModuleLoader.EventType;
  43. var observer;
  44. function setUpPage() {
  45. goog.testing.TestCase.getActiveTestCase().promiseTimeout = 10000; // 10s
  46. }
  47. function setUp() {
  48. modA1Loaded = false;
  49. modA2Loaded = false;
  50. modB1Loaded = false;
  51. goog.provide = goog.nullFunction;
  52. moduleManager = goog.module.ModuleManager.getInstance();
  53. stubs.replace(moduleManager, 'getBackOff_', goog.functions.constant(0));
  54. moduleLoader = new goog.module.ModuleLoader();
  55. observer = new goog.testing.events.EventObserver();
  56. goog.events.listen(moduleLoader, goog.object.getValues(EventType), observer);
  57. moduleManager.setLoader(moduleLoader);
  58. moduleManager.setAllModuleInfo({'modA': [], 'modB': ['modA']});
  59. moduleManager.setModuleUris({
  60. 'modA': ['testdata/modA_1.js', 'testdata/modA_2.js'],
  61. 'modB': ['testdata/modB_1.js']
  62. });
  63. assertNotLoaded('modA');
  64. assertNotLoaded('modB');
  65. assertFalse(modA1Loaded);
  66. }
  67. function tearDown() {
  68. stubs.reset();
  69. goog.dispose(moduleLoader);
  70. // Ensure that the module manager was created.
  71. assertNotNull(goog.module.ModuleManager.getInstance());
  72. moduleManager = goog.module.ModuleManager.instance_ = null;
  73. // tear down the module loaded flag.
  74. modA1Loaded = false;
  75. // Remove all the fake scripts.
  76. var scripts =
  77. goog.array.clone(goog.dom.getElementsByTagName(goog.dom.TagName.SCRIPT));
  78. for (var i = 0; i < scripts.length; i++) {
  79. if (scripts[i].src.indexOf('testdata') != -1) {
  80. goog.dom.removeNode(scripts[i]);
  81. }
  82. }
  83. }
  84. function testLoadModuleA() {
  85. return new goog
  86. .Promise(function(resolve, reject) {
  87. moduleManager.execOnLoad('modA', function() {
  88. assertLoaded('modA');
  89. assertNotLoaded('modB');
  90. assertTrue(modA1Loaded);
  91. // The code is not evaluated immediately, but only after a browser
  92. // yield.
  93. assertEquals(
  94. 'EVALUATE_CODE', 0,
  95. observer.getEvents(EventType.EVALUATE_CODE).length);
  96. assertEquals(
  97. 'REQUEST_SUCCESS', 1,
  98. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  99. assertArrayEquals(
  100. ['modA'],
  101. observer.getEvents(EventType.REQUEST_SUCCESS)[0].moduleIds);
  102. assertEquals(
  103. 'REQUEST_ERROR', 0,
  104. observer.getEvents(EventType.REQUEST_ERROR).length);
  105. resolve();
  106. });
  107. })
  108. .then(function() {
  109. assertEquals(
  110. 'EVALUATE_CODE after tick', 1,
  111. observer.getEvents(EventType.EVALUATE_CODE).length);
  112. assertEquals(
  113. 'REQUEST_SUCCESS after tick', 1,
  114. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  115. assertEquals(
  116. 'REQUEST_ERROR after tick', 0,
  117. observer.getEvents(EventType.REQUEST_ERROR).length);
  118. });
  119. }
  120. function testLoadModuleB() {
  121. return new goog
  122. .Promise(function(resolve, reject) {
  123. moduleManager.execOnLoad('modB', resolve);
  124. })
  125. .then(function() {
  126. assertLoaded('modA');
  127. assertLoaded('modB');
  128. assertTrue(modA1Loaded);
  129. });
  130. }
  131. function testLoadDebugModuleA() {
  132. moduleLoader.setDebugMode(true);
  133. return new goog
  134. .Promise(function(resolve, reject) {
  135. moduleManager.execOnLoad('modA', resolve);
  136. })
  137. .then(function() {
  138. assertLoaded('modA');
  139. assertNotLoaded('modB');
  140. assertTrue(modA1Loaded);
  141. });
  142. }
  143. function testLoadDebugModuleB() {
  144. moduleLoader.setDebugMode(true);
  145. return new goog
  146. .Promise(function(resolve, reject) {
  147. moduleManager.execOnLoad('modB', resolve);
  148. })
  149. .then(function() {
  150. assertLoaded('modA');
  151. assertLoaded('modB');
  152. assertTrue(modA1Loaded);
  153. });
  154. }
  155. function testLoadDebugModuleAThenB() {
  156. // Swap the script tags of module A, to introduce a race condition.
  157. // See the comments on this in ModuleLoader's debug loader.
  158. moduleManager.setModuleUris({
  159. 'modA': ['testdata/modA_2.js', 'testdata/modA_1.js'],
  160. 'modB': ['testdata/modB_1.js']
  161. });
  162. moduleLoader.setDebugMode(true);
  163. return new goog
  164. .Promise(function(resolve, reject) {
  165. moduleManager.execOnLoad('modB', resolve);
  166. })
  167. .then(function() {
  168. assertLoaded('modA');
  169. assertLoaded('modB');
  170. var scripts = goog.array.clone(
  171. goog.dom.getElementsByTagName(goog.dom.TagName.SCRIPT));
  172. var seenLastScriptOfModuleA = false;
  173. for (var i = 0; i < scripts.length; i++) {
  174. var uri = scripts[i].src;
  175. if (uri.indexOf('modA_1.js') >= 0) {
  176. seenLastScriptOfModuleA = true;
  177. } else if (uri.indexOf('modB') >= 0) {
  178. assertTrue(seenLastScriptOfModuleA);
  179. }
  180. }
  181. });
  182. }
  183. function testSourceInjection() {
  184. moduleLoader.setSourceUrlInjection(true);
  185. return assertSourceInjection();
  186. }
  187. function testSourceInjectionViaDebugMode() {
  188. moduleLoader.setDebugMode(true);
  189. return assertSourceInjection();
  190. }
  191. function assertSourceInjection() {
  192. return new goog
  193. .Promise(function(resolve, reject) {
  194. moduleManager.execOnLoad('modB', resolve);
  195. })
  196. .then(function() {
  197. assertTrue(!!throwErrorInModuleB);
  198. var ex = assertThrows(function() { throwErrorInModuleB(); });
  199. if (!ex.stack) {
  200. return;
  201. }
  202. var stackTrace = ex.stack.toString();
  203. var expectedString = 'testdata/modB_1.js';
  204. if (goog.module.ModuleLoader.supportsSourceUrlStackTraces()) {
  205. // Source URL should be added in eval or in jsloader.
  206. assertContains(expectedString, stackTrace);
  207. } else if (moduleLoader.getDebugMode()) {
  208. // Browsers used jsloader, thus URLs are present.
  209. assertContains(expectedString, stackTrace);
  210. } else {
  211. // Browser used eval, does not support source URL.
  212. assertNotContains(expectedString, stackTrace);
  213. }
  214. });
  215. }
  216. function testModuleLoaderRecursesTooDeep(opt_numModules) {
  217. // There was a bug in the module loader where it would retry recursively
  218. // whenever there was a synchronous failure in the module load. When you
  219. // asked for modB, it would try to load its dependency modA. When modA
  220. // failed, it would move onto modB, and then start over, repeating until it
  221. // ran out of stack.
  222. var numModules = opt_numModules || 1;
  223. var uris = {};
  224. var deps = {};
  225. var mods = [];
  226. for (var num = 0; num < numModules; num++) {
  227. var modName = 'mod' + num;
  228. mods.unshift(modName);
  229. uris[modName] = [];
  230. deps[modName] = num ? ['mod' + (num - 1)] : [];
  231. for (var i = 0; i < 5; i++) {
  232. uris[modName].push(
  233. 'http://www.google.com/crossdomain' + num + 'x' + i + '.js');
  234. }
  235. }
  236. moduleManager.setAllModuleInfo(deps);
  237. moduleManager.setModuleUris(uris);
  238. // Make all XHRs throw an error, so that we test the error-handling
  239. // functionality.
  240. var oldXmlHttp = goog.net.XmlHttp;
  241. stubs.set(goog.net, 'XmlHttp', function() {
  242. return {open: goog.functions.error('mock error'), abort: goog.nullFunction};
  243. });
  244. goog.object.extend(goog.net.XmlHttp, oldXmlHttp);
  245. var errorCount = 0;
  246. var errorIds = [];
  247. var errorHandler = function(ignored, modId) {
  248. errorCount++;
  249. errorIds.push(modId);
  250. };
  251. moduleManager.registerCallback(
  252. goog.module.ModuleManager.CallbackType.ERROR, errorHandler);
  253. moduleManager.execOnLoad(
  254. mods[0], function() { fail('modB should not load successfully'); });
  255. assertEquals(mods.length, errorCount);
  256. goog.array.sort(mods);
  257. goog.array.sort(errorIds);
  258. assertArrayEquals(mods, errorIds);
  259. assertArrayEquals([], moduleManager.requestedModuleIdsQueue_);
  260. assertArrayEquals([], moduleManager.userInitiatedLoadingModuleIds_);
  261. }
  262. function testModuleLoaderRecursesTooDeep2modules() {
  263. testModuleLoaderRecursesTooDeep(2);
  264. }
  265. function testModuleLoaderRecursesTooDeep3modules() {
  266. testModuleLoaderRecursesTooDeep(3);
  267. }
  268. function testModuleLoaderRecursesTooDeep4modules() {
  269. testModuleLoaderRecursesTooDeep(3);
  270. }
  271. function testErrback() {
  272. // Don't run this test on IE, because the way the test runner catches
  273. // errors on IE plays badly with the simulated errors in the test.
  274. if (goog.userAgent.IE) return;
  275. // Modules will throw an exception if this boolean is set to true.
  276. modA1Loaded = true;
  277. return new goog.Promise(function(resolve, reject) {
  278. var errorHandler = function() {
  279. assertNotLoaded('modA');
  280. resolve();
  281. };
  282. moduleManager.registerCallback(
  283. goog.module.ModuleManager.CallbackType.ERROR, errorHandler);
  284. moduleManager.execOnLoad(
  285. 'modA', function() { fail('modA should not load successfully'); });
  286. });
  287. }
  288. function testEventError() {
  289. // Don't run this test on older IE, because the way the test runner catches
  290. // errors on IE plays badly with the simulated errors in the test.
  291. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(11)) {
  292. return;
  293. }
  294. // Modules will throw an exception if this boolean is set to true.
  295. modA1Loaded = true;
  296. return new goog
  297. .Promise(function(resolve, reject) {
  298. var errorHandler = function() {
  299. assertNotLoaded('modA');
  300. resolve();
  301. };
  302. moduleManager.registerCallback(
  303. goog.module.ModuleManager.CallbackType.ERROR, errorHandler);
  304. moduleManager.execOnLoad(
  305. 'modA', function() { fail('modA should not load successfully'); });
  306. })
  307. .then(function() {
  308. assertEquals(
  309. 'EVALUATE_CODE', 3,
  310. observer.getEvents(EventType.EVALUATE_CODE).length);
  311. assertUndefined(observer.getEvents(EventType.EVALUATE_CODE)[0].error);
  312. assertEquals(
  313. 'REQUEST_SUCCESS', 3,
  314. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  315. assertUndefined(observer.getEvents(EventType.REQUEST_SUCCESS)[0].error);
  316. var requestErrors = observer.getEvents(EventType.REQUEST_ERROR);
  317. assertEquals('REQUEST_ERROR', 3, requestErrors.length);
  318. assertNotNull(requestErrors[0].error);
  319. var expectedString = 'loaded twice';
  320. var messageAndStack =
  321. requestErrors[0].error.message + requestErrors[0].error.stack;
  322. assertContains(expectedString, messageAndStack);
  323. });
  324. }
  325. function testPrefetchThenLoadModuleA() {
  326. moduleManager.prefetchModule('modA');
  327. stubs.set(goog.net.BulkLoader.prototype, 'load', function() {
  328. fail('modA should not be reloaded');
  329. });
  330. return new goog
  331. .Promise(function(resolve, reject) {
  332. moduleManager.execOnLoad('modA', resolve);
  333. })
  334. .then(function() {
  335. assertLoaded('modA');
  336. assertEquals(
  337. 'REQUEST_SUCCESS', 1,
  338. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  339. assertArrayEquals(
  340. ['modA'],
  341. observer.getEvents(EventType.REQUEST_SUCCESS)[0].moduleIds);
  342. assertEquals(
  343. 'REQUEST_ERROR', 0,
  344. observer.getEvents(EventType.REQUEST_ERROR).length);
  345. });
  346. }
  347. function testPrefetchThenLoadModuleB() {
  348. moduleManager.prefetchModule('modB');
  349. stubs.set(goog.net.BulkLoader.prototype, 'load', function() {
  350. fail('modA and modB should not be reloaded');
  351. });
  352. return new goog
  353. .Promise(function(resolve, reject) {
  354. moduleManager.execOnLoad('modB', resolve);
  355. })
  356. .then(function() {
  357. assertLoaded('modA');
  358. assertLoaded('modB');
  359. assertEquals(
  360. 'REQUEST_SUCCESS', 2,
  361. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  362. assertArrayEquals(
  363. ['modA'],
  364. observer.getEvents(EventType.REQUEST_SUCCESS)[0].moduleIds);
  365. assertArrayEquals(
  366. ['modB'],
  367. observer.getEvents(EventType.REQUEST_SUCCESS)[1].moduleIds);
  368. assertEquals(
  369. 'REQUEST_ERROR', 0,
  370. observer.getEvents(EventType.REQUEST_ERROR).length);
  371. });
  372. }
  373. function testPrefetchModuleAThenLoadModuleB() {
  374. moduleManager.prefetchModule('modA');
  375. return new goog
  376. .Promise(function(resolve, reject) {
  377. moduleManager.execOnLoad('modB', resolve);
  378. })
  379. .then(function() {
  380. assertLoaded('modA');
  381. assertLoaded('modB');
  382. assertEquals(
  383. 'REQUEST_SUCCESS', 2,
  384. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  385. assertArrayEquals(
  386. ['modA'],
  387. observer.getEvents(EventType.REQUEST_SUCCESS)[0].moduleIds);
  388. assertArrayEquals(
  389. ['modB'],
  390. observer.getEvents(EventType.REQUEST_SUCCESS)[1].moduleIds);
  391. assertEquals(
  392. 'REQUEST_ERROR', 0,
  393. observer.getEvents(EventType.REQUEST_ERROR).length);
  394. });
  395. }
  396. function testLoadModuleBThenPrefetchModuleA() {
  397. return new goog
  398. .Promise(function(resolve, reject) {
  399. moduleManager.execOnLoad('modB', resolve);
  400. })
  401. .then(function() {
  402. assertLoaded('modA');
  403. assertLoaded('modB');
  404. assertEquals(
  405. 'REQUEST_SUCCESS', 2,
  406. observer.getEvents(EventType.REQUEST_SUCCESS).length);
  407. assertArrayEquals(
  408. ['modA'],
  409. observer.getEvents(EventType.REQUEST_SUCCESS)[0].moduleIds);
  410. assertArrayEquals(
  411. ['modB'],
  412. observer.getEvents(EventType.REQUEST_SUCCESS)[1].moduleIds);
  413. assertEquals(
  414. 'REQUEST_ERROR', 0,
  415. observer.getEvents(EventType.REQUEST_ERROR).length);
  416. assertThrows('Module load already requested: modB', function() {
  417. moduleManager.prefetchModule('modA');
  418. });
  419. });
  420. }
  421. function testPrefetchModuleWithBatchModeEnabled() {
  422. moduleManager.setBatchModeEnabled(true);
  423. assertThrows(
  424. 'Modules prefetching is not supported in batch mode',
  425. function() { moduleManager.prefetchModule('modA'); });
  426. }
  427. function testLoadErrorCallbackExecutedWhenPrefetchFails() {
  428. // Make all XHRs throw an error, so that we test the error-handling
  429. // functionality.
  430. var oldXmlHttp = goog.net.XmlHttp;
  431. stubs.set(goog.net, 'XmlHttp', function() {
  432. return {open: goog.functions.error('mock error'), abort: goog.nullFunction};
  433. });
  434. goog.object.extend(goog.net.XmlHttp, oldXmlHttp);
  435. var errorCount = 0;
  436. var errorHandler = function() { errorCount++; };
  437. moduleManager.registerCallback(
  438. goog.module.ModuleManager.CallbackType.ERROR, errorHandler);
  439. moduleLoader.prefetchModule('modA', moduleManager.moduleInfoMap_['modA']);
  440. moduleLoader.loadModules(['modA'], moduleManager.moduleInfoMap_, function() {
  441. fail('modA should not load successfully');
  442. }, errorHandler);
  443. assertEquals(1, errorCount);
  444. }
  445. function assertLoaded(id) {
  446. assertTrue(moduleManager.getModuleInfo(id).isLoaded());
  447. }
  448. function assertNotLoaded(id) {
  449. assertFalse(moduleManager.getModuleInfo(id).isLoaded());
  450. }