Files
Pete Bacon Darwin 23e4138c07 chore(modules): execute modules unit tests in one karma run
SauceLabs  is struggling to keep connecting and disconnecting
for each of the modules unit test runs. This commit puts most of
the module tests into IIFEs so that they can be run in one go.

* ngMock is still tested separately since unlike the other tests
it doesn't want to have a pre-built version of ngMock available.

* ngAnimate is still tested separately because it does some funny
things with globals that were difficult to isolate in the main modules
test run.
2018-12-12 08:10:12 +00:00

319 lines
11 KiB
JavaScript

'use strict';
var fs = require('fs');
var shell = require('shelljs');
var grunt = require('grunt');
var spawn = require('npm-run').spawn;
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
module.exports = {
startKarma: function(config, singleRun, done) {
var browsers = grunt.option('browsers');
var reporters = grunt.option('reporters');
var noColor = grunt.option('no-colors');
var port = grunt.option('port');
var p = spawn('karma', ['start', config,
singleRun ? '--single-run=true' : '',
reporters ? '--reporters=' + reporters : '',
browsers ? '--browsers=' + browsers : '',
noColor ? '--no-colors' : '',
port ? '--port=' + port : ''
]);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code) {
if (code !== 0) grunt.fail.warn('Karma test(s) failed. Exit code: ' + code);
done();
});
},
updateWebdriver: function(done) {
if (process.env.TRAVIS) {
// Skip the webdriver-manager update on Travis, since the browsers will
// be provided remotely.
done();
return;
}
var p = spawn('webdriver-manager', ['update']);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code) {
if (code !== 0) grunt.fail.warn('Webdriver failed to update');
done();
});
},
startProtractor: function(config, done) {
var sauceUser = grunt.option('sauceUser');
var sauceKey = grunt.option('sauceKey');
var tunnelIdentifier = grunt.option('capabilities.tunnel-identifier');
var sauceBuild = grunt.option('capabilities.build');
var browser = grunt.option('browser');
var specs = grunt.option('specs');
var args = [config];
if (sauceUser) args.push('--sauceUser=' + sauceUser);
if (sauceKey) args.push('--sauceKey=' + sauceKey);
if (tunnelIdentifier) args.push('--capabilities.tunnel-identifier=' + tunnelIdentifier);
if (sauceBuild) args.push('--capabilities.build=' + sauceBuild);
if (specs) args.push('--specs=' + specs);
if (browser) {
args.push('--browser=' + browser);
}
var p = spawn('protractor', args);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code) {
if (code !== 0) grunt.fail.warn('Protractor test(s) failed. Exit code: ' + code);
done();
});
},
wrap(src, name) {
return [`src/${name}.prefix`, ...src, `src/${name}.suffix`];
},
addStyle: function(src, styles, minify) {
styles = styles.reduce(processCSS.bind(this), {
js: [src],
css: []
});
return {
js: styles.js.join('\n'),
css: styles.css.join('\n')
};
function processCSS(state, file) {
var css = fs.readFileSync(file).toString(),
js;
state.css.push(css);
if (minify) {
css = css
.replace(/\r?\n/g, '')
.replace(/\/\*.*?\*\//g, '')
.replace(/:\s+/g, ':')
.replace(/\s*\{\s*/g, '{')
.replace(/\s*\}\s*/g, '}')
.replace(/\s*,\s*/g, ',')
.replace(/\s*;\s*/g, ';');
}
//escape for js
css = css
.replace(/\\/g, '\\\\')
.replace(/'/g, '\\\'')
.replace(/\r?\n/g, '\\n');
js = '!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(\'<style type="text/css">' + css + '</style>\');';
state.js.push(js);
return state;
}
},
process: function(src, NG_VERSION, strict) {
var processed = src
.replace(/(['"])NG_VERSION_FULL\1/g, NG_VERSION.full)
.replace(/(['"])NG_VERSION_MAJOR\1/, NG_VERSION.major)
.replace(/(['"])NG_VERSION_MINOR\1/, NG_VERSION.minor)
.replace(/(['"])NG_VERSION_DOT\1/, NG_VERSION.patch)
.replace(/(['"])NG_VERSION_CDN\1/, NG_VERSION.cdn)
.replace(/(['"])NG_VERSION_CODENAME\1/, NG_VERSION.codeName);
if (strict !== false) processed = this.singleStrict(processed, '\n\n', true);
return processed;
},
build: function(config, fn) {
var files = grunt.file.expand(config.src);
// grunt.file.expand might reorder the list of files
// when it is expanding globs, so we use prefix and suffix
// fields to ensure that files are at the start of end of
// the list (primarily for wrapping in an IIFE).
if (config.prefix) {
files = grunt.file.expand(config.prefix).concat(files);
}
if (config.suffix) {
files = files.concat(grunt.file.expand(config.suffix));
}
var styles = config.styles;
var processedStyles;
//concat
var src = files.map(function(filepath) {
return grunt.file.read(filepath);
}).join(grunt.util.normalizelf('\n'));
//process
var processed = this.process(src, grunt.config('NG_VERSION'), config.strict);
if (styles) {
processedStyles = this.addStyle(processed, styles.css, styles.minify);
processed = processedStyles.js;
if (config.styles.generateCspCssFile) {
grunt.file.write(removeSuffix(config.dest) + '-csp.css', CSP_CSS_HEADER + processedStyles.css);
}
}
//write
grunt.file.write(config.dest, processed);
grunt.log.ok('File ' + config.dest + ' created.');
fn();
function removeSuffix(fileName) {
return fileName.replace(/\.js$/, '');
}
},
singleStrict: function(src, insert) {
return src
.replace(/\s*("|')use strict("|');\s*/g, insert) // remove all file-specific strict mode flags
.replace(/(\(function\([^)]*\)\s*\{)/, '$1\'use strict\';'); // add single strict mode flag
},
sourceMap: function(mapFile, fileContents) {
var sourceMapLine = '//# sourceMappingURL=' + mapFile + '\n';
return fileContents + sourceMapLine;
},
min: function(file, done) {
var classPathSep = (process.platform === 'win32') ? ';' : ':';
var minFile = file.replace(/\.js$/, '.min.js');
var mapFile = minFile + '.map';
var mapFileName = mapFile.match(/[^/]+$/)[0];
var errorFileName = file.replace(/\.js$/, '-errors.json');
var versionNumber = grunt.config('NG_VERSION').full;
var compilationLevel = (file === 'build/angular-message-format.js') ?
'ADVANCED_OPTIMIZATIONS' : 'SIMPLE_OPTIMIZATIONS';
shell.exec(
'java ' +
this.java32flags() + ' ' +
this.memoryRequirement() + ' ' +
'-cp vendor/closure-compiler/compiler.jar' + classPathSep +
'vendor/ng-closure-runner/ngcompiler.jar ' +
'org.angularjs.closurerunner.NgClosureRunner ' +
'--compilation_level ' + compilationLevel + ' ' +
'--language_in ECMASCRIPT5_STRICT ' +
'--minerr_pass ' +
'--minerr_errors ' + errorFileName + ' ' +
'--minerr_url http://errors.angularjs.org/' + versionNumber + '/ ' +
'--source_map_format=V3 ' +
'--create_source_map ' + mapFile + ' ' +
'--js ' + file + ' ' +
'--js_output_file ' + minFile,
function(code) {
if (code !== 0) grunt.fail.warn('Error minifying ' + file);
// closure creates the source map relative to build/ folder, we need to strip those references
grunt.file.write(mapFile, grunt.file.read(mapFile).replace('"file":"build/', '"file":"').
replace('"sources":["build/','"sources":["'));
// move add use strict into the closure + add source map pragma
grunt.file.write(minFile, this.sourceMap(mapFileName, this.singleStrict(grunt.file.read(minFile), '\n')));
grunt.log.ok(file + ' minified into ' + minFile);
done();
}.bind(this));
},
memoryRequirement: function() {
return (process.platform === 'win32') ? '' : '-Xmx2g';
},
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
java32flags: function() {
if (process.platform === 'win32') return '';
if (shell.exec('java -d32 -version 2>&1', {silent: true}).code !== 0) return '';
return ' -d32 -client';
},
//collects and combines error messages stripped out in minify step
collectErrors: function() {
var combined = {
id: 'ng',
generated: new Date().toString(),
errors: {}
};
grunt.file.expand('build/*-errors.json').forEach(function(file) {
var errors = grunt.file.readJSON(file),
namespace;
Object.keys(errors).forEach(function(prop) {
if (typeof errors[prop] === 'object') {
namespace = errors[prop];
if (combined.errors[prop]) {
Object.keys(namespace).forEach(function(code) {
if (combined.errors[prop][code] && combined.errors[prop][code] !== namespace[code]) {
grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!');
} else {
combined.errors[prop][code] = namespace[code];
}
});
} else {
combined.errors[prop] = namespace;
}
} else {
if (combined.errors[prop] && combined.errors[prop] !== errors[prop]) {
grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!');
} else {
combined.errors[prop] = errors[prop];
}
}
});
});
grunt.file.write('build/errors.json', JSON.stringify(combined));
grunt.file.expand('build/*-errors.json').forEach(grunt.file.delete);
},
//csp connect middleware
conditionalCsp: function() {
return function(req, res, next) {
var CSP = /\.csp\W/;
if (CSP.test(req.url)) {
res.setHeader('X-WebKit-CSP', 'default-src \'self\';');
res.setHeader('X-Content-Security-Policy', 'default-src \'self\'');
res.setHeader('Content-Security-Policy', 'default-src \'self\'');
}
next();
};
},
//rewrite connect middleware
rewrite: function() {
return function(req, res, next) {
var REWRITE = /\/(guide|api|cookbook|misc|tutorial|error).*$/,
IGNORED = /(\.(css|js|png|jpg|gif|svg)$|partials\/.*\.html$)/,
match;
if (!IGNORED.test(req.url) && (match = req.url.match(REWRITE))) {
console.log('rewriting', req.url);
req.url = req.url.replace(match[0], '/index.html');
}
next();
};
},
// Our Firebase projects are in subfolders, but Travis expects them in the root,
// so we need to modify the upload folder path and copy the file into the root
firebaseDocsJsonForTravis: function() {
var docsScriptFolder = 'scripts/docs.angularjs.org-firebase';
var fileName = docsScriptFolder + '/firebase.json';
var json = grunt.file.readJSON(fileName);
(json.hosting || (json.hosting = {})).public = 'deploy/docs';
(json.functions || (json.functions = {})).source = docsScriptFolder + '/functions';
grunt.file.write('firebase.json', JSON.stringify(json));
}
};