// 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. Regardless 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. Regardless 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()); }