blanket
Version:
seamless js code coverage
339 lines (324 loc) • 14.9 kB
JavaScript
(function(_blanket){
_blanket.extend({
utils: {
normalizeBackslashes: function(str) {
return str.replace(/\\/g, '/');
},
matchPatternAttribute: function(filename,pattern){
if (typeof pattern === 'string'){
if (pattern.indexOf("[") === 0){
//treat as array
var pattenArr = pattern.slice(1,pattern.length-1).split(",");
return pattenArr.some(function(elem){
return _blanket.utils.matchPatternAttribute(filename,_blanket.utils.normalizeBackslashes(elem.slice(1,-1)));
//return filename.indexOf(_blanket.utils.normalizeBackslashes(elem.slice(1,-1))) > -1;
});
}else if ( pattern.indexOf("//") === 0){
var ex = pattern.slice(2,pattern.lastIndexOf('/'));
var mods = pattern.slice(pattern.lastIndexOf('/')+1);
var regex = new RegExp(ex,mods);
return regex.test(filename);
}else if (pattern.indexOf("#") === 0){
return window[pattern.slice(1)].call(window,filename);
}else{
return filename.indexOf(_blanket.utils.normalizeBackslashes(pattern)) > -1;
}
}else if ( pattern instanceof Array ){
return pattern.some(function(elem){
return _blanket.utils.matchPatternAttribute(filename,elem);
});
}else if (pattern instanceof RegExp){
return pattern.test(filename);
}else if (typeof pattern === "function"){
return pattern.call(window,filename);
}
},
blanketEval: function(data){
_blanket._addScript(data);
},
collectPageScripts: function(){
var toArray = Array.prototype.slice;
var selectedScripts=[],scriptNames=[];
var filter = _blanket.options("filter");
function selectAllScripts() {
var browserScripts = toArray.call(document.scripts);
var blanketScripts = toArray.call(document.getElementsByTagName('blanket'));
return browserScripts.concat(blanketScripts);
}
if(filter != null){
//global filter in place, data-cover-only
var antimatch = _blanket.options("antifilter");
selectedScripts = selectAllScripts()
.filter(function(s){
return toArray.call(s.attributes).filter(function(sn){
return sn.nodeName === "src" && _blanket.utils.matchPatternAttribute(sn.nodeValue,filter) &&
(typeof antimatch === "undefined" || !_blanket.utils.matchPatternAttribute(sn.nodeValue,antimatch));
}).length === 1;
});
}else{
selectedScripts = toArray.call(document.querySelectorAll("script[data-cover], blanket"));
}
scriptNames = selectedScripts.map(function(s){
return _blanket.utils.qualifyURL(
toArray.call(s.attributes).filter(
function(sn){
return sn.nodeName === "src";
})[0].nodeValue);
});
if (!filter){
_blanket.options("filter","['"+scriptNames.join("','")+"']");
}
return scriptNames;
},
loadAll: function(nextScript,cb,preprocessor){
/**
* load dependencies
* @param {nextScript} factory for priority level
* @param {cb} the done callback
*/
var currScript=nextScript();
var isLoaded = _blanket.utils.scriptIsLoaded(
currScript,
_blanket.utils.ifOrdered,
nextScript,
cb
);
if (!(_blanket.utils.cache[currScript] && _blanket.utils.cache[currScript].loaded)){
var attach = function(){
if (_blanket.options("debug")) {console.log("BLANKET-Mark script:"+currScript+", as loaded and move to next script.");}
isLoaded();
};
var whenDone = function(result){
if (_blanket.options("debug")) {console.log("BLANKET-File loading finished");}
if (typeof result !== 'undefined'){
if (_blanket.options("debug")) {console.log("BLANKET-Add file to DOM.");}
_blanket._addScript(result);
}
attach();
};
_blanket.utils.attachScript(
{
url: currScript
},
function (content){
_blanket.utils.processFile(
content,
currScript,
whenDone,
whenDone
);
}
);
}else{
isLoaded();
}
},
attachScript: function(options,cb){
var timeout = _blanket.options("timeout") || 3000;
setTimeout(function(){
if (!_blanket.utils.cache[options.url].loaded){
throw new Error("error (timeout=" + timeout + ") loading source script: " + options.url);
}
},timeout);
_blanket.utils.getFile(
options.url,
cb,
function(){ throw new Error("error loading source script: " + options.url);}
);
},
ifOrdered: function(nextScript,cb){
/**
* ordered loading callback
* @param {nextScript} factory for priority level
* @param {cb} the done callback
*/
var currScript = nextScript(true);
if (currScript){
_blanket.utils.loadAll(nextScript,cb);
}else{
cb(new Error("Error in loading chain."));
}
},
scriptIsLoaded: function(url,orderedCb,nextScript,cb){
/**
* returns a callback that checks a loading list to see if a script is loaded.
* @param {orderedCb} callback if ordered loading is being done
* @param {nextScript} factory for next priority level
* @param {cb} the done callback
*/
if (_blanket.options("debug")) {console.log("BLANKET-Returning function");}
return function(){
if (_blanket.options("debug")) {console.log("BLANKET-Marking file as loaded: "+url);}
_blanket.utils.cache[url].loaded=true;
if (_blanket.utils.allLoaded()){
if (_blanket.options("debug")) {console.log("BLANKET-All files loaded");}
cb();
}else if (orderedCb){
//if it's ordered we need to
//traverse down to the next
//priority level
if (_blanket.options("debug")) {console.log("BLANKET-Load next file.");}
orderedCb(nextScript,cb);
}
};
},
cache: {},
allLoaded: function (){
/**
* check if depdencies are loaded in cache
*/
var cached = Object.keys(_blanket.utils.cache);
for (var i=0;i<cached.length;i++){
if (!_blanket.utils.cache[cached[i]].loaded){
return false;
}
}
return true;
},
processFile: function (content,url,cb,oldCb) {
var match = _blanket.options("filter");
//we check the never matches first
var antimatch = _blanket.options("antifilter");
if (typeof antimatch !== "undefined" &&
_blanket.utils.matchPatternAttribute(url,antimatch)
){
oldCb(content);
if (_blanket.options("debug")) {console.log("BLANKET-File will never be instrumented:"+url);}
_blanket.requiringFile(url,true);
}else if (_blanket.utils.matchPatternAttribute(url,match)){
if (_blanket.options("debug")) {console.log("BLANKET-Attempting instrument of:"+url);}
_blanket.instrument({
inputFile: content,
inputFileName: url
},function(instrumented){
try{
if (_blanket.options("debug")) {console.log("BLANKET-instrument of:"+url+" was successfull.");}
_blanket.utils.blanketEval(instrumented);
cb();
_blanket.requiringFile(url,true);
}
catch(err){
if (_blanket.options("ignoreScriptError")){
//we can continue like normal if
//we're ignoring script errors,
//but otherwise we don't want
//to completeLoad or the error might be
//missed.
if (_blanket.options("debug")) { console.log("BLANKET-There was an error loading the file:"+url); }
cb(content);
_blanket.requiringFile(url,true);
}else{
var e = new Error("Error parsing instrumented code: "+err);
e.error = err;
throw e;
}
}
});
}else{
if (_blanket.options("debug")) { console.log("BLANKET-Loading (without instrumenting) the file:"+url);}
oldCb(content);
_blanket.requiringFile(url,true);
}
},
cacheXhrConstructor: function(){
var Constructor, createXhr, i, progId;
if (typeof XMLHttpRequest !== "undefined") {
Constructor = XMLHttpRequest;
this.createXhr = function() { return new Constructor(); };
} else if (typeof ActiveXObject !== "undefined") {
Constructor = ActiveXObject;
for (i = 0; i < 3; i += 1) {
progId = progIds[i];
try {
new ActiveXObject(progId);
break;
} catch (e) {}
}
this.createXhr = function() { return new Constructor(progId); };
}
},
craeteXhr: function () {
throw new Error("cacheXhrConstructor is supposed to overwrite this function.");
},
getFile: function(url, callback, errback, onXhr){
var foundInSession = false;
if (_blanket.blanketSession){
var files = Object.keys(_blanket.blanketSession);
for (var i=0; i<files.length;i++ ){
var key = files[i];
if (url.indexOf(key) > -1){
callback(_blanket.blanketSession[key]);
foundInSession=true;
return;
}
}
}
if (!foundInSession){
var xhr = _blanket.utils.createXhr();
xhr.open('GET', url, true);
//Allow overrides specified in config
if (onXhr) {
onXhr(xhr, url);
}
xhr.onreadystatechange = function (evt) {
var status, err;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
status = xhr.status;
if ((status > 399 && status < 600) /*||
(status === 0 &&
navigator.userAgent.toLowerCase().indexOf('firefox') > -1)
*/ ) {
//An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr;
errback(err);
} else {
callback(xhr.responseText);
}
}
};
try{
xhr.send(null);
}catch(e){
if (e.code && (e.code === 101 || e.code === 1012) && _blanket.options("ignoreCors") === false){
//running locally and getting error from browser
_blanket.showManualLoader();
} else {
throw e;
}
}
}
}
}
});
(function(){
var require = blanket.options("commonJS") ? blanket._commonjs.require : window.require;
var requirejs = blanket.options("commonJS") ? blanket._commonjs.requirejs : window.requirejs;
if (!_blanket.options("engineOnly") && _blanket.options("existingRequireJS")){
_blanket.utils.oldloader = requirejs.load;
requirejs.load = function (context, moduleName, url) {
_blanket.requiringFile(url);
_blanket.utils.getFile(url,
function(content){
_blanket.utils.processFile(
content,
url,
function newLoader(){
context.completeLoad(moduleName);
},
function oldLoader(){
_blanket.utils.oldloader(context, moduleName, url);
}
);
}, function (err) {
_blanket.requiringFile();
throw err;
});
};
}
// Save the XHR constructor, just in case frameworks like Sinon would sandbox it.
_blanket.utils.cacheXhrConstructor();
})();
})(blanket);