| // Copyright 2008 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.module.ModuleManagerTest'); |
| goog.setTestOnly('goog.module.ModuleManagerTest'); |
| |
| goog.require('goog.array'); |
| goog.require('goog.functions'); |
| goog.require('goog.module.BaseModule'); |
| goog.require('goog.module.ModuleManager'); |
| goog.require('goog.testing'); |
| goog.require('goog.testing.MockClock'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.testing.recordFunction'); |
| |
| |
| |
| var clock; |
| var requestCount = 0; |
| |
| |
| function tearDown() { |
| clock.dispose(); |
| } |
| |
| function setUp() { |
| clock = new goog.testing.MockClock(true); |
| requestCount = 0; |
| } |
| |
| function getModuleManager(infoMap) { |
| var mm = new goog.module.ModuleManager(); |
| mm.setAllModuleInfo(infoMap); |
| |
| mm.isModuleLoaded = function(id) { |
| return this.getModuleInfo(id).isLoaded(); |
| }; |
| return mm; |
| } |
| |
| function createSuccessfulBatchLoader(moduleMgr) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| requestCount++; |
| setTimeout(goog.bind(this.onLoad, this, ids.concat(), 0), 5); |
| }, |
| onLoad: function(ids, idxLoaded) { |
| moduleMgr.beforeLoadModuleCode(ids[idxLoaded]); |
| moduleMgr.setLoaded(ids[idxLoaded]); |
| moduleMgr.afterLoadModuleCode(ids[idxLoaded]); |
| var idx = idxLoaded + 1; |
| if (idx < ids.length) { |
| setTimeout(goog.bind(this.onLoad, this, ids, idx), 2); |
| } |
| }}; |
| } |
| |
| function createSuccessfulNonBatchLoader(moduleMgr) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| requestCount++; |
| setTimeout(function() { |
| moduleMgr.beforeLoadModuleCode(ids[0]); |
| moduleMgr.setLoaded(ids[0]); |
| moduleMgr.afterLoadModuleCode(ids[0]); |
| if (opt_successFn) { |
| opt_successFn(); |
| } |
| }, 5); |
| }}; |
| } |
| |
| function createUnsuccessfulLoader(moduleMgr, status) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| moduleMgr.beforeLoadModuleCode(ids[0]); |
| setTimeout(function() { opt_errFn(status); }, 5); |
| }}; |
| } |
| |
| function createUnsuccessfulBatchLoader(moduleMgr, status) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| setTimeout(function() { opt_errFn(status); }, 5); |
| }}; |
| } |
| |
| function createTimeoutLoader(moduleMgr, status) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| setTimeout(function() { opt_timeoutFn(status); }, 5); |
| }}; |
| } |
| |
| |
| /** |
| * Tests loading a module under different conditions i.e. unloaded |
| * module, already loaded module, module loaded through user initiated |
| * actions, synchronous callback for a module that has been already |
| * loaded. Test both batch and non-batch loaders. |
| */ |
| function testExecOnLoad() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| execOnLoad_(mm); |
| |
| mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| mm.setBatchModeEnabled(true); |
| execOnLoad_(mm); |
| } |
| |
| |
| /** |
| * Tests execOnLoad with the specified module manager. |
| * @param {goog.module.ModuleManager} mm The module manager. |
| */ |
| function execOnLoad_(mm) { |
| // When module is unloaded, execOnLoad is async. |
| var execCalled1 = false; |
| mm.execOnLoad('a', function() { execCalled1 = true; }); |
| assertFalse('module "a" should not be loaded', mm.isModuleLoaded('a')); |
| assertTrue('module "a" should be loading', mm.isModuleLoading('a')); |
| assertFalse('execCalled1 should not be set yet', execCalled1); |
| assertTrue('ModuleManager should be active', mm.isActive()); |
| assertFalse( |
| 'ModuleManager should not be user active', mm.isUserActive()); |
| clock.tick(5); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse( |
| 'module "a" should not be loading', mm.isModuleLoading('a')); |
| assertTrue('execCalled1 should be set', execCalled1); |
| assertFalse('ModuleManager should not be active', mm.isActive()); |
| assertFalse( |
| 'ModuleManager should not be user active', mm.isUserActive()); |
| |
| // When module is already loaded, execOnLoad is still async unless |
| // specified otherwise. |
| var execCalled2 = false; |
| mm.execOnLoad('a', function() { execCalled2 = true; }); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse( |
| 'module "a" should not be loading', mm.isModuleLoading('a')); |
| assertFalse('execCalled2 should not be set yet', execCalled2); |
| clock.tick(5); |
| assertTrue('execCalled2 should be set', execCalled2); |
| |
| // When module is unloaded, execOnLoad is async (user active). |
| var execCalled5 = false; |
| mm.execOnLoad('c', |
| function() { execCalled5 = true; }, null, null, true); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| assertTrue('module "c" should be loading', mm.isModuleLoading('c')); |
| assertFalse('execCalled1 should not be set yet', execCalled5); |
| assertTrue('ModuleManager should be active', mm.isActive()); |
| assertTrue('ModuleManager should be user active', mm.isUserActive()); |
| clock.tick(5); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| assertFalse( |
| 'module "c" should not be loading', mm.isModuleLoading('c')); |
| assertTrue('execCalled1 should be set', execCalled5); |
| assertFalse('ModuleManager should not be active', mm.isActive()); |
| assertFalse( |
| 'ModuleManager should not be user active', mm.isUserActive()); |
| |
| // When module is already loaded, execOnLoad is still synchronous when |
| // so specified |
| var execCalled6 = false; |
| mm.execOnLoad('c', function() { execCalled6 = true; }, |
| undefined, undefined, undefined, true); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| assertFalse( |
| 'module "c" should not be loading', mm.isModuleLoading('c')); |
| assertTrue('execCalled6 should be set', execCalled6); |
| clock.tick(5); |
| assertTrue('execCalled6 should still be set', execCalled6); |
| |
| } |
| |
| |
| /** |
| * Test aborting the callback called on module load. |
| */ |
| function testExecOnLoadAbort() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| // When module is unloaded and abort is called, module still gets |
| // loaded, but callback is cancelled. |
| var execCalled1 = false; |
| var callback1 = mm.execOnLoad('b', function() { execCalled1 = true; }); |
| callback1.abort(); |
| clock.tick(5); |
| assertTrue('module "b" should be loaded', mm.isModuleLoaded('b')); |
| assertFalse('execCalled3 should not be set', execCalled1); |
| |
| // When module is already loaded, execOnLoad is still async, so calling |
| // abort should still cancel the callback. |
| var execCalled2 = false; |
| var callback2 = mm.execOnLoad('a', function() { execCalled2 = true; }); |
| callback2.abort(); |
| clock.tick(5); |
| assertFalse('execCalled2 should not be set', execCalled2); |
| } |
| |
| |
| /** |
| * Test preloading modules and ensure that the before load, after load |
| * and set load called are called only once per module. |
| */ |
| function testExecOnLoadWhilePreloadingAndViceVersa() { |
| var mm = getModuleManager({'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| execOnLoadWhilePreloadingAndViceVersa_(mm); |
| |
| mm = getModuleManager({'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| mm.setBatchModeEnabled(true); |
| execOnLoadWhilePreloadingAndViceVersa_(mm); |
| } |
| |
| |
| /** |
| * Perform tests with the specified module manager. |
| * @param {goog.module.ModuleManager} mm The module manager. |
| */ |
| function execOnLoadWhilePreloadingAndViceVersa_(mm) { |
| var mm = getModuleManager({'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var origSetLoaded = mm.setLoaded; |
| var calls = [0, 0, 0]; |
| mm.beforeLoadModuleCode = function(id) { |
| calls[0]++; |
| }; |
| mm.setLoaded = function(id) { |
| calls[1]++; |
| origSetLoaded.call(mm, id); |
| }; |
| mm.afterLoadModuleCode = function(id) { |
| calls[2]++; |
| }; |
| |
| mm.preloadModule('c', 2); |
| assertFalse( |
| 'module "c" should not be loading yet', mm.isModuleLoading('c')); |
| clock.tick(2); |
| assertTrue( |
| 'module "c" should now be loading', mm.isModuleLoading('c')); |
| mm.execOnLoad('c', function() {}); |
| assertTrue( |
| 'module "c" should still be loading', mm.isModuleLoading('c')); |
| clock.tick(5); |
| assertFalse( |
| 'module "c" should be done loading', mm.isModuleLoading('c')); |
| assertEquals( |
| 'beforeLoad should only be called once for "c"', 1, calls[0]); |
| assertEquals( |
| 'setLoaded should only be called once for "c"', 1, calls[1]); |
| assertEquals( |
| 'afterLoad should only be called once for "c"', 1, calls[2]); |
| |
| mm.execOnLoad('d', function() {}); |
| assertTrue( |
| 'module "d" should now be loading', mm.isModuleLoading('d')); |
| mm.preloadModule('d', 2); |
| clock.tick(5); |
| assertFalse( |
| 'module "d" should be done loading', mm.isModuleLoading('d')); |
| assertTrue( |
| 'module "d" should now be loaded', mm.isModuleLoaded('d')); |
| assertEquals( |
| 'beforeLoad should only be called once for "d"', 2, calls[0]); |
| assertEquals( |
| 'setLoaded should only be called once for "d"', 2, calls[1]); |
| assertEquals( |
| 'afterLoad should only be called once for "d"', 2, calls[2]); |
| } |
| |
| |
| /** |
| * Tests that multiple callbacks on the same module don't cause |
| * confusion about the active state after the module is finally loaded. |
| */ |
| function testUserInitiatedExecOnLoadEventuallyLeavesManagerIdle() { |
| var mm = getModuleManager({'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack1 = false; |
| var calledBack2 = false; |
| |
| mm.execOnLoad( |
| 'c', |
| function() { |
| calledBack1 = true; |
| }, |
| undefined, |
| undefined, |
| true); |
| mm.execOnLoad( |
| 'c', |
| function() { |
| calledBack2 = true; |
| }, |
| undefined, |
| undefined, |
| true); |
| mm.load('c'); |
| |
| assertTrue( |
| 'Manager should be active while waiting for load', mm.isUserActive()); |
| |
| clock.tick(5); |
| |
| assertTrue('First callback should be called', calledBack1); |
| assertTrue('Second callback should be called', calledBack2); |
| assertFalse( |
| 'Manager should be inactive after loading is complete', |
| mm.isUserActive()); |
| } |
| |
| |
| /** |
| * Tests loading a module by requesting a Deferred object. |
| */ |
| function testLoad() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| var d = mm.load('a'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| d.addErrback(function(err) { |
| error = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertNull(error); |
| assertFalse(mm.isUserActive()); |
| |
| clock.tick(5); |
| |
| assertTrue(calledBack); |
| assertNull(error); |
| } |
| |
| |
| /** |
| * Tests loading 2 modules asserting that the loads happen in parallel |
| * in one unit of time. |
| */ |
| function testLoad_concurrent() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setConcurrentLoadingEnabled(true); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| mm.load('a'); |
| mm.load('b'); |
| assertEquals(2, requestCount); |
| // Only time for one serialized download. |
| clock.tick(5); |
| |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| assertTrue(mm.getModuleInfo('b').isLoaded()); |
| } |
| |
| function testLoad_concurrentSecondIsDepOfFist() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setConcurrentLoadingEnabled(true); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| mm.loadMultiple(['a', 'b']); |
| mm.load('b'); |
| assertEquals('No 2nd request expected', 1, requestCount); |
| // Only time for one serialized download. |
| clock.tick(5); |
| clock.tick(2); // Makes second module come in from batch requst. |
| |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| assertTrue(mm.getModuleInfo('b').isLoaded()); |
| } |
| |
| function testLoad_nonConcurrent() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| mm.load('a'); |
| mm.load('b'); |
| assertEquals(1, requestCount); |
| // Only time for one serialized download. |
| clock.tick(5); |
| |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| assertFalse(mm.getModuleInfo('b').isLoaded()); |
| } |
| |
| function testLoadUnknown() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| var e = assertThrows(function() { |
| mm.load('DoesNotExist'); |
| }); |
| assertEquals('Unknown module: DoesNotExist', e.message); |
| } |
| |
| |
| /** |
| * Tests loading multiple modules by requesting a Deferred object. |
| */ |
| function testLoadMultiple() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error2 = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| |
| clock.tick(5); |
| assertTrue(calledBack); |
| assertFalse(calledBack2); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(2); |
| |
| assertTrue(calledBack); |
| assertTrue(calledBack2); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertTrue('module "b" should be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| assertNull(error); |
| assertNull(error2); |
| } |
| |
| |
| /** |
| * Tests loading multiple modules with deps by requesting a Deferred object. |
| */ |
| function testLoadMultipleWithDeps() { |
| var mm = getModuleManager({'a': [], 'b': ['c'], 'c': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error2 = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| |
| clock.tick(5); |
| assertTrue(calledBack); |
| assertFalse(calledBack2); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(2); |
| |
| assertFalse(calledBack2); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(2); |
| |
| assertTrue(calledBack2); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertTrue('module "b" should be loaded', mm.isModuleLoaded('b')); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| assertNull(error); |
| assertNull(error2); |
| } |
| |
| |
| /** |
| * Tests loading multiple modules by requesting a Deferred object when |
| * a server error occurs. |
| */ |
| function testLoadMultipleWithErrors() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setLoader(createUnsuccessfulLoader(mm, 500)); |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| var calledBack3 = false; |
| var error3 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b', 'c']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error2 = err; |
| }); |
| dMap['c'].addCallback(function(ctx) { |
| calledBack3 = true; |
| }); |
| dMap['c'].addErrback(function(err) { |
| error3 = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| |
| clock.tick(4); |
| |
| // A module request is now underway using the unsuccessful loader. |
| // We substitute a successful loader for future module load requests. |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| clock.tick(1); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertFalse('module "a" should not be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| // Retry should happen after a backoff |
| clock.tick(5 + mm.getBackOff_()); |
| |
| assertTrue(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(2); |
| assertTrue(calledBack2); |
| assertFalse(calledBack3); |
| assertTrue('module "b" should be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(2); |
| assertTrue(calledBack3); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| |
| assertNull(error); |
| assertNull(error2); |
| assertNull(error3); |
| } |
| |
| |
| /** |
| * Tests loading multiple modules by requesting a Deferred object when |
| * consecutive server error occur and the loader falls back to serial |
| * loads. |
| */ |
| function testLoadMultipleWithErrorsFallbackOnSerial() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setLoader(createUnsuccessfulLoader(mm, 500)); |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| var calledBack3 = false; |
| var error3 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b', 'c']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error2 = err; |
| }); |
| dMap['c'].addCallback(function(ctx) { |
| calledBack3 = true; |
| }); |
| dMap['c'].addErrback(function(err) { |
| error3 = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| |
| clock.tick(5); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertFalse('module "a" should not be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| // Retry should happen and fail after a backoff |
| clock.tick(5 + mm.getBackOff_()); |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertFalse('module "a" should not be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| // A second retry should happen after a backoff |
| clock.tick(4 + mm.getBackOff_()); |
| // The second retry is now underway using the unsuccessful loader. |
| // We substitute a successful loader for future module load requests. |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| clock.tick(1); |
| |
| // A second retry should fail now |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertFalse('module "a" should not be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| // Each module should be loaded individually now, each taking 5 ticks |
| |
| clock.tick(5); |
| assertTrue(calledBack); |
| assertFalse(calledBack2); |
| assertFalse(calledBack3); |
| assertTrue('module "a" should be loaded', mm.isModuleLoaded('a')); |
| assertFalse('module "b" should not be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(5); |
| assertTrue(calledBack2); |
| assertFalse(calledBack3); |
| assertTrue('module "b" should be loaded', mm.isModuleLoaded('b')); |
| assertFalse('module "c" should not be loaded', mm.isModuleLoaded('c')); |
| |
| clock.tick(5); |
| assertTrue(calledBack3); |
| assertTrue('module "c" should be loaded', mm.isModuleLoaded('c')); |
| |
| assertNull(error); |
| assertNull(error2); |
| assertNull(error3); |
| } |
| |
| |
| /** |
| * Tests loading a module by user action by requesting a Deferred object. |
| */ |
| function testLoadForUser() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| var d = mm.load('a', true); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| d.addErrback(function(err) { |
| error = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertNull(error); |
| assertTrue(mm.isUserActive()); |
| |
| clock.tick(5); |
| |
| assertTrue(calledBack); |
| assertNull(error); |
| } |
| |
| |
| /** |
| * Tests that preloading a module calls back the deferred object. |
| */ |
| function testPreloadDeferredWhenNotLoaded() { |
| var mm = getModuleManager({'a': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| |
| var d = mm.preloadModule('a'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| |
| // First load should take five ticks. |
| assertFalse('module "a" should not be loaded yet', calledBack); |
| clock.tick(5); |
| assertTrue('module "a" should be loaded', calledBack); |
| } |
| |
| |
| /** |
| * Tests preloading an already loaded module. |
| */ |
| function testPreloadDeferredWhenLoaded() { |
| var mm = getModuleManager({'a': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| |
| mm.preloadModule('a'); |
| clock.tick(5); |
| |
| var d = mm.preloadModule('a'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| |
| // Module is already loaded, should be called back after the setTimeout |
| // in preloadModule. |
| assertFalse('deferred for module "a" should not be called yet', calledBack); |
| clock.tick(1); |
| assertTrue('module "a" should be loaded', calledBack); |
| } |
| |
| |
| /** |
| * Tests preloading a module that is currently loading. |
| */ |
| function testPreloadDeferredWhenLoading() { |
| var mm = getModuleManager({'a': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| mm.preloadModule('a'); |
| clock.tick(1); |
| |
| // 'b' is in the middle of loading, should get called back when it's done. |
| var calledBack = false; |
| var d = mm.preloadModule('a'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| |
| assertFalse('module "a" should not be loaded yet', calledBack); |
| clock.tick(4); |
| assertTrue('module "a" should be loaded', calledBack); |
| } |
| |
| |
| /** |
| * Tests that load doesn't trigger another load if a module is already |
| * preloading. |
| */ |
| function testLoadWhenPreloading() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var origSetLoaded = mm.setLoaded; |
| var calls = [0, 0, 0]; |
| mm.beforeLoadModuleCode = function(id) { |
| calls[0]++; |
| }; |
| mm.setLoaded = function(id) { |
| calls[1]++; |
| origSetLoaded.call(mm, id); |
| }; |
| mm.afterLoadModuleCode = function(id) { |
| calls[2]++; |
| }; |
| |
| var calledBack = false; |
| var error = null; |
| |
| mm.preloadModule('c', 2); |
| assertFalse( |
| 'module "c" should not be loading yet', mm.isModuleLoading('c')); |
| clock.tick(2); |
| assertTrue( |
| 'module "c" should now be loading', mm.isModuleLoading('c')); |
| |
| var d = mm.load('c'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| d.addErrback(function(err) { |
| error = err; |
| }); |
| |
| assertTrue( |
| 'module "c" should still be loading', mm.isModuleLoading('c')); |
| clock.tick(5); |
| assertFalse( |
| 'module "c" should be done loading', mm.isModuleLoading('c')); |
| assertEquals( |
| 'beforeLoad should only be called once for "c"', 1, calls[0]); |
| assertEquals( |
| 'setLoaded should only be called once for "c"', 1, calls[1]); |
| assertEquals( |
| 'afterLoad should only be called once for "c"', 1, calls[2]); |
| |
| assertTrue(calledBack); |
| assertNull(error); |
| } |
| |
| |
| /** |
| * Tests that load doesn't trigger another load if a module is already |
| * preloading. |
| */ |
| function testLoadMultipleWhenPreloading() { |
| var mm = getModuleManager({'a': [], 'b': ['d'], 'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| mm.setBatchModeEnabled(true); |
| |
| var origSetLoaded = mm.setLoaded; |
| var calls = {'a': [0, 0, 0], 'b': [0, 0, 0], |
| 'c': [0, 0, 0], 'd': [0, 0, 0]}; |
| mm.beforeLoadModuleCode = function(id) { |
| calls[id][0]++; |
| }; |
| mm.setLoaded = function(id) { |
| calls[id][1]++; |
| origSetLoaded.call(mm, id); |
| }; |
| mm.afterLoadModuleCode = function(id) { |
| calls[id][2]++; |
| }; |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| var calledBack3 = false; |
| var error3 = null; |
| |
| mm.preloadModule('c', 2); |
| mm.preloadModule('d', 3); |
| assertFalse( |
| 'module "c" should not be loading yet', mm.isModuleLoading('c')); |
| assertFalse( |
| 'module "d" should not be loading yet', mm.isModuleLoading('d')); |
| clock.tick(2); |
| assertTrue( |
| 'module "c" should now be loading', mm.isModuleLoading('c')); |
| clock.tick(1); |
| assertTrue( |
| 'module "d" should now be loading', mm.isModuleLoading('d')); |
| |
| var dMap = mm.loadMultiple(['a', 'b', 'c']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error2 = err; |
| }); |
| dMap['c'].addCallback(function(ctx) { |
| calledBack3 = true; |
| }); |
| dMap['c'].addErrback(function(err) { |
| error3 = err; |
| }); |
| |
| assertTrue( |
| 'module "a" should be loading', mm.isModuleLoading('a')); |
| assertTrue( |
| 'module "b" should be loading', mm.isModuleLoading('b')); |
| assertTrue( |
| 'module "c" should still be loading', mm.isModuleLoading('c')); |
| clock.tick(4); |
| assertTrue(calledBack3); |
| |
| assertFalse( |
| 'module "c" should be done loading', mm.isModuleLoading('c')); |
| assertTrue( |
| 'module "d" should still be loading', mm.isModuleLoading('d')); |
| clock.tick(5); |
| assertFalse( |
| 'module "d" should be done loading', mm.isModuleLoading('d')); |
| |
| assertFalse(calledBack); |
| assertFalse(calledBack2); |
| assertTrue( |
| 'module "a" should still be loading', mm.isModuleLoading('a')); |
| assertTrue( |
| 'module "b" should still be loading', mm.isModuleLoading('b')); |
| clock.tick(7); |
| |
| assertTrue(calledBack); |
| assertTrue(calledBack2); |
| assertFalse( |
| 'module "a" should be done loading', mm.isModuleLoading('a')); |
| assertFalse( |
| 'module "b" should be done loading', mm.isModuleLoading('b')); |
| |
| assertEquals( |
| 'beforeLoad should only be called once for "a"', 1, calls['a'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "a"', 1, calls['a'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "a"', 1, calls['a'][2]); |
| assertEquals( |
| 'beforeLoad should only be called once for "b"', 1, calls['b'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "b"', 1, calls['b'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "b"', 1, calls['b'][2]); |
| assertEquals( |
| 'beforeLoad should only be called once for "c"', 1, calls['c'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "c"', 1, calls['c'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "c"', 1, calls['c'][2]); |
| assertEquals( |
| 'beforeLoad should only be called once for "d"', 1, calls['d'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "d"', 1, calls['d'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "d"', 1, calls['d'][2]); |
| |
| assertNull(error); |
| assertNull(error2); |
| assertNull(error3); |
| } |
| |
| |
| /** |
| * Tests that the deferred is still called when loadMultiple loads modules |
| * that are already preloading. |
| */ |
| function testLoadMultipleWhenPreloadingSameModules() { |
| var mm = getModuleManager({'a': [], 'b': ['d'], 'c': [], 'd': []}); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| mm.setBatchModeEnabled(true); |
| |
| var origSetLoaded = mm.setLoaded; |
| var calls = {'c': [0, 0, 0], 'd': [0, 0, 0]}; |
| mm.beforeLoadModuleCode = function(id) { |
| calls[id][0]++; |
| }; |
| mm.setLoaded = function(id) { |
| calls[id][1]++; |
| origSetLoaded.call(mm, id); |
| }; |
| mm.afterLoadModuleCode = function(id) { |
| calls[id][2]++; |
| }; |
| |
| var calledBack = false; |
| var error = null; |
| var calledBack2 = false; |
| var error2 = null; |
| |
| mm.preloadModule('c', 2); |
| mm.preloadModule('d', 3); |
| assertFalse( |
| 'module "c" should not be loading yet', mm.isModuleLoading('c')); |
| assertFalse( |
| 'module "d" should not be loading yet', mm.isModuleLoading('d')); |
| clock.tick(2); |
| assertTrue( |
| 'module "c" should now be loading', mm.isModuleLoading('c')); |
| clock.tick(1); |
| assertTrue( |
| 'module "d" should now be loading', mm.isModuleLoading('d')); |
| |
| var dMap = mm.loadMultiple(['c', 'd']); |
| dMap['c'].addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| dMap['c'].addErrback(function(err) { |
| error = err; |
| }); |
| dMap['d'].addCallback(function(ctx) { |
| calledBack2 = true; |
| }); |
| dMap['d'].addErrback(function(err) { |
| error2 = err; |
| }); |
| |
| assertTrue( |
| 'module "c" should still be loading', mm.isModuleLoading('c')); |
| clock.tick(4); |
| assertFalse( |
| 'module "c" should be done loading', mm.isModuleLoading('c')); |
| assertTrue( |
| 'module "d" should still be loading', mm.isModuleLoading('d')); |
| clock.tick(5); |
| assertFalse( |
| 'module "d" should be done loading', mm.isModuleLoading('d')); |
| |
| assertTrue(calledBack); |
| assertTrue(calledBack2); |
| |
| assertEquals( |
| 'beforeLoad should only be called once for "c"', 1, calls['c'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "c"', 1, calls['c'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "c"', 1, calls['c'][2]); |
| assertEquals( |
| 'beforeLoad should only be called once for "d"', 1, calls['d'][0]); |
| assertEquals( |
| 'setLoaded should only be called once for "d"', 1, calls['d'][1]); |
| assertEquals( |
| 'afterLoad should only be called once for "d"', 1, calls['d'][2]); |
| |
| assertNull(error); |
| assertNull(error2); |
| } |
| |
| |
| /** |
| * Tests loading a module via load when the module is already |
| * loaded. The deferred's callback should be called immediately. |
| */ |
| function testLoadWhenLoaded() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var calledBack = false; |
| var error = null; |
| |
| mm.preloadModule('b', 2); |
| clock.tick(10); |
| |
| assertFalse( |
| 'module "b" should be done loading', mm.isModuleLoading('b')); |
| |
| var d = mm.load('b'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| d.addErrback(function(err) { |
| error = err; |
| }); |
| |
| assertTrue(calledBack); |
| assertNull(error); |
| } |
| |
| |
| /** |
| * Tests that the deferred's errbacks are called if the module fails to load. |
| */ |
| function testLoadWithFailingModule() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 401)); |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| cause); |
| firedLoadFailed = true; |
| }); |
| var calledBack = false; |
| var error = null; |
| |
| var d = mm.load('a'); |
| d.addCallback(function(ctx) { |
| calledBack = true; |
| }); |
| d.addErrback(function(err) { |
| error = err; |
| }); |
| |
| assertFalse(calledBack); |
| assertNull(error); |
| |
| clock.tick(500); |
| |
| assertFalse(calledBack); |
| |
| // NOTE: Deferred always calls errbacks with an Error object. For now the |
| // module manager just passes the FailureType which gets set as the Error |
| // object's message. |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error.message)); |
| } |
| |
| |
| /** |
| * Tests that the deferred's errbacks are called if a module fails to load. |
| */ |
| function testLoadMultipleWithFailingModule() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 401)); |
| mm.setBatchModeEnabled(true); |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| cause); |
| }); |
| var calledBack11 = false; |
| var error11 = null; |
| var calledBack12 = false; |
| var error12 = null; |
| var calledBack21 = false; |
| var error21 = null; |
| var calledBack22 = false; |
| var error22 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack11 = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error11 = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack12 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error12 = err; |
| }); |
| |
| var dMap2 = mm.loadMultiple(['b', 'c']); |
| dMap2['b'].addCallback(function(ctx) { |
| calledBack21 = true; |
| }); |
| dMap2['b'].addErrback(function(err) { |
| error21 = err; |
| }); |
| dMap2['c'].addCallback(function(ctx) { |
| calledBack22 = true; |
| }); |
| dMap2['c'].addErrback(function(err) { |
| error22 = err; |
| }); |
| |
| assertFalse(calledBack11); |
| assertFalse(calledBack12); |
| assertFalse(calledBack21); |
| assertFalse(calledBack22); |
| assertNull(error11); |
| assertNull(error12); |
| assertNull(error21); |
| assertNull(error22); |
| |
| clock.tick(5); |
| |
| assertFalse(calledBack11); |
| assertFalse(calledBack12); |
| assertFalse(calledBack21); |
| assertFalse(calledBack22); |
| |
| // NOTE: Deferred always calls errbacks with an Error object. For now the |
| // module manager just passes the FailureType which gets set as the Error |
| // object's message. |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error11.message)); |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error12.message)); |
| |
| // The first deferred of the second load should be called since it asks for |
| // one of the failed modules. |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error21.message)); |
| |
| // The last deferred should be dropped so it is neither called back nor an |
| // error. |
| assertFalse(calledBack22); |
| assertNull(error22); |
| } |
| |
| |
| /** |
| * Tests that the right dependencies are cancelled on a loadMultiple failure. |
| */ |
| function testLoadMultipleWithFailingModuleDependencies() { |
| var mm = getModuleManager( |
| {'a': [], 'b': [], 'c': ['b'], 'd': ['c'], 'e': []}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 401)); |
| mm.setBatchModeEnabled(true); |
| var cancelledIds = []; |
| |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| cause); |
| cancelledIds.push(id); |
| }); |
| var calledBack11 = false; |
| var error11 = null; |
| var calledBack12 = false; |
| var error12 = null; |
| var calledBack21 = false; |
| var error21 = null; |
| var calledBack22 = false; |
| var error22 = null; |
| var calledBack23 = false; |
| var error23 = null; |
| |
| var dMap = mm.loadMultiple(['a', 'b']); |
| dMap['a'].addCallback(function(ctx) { |
| calledBack11 = true; |
| }); |
| dMap['a'].addErrback(function(err) { |
| error11 = err; |
| }); |
| dMap['b'].addCallback(function(ctx) { |
| calledBack12 = true; |
| }); |
| dMap['b'].addErrback(function(err) { |
| error12 = err; |
| }); |
| |
| var dMap2 = mm.loadMultiple(['c', 'd', 'e']); |
| dMap2['c'].addCallback(function(ctx) { |
| calledBack21 = true; |
| }); |
| dMap2['c'].addErrback(function(err) { |
| error21 = err; |
| }); |
| dMap2['d'].addCallback(function(ctx) { |
| calledBack22 = true; |
| }); |
| dMap2['d'].addErrback(function(err) { |
| error22 = err; |
| }); |
| dMap2['e'].addCallback(function(ctx) { |
| calledBack23 = true; |
| }); |
| dMap2['e'].addErrback(function(err) { |
| error23 = err; |
| }); |
| |
| assertFalse(calledBack11); |
| assertFalse(calledBack12); |
| assertFalse(calledBack21); |
| assertFalse(calledBack22); |
| assertFalse(calledBack23); |
| assertNull(error11); |
| assertNull(error12); |
| assertNull(error21); |
| assertNull(error22); |
| assertNull(error23); |
| |
| clock.tick(5); |
| |
| assertFalse(calledBack11); |
| assertFalse(calledBack12); |
| assertFalse(calledBack21); |
| assertFalse(calledBack22); |
| assertFalse(calledBack23); |
| |
| // NOTE: Deferred always calls errbacks with an Error object. For now the |
| // module manager just passes the FailureType which gets set as the Error |
| // object's message. |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error11.message)); |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| Number(error12.message)); |
| |
| // Check that among the failed modules, 'c' and 'd' are also cancelled |
| // due to dependencies. |
| assertTrue(goog.array.equals(['a', 'b', 'c', 'd'], cancelledIds.sort())); |
| } |
| |
| |
| /** |
| * Tests that when loading multiple modules, the input array is not modified |
| * when it has duplicates. |
| */ |
| function testLoadMultipleWithDuplicates() { |
| var mm = getModuleManager({'a': [], 'b': []}); |
| mm.setBatchModeEnabled(true); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| |
| var listWithDuplicates = ['a', 'a', 'b']; |
| mm.loadMultiple(listWithDuplicates); |
| assertArrayEquals('loadMultiple should not modify its input', |
| ['a', 'a', 'b'], listWithDuplicates); |
| } |
| |
| |
| /** |
| * Test loading dependencies transitively. |
| */ |
| function testLoadingDepsInNonBatchMode1() { |
| var mm = getModuleManager({ |
| 'i': [], |
| 'j': [], |
| 'k': ['j'], |
| 'l': ['i', 'j', 'k']}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| mm.preloadModule('j'); |
| clock.tick(5); |
| assertTrue('module "j" should be loaded', mm.isModuleLoaded('j')); |
| assertFalse( |
| 'module "i" should not be loaded (1)', mm.isModuleLoaded('i')); |
| assertFalse( |
| 'module "k" should not be loaded (1)', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (1)', mm.isModuleLoaded('l')); |
| |
| // When loading a module in non-batch mode, its dependencies should be |
| // requested independently, and in dependency order. |
| mm.preloadModule('l'); |
| clock.tick(5); |
| assertTrue('module "i" should be loaded', mm.isModuleLoaded('i')); |
| assertFalse( |
| 'module "k" should not be loaded (2)', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (2)', mm.isModuleLoaded('l')); |
| clock.tick(5); |
| assertTrue('module "k" should be loaded', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (3)', mm.isModuleLoaded('l')); |
| clock.tick(5); |
| assertTrue( |
| 'module "l" should be loaded', mm.isModuleLoaded('l')); |
| } |
| |
| |
| /** |
| * Test loading dependencies transitively and in dependency order. |
| */ |
| function testLoadingDepsInNonBatchMode2() { |
| var mm = getModuleManager({ |
| 'h': [], |
| 'i': ['h'], |
| 'j': ['i'], |
| 'k': ['j'], |
| 'l': ['i', 'j', 'k'], |
| 'm': ['l']}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| // When loading a module in non-batch mode, its dependencies should be |
| // requested independently, and in dependency order. The order in this |
| // case should be h,i,j,k,l,m. |
| mm.preloadModule('m'); |
| clock.tick(5); |
| assertTrue('module "h" should be loaded', mm.isModuleLoaded('h')); |
| assertFalse( |
| 'module "i" should not be loaded (1)', mm.isModuleLoaded('i')); |
| assertFalse( |
| 'module "j" should not be loaded (1)', mm.isModuleLoaded('j')); |
| assertFalse( |
| 'module "k" should not be loaded (1)', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (1)', mm.isModuleLoaded('l')); |
| assertFalse( |
| 'module "m" should not be loaded (1)', mm.isModuleLoaded('m')); |
| |
| clock.tick(5); |
| assertTrue('module "i" should be loaded', mm.isModuleLoaded('i')); |
| assertFalse( |
| 'module "j" should not be loaded (2)', mm.isModuleLoaded('j')); |
| assertFalse( |
| 'module "k" should not be loaded (2)', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (2)', mm.isModuleLoaded('l')); |
| assertFalse( |
| 'module "m" should not be loaded (2)', mm.isModuleLoaded('m')); |
| |
| clock.tick(5); |
| assertTrue('module "j" should be loaded', mm.isModuleLoaded('j')); |
| assertFalse( |
| 'module "k" should not be loaded (3)', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (3)', mm.isModuleLoaded('l')); |
| assertFalse( |
| 'module "m" should not be loaded (3)', mm.isModuleLoaded('m')); |
| |
| clock.tick(5); |
| assertTrue('module "k" should be loaded', mm.isModuleLoaded('k')); |
| assertFalse( |
| 'module "l" should not be loaded (4)', mm.isModuleLoaded('l')); |
| assertFalse( |
| 'module "m" should not be loaded (4)', mm.isModuleLoaded('m')); |
| |
| clock.tick(5); |
| assertTrue('module "l" should be loaded', mm.isModuleLoaded('l')); |
| assertFalse( |
| 'module "m" should not be loaded (5)', mm.isModuleLoaded('m')); |
| |
| clock.tick(5); |
| assertTrue('module "m" should be loaded', mm.isModuleLoaded('m')); |
| } |
| |
| function testLoadingDepsInBatchMode() { |
| var mm = getModuleManager({ |
| 'e': [], |
| 'f': [], |
| 'g': ['f'], |
| 'h': ['e', 'f', 'g']}); |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| mm.setBatchModeEnabled(true); |
| |
| mm.preloadModule('f'); |
| clock.tick(5); |
| assertTrue('module "f" should be loaded', mm.isModuleLoaded('f')); |
| assertFalse( |
| 'module "e" should not be loaded (1)', mm.isModuleLoaded('e')); |
| assertFalse( |
| 'module "g" should not be loaded (1)', mm.isModuleLoaded('g')); |
| assertFalse( |
| 'module "h" should not be loaded (1)', mm.isModuleLoaded('h')); |
| |
| // When loading a module in batch mode, its not-yet-loaded dependencies |
| // should be requested at the same time, and in dependency order. |
| mm.preloadModule('h'); |
| clock.tick(5); |
| assertTrue('module "e" should be loaded', mm.isModuleLoaded('e')); |
| assertFalse( |
| 'module "g" should not be loaded (2)', mm.isModuleLoaded('g')); |
| assertFalse( |
| 'module "h" should not be loaded (2)', mm.isModuleLoaded('h')); |
| clock.tick(2); |
| assertTrue( |
| 'module "g" should be loaded', mm.isModuleLoaded('g')); |
| assertFalse( |
| 'module "h" should not be loaded (3)', mm.isModuleLoaded('h')); |
| clock.tick(2); |
| assertTrue( |
| 'module "h" should be loaded', mm.isModuleLoaded('h')); |
| } |
| |
| |
| /** |
| * Test unauthorized errors while loading modules. |
| */ |
| function testUnauthorizedLoading() { |
| var mm = getModuleManager({ |
| 'm': [], |
| 'n': [], |
| 'o': ['n']}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 401)); |
| |
| // Callback checks for an unauthorized error |
| var firedLoadFailed = false; |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.UNAUTHORIZED, |
| cause); |
| firedLoadFailed = true; |
| }); |
| mm.execOnLoad('o', function() {}); |
| assertTrue('module "o" should be loading', mm.isModuleLoading('o')); |
| assertTrue('module "n" should be loading', mm.isModuleLoading('n')); |
| clock.tick(5); |
| assertTrue( |
| 'should have called unauthorized module callback', firedLoadFailed); |
| assertFalse( |
| 'module "o" should not be loaded', mm.isModuleLoaded('o')); |
| assertFalse( |
| 'module "o" should not be loading', mm.isModuleLoading('o')); |
| assertFalse( |
| 'module "n" should not be loaded', mm.isModuleLoaded('n')); |
| assertFalse( |
| 'module "n" should not be loading', mm.isModuleLoading('n')); |
| } |
| |
| |
| /** |
| * Test error loading modules which are retried. |
| */ |
| function testErrorLoadingModule() { |
| var mm = getModuleManager({ |
| 'p': ['q'], |
| 'q': [], |
| 'r': ['q', 'p']}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 500)); |
| |
| mm.preloadModule('r'); |
| clock.tick(4); |
| |
| // A module request is now underway using the unsuccessful loader. |
| // We substitute a successful loader for future module load requests. |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| clock.tick(1); |
| assertFalse( |
| 'module "q" should not be loaded (1)', mm.isModuleLoaded('q')); |
| assertFalse( |
| 'module "p" should not be loaded (1)', mm.isModuleLoaded('p')); |
| assertFalse( |
| 'module "r" should not be loaded (1)', mm.isModuleLoaded('r')); |
| |
| // Failed loads are automatically retried after a backOff. |
| clock.tick(5 + mm.getBackOff_()); |
| assertTrue('module "q" should be loaded', mm.isModuleLoaded('q')); |
| assertFalse( |
| 'module "p" should not be loaded (2)', mm.isModuleLoaded('p')); |
| assertFalse( |
| 'module "r" should not be loaded (2)', mm.isModuleLoaded('r')); |
| |
| // A successful load decrements the backOff. |
| clock.tick(5); |
| assertTrue('module "p" should be loaded', mm.isModuleLoaded('p')); |
| assertFalse( |
| 'module "r" should not be loaded (3)', mm.isModuleLoaded('r')); |
| clock.tick(5); |
| assertTrue( |
| 'module "r" should be loaded', mm.isModuleLoaded('r')); |
| } |
| |
| |
| /** |
| * Tests error loading modules which are retried. |
| */ |
| function testErrorLoadingModule_batchMode() { |
| var mm = getModuleManager({ |
| 'p': ['q'], |
| 'q': [], |
| 'r': ['q', 'p']}); |
| mm.setLoader(createUnsuccessfulBatchLoader(mm, 500)); |
| mm.setBatchModeEnabled(true); |
| |
| mm.preloadModule('r'); |
| clock.tick(4); |
| |
| // A module request is now underway using the unsuccessful loader. |
| // We substitute a successful loader for future module load requests. |
| mm.setLoader(createSuccessfulBatchLoader(mm)); |
| clock.tick(1); |
| assertFalse( |
| 'module "q" should not be loaded (1)', mm.isModuleLoaded('q')); |
| assertFalse( |
| 'module "p" should not be loaded (1)', mm.isModuleLoaded('p')); |
| assertFalse( |
| 'module "r" should not be loaded (1)', mm.isModuleLoaded('r')); |
| |
| // Failed loads are automatically retried after a backOff. |
| clock.tick(5 + mm.getBackOff_()); |
| assertTrue('module "q" should be loaded', mm.isModuleLoaded('q')); |
| clock.tick(2); |
| assertTrue( |
| 'module "p" should not be loaded (2)', mm.isModuleLoaded('p')); |
| clock.tick(2); |
| assertTrue( |
| 'module "r" should not be loaded (2)', mm.isModuleLoaded('r')); |
| } |
| |
| |
| /** |
| * Test consecutive errors in loading modules. |
| */ |
| function testConsecutiveErrors() { |
| var mm = getModuleManager({'s': []}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 500)); |
| |
| // Register an error callback for consecutive failures. |
| var firedLoadFailed = false; |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.CONSECUTIVE_FAILURES, |
| cause); |
| firedLoadFailed = true; |
| }); |
| |
| mm.preloadModule('s'); |
| assertFalse( |
| 'module "s" should not be loaded (0)', mm.isModuleLoaded('s')); |
| |
| // Fail twice. |
| for (var i = 0; i < 2; i++) { |
| clock.tick(5 + mm.getBackOff_()); |
| assertFalse( |
| 'module "s" should not be loaded (1)', mm.isModuleLoaded('s')); |
| assertFalse( |
| 'should not fire failed callback (1)', firedLoadFailed); |
| } |
| |
| // Fail a third time and check that the callback is fired. |
| clock.tick(5 + mm.getBackOff_()); |
| assertFalse( |
| 'module "s" should not be loaded (2)', mm.isModuleLoaded('s')); |
| assertTrue( |
| 'should have fired failed callback', firedLoadFailed); |
| |
| // Check that it doesn't attempt to load the module anymore after it has |
| // failed. |
| var triedLoad = false; |
| mm.setLoader({ |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn) { |
| triedLoad = true; |
| }}); |
| |
| // Also reset the failed callback flag and make sure it isn't called |
| // again. |
| firedLoadFailed = false; |
| clock.tick(10 + mm.getBackOff_()); |
| assertFalse( |
| 'module "s" should not be loaded (3)', mm.isModuleLoaded('s')); |
| assertFalse('No more loads should have been tried', triedLoad); |
| assertFalse('The load failed callback should be fired only once', |
| firedLoadFailed); |
| } |
| |
| |
| /** |
| * Test loading errors due to old code. |
| */ |
| function testOldCodeGoneError() { |
| var mm = getModuleManager({'s': []}); |
| mm.setLoader(createUnsuccessfulLoader(mm, 410)); |
| |
| // Callback checks for an old code failure |
| var firedLoadFailed = false; |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.OLD_CODE_GONE, |
| cause); |
| firedLoadFailed = true; |
| }); |
| |
| mm.preloadModule('s', 0); |
| assertFalse( |
| 'module "s" should not be loaded (0)', mm.isModuleLoaded('s')); |
| clock.tick(5); |
| assertFalse( |
| 'module "s" should not be loaded (1)', mm.isModuleLoaded('s')); |
| assertTrue( |
| 'should have called old code gone callback', firedLoadFailed); |
| } |
| |
| |
| /** |
| * Test timeout. |
| */ |
| function testTimeout() { |
| var mm = getModuleManager({'s': []}); |
| mm.setLoader(createTimeoutLoader(mm)); |
| |
| // Callback checks for timeout |
| var firedTimeout = false; |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| function(callbackType, id, cause) { |
| assertEquals('Failure cause was not as expected', |
| goog.module.ModuleManager.FailureType.TIMEOUT, |
| cause); |
| firedTimeout = true; |
| }); |
| |
| mm.preloadModule('s', 0); |
| assertFalse( |
| 'module "s" should not be loaded (0)', mm.isModuleLoaded('s')); |
| clock.tick(5); |
| assertFalse( |
| 'module "s" should not be loaded (1)', mm.isModuleLoaded('s')); |
| assertTrue( |
| 'should have called timeout callback', firedTimeout); |
| } |
| |
| |
| /** |
| * Tests that an error during execOnLoad will trigger the error callback. |
| */ |
| function testExecOnLoadError() { |
| // Expect two callbacks, each of which will be called with callback type |
| // ERROR, the right module id and failure type INIT_ERROR. |
| var errorCallback1 = goog.testing.createFunctionMock('callback1'); |
| errorCallback1(goog.module.ModuleManager.CallbackType.ERROR, 'b', |
| goog.module.ModuleManager.FailureType.INIT_ERROR); |
| |
| var errorCallback2 = goog.testing.createFunctionMock('callback2'); |
| errorCallback2(goog.module.ModuleManager.CallbackType.ERROR, 'b', |
| goog.module.ModuleManager.FailureType.INIT_ERROR); |
| |
| errorCallback1.$replay(); |
| errorCallback2.$replay(); |
| |
| var mm = new goog.module.ModuleManager(); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| // Register the first callback before setting the module info map. |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| errorCallback1); |
| |
| mm.setAllModuleInfo({'a': [], 'b': [], 'c': []}); |
| |
| // Register the second callback after setting the module info map. |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| errorCallback2); |
| |
| var execOnLoadBCalled = false; |
| mm.execOnLoad('b', function() { |
| execOnLoadBCalled = true; |
| throw new Error(); |
| }); |
| |
| assertThrows(function() { |
| clock.tick(5); |
| }); |
| |
| assertTrue('execOnLoad should have been called on module b.', |
| execOnLoadBCalled); |
| errorCallback1.$verify(); |
| errorCallback2.$verify(); |
| } |
| |
| |
| /** |
| * Tests that an error during execOnLoad will trigger the error callback. |
| * Uses setAllModuleInfoString rather than setAllModuleInfo. |
| */ |
| function testExecOnLoadErrorModuleInfoString() { |
| // Expect a callback to be called with callback type ERROR, the right module |
| // id and failure type INIT_ERROR. |
| var errorCallback = goog.testing.createFunctionMock('callback'); |
| errorCallback(goog.module.ModuleManager.CallbackType.ERROR, 'b', |
| goog.module.ModuleManager.FailureType.INIT_ERROR); |
| |
| errorCallback.$replay(); |
| |
| var mm = new goog.module.ModuleManager(); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| // Register the first callback before setting the module info map. |
| mm.registerCallback(goog.module.ModuleManager.CallbackType.ERROR, |
| errorCallback); |
| |
| mm.setAllModuleInfoString('a/b/c'); |
| |
| var execOnLoadBCalled = false; |
| mm.execOnLoad('b', function() { |
| execOnLoadBCalled = true; |
| throw new Error(); |
| }); |
| |
| assertThrows(function() { |
| clock.tick(5); |
| }); |
| |
| assertTrue('execOnLoad should have been called on module b.', |
| execOnLoadBCalled); |
| errorCallback.$verify(); |
| } |
| |
| |
| /** |
| * Make sure ModuleInfo objects in moduleInfoMap_ get disposed. |
| */ |
| function testDispose() { |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| |
| var moduleInfoA = mm.getModuleInfo('a'); |
| assertNotNull(moduleInfoA); |
| var moduleInfoB = mm.getModuleInfo('b'); |
| assertNotNull(moduleInfoB); |
| var moduleInfoC = mm.getModuleInfo('c'); |
| assertNotNull(moduleInfoC); |
| |
| mm.dispose(); |
| assertTrue(moduleInfoA.isDisposed()); |
| assertTrue(moduleInfoB.isDisposed()); |
| assertTrue(moduleInfoC.isDisposed()); |
| } |
| |
| function testDependencyOrderingWithSimpleDeps() { |
| var mm = getModuleManager({ |
| 'a': ['b', 'c'], |
| 'b': ['d'], |
| 'c': ['e', 'f'], |
| 'd': [], |
| 'e': [], |
| 'f': [] |
| }); |
| var ids = mm.getNotYetLoadedTransitiveDepIds_('a'); |
| assertDependencyOrder(ids, mm); |
| assertArrayEquals(['d', 'e', 'f', 'b', 'c', 'a'], ids); |
| } |
| |
| function testDependencyOrderingWithCommonDepsInDeps() { |
| // Tests to make sure that if dependencies of the root are loaded before |
| // their common dependencies. |
| var mm = getModuleManager({ |
| 'a': ['b', 'c'], |
| 'b': ['d'], |
| 'c': ['d'], |
| 'd': [] |
| }); |
| var ids = mm.getNotYetLoadedTransitiveDepIds_('a'); |
| assertDependencyOrder(ids, mm); |
| assertArrayEquals(['d', 'b', 'c', 'a'], ids); |
| } |
| |
| function testDependencyOrderingWithCommonDepsInRoot1() { |
| // Tests the case where a dependency of the root depends on another |
| // dependency of the root. Irregardless of ordering in the root's |
| // deps. |
| var mm = getModuleManager({ |
| 'a': ['b', 'c'], |
| 'b': ['c'], |
| 'c': [] |
| }); |
| var ids = mm.getNotYetLoadedTransitiveDepIds_('a'); |
| assertDependencyOrder(ids, mm); |
| assertArrayEquals(['c', 'b', 'a'], ids); |
| } |
| |
| function testDependencyOrderingWithCommonDepsInRoot2() { |
| // Tests the case where a dependency of the root depends on another |
| // dependency of the root. Irregardless of ordering in the root's |
| // deps. |
| var mm = getModuleManager({ |
| 'a': ['b', 'c'], |
| 'b': [], |
| 'c': ['b'] |
| }); |
| var ids = mm.getNotYetLoadedTransitiveDepIds_('a'); |
| assertDependencyOrder(ids, mm); |
| assertArrayEquals(['b', 'c', 'a'], ids); |
| } |
| |
| function testDependencyOrderingWithGmailExample() { |
| // Real dependency graph taken from gmail. |
| var mm = getModuleManager({ |
| 's': ['dp', 'ml', 'md'], |
| 'dp': ['a'], |
| 'ml': ['ld', 'm'], |
| 'ld': ['a'], |
| 'm': ['ad', 'mh', 'n'], |
| 'md': ['mh', 'ld'], |
| 'a': [], |
| 'mh': [], |
| 'ad': [], |
| 'n': [] |
| }); |
| |
| mm.setLoaded('a'); |
| mm.setLoaded('m'); |
| mm.setLoaded('n'); |
| mm.setLoaded('ad'); |
| mm.setLoaded('mh'); |
| |
| var ids = mm.getNotYetLoadedTransitiveDepIds_('s'); |
| assertDependencyOrder(ids, mm); |
| assertArrayEquals(['ld', 'dp', 'ml', 'md', 's'], ids); |
| } |
| |
| function assertDependencyOrder(list, mm) { |
| var seen = {}; |
| for (var i = 0; i < list.length; i++) { |
| var id = list[i]; |
| seen[id] = true; |
| var deps = mm.getModuleInfo(id).getDependencies(); |
| for (var j = 0; j < deps.length; j++) { |
| var dep = deps[j]; |
| assertTrue('Unresolved dependency [' + dep + '] for [' + id + '].', |
| seen[dep] || mm.getModuleInfo(dep).isLoaded()); |
| } |
| } |
| } |
| |
| function testRegisterInitializationCallback() { |
| var initCalled = 0; |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| mm.setLoader(createSuccessfulNonBatchLoaderWithRegisterInitCallback(mm, |
| function() { |
| ++initCalled; |
| })); |
| execOnLoad_(mm); |
| // execOnLoad_ loads modules a and c |
| assertTrue(initCalled == 2); |
| } |
| |
| function createSuccessfulNonBatchLoaderWithRegisterInitCallback( |
| moduleMgr, fn) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| moduleMgr.beforeLoadModuleCode(ids[0]); |
| moduleMgr.registerInitializationCallback(fn); |
| setTimeout(function() { |
| moduleMgr.setLoaded(ids[0]); |
| moduleMgr.afterLoadModuleCode(ids[0]); |
| if (opt_successFn) { |
| opt_successFn(); |
| } |
| }, 5); |
| }}; |
| } |
| |
| function testSetModuleConstructor() { |
| var initCalled = 0; |
| var mm = getModuleManager({'a': [], 'b': [], 'c': []}); |
| var info = { |
| 'a': { ctor: AModule, count: 0 }, |
| 'b': { ctor: BModule, count: 0 }, |
| 'c': { ctor: CModule, count: 0 } |
| }; |
| function AModule() { |
| ++info['a'].count; |
| goog.module.BaseModule.call(this); |
| } |
| goog.inherits(AModule, goog.module.BaseModule); |
| function BModule() { |
| ++info['b'].count; |
| goog.module.BaseModule.call(this); |
| } |
| goog.inherits(BModule, goog.module.BaseModule); |
| function CModule() { |
| ++info['c'].count; |
| goog.module.BaseModule.call(this); |
| } |
| goog.inherits(CModule, goog.module.BaseModule); |
| |
| mm.setLoader(createSuccessfulNonBatchLoaderWithConstructor(mm, info)); |
| execOnLoad_(mm); |
| assertTrue(info['a'].count == 1); |
| assertTrue(info['b'].count == 0); |
| assertTrue(info['c'].count == 1); |
| assertTrue(mm.getModuleInfo('a').getModule() instanceof AModule); |
| assertTrue(mm.getModuleInfo('c').getModule() instanceof CModule); |
| } |
| |
| |
| /** |
| * Tests that a call to load the loading module during module initialization |
| * doesn't trigger a second load. |
| */ |
| function testLoadWhenInitializing() { |
| var mm = getModuleManager({'a': []}); |
| mm.setLoader(createSuccessfulNonBatchLoader(mm)); |
| |
| var info = { |
| 'a': { ctor: AModule, count: 0 } |
| }; |
| function AModule() { |
| ++info['a'].count; |
| goog.module.BaseModule.call(this); |
| } |
| goog.inherits(AModule, goog.module.BaseModule); |
| AModule.prototype.initialize = function() { |
| mm.load('a'); |
| }; |
| mm.setLoader(createSuccessfulNonBatchLoaderWithConstructor(mm, info)); |
| mm.preloadModule('a'); |
| clock.tick(5); |
| assertEquals(info['a'].count, 1); |
| } |
| |
| function testErrorInEarlyCallback() { |
| var errback = goog.testing.recordFunction(); |
| var callback = goog.testing.recordFunction(); |
| var mm = getModuleManager({'a': [], 'b': ['a']}); |
| mm.getModuleInfo('a').registerEarlyCallback(goog.functions.error('error')); |
| mm.getModuleInfo('a').registerCallback(callback); |
| mm.getModuleInfo('a').registerErrback(errback); |
| |
| mm.setLoader(createSuccessfulNonBatchLoaderWithConstructor( |
| mm, createModulesFor('a', 'b'))); |
| mm.preloadModule('b'); |
| var e = assertThrows(function() { |
| clock.tick(5); |
| }); |
| |
| assertEquals('error', e.message); |
| assertEquals(0, callback.getCallCount()); |
| assertEquals(1, errback.getCallCount()); |
| assertEquals(goog.module.ModuleManager.FailureType.INIT_ERROR, |
| errback.getLastCall().getArguments()[0]); |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| assertFalse(mm.getModuleInfo('b').isLoaded()); |
| |
| clock.tick(5); |
| assertTrue(mm.getModuleInfo('b').isLoaded()); |
| } |
| |
| function testErrorInNormalCallback() { |
| var earlyCallback = goog.testing.recordFunction(); |
| var errback = goog.testing.recordFunction(); |
| var mm = getModuleManager({'a': [], 'b': ['a']}); |
| mm.getModuleInfo('a').registerEarlyCallback(earlyCallback); |
| mm.getModuleInfo('a').registerEarlyCallback(goog.functions.error('error')); |
| mm.getModuleInfo('a').registerErrback(errback); |
| |
| mm.setLoader(createSuccessfulNonBatchLoaderWithConstructor( |
| mm, createModulesFor('a', 'b'))); |
| mm.preloadModule('b'); |
| var e = assertThrows(function() { |
| clock.tick(10); |
| }); |
| clock.tick(10); |
| |
| assertEquals('error', e.message); |
| assertEquals(1, errback.getCallCount()); |
| assertEquals(goog.module.ModuleManager.FailureType.INIT_ERROR, |
| errback.getLastCall().getArguments()[0]); |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| assertTrue(mm.getModuleInfo('b').isLoaded()); |
| } |
| |
| function testErrorInErrback() { |
| var mm = getModuleManager({'a': [], 'b': ['a']}); |
| mm.getModuleInfo('a').registerCallback(goog.functions.error('error1')); |
| mm.getModuleInfo('a').registerErrback(goog.functions.error('error2')); |
| |
| mm.setLoader(createSuccessfulNonBatchLoaderWithConstructor( |
| mm, createModulesFor('a', 'b'))); |
| mm.preloadModule('a'); |
| var e = assertThrows(function() { |
| clock.tick(10); |
| }); |
| assertEquals('error1', e.message); |
| var e = assertThrows(function() { |
| clock.tick(10); |
| }); |
| assertEquals('error2', e.message); |
| assertTrue(mm.getModuleInfo('a').isLoaded()); |
| } |
| |
| function createModulesFor(var_args) { |
| var result = {}; |
| for (var i = 0; i < arguments.length; i++) { |
| var key = arguments[i]; |
| result[key] = {ctor: goog.module.BaseModule}; |
| } |
| return result; |
| } |
| |
| function createSuccessfulNonBatchLoaderWithConstructor(moduleMgr, info) { |
| return { |
| loadModules: function(ids, moduleInfoMap, opt_successFn, opt_errFn, |
| opt_timeoutFn) { |
| setTimeout(function() { |
| moduleMgr.beforeLoadModuleCode(ids[0]); |
| moduleMgr.setModuleConstructor(info[ids[0]].ctor); |
| moduleMgr.setLoaded(ids[0]); |
| moduleMgr.afterLoadModuleCode(ids[0]); |
| if (opt_successFn) { |
| opt_successFn(); |
| } |
| }, 5); |
| }}; |
| } |
| |
| function testInitCallbackInBaseModule() { |
| var mm = new goog.module.ModuleManager(); |
| var called = false; |
| var context; |
| mm.registerInitializationCallback(function(mcontext) { |
| called = true; |
| context = mcontext; |
| }); |
| mm.setAllModuleInfo({'a': [], 'b': ['a']}); |
| assertTrue('Base initialization not called', called); |
| assertNull('Context should still be null', context); |
| |
| var mm = new goog.module.ModuleManager(); |
| called = false; |
| mm.registerInitializationCallback(function(mcontext) { |
| called = true; |
| context = mcontext; |
| }); |
| var appContext = {}; |
| mm.setModuleContext(appContext); |
| assertTrue('Base initialization not called after setModuleContext', called); |
| assertEquals('Did not receive module context', appContext, context); |
| } |
| |
| function testSetAllModuleInfoString() { |
| var info = 'base/one:0/two:0/three:0,1,2/four:0,3/five:'; |
| var mm = new goog.module.ModuleManager(); |
| mm.setAllModuleInfoString(info); |
| |
| assertNotNull('Base should exist', mm.getModuleInfo('base')); |
| assertNotNull('One should exist', mm.getModuleInfo('one')); |
| assertNotNull('Two should exist', mm.getModuleInfo('two')); |
| assertNotNull('Three should exist', mm.getModuleInfo('three')); |
| assertNotNull('Four should exist', mm.getModuleInfo('four')); |
| assertNotNull('Five should exist', mm.getModuleInfo('five')); |
| |
| assertArrayEquals(['base', 'one', 'two'], |
| mm.getModuleInfo('three').getDependencies()); |
| assertArrayEquals(['base', 'three'], |
| mm.getModuleInfo('four').getDependencies()); |
| assertArrayEquals([], |
| mm.getModuleInfo('five').getDependencies()); |
| } |
| |
| function testSetAllModuleInfoStringWithEmptyString() { |
| var mm = new goog.module.ModuleManager(); |
| var called = false; |
| var context; |
| mm.registerInitializationCallback(function(mcontext) { |
| called = true; |
| context = mcontext; |
| }); |
| mm.setAllModuleInfoString(''); |
| assertTrue('Initialization not called', called); |
| } |
| |
| function testBackOffAmounts() { |
| var mm = new goog.module.ModuleManager(); |
| assertEquals(0, mm.getBackOff_()); |
| |
| mm.consecutiveFailures_++; |
| assertEquals(5000, mm.getBackOff_()); |
| |
| mm.consecutiveFailures_++; |
| assertEquals(20000, mm.getBackOff_()); |
| } |
| |
| |
| /** |
| * Tests that the IDLE callbacks are executed for active->idle transitions |
| * after setAllModuleInfoString with currently loading modules. |
| */ |
| function testIdleCallbackWithInitialModules() { |
| var callback = goog.testing.recordFunction(); |
| |
| var mm = new goog.module.ModuleManager(); |
| mm.setAllModuleInfoString('a', ['a']); |
| mm.registerCallback( |
| goog.module.ModuleManager.CallbackType.IDLE, callback); |
| |
| assertTrue(mm.isActive()); |
| |
| mm.beforeLoadModuleCode('a'); |
| |
| assertEquals(0, callback.getCallCount()); |
| |
| mm.setLoaded('a'); |
| mm.afterLoadModuleCode('a'); |
| |
| assertFalse(mm.isActive()); |
| |
| assertEquals(1, callback.getCallCount()); |
| } |