karma-systemjs
Version:
A Karma plugin. Adapter for SystemJS module loader.
229 lines (211 loc) • 8.47 kB
JavaScript
(function(window) {
'use strict';
var adapter = {
/**
* Takes a file path and the baseURL and returns the module name
* to pass to System.import()
* @param filePath {string}
* @param baseURL {string}
* @param System {object}
* @returns {string}
*/
getModuleNameFromPath: function(filePath, baseURL, System) {
// Convert file paths to module name by stripping the baseURL and the ".js" extension
if (System.defaultJSExtensions) {
filePath = filePath.replace(/\.js$/, '');
}
return filePath
.replace(new RegExp('^' + baseURL.replace('/', '\/')), '');
},
/**
* Returns the modules names for files that match a given import RegExp.
* @param filePaths {object}
* @param importRegexp {object}
* @param System {object}
* @returns {string[]}
*/
getMatchingModulesToImport: function(filePaths, importRegexp, System) {
var moduleNames = [];
for (var filePath in filePaths) {
if (filePaths.hasOwnProperty(filePath) && importRegexp.test(filePath)) {
moduleNames.push(adapter.getModuleNameFromPath(filePath, System.baseURL, System));
}
}
return moduleNames;
},
/**
* Handles calling System.import() for files where each import is made in parallel, returning a single promise
* that resolves once all imports have completed.
* @param System {object}
* @param Promise {object}
* @param files {object}
* @param importRegexps {object[]}
* @returns {promise}
*/
parallelImportFiles: function(System, Promise, files, importRegexps) {
// Run all imports in parallel
var importPromises = [];
for (var x = 0; x < importRegexps.length; x++) {
var moduleNames = adapter.getMatchingModulesToImport(files, importRegexps[x], System);
for (var i = 0; i < moduleNames.length; i++) {
importPromises.push(System.import(moduleNames[i]));
}
}
return Promise.all(importPromises);
},
/**
* Chains a System.import() call onto an existing promise, returning the new promise.
* @param promise {promise}
* @param moduleName {string}
* @param System {object}
* @returns {promise}
*/
chainImport: function(promise, moduleName, System) {
return promise.then(function() {
return System.import(moduleName);
});
},
/**
* Handles calling System.import() for files where each import promise is chained into the next import promise,
* returning a promise that resolves once the last import has completed.
* @param System {object}
* @param Promise {object}
* @param files {object}
* @param importRegexps {object[]}
* @returns {promise}
*/
sequentialImportFiles: function(System, Promise, files, importRegexps) {
// Chain import promises to maintain sequence
var promise = Promise.resolve();
for (var x = 0; x < importRegexps.length; x++) {
var moduleNames = adapter.getMatchingModulesToImport(files, importRegexps[x], System);
for (var i = 0; i < moduleNames.length; i++) {
promise = adapter.chainImport(promise, moduleNames[i], System);
}
}
return promise;
},
/**
* Calls System.import on all the files that match one of the importPatterns.
* Returns a single promise which resolves once all imports are complete.
* @param System {object}
* @param Promise {object}
* @param files {object} key/value map of filePaths to change counters
* @param importRegexps {RegExp[]}
* @param [strictImportSequence=false] {boolean} If true, System.import calls are chained to preserve sequence.
* @returns {promise}
*/
importFiles: function(System, Promise, files, importRegexps, strictImportSequence) {
if (strictImportSequence) {
return adapter.sequentialImportFiles(System, Promise, files, importRegexps)
} else {
return adapter.parallelImportFiles(System, Promise, files, importRegexps)
}
},
/**
* Changes the 'baseURL' to include the '/base/' path that karma
* serves files from.
* @param originalBaseURL {string}
* @returns {string}
*/
updateBaseURL: function(originalBaseURL) {
if (!originalBaseURL) {
return '/base/';
} else if (originalBaseURL.indexOf('./') === 0) {
return originalBaseURL.replace('./', '/base/');
} else if (originalBaseURL.indexOf('/') !== 0) {
return '/base/' + originalBaseURL;
} else {
return '/base' + originalBaseURL;
}
},
/**
* Has SystemJS load each test suite, then starts Karma
* @param karma {object}
* @param System {object}
* @param Promise {object}
*/
run: function(karma, System, Promise) {
// Fail fast if any of the dependencies are undefined
if (!karma) {
(console.error || console.log)('Error: Not setup properly. window.__karma__ is undefined');
return;
}
if (!System) {
(console.error || console.log)('Error: Not setup properly. window.System is undefined');
return;
}
if (!Promise) {
(console.error || console.log)('Error: Not setup properly. window.Promise is undefined');
return;
}
// Stop karma from starting automatically on load
karma.loaded = function() {
// Load SystemJS configuration from karma config
// And update baseURL with '/base', where Karma serves files from
if (karma.config.systemjs.config) {
// SystemJS config is converted to a JSON string by the framework
// https://github.com/rolaveric/karma-systemjs/issues/44
karma.config.systemjs.config = JSON.parse(karma.config.systemjs.config);
karma.config.systemjs.config.baseURL = adapter.updateBaseURL(karma.config.systemjs.config.baseURL);
System.config(karma.config.systemjs.config);
// Exclude bundle configurations if useBundles option is not specified
if (!karma.config.systemjs.useBundles) {
System.bundles = [];
}
} else {
System.config({baseURL: '/base/'});
}
// Convert the 'importPatterns' into 'importRegexps'
var importPatterns = karma.config.systemjs.importPatterns;
var importRegexps = [];
for (var x = 0; x < importPatterns.length; x++) {
importRegexps.push(new RegExp(importPatterns[x]));
}
// Import each test suite using SystemJS
var testSuitePromise;
try {
testSuitePromise = adapter.importFiles(System, Promise, karma.files, importRegexps, karma.config.systemjs.strictImportSequence);
} catch (e) {
karma.error(adapter.decorateErrorWithHints(e, System));
return;
}
// Once all imports are complete...
testSuitePromise.then(function () {
karma.start();
}, function (e) {
karma.error(adapter.decorateErrorWithHints(e, System));
});
};
},
/**
* Checks errors to see if they match known issues, and tries to decorate them
* with hints on how to resolve them.
* @param err {string}
* @param System {object}
* @returns {string}
*/
decorateErrorWithHints: function(err, System) {
err = String(err);
// Look for common issues in the error message, and try to add hints to them
switch (true) {
// Some people use ".es6" instead of ".js" for ES6 code
case /^Error loading ".*\.es6" at .*\.es6\.js/.test(err):
return err + '\nHint: If you use ".es6" as an extension, ' +
'add this to your SystemJS paths config: {"*.es6": "*.es6"}';
case /^TypeError: Illegal module name "\/base\//.test(err):
return err + '\nHint: Is the working directory different when you run karma?' +
'\nYou may need to change the baseURL of your SystemJS config inside your karma config.' +
'\nIt\'s currently checking "' + System.baseURL + '"' +
'\nNote: "/base/" is where karma serves files from.';
}
return err;
}
};
if (window.System) {
adapter.run(window.__karma__, window.System, window.Promise);
} else {
//if no System global, expose global for unit testing
window.karmaSystemjsAdapter = adapter;
}
})(window);