diff --git a/.gitignore b/.gitignore
index d3277019a06f1..39a72e9b2da08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,4 +23,5 @@ docs/js/examples
docs/downloads
examples/shared/*.js
test/the-files-to-test.generated.js
-sauce_connect.log*
+*.log*
+chrome-user-data
diff --git a/Gruntfile.js b/Gruntfile.js
index b7bb12257bd1a..3a905b4eaf0ef 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -70,6 +70,11 @@ module.exports = function(grunt) {
grunt.registerTask('build:transformer', ['jsx:debug', 'browserify:transformer']);
grunt.registerTask('build:min', ['jsx:release', 'version-check', 'browserify:min']);
grunt.registerTask('build:addons-min', ['jsx:debug', 'browserify:addonsMin']);
+ grunt.registerTask('build:withCodeCoverageLogging', [
+ 'jsx:debug',
+ 'version-check',
+ 'browserify:withCodeCoverageLogging'
+ ]);
grunt.registerTask('build:test', [
'jsx:test',
'version-check',
@@ -78,11 +83,19 @@ module.exports = function(grunt) {
grunt.registerTask('webdriver-phantomjs', webdriverPhantomJSTask);
+ grunt.registerTask('coverage:parse', require('./grunt/tasks/coverage-parse'));
+
grunt.registerTask('test:webdriver:phantomjs', [
'connect',
'webdriver-phantomjs',
'webdriver-jasmine:local'
]);
+ grunt.registerTask('test:coverage', [
+ 'build:test',
+ 'build:withCodeCoverageLogging',
+ 'test:webdriver:phantomjs',
+ 'coverage:parse'
+ ]);
grunt.registerTask('test', ['build:test', 'build:basic', 'test:webdriver:phantomjs']);
grunt.registerTask('npm:test', ['build', 'npm:pack']);
diff --git a/grunt/config/browserify.js b/grunt/config/browserify.js
index 953251d4c746f..5bf86631f1855 100644
--- a/grunt/config/browserify.js
+++ b/grunt/config/browserify.js
@@ -95,10 +95,23 @@ var addonsMin = grunt.util._.merge({}, addons, {
after: [minify, bannerify]
});
+var withCodeCoverageLogging = {
+ entries: [
+ './build/modules/React.js'
+ ],
+ outfile: './build/react.js',
+ debug: true,
+ standalone: 'React',
+ transforms: [
+ require('coverify')
+ ]
+};
+
module.exports = {
basic: basic,
min: min,
transformer: transformer,
addons: addons,
- addonsMin: addonsMin
+ addonsMin: addonsMin,
+ withCodeCoverageLogging: withCodeCoverageLogging
};
diff --git a/grunt/config/server.js b/grunt/config/server.js
index 6227c8e714d45..4e2a323cbddd6 100644
--- a/grunt/config/server.js
+++ b/grunt/config/server.js
@@ -1,11 +1,62 @@
'use strict';
-module.exports = function(grunt) {
+module.exports = function(grunt){
+ var coverageWriteStream;
+
+ grunt.task.registerTask('finalize-coverage-stream', function(){
+ if (!coverageWriteStream) {
+ return;
+ }
+ var done = this.async();
+ coverageWriteStream.once('close', done);
+ coverageWriteStream.end();
+ coverageWriteStream = null;
+ });
+
+ function consoleLoggerMiddleware(req, res, next) {
+ if (!(req.method == 'POST' && req._parsedUrl.pathname.replace(/\//g,'') == 'console' && Array.isArray(req.body))) {
+ return next();
+ }
+ res.write('');
+ res.end('Got it, thanks!');
+
+ req.body.forEach(function(log){
+ if (log.message.indexOf('not ok ') === 0) {
+ log.type = 'error';
+ } else if (log.message.indexOf('ok ') === 0) {
+ log.type = 'ok';
+ } else if (log.message.indexOf('COVER') === 0) {
+ log.type = 'coverage';
+ } else if (log.message.indexOf('DONE\t') === 0) {
+ log.type = 'coverage done';
+ }
+
+ if (log.type == 'error') {
+ grunt.log.error(log.message);
+ } else if (log.type == 'ok') {
+ grunt.log.ok(log.message);
+ } else if (log.type == 'log') {
+ grunt.log.writeln(log.message);
+ } else if (log.type == 'coverage') {
+ if (!coverageWriteStream) {
+ coverageWriteStream = require('fs').createWriteStream(__dirname + '/../../coverage.log');
+ }
+ coverageWriteStream.write(log.message + '\n');
+ } else if (log.type == 'coverage done') {
+ grunt.task.run('finalize-coverage-stream');
+ } else {
+ grunt.verbose.writeln(log);
+ }
+ });
+ }
function testResultLoggerMiddleware(req, res, next) {
if (!(req.method == 'POST' && req._parsedUrl.pathname.indexOf('/reportTestResults') === 0)) {
return next();
}
+ res.write('');
+ res.end('Got it, thanks!');
+
var logType = 'writeln';
var message = req.body;
@@ -23,8 +74,6 @@ module.exports = function(grunt) {
message = JSON.stringify(message, null, 2);
}
grunt.log[logType]('[%s][%s]', req.headers['user-agent'], Date.now(), message);
- res.write('');
- res.end('Got it, thanks!');
}
return {
@@ -40,13 +89,14 @@ module.exports = function(grunt) {
return [
connect.json(),
+ consoleLoggerMiddleware,
testResultLoggerMiddleware,
connect.logger({format:'[:user-agent][:timestamp] :method :url', stream:grunt.verbose}),
connect.static(options.base),
connect.directory(options.base)
];
- },
+ }
}
}
};
diff --git a/grunt/tasks/browserify.js b/grunt/tasks/browserify.js
index d43c0841f8619..c83ed7e65c4d1 100644
--- a/grunt/tasks/browserify.js
+++ b/grunt/tasks/browserify.js
@@ -46,7 +46,7 @@ module.exports = function() {
};
// TODO: make sure this works, test with this too
- config.transforms.forEach(bundle.transform, this);
+ config.transforms.forEach(bundle.transform, bundle);
// Actually bundle it up
var _this = this;
diff --git a/grunt/tasks/coverage-parse.js b/grunt/tasks/coverage-parse.js
new file mode 100644
index 0000000000000..223bed34bc551
--- /dev/null
+++ b/grunt/tasks/coverage-parse.js
@@ -0,0 +1,53 @@
+"use strict";
+var grunt = require('grunt');
+
+module.exports = function(){
+ var ROOT = require('path').normalize(__dirname + '/../..');
+ var done = this.async();
+ var uncoveredExpressionCount = 0;
+ var uncoveredLineCount = 0;
+
+ require('fs').createReadStream(ROOT + '/coverage.log')
+ .pipe(require('coverify/parse')(function(error, results){
+ if (error) {
+ grunt.fatal(error);
+ }
+
+ Object.keys(results)
+ .sort(function(a, b){
+ return results[a].length - results[b].length;
+ })
+ .reverse()
+ .forEach(function(path){
+ if (results[path].length === 0) {
+ return;
+ }
+ var relativePath = path.replace(ROOT, '');
+ uncoveredExpressionCount += results[path].length;
+ grunt.log.error(results[path].length + ' expressions not covered ' + relativePath);
+
+ results[path].forEach(function(c){
+ uncoveredLineCount += c.code.split('\n').length;
+ console.log('txmt://open?url=' + encodeURIComponent('file://' + path) + '&line=' + (c.lineNum+1) + '&column=' + (c.column[0]+2));
+ });
+ console.log('');
+ })
+ ;
+
+ Object.keys(results).sort().forEach(function(path){
+ if (results[path].length > 0) {
+ return;
+ }
+ var relativePath = path.replace(ROOT, '');
+ grunt.log.ok('100% coverage ' + relativePath);
+ });
+
+ if (uncoveredExpressionCount > 0) {
+ grunt.log.error(uncoveredExpressionCount + ' expressions not covered');
+ }
+ if (uncoveredLineCount > 0) {
+ grunt.log.error(uncoveredLineCount + ' lines not covered');
+ }
+ done();
+ }));
+};
diff --git a/grunt/tasks/populist.js b/grunt/tasks/populist.js
index 034e97b3a6c63..6efa1e5132af6 100644
--- a/grunt/tasks/populist.js
+++ b/grunt/tasks/populist.js
@@ -34,6 +34,6 @@ module.exports = function() {
}).then(function(output) {
grunt.file.write(config.outfile, output);
theFilesToTestScript.end();
- done();
+ theFilesToTestScript.once('close', done);
});
};
diff --git a/package.json b/package.json
index 7a30d06e1c4e5..661fc7c044824 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,8 @@
"grunt-contrib-connect": "~0.5.0",
"es5-shim": "~2.1.0",
"wd": "~0.2.2",
- "sauce-tunnel": "~1.1.0"
+ "sauce-tunnel": "~1.1.0",
+ "coverify": "~0.1.1"
},
"engines": {
"node": ">=0.10.0"
diff --git a/src/environment/__tests__/ReactWebWorker-test.js b/src/environment/__tests__/ReactWebWorker-test.js
index 8703dbb2a1cf1..287163bb7d3b6 100644
--- a/src/environment/__tests__/ReactWebWorker-test.js
+++ b/src/environment/__tests__/ReactWebWorker-test.js
@@ -31,6 +31,8 @@ describe('ReactWebWorker', function() {
var data = JSON.parse(e.data);
if (data.type == 'error') {
error = data.message + "\n" + data.stack;
+ } else if (data.type == 'log') {
+ console.log(data.message);
} else {
expect(data.type).toBe('done');
done = true;
@@ -42,7 +44,7 @@ describe('ReactWebWorker', function() {
});
runs(function() {
if (error) {
- console.log(error);
+ console.error(error);
throw new Error(error);
}
});
diff --git a/src/test/worker.js b/src/test/worker.js
index 7ba7e1ffc734c..10691fda0bbe0 100644
--- a/src/test/worker.js
+++ b/src/test/worker.js
@@ -1,6 +1,26 @@
/* jshint worker: true */
"use strict";
+if (typeof console == 'undefined') {
+ this.console = {
+ error: function(e){
+ postMessage(JSON.stringify({
+ type: 'error',
+ message: e.message,
+ stack: e.stack
+ }));
+ },
+ log: function(message){
+ postMessage(JSON.stringify({
+ type: 'log',
+ message: message
+ }));
+ }
+ }
+}
+
+console.log('worker BEGIN');
+
// The UMD wrapper tries to store on `global` if `window` isn't available
var global = {};
importScripts("phantomjs-shims.js");
@@ -8,13 +28,11 @@ importScripts("phantomjs-shims.js");
try {
importScripts("../../build/react.js");
} catch (e) {
- postMessage(JSON.stringify({
- type: 'error',
- message: e.message,
- stack: e.stack
- }));
+ console.error(e);
}
postMessage(JSON.stringify({
type: 'done'
}));
+
+console.log('worker END');
diff --git a/test/lib/reportTestResults.browser.js b/test/lib/reportTestResults.browser.js
index 92010ecd7afaa..a521b735d458b 100644
--- a/test/lib/reportTestResults.browser.js
+++ b/test/lib/reportTestResults.browser.js
@@ -1,34 +1,44 @@
+var __DEBUG__ = location.search.substring(1).indexOf('debug') != -1;
+
if (typeof console == 'undefined') console = {
log: function(){},
warn: function(){},
error: function(){}
};
+var __consoleReport__ = [];
+
console._log = console.log;
console.log = function(message){
console._log(message);
- postDataToURL({type:'log', message:message}, '/reportTestResults');
+ if (__DEBUG__) postDataToURL({type:'log', message:message}, '/reportTestResults');
+ else __consoleReport__.push({type:'log', message:message});
}
+
console._error = console.error;
console.error = function(message){
console._error(message);
- postDataToURL({type:'error', message:message}, '/reportTestResults');
+ if (__DEBUG__) postDataToURL({type:'error', message:message}, '/reportTestResults');
+ else __consoleReport__.push({type:'error', message:message});
+}
+
+console._flush = function(){
+ postDataToURL(__consoleReport__, '/console');
+ __consoleReport__.length = 0;
}
;(function(env){
env.addReporter(new jasmine.JSReporter());
- if (location.search.substring(1).indexOf('debug') != -1){
- env.addReporter(new TAPReporter(console.log.bind(console)));
- }
+ env.addReporter(new TAPReporter(console.log.bind(console)));
function report(){
if (typeof jasmine.getJSReport != 'function') {
- console.log("typeof jasmine.getJSReport != 'function'");
return setTimeout(report, 100);
}
- postDataToURL(jasmine.getJSReport(), '/reportTestResults', function(error, results){
- if (error) return console.error(error);
- });
+ if (!__DEBUG__) {
+ console.log('DONE\t' + navigator.userAgent);
+ console._flush();
+ }
}
var oldCallback = env.currentRunner().finishCallback;