2201 lines
64 KiB
JavaScript
2201 lines
64 KiB
JavaScript
// 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');
|
|
goog.require('goog.userAgent');
|
|
|
|
var clock;
|
|
var requestCount = 0;
|
|
|
|
function setUpPage() {
|
|
clock = new goog.testing.MockClock(true);
|
|
}
|
|
|
|
function tearDownPage() {
|
|
clock.dispose();
|
|
}
|
|
|
|
function setUp() {
|
|
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();
|
|
});
|
|
|
|
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();
|
|
});
|
|
|
|
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');
|
|
clock.tick(5);
|
|
|
|
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');
|
|
clock.tick(10);
|
|
|
|
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);
|
|
});
|
|
assertContains('Module errback failure', e.message);
|
|
if (!goog.userAgent.IE) {
|
|
assertContains('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());
|
|
}
|