raptor
Version:
RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.
903 lines (779 loc) • 37.4 kB
JavaScript
require('./_helper.js');
var raptor = require('raptor');
var define = raptor.createDefine(module);
xdescribe('optimizer module', function() {
"use strict";
var logger = require('raptor/logging').logger('raptor-optimizer-spec'),
forEachEntry = raptor.forEachEntry,
forEach = raptor.forEach,
compileAndRender = helpers.templating.compileAndRender,
compileAndRenderAsync = helpers.templating.compileAndRenderAsync,
testOptimizer = function(config) {
var enabledExtensions = require('raptor/packaging').createExtensionCollection(config.enabledExtensions);
var optimizer = require('raptor/optimizer');
var pageIncludes = config.pageIncludes;
var done = config.done;
if (!done) {
throw new Error('done argument is required');
}
var packageManifest = require('raptor/packaging').createPackageManifest();
packageManifest.setDependencies(pageIncludes);
logger.debug("--------------------");
logger.debug('Begin optimizer test for page "' + config.pageName + '":');
var BundleMappings = require('raptor/optimizer/BundleMappings');
var PageBundles = require('raptor/optimizer/PageBundles');
function onError(e) {
done(e);
}
//Get the page dependencies
function checkPageBundles(pageBundles) {
var includesByKey = {},
duplicates = [];
var actualBundlesBySlot = {},
includeCountsByBundle = {};
var bundleToString = function(bundle, indent) {
var code = bundle.readCode();
if (code) {
code = code.replace(/[\n]/g, '\\n');
}
return indent + "name: " + bundle.getName() + "\n" + indent + "slot: " + bundle.getSlot() + "\n" + indent + "contentType: " + bundle.getContentType() + "\n" + indent + "code: " + code + "\n" + indent + "checksum: " + bundle.calculateChecksum();
};
pageBundles.forEachBundle(function(bundle) {
var actualBundles = actualBundlesBySlot[bundle.getSlot()];
if (!actualBundles) {
actualBundles = actualBundlesBySlot[bundle.getSlot()] = [];
}
actualBundles.push(bundle);
logger.debug("Bundle for " + config.pageName + ":\n"+ bundleToString(bundle, " "));
bundle.forEachDependency(function(include) {
var key = include.getKey();
if (includesByKey[key]) {
duplicates.push(include);
}
else {
includesByKey[key] = true;
}
var includeCount = includeCountsByBundle[bundle.name];
if (!includeCount) {
includeCountsByBundle[bundle.name] = 0;
}
});
});
var compareBundle = function(actual, expected) {
if (expected.name) {
expect(expected.name).toEqual(actual.getName());
}
if (expected.slot) {
expect(expected.slot).toEqual(actual.getSlot());
}
if (expected.contentType) {
expect(expected.contentType).toEqual(actual.getContentType());
}
if (expected.code) {
expect(expected.code).toEqual(actual.readCode());
}
};
if (config.expectedAsyncRequires) {
var asyncRequires = [];
pageBundles.forEachAsyncRequire(function(asyncRequire) {
asyncRequires.push(asyncRequire);
});
expect(Object.keys(config.expectedAsyncRequires).length).toEqual(asyncRequires.length);
forEach(asyncRequires, function(asyncRequire) {
logger.debug("\nAsync require " + config.pageName + ":\n name: " + asyncRequire.getName() + "\n requires: [" + asyncRequire.getRequires().join(", ") + "]");
forEach(asyncRequire.getBundles(), function(bundle) {
logger.debug(" Bundle:\n" + bundleToString(bundle, " "));
});
var expectedAsyncRequire = config.expectedAsyncRequires[asyncRequire.getName()];
expect(expectedAsyncRequire).toNotEqual(null);
if (expectedAsyncRequire) {
if (expectedAsyncRequire.bundles) {
var actualBundles = asyncRequire.getBundles();
forEach(actualBundles, function(actualBundle, i) {
var expectedBundle = expectedAsyncRequire.bundles[i] || {};
compareBundle(actualBundle, expectedBundle);
});
}
if (expectedAsyncRequire.requires) {
var actualRequires = asyncRequire.getRequires();
forEach(actualRequires, function(actualRequire, i) {
var expectedRequire = expectedAsyncRequire.requires[i];
expect(expectedRequire).toEqual(actualRequire);
});
}
}
});
}
if (config.expectedBundles) {
forEachEntry(config.expectedBundles, function(slot, expectedBundles) {
var actualBundles = actualBundlesBySlot[slot] || [];
expect(actualBundles.length).toEqual(expectedBundles.length);
if (actualBundles.length === expectedBundles.length) {
forEach(expectedBundles, function(expectedBundle, i) {
var actualBundle = actualBundles[i];
compareBundle(actualBundle, expectedBundle);
}, this);
}
});
}
expect(duplicates).toEqualArray([]);
forEach(config.expectedMappings, function(expected) {
optimizer.forEachDependency({
dependencies: expected.include,
enabledExtensions: config.enabledExtensions,
handleDependency: function(dependency) {
if (!dependency.isPackageDependency()) {
var targetBundle = pageBundles.getBundleMappings().getBundleForDependency(dependency),
targetBundleName = targetBundle ? targetBundle.getName() : undefined;
if (!targetBundleName && expected.toBundle) {
targetBundleName = "(no bundle for " + dependency.toString() + ")";
}
expect(targetBundleName).toEqual(expected.toBundle);
}
},
thisObj: this
});
});
if (config.expectedBundleCount) {
expect(pageBundles.getBundleCount()).toEqual(config.expectedBundleCount);
}
if (config.test) {
config.test(pageBundles);
}
if (config.success) {
config.success(pageBundles);
}
done();
}
function buildPageBundles(bundleMappings) {
var pageBundles = new PageBundles({
pageName: config.pageName,
inPlaceDeploymentEnabled: false,
bundlingEnabled: true,
sourceUrlResolver: null,
enabledExtensions: enabledExtensions,
packageManifest: packageManifest,
bundleMappings: bundleMappings
});
pageBundles.build().then(checkPageBundles, onError);
}
//Create the bundle mappings from the bundle set
var bundleMappings = new BundleMappings(config.enabledExtensions);
var promiseChain = null;
forEach(config.bundleSet, function(bundleConfig) {
var bundleName = bundleConfig.name;
if (!promiseChain) {
promiseChain = bundleMappings.addDependenciesToBundle(bundleConfig.includes, bundleName);
}
else {
promiseChain = promiseChain.then(function() {
return bundleMappings.addDependenciesToBundle(bundleConfig.includes, bundleName);
}, onError);
}
});
if (promiseChain) {
promiseChain.then(function() {
buildPageBundles(bundleMappings);
})
}
else {
buildPageBundles(bundleMappings);
}
};
it('should handle de-duplication correctly', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.moduleA" },
{ "module": "test.optimizer.moduleB" }]
},
{
name: "bundleB",
includes: [{ "module": "test.optimizer.moduleB" },
{ "module": "test.optimizer.moduleC" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageA",
pageIncludes: [{ "module": "test.optimizer.moduleA" },
{ "module": "test.optimizer.moduleB" },
{ "module": "test.optimizer.moduleC" }],
expectedMappings: [{
include: { "module": "test.optimizer.moduleA" },
toBundle: "bundleA"
},
{
include: { "module": "test.optimizer.moduleB" },
toBundle: "bundleA"
},
{
include: { "module": "test.optimizer.moduleC" },
toBundle: "bundleB"
}],
expectedBundles: {
"body": [
{
name: "bundleA",
contentType: "application/javascript",
code: "moduleA\nmoduleB"
},
{
name: "bundleB",
contentType: "application/javascript",
code: "moduleC"
}
]
},
done: done
});
});
it('should handle page dependencies correctly', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.moduleA" }]
},
{
name: "bundleB",
includes: [{ "module": "test.optimizer.moduleB" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageB",
pageIncludes: [{ "module": "test.optimizer.moduleA" },
{ "module": "test.optimizer.moduleB" },
{ "module": "test.optimizer.moduleC" }],
expectedMappings: [{
include: { "module": "test.optimizer.moduleA" },
toBundle: "bundleA"
},
{
include: { "module": "test.optimizer.moduleB" },
toBundle: "bundleB"
},
{
include: { "module": "test.optimizer.moduleC" },
toBundle: "pageB"
}],
expectedBundles: {
"body": [
{
name: "bundleA",
contentType: "application/javascript",
code: "moduleA"
},
{
name: "bundleB",
contentType: "application/javascript",
code: "moduleB"
},
{
name: "pageB",
contentType: "application/javascript",
code: "moduleC"
}
]
},
done: done
});
});
it('should allow slot for resource to be overridden', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.slotA" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageC",
pageIncludes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.slotA" }],
expectedBundles: {
"head": [
{
name: "bundleA",
contentType: "text/css",
code: "mixedA_css"
},
{
name: "bundleA",
contentType: "application/javascript",
code: "slotA_js"
}
],
"body": [
{
name: "bundleA",
contentType: "text/css",
code: "slotA_css"
},
{
name: "bundleA",
contentType: "application/javascript",
code: "mixedA_js"
}
]
},
done: done
});
});
it('should allow custom slots for resources', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.slotB" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageD",
pageIncludes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.slotB" }],
expectedBundles: {
"head": [
{
name: "bundleA",
contentType: "text/css",
code: "mixedA_css"
}
],
"custom-head": [
{
name: "bundleA",
contentType: "text/css",
code: "slotB_css"
}
],
"body": [
{
name: "bundleA",
contentType: "application/javascript",
code: "mixedA_js"
}
],
"custom-body": [
{
name: "bundleA",
contentType: "application/javascript",
code: "slotB_js"
}
]
},
done: done
});
});
it('should allow asynchronous modules', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.nestedA" }]
},
{
name: "bundleB",
includes: [{ "module": "test.optimizer.asyncA" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageE",
pageIncludes: [{ "module": "test.optimizer.asyncA" }],
expectedBundles: {
"head": [
{
name: "bundleB",
contentType: "text/css",
code: "asyncA_css"
}
],
"body": [
{
name: "pageE",
contentType: "application/javascript",
code: "moduleA"
},
{
name: "bundleB",
contentType: "application/javascript",
code: "asyncA_js"
}
]
},
expectedAsyncRequires: {
"test.optimizer.mixedA": {
requires: [],
bundles: [
{
name: "bundleA",
slot: "body",
contentType: "application/javascript",
code: "mixedA_js\nnestedA_js"
},
{
name: "bundleA",
slot: "head",
contentType: "text/css",
code: "mixedA_css\nnestedA_css"
}
]
},
"test.optimizer.nestedA": {
requires: ["test.optimizer.nestedB"],
bundles: [
{
name: "bundleA",
slot: "body",
contentType: "application/javascript",
code: "mixedA_js\nnestedA_js"
},
{
name: "bundleA",
slot: "head",
contentType: "text/css",
code: "mixedA_css\nnestedA_css"
}
]
},
"test.optimizer.nestedB": {
requires: [],
bundles: [
{
name: "pageE-async",
slot: "body",
contentType: "application/javascript",
code: "nestedB_js"
},
{
name: "pageE-async",
slot: "head",
contentType: "text/css",
code: "nestedB_css"
}
]
}
},
done: done
});
});
it('should allow page dependencies to be written to disk', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.mixedA" },
{ "module": "test.optimizer.nestedA" }]
},
{
name: "bundleB",
includes: [{ "module": "test.optimizer.asyncA" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "pageE",
pageIncludes: [{ "module": "test.optimizer.asyncA" },
{ type: "loader-metadata" }],
expectedBundles: {
"head": [
{
name: "bundleB",
contentType: "text/css",
code: "asyncA_css"
}
],
"body": [
{
name: "pageE",
contentType: "application/javascript",
code: "moduleA"
},
{
name: "bundleB",
contentType: "application/javascript",
code: "asyncA_js"
}
]
},
expectedAsyncRequires: {
"test.optimizer.mixedA": {
requires: [],
bundles: [
{
name: "bundleA",
slot: "body",
contentType: "application/javascript",
code: "mixedA_js\nnestedA_js"
},
{
name: "bundleA",
slot: "head",
contentType: "text/css",
code: "mixedA_css\nnestedA_css"
}
]
},
"test.optimizer.nestedA": {
requires: ["test.optimizer.nestedB"],
bundles: [
{
name: "bundleA",
slot: "body",
contentType: "application/javascript",
code: "mixedA_js\nnestedA_js"
},
{
name: "bundleA",
slot: "head",
contentType: "text/css",
code: "mixedA_css\nnestedA_css"
}
]
},
"test.optimizer.nestedB": {
requires: [],
bundles: [
{
name: "pageE-async",
slot: "body",
contentType: "application/javascript",
code: "nestedB_js"
},
{
name: "pageE-async",
slot: "head",
contentType: "text/css",
code: "nestedB_css"
}
]
}
},
done: done,
success: function(pageBundles) {
var Config = require('raptor/optimizer/Config');
var config = new Config();
config.setOutputDir("/some/dir/static");
var BundleFileWriter = require('raptor/optimizer/BundleFileWriter');
var BundleUrlBuilder = require('raptor/optimizer/BundleUrlBuilder');
var urlBuilder = new BundleUrlBuilder("http://localhost:8080/static/");
var writer = new BundleFileWriter(config, urlBuilder);
var writtenFiles = {};
writer.writeBundleFile = function(outputFile, code) {
var outputPath = outputFile.getAbsolutePath();
logger.debug('Writing bundle file "' + outputPath + '" to disk. Code: ' + code);
writtenFiles[outputPath] = code;
};
var optimizedPage = writer.writePageBundles(pageBundles);
var htmlBySlot = optimizedPage.getHtmlBySlot();
expect(htmlBySlot.head).toNotEqual(null);
expect(htmlBySlot.body).toNotEqual(null);
expect(Object.keys(writtenFiles).length).toEqual(7);
done();
}
});
});
xit('should allow output filters', function(done) {
testOptimizer({
bundleSet: [
{
name: "bundleA",
includes: [{ "module": "test.optimizer.filtersA" }]
}
],
enabledExtensions: ["jquery", "browser"],
pageName: "filters",
pageIncludes: [{ "module": "test.optimizer.filtersA" }],
done: done,
success: function(pageBundles) {
var Config = require('raptor/optimizer/Config');
var config = new Config();
config.setOutputDir("/some/dir/static");
var BundleFileWriter = require('raptor/optimizer/BundleFileWriter');
var BundleUrlBuilder = require('raptor/optimizer/BundleUrlBuilder');
var urlBuilder = new BundleUrlBuilder("http://localhost:8080/static/");
var writer = new BundleFileWriter(config, urlBuilder);
writer.addFilter(function(code, contentType) {
if (contentType === 'application/javascript') {
return code.toUpperCase();
}
else if (contentType === 'text/css') {
return code.toLowerCase();
}
else {
return code;
}
});
var writtenCode = {};
writer.writeBundleFile = function(outputFile, code) {
var outputPath = outputFile.getAbsolutePath();
logger.debug('Writing bundle file "' + outputPath + '" to disk. Code: ' + code);
writtenCode[code] = outputPath;
};
writer.writePageBundles(pageBundles);
expect(writtenCode["FILTERSA_JS"]).toNotEqual(null);
expect(writtenCode["filtersa_css"]).toNotEqual(null);
done();
}
});
});
xit('should allow for a simple optimizer project', function(done) {
var configPath = require('raptor/files').joinPaths(__dirname, 'resources/optimizer/project-a/optimizer-config.xml');
var packageResource = require('raptor/resources').createFileResource(require('raptor/files').joinPaths(__dirname, 'resources/optimizer/project-a/page1-package.json'));
var pageOptimizer = require('raptor/optimizer').createPageOptimizer(configPath);
pageOptimizer.optimizePage(
{
name: "page1",
packageResource: packageResource
})
.then(
function(optimizedPage) {
var pageIncludes = optimizedPage.getHtmlBySlot("page1");
expect(pageIncludes.body.indexOf('<script')).toNotEqual(-1);
done();
},
done);
});
it("should allow for optimizer tags in templates", function(done) {
var template = require('raptor/templating');
var renderContext = template.createContext();
var configPath = require('raptor/files').joinPaths(__dirname, '/resources/optimizer/project-a/optimizer-config.xml');
require('raptor/optimizer').configure(configPath);
var pending = 0;
var runCount = 3;
var runResults = new Array(runCount);
function runComplete() {
if (--pending === 0) {
done();
}
}
function runTest(num) {
pending++;
compileAndRenderAsync("/test-templates/optimizer.rhtml", {}, renderContext)
.then(
function(context) {
var output = context.getOutput();
runResults[num] = output;
runComplete();
},
done);
}
for (var i=0; i<runResults; i++) {
runTest(i);
}
});
it("should allow for optimizing a page without a configuration file", function(done) {
require('raptor/optimizer').configureDefault();
var bundles = {},
File = require('raptor/files/File');
var oldWriteBundleFile = require('raptor/optimizer').pageOptimizer.getWriter().writeBundleFile;
require('raptor/optimizer').pageOptimizer.writer.writeBundleFile = function(outputPath, code) {
var file = new File(outputPath);
var filename = file.getName();
bundles[filename] = code;
logger.debug('Writing bundle file "' + outputPath + '" to disk. Code: ' + code);
};
function restoreWriter() {
require('raptor/optimizer').pageOptimizer.writer.writeBundleFile = oldWriteBundleFile;
}
require('raptor/optimizer').optimizePage({
name: "page1",
packageFile: require('raptor/files').joinPaths(__dirname, 'resources/optimizer/project-a/page1-package.json')
})
.then(
function(optimizedPage) {
console.error(require('raptor/debug').prettyPrint(bundles));
console.error(require('raptor/debug').prettyPrint(optimizedPage));
expect(Object.keys(optimizedPage.getHtmlBySlot()).length).toEqual(2);
expect(optimizedPage.getHtmlBySlot()['body']).toEqual("<script type=\"text/javascript\" src=\"/static/page1-d14bc332.js\"></script>");
expect(optimizedPage.getHtmlBySlot()['head']).toEqual("<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/page1-4b176a91.css\">");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].requires[0]).toEqual("test.optimizer.nestedB");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].requires.length).toEqual(1);
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].css[0]).toEqual("/static/page1-async-1929e414.css");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].css.length).toEqual(1);
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].js[0]).toEqual("/static/page1-async-c17b7d9b.js");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedA"].js.length).toEqual(1);
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedB"].css[0]).toEqual("/static/page1-async-1929e414.css");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedB"].css.length).toEqual(1);
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedB"].js[0]).toEqual("/static/page1-async-c17b7d9b.js");
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedB"].js.length).toEqual(1);
expect(optimizedPage.getLoaderMetadata()["test.optimizer.nestedB"].hasOwnProperty('requires')).toEqual(false);
expect(Object.keys(optimizedPage.getLoaderMetadata()).length).toEqual(2);
expect(Object.keys(bundles).length).toEqual(4);
expect(bundles["page1-async-c17b7d9b.js"]).toEqual("nestedB_js\nnestedA_js");
expect(bundles["page1-async-1929e414.css"]).toEqual("nestedB_css\nnestedA_css");
expect(bundles["page1-d14bc332.js"]).toEqual("moduleA\nmixedA_js\nmixedB_js\nasyncA_js");
expect(bundles["page1-4b176a91.css"]).toEqual("mixedA_css\nmixedB_css\nasyncA_css");
done();
},
function(e) {
done(e);
})
.then(restoreWriter, restoreWriter);
});
it("should allow for accessing page configs loaded from XML configuration file", function() {
var template = require('raptor/templating');
var renderContext = template.createContext();
var configPath = require('raptor/files').joinPaths(__dirname, '/resources/optimizer/project-a/optimizer-config.xml');
require('raptor/optimizer').configure(configPath);
var config = require('raptor/optimizer').getDefaultPageOptimizer().getConfig();
var pageConfigsByName = {};
config.forEachPageConfig(function(pageConfig) {
pageConfigsByName[pageConfig.getName()] = pageConfig;
}, this);
expect(pageConfigsByName['page1']).toNotEqual(null);
expect(pageConfigsByName['page2']).toNotEqual(null);
expect(pageConfigsByName['page3']).toNotEqual(null);
expect(Object.keys(pageConfigsByName).length).toEqual(3);
var page2Manifest = pageConfigsByName['page2'].getPackageManifest();
var page2Dependencies = page2Manifest.getDependencies();
expect(page2Dependencies.length).toEqual(2);
expect(page2Dependencies[0].type).toEqual("module");
expect(page2Dependencies[0].name).toEqual("test.optimizer.moduleA");
expect(page2Dependencies[1].type).toEqual("module");
expect(page2Dependencies[1].name).toEqual("test.optimizer.mixedA");
var page3Manifest = pageConfigsByName['page3'].getPackageManifest();
var page3Dependencies = page3Manifest.getDependencies();
expect(page3Dependencies.length).toEqual(4);
expect(page3Dependencies[0].type).toEqual("module");
expect(page3Dependencies[0].name).toEqual("test.optimizer.moduleA");
expect(page3Dependencies[1].type).toEqual("module");
expect(page3Dependencies[1].name).toEqual("test.optimizer.mixedA");
expect(page3Dependencies[2].type).toEqual("module");
expect(page3Dependencies[2].name).toEqual("test.optimizer.mixedB");
expect(page3Dependencies[3].type).toEqual("module");
expect(page3Dependencies[3].name).toEqual("test.optimizer.asyncA");
});
it("should allow for URLs with the file:// protocol when in-place deployment is enabled", function(done) {
var template = require('raptor/templating');
var renderContext = template.createContext();
var configPath = require('raptor/files').joinPaths(__dirname, '/resources/optimizer/file-urls/optimizer-config.xml');
var bundles = {},
File = require('raptor/files/File');
require('raptor/optimizer').configure(configPath);
var config = require('raptor/optimizer').getDefaultPageOptimizer().getConfig();
require('raptor/optimizer').pageOptimizer.getWriter().writeBundleFile = function(outputPath, code) {
var file = new File(outputPath);
var filename = file.getName();
bundles[filename] = code;
logger.debug('Writing bundle file "' + outputPath + '" to disk. Code: ' + code);
};
require('raptor/optimizer').optimizePage({
name: "page1",
packageFile: require('raptor/files').joinPaths(__dirname, 'resources/optimizer/file-urls/page1-package.json')
})
.then(
function(optimizedPage) {
var jsFiles = optimizedPage.getJavaScriptFiles();
var cssFiles = optimizedPage.getCSSFiles();
expect(jsFiles.length).toEqual(3);
expect(cssFiles.length).toEqual(2);
done();
},
function(e) {
done(e);
});
});
});