Added JUnit xml output (via Kelvin Luck @vitch)

Passing console.log from browser to verbose grunt logging
Support for templates as separate node modules
Removed internal requirejs template (see grunt-template-jasmine-requirejs)
General refactor
This commit is contained in:
Jarrod Overson 2013-01-08 11:28:26 -08:00
parent 81d45689d0
commit 7fd976a2a1
20 changed files with 222 additions and 838 deletions

View File

@ -1,5 +1,5 @@
{
"curly": true,
"curly": false,
"eqeqeq": true,
"immed": true,
"latedef": true,

View File

@ -1,4 +1,11 @@
v0.2.0:
v0.3.0:
date: 2013-01-08
changes:
- Added JUnit xml output (via Kelvin Luck @vitch)
- Passing console.log from browser to verbose grunt logging
- Support for templates as separate node modules
- Removed internal requirejs template (see grunt-template-jasmine-requirejs)
v0.2.0:
date: 2012-12-03
changes:
- Generalized requirejs template config

View File

@ -32,7 +32,10 @@ module.exports = function(grunt) {
jasmine: {
options: {
specs: 'test/fixtures/pivotal/spec/*Spec.js',
helpers: 'test/fixtures/pivotal/spec/*Helper.js'
helpers: 'test/fixtures/pivotal/spec/*Helper.js',
junit: {
path: 'junit'
}
},
pivotal: {
src: 'test/fixtures/pivotal/src/**/*.js'
@ -40,20 +43,10 @@ module.exports = function(grunt) {
customTemplate: {
src: 'test/fixtures/pivotal/src/**/*.js',
options: {
template: 'test/fixtures/customTemplate/custom.tmpl'
}
},
requirejs: {
src: 'test/fixtures/requirejs/src/**/*.js',
options: {
specs: 'test/fixtures/requirejs/spec/*Spec.js',
helpers: 'test/fixtures/requirejs/spec/*Helper.js',
host: 'http://127.0.0.1:<%= connect.test.port %>/',
template: 'requirejs',
templateOptions: {
requireConfig : {
baseUrl: './test/fixtures/requirejs/src/'
}
template: 'test/fixtures/customTemplate/custom.tmpl',
junit: {
path: 'junit/customTemplate',
consolidate: true
}
}
}
@ -73,6 +66,6 @@ module.exports = function(grunt) {
grunt.registerTask('watch-test', ['connect', 'watch']);
grunt.registerTask('test', ['jasmine:pivotal', 'jasmine:customTemplate', 'connect', 'jasmine:requirejs', 'nodeunit']);
grunt.registerTask('default', ['jshint', 'test', 'build-contrib']);
grunt.registerTask('test', ['jshint', 'jasmine:pivotal', 'jasmine:customTemplate', 'nodeunit']);
grunt.registerTask('default', ['test', 'build-contrib']);
};

107
README.md
View File

@ -31,11 +31,11 @@ Run your tests on your local filesystem or via a server task like [grunt-contrib
#### AMD Support
Includes a SpecRunner template customized for use with AMD modules and requirejs.
Supports AMD tests via the [grunt-template-jasmine-requirejs](https://github.com/jsoverson/grunt-template-jasmine-requirejs) module
#### Customize your SpecRunner with your own template
Supply your own underscore template to automatically build your SpecRunner custom to your use.
Supply your templates that will be used to automatically build the SpecRunner.
#### Example application usage
@ -71,8 +71,20 @@ Type: `String|Array`
Type: `String`
Default: `_SpecRunner.html`
This is the auto-generated specfile that phantomjs will use to run your tests. This is automatically deleted upon normal
runs
This is the auto-generated specfile that phantomjs will use to run your tests.
This is automatically deleted upon normal runs
#### options.junit.path
Type: `String`
Default: undefined
Path to output JUnit xml
#### options.junit.consolidate
Type: `Boolean`
Default: `false`
Consolidate the JUnit XML so that there is one file per top level suite.
#### options.host
Type: `String`
@ -95,13 +107,15 @@ host : 'http://127.0.0.1:<%= connect.port %>/'
Not defining a host will mean your specs will be run from the local filesystem.
#### options.template
Type: `String`
Default: `default`
Options: `default`, `requirejs`, `yourcustomtemplate.tmpl`
Type: `String` `Object`
Default: undefined
Specify a custom template to use when generating your Spec Runner. Templates are parsed as underscore templates and provided
Specify a custom template used to generate your Spec Runner. Templates are parsed as underscore templates and provided
the expanded list of files needed to build a specrunner.
You can specify an object with a `process` method that will be called as a template function.
See the [Template API Documentation](needs-wiki-link) for more details.
#### options.templateOptions
Type: `Object`
Default: `{}`
@ -124,32 +138,6 @@ watch: {
}
```
### Template Options
#### Default template
No specific options are expected or used.
#### RequireJS template
##### templateOptions.requirejs
Type: `String`
The path to requirejs if you need to specify an alternate version.
##### templateOptions.loaderPlugin
Type: `String`
The loader plugin to prefix all loaded `src` files. This is useful for processing
your specs through the likes of CoffeeScript or TypeScript plugins. Keep in mind
you will need to specify the path to the plugin in the require config.
##### templateOptions.requireConfig
Type: `Object`
This object is `JSON.stringify()`-ed into the template and passed into `require.config()`
#### Basic Use
@ -193,57 +181,12 @@ grunt.initConfig({
#### Sample RequireJS usage
```js
// Example configuration
grunt.initConfig({
connect: {
test : {
port : 8000
}
}
jasmine: {
requirejs: {
src: 'src/**/*.js',
options: {
specs: 'spec/*Spec.js',
helpers: 'spec/*Helper.js',
host: 'http://127.0.0.1:8000/',
template: 'requirejs',
templateOptions: {
requireConfig: {
baseUrl: 'src/'
}
}
}
}
}
}
```
*Note* the usage of the 'connect' task configuration. You will need to use a task like
[grunt-contrib-connect][] if you need to test your tasks on a running server.
[grunt-contrib-connect]: https://github.com/gruntjs/grunt-contrib-connect
#### RequireJS notes
If you end up using the requirejs template, it's worth looking at the
[RequireJS template source](https://github.com/gruntjs/grunt-contrib-jasmine/blob/master/tasks/jasmine/templates/RequireJSRunner.tmpl)
in order to familiarize yourself with how it loads your files. The load process essentially
consists of a series of nested `require` blocks, incrementally loading your source and specs:
```js
require([*YOUR SOURCE*], function() {
require([*YOUR SPECS*], function() {
require([*GRUNT-CONTRIB-JASMINE FILES*], function() {
// at this point your tests are already running.
}
}
}
```
Please see the [grunt-template-jasmine-requirejs](https://github.com/jsoverson/grunt-template-jasmine-requirejs)
## Release History
* 2013-01-07v0.3.0Added JUnit xml output (via Kelvin Luck @vitch) Passing console.log from browser to verbose grunt logging Support for templates as separate node modules Removed internal requirejs template (see grunt-template-jasmine-requirejs)
* 2012-12-02v0.2.0Generalized requirejs template config Added loader plugin Tests for templates Updated jasmine to 1.3.0
* 2012-11-23v0.1.2Updated for new grunt/grunt-contrib apis
* 2012-11-06v0.1.1Fixed race condition in requirejs template
@ -253,4 +196,4 @@ require([*YOUR SOURCE*], function() {
Task submitted by [Jarrod Overson](http://jarrodoverson.com)
*This file was generated on Thu Dec 13 2012 09:07:25.*
*This file was generated on Tue Jan 08 2013 11:28:08.*

View File

@ -41,50 +41,4 @@ grunt.initConfig({
## Sample RequireJS usage
```js
// Example configuration
grunt.initConfig({
connect: {
test : {
port : 8000
}
}
jasmine: {
requirejs: {
src: 'src/**/*.js',
options: {
specs: 'spec/*Spec.js',
helpers: 'spec/*Helper.js',
host: 'http://127.0.0.1:8000/',
template: 'requirejs',
templateOptions: {
requireConfig: {
baseUrl: 'src/'
}
}
}
}
}
}
```
*Note* the usage of the 'connect' task configuration. You will need to use a task like
[grunt-contrib-connect][] if you need to test your tasks on a running server.
[grunt-contrib-connect]: https://github.com/gruntjs/grunt-contrib-connect
## RequireJS notes
If you end up using the requirejs template, it's worth looking at the
[RequireJS template source](https://github.com/gruntjs/grunt-contrib-jasmine/blob/master/tasks/jasmine/templates/RequireJSRunner.tmpl)
in order to familiarize yourself with how it loads your files. The load process essentially
consists of a series of nested `require` blocks, incrementally loading your source and specs:
```js
require([*YOUR SOURCE*], function() {
require([*YOUR SPECS*], function() {
require([*GRUNT-CONTRIB-JASMINE FILES*], function() {
// at this point your tests are already running.
}
}
}
```
Please see the [grunt-template-jasmine-requirejs](https://github.com/jsoverson/grunt-template-jasmine-requirejs)

View File

@ -25,8 +25,20 @@ Type: `String|Array`
Type: `String`
Default: `_SpecRunner.html`
This is the auto-generated specfile that phantomjs will use to run your tests. This is automatically deleted upon normal
runs
This is the auto-generated specfile that phantomjs will use to run your tests.
This is automatically deleted upon normal runs
## options.junit.path
Type: `String`
Default: undefined
Path to output JUnit xml
## options.junit.consolidate
Type: `Boolean`
Default: `false`
Consolidate the JUnit XML so that there is one file per top level suite.
## options.host
Type: `String`
@ -49,27 +61,21 @@ host : 'http://127.0.0.1:<%= connect.port %>/'
Not defining a host will mean your specs will be run from the local filesystem.
## options.template
Type: `String`
Default: `default`
Options: `default`, `requirejs`, `yourcustomtemplate.tmpl`
Type: `String` `Object`
Default: undefined
Specify a custom template to use when generating your Spec Runner. Templates are parsed as underscore templates and provided
Specify a custom template used to generate your Spec Runner. Templates are parsed as underscore templates and provided
the expanded list of files needed to build a specrunner.
You can specify an object with a `process` method that will be called as a template function.
See the [Template API Documentation](needs-wiki-link) for more details.
## options.templateOptions
Type: `Object`
Default: `{}`
These options will be passed to your template as an 'options' hash so that you can provide settings to your template.
## options.junit
Type: `Object`
Default: `{}`
Set `options.junit.path` to generate JUnit compatible XML from the task (for use in a CI system such as Jenkins).
Set `options.junit.consolidate` to consolidate the generated XML files so that there is one file per top level suite.
# Flags
Name: `build`
@ -85,29 +91,3 @@ watch: {
}
}
```
# Template Options
## Default template
No specific options are expected or used.
## RequireJS template
### templateOptions.requirejs
Type: `String`
The path to requirejs if you need to specify an alternate version.
### templateOptions.loaderPlugin
Type: `String`
The loader plugin to prefix all loaded `src` files. This is useful for processing
your specs through the likes of CoffeeScript or TypeScript plugins. Keep in mind
you will need to specify the path to the plugin in the require config.
### templateOptions.requireConfig
Type: `Object`
This object is `JSON.stringify()`-ed into the template and passed into `require.config()`

View File

@ -12,11 +12,11 @@ Run your tests on your local filesystem or via a server task like [grunt-contrib
## AMD Support
Includes a SpecRunner template customized for use with AMD modules and requirejs.
Supports AMD tests via the [grunt-template-jasmine-requirejs](https://github.com/jsoverson/grunt-template-jasmine-requirejs) module
## Customize your SpecRunner with your own template
Supply your own underscore template to automatically build your SpecRunner custom to your use.
Supply your templates that will be used to automatically build the SpecRunner.
## Example application usage

View File

@ -29,7 +29,8 @@
},
"dependencies": {
"grunt-lib-phantomjs": "~0.1.0",
"grunt": "~0.4.0"
"grunt": "~0.4.0",
"rimraf": "~2.0.3"
},
"devDependencies": {
"grunt-contrib-internal": "~0.1.1",

View File

@ -5,21 +5,21 @@
* Licensed under the MIT license.
*/
/*jshint node:true, curly:false*/
'use strict';
module.exports = function(grunt) {
// node api
var fs = require('fs'),
path = require('path');
path = require('path');
// npm lib
var phantomjs = require('grunt-lib-phantomjs').init(grunt);
// local lib
var jasmine = require('./lib/jasmine');
var jasmine = require('./lib/jasmine').init(grunt);
var junitTemplate = __dirname + '/jasmine/templates/JUnit.tmpl';
var status = {
specs : 0,
@ -30,18 +30,6 @@ module.exports = function(grunt) {
duration : 0
};
var runners = {
default : __dirname + '/jasmine/templates/DefaultRunner.tmpl',
requirejs : __dirname + '/jasmine/templates/RequireJSRunner.tmpl',
junit : __dirname + '/jasmine/templates/JUnit.tmpl'
};
var runnerOptions = {
requirejs : {
requirejs : jasmine.getRelativeFileList(__dirname + '/../vendor/require-2.1.1.js')[0]
}
};
grunt.registerMultiTask('jasmine', 'Run jasmine specs headlessly through PhantomJS.', function() {
// Merge task-specific options with these defaults.
@ -52,15 +40,12 @@ module.exports = function(grunt) {
vendor : [],
outfile : '_SpecRunner.html',
host : '',
template: 'default',
template : __dirname + '/jasmine/templates/DefaultRunner.tmpl',
templateOptions : {},
phantomjs : {},
junit: {}
});
grunt.util._.defaults(options.templateOptions, runnerOptions[options.template] || {});
if (!options.template.match(/\.tmpl$/)) options.template = runners[options.template];
if (grunt.option('debug')) {
grunt.log.debug(options);
}
@ -84,7 +69,6 @@ module.exports = function(grunt) {
});
function phantomRunner(options,cb){
var file = options.outfile;
@ -101,11 +85,11 @@ module.exports = function(grunt) {
cb(err,status);
}
});
}
function teardown(options) {
if (fs.statSync(options.outfile).isFile()) fs.unlink(options.outfile);
jasmine.cleanTemp();
// Have to explicitly unregister nested wildcards. Need to file a bug for EventEmitter2
phantomjs.removeAllListeners('*');
@ -145,16 +129,6 @@ module.exports = function(grunt) {
grunt.event.emit.apply(grunt.event, args);
});
// Not used?
// phantomjs.on('jasmine.writeFile',function(type,filename, xml){
// var dir = options[type] && options[type].output;
// if (dir) {
// grunt.file.mkdir(dir);
// grunt.file.write(path.join(dir, filename), xml);
// }
// });
phantomjs.on('jasmine.reportRunnerStarting',function(suites) {
grunt.verbose.writeln('Starting...');
thisRun.start_time = (new Date()).getTime();
@ -208,40 +182,17 @@ module.exports = function(grunt) {
phantomjs.on('jasmine.reportJUnitResults',function(junitData){
if (options.junit && options.junit.path) {
var template = grunt.file.read(junitTemplate);
if (options.junit.consolidate) {
grunt.util._(junitData.consolidatedSuites).each(
function(suites)
{
grunt.file.copy(runners.junit, path.join(options.junit.path, 'TEST-' + suites[0].name.replace(/[^\w]/g, '') + '.xml'), {
process: function(src) {
return grunt.util._.template(
src,
{
testsuites: suites
}
);
}
});
}
);
grunt.util._(junitData.consolidatedSuites).each(function(suites){
var xmlFile = path.join(options.junit.path, 'TEST-' + suites[0].name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, grunt.util._.template(template, { testsuites: suites}));
});
} else {
junitData.suites.forEach(
function(suiteData)
{
grunt.file.copy(runners.junit, path.join(options.junit.path, 'TEST-' + suiteData.name.replace(/[^\w]/g, '') + '.xml'), {
process: function(src) {
return grunt.util._.template(
src,
{
testsuites: [suiteData]
}
);
}
});
}
);
junitData.suites.forEach(function(suiteData){
var xmlFile = path.join(options.junit.path, 'TEST-' + suiteData.name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, grunt.util._.template(template, { testsuites: [suiteData] }));
});
}
}
});

View File

@ -1,94 +0,0 @@
/*global jasmine */
(function()
{
'use strict';
function getNestedSuiteName(suite)
{
var names = [];
while (suite) {
names.unshift(suite.description);
suite = suite.parentSuite;
}
return names.join(' ');
}
function getTopLevelSuiteId(suite)
{
var id;
while (suite) {
id = suite.id;
suite = suite.parentSuite;
}
return id;
}
jasmine.JUnitDataReporter = function()
{
};
jasmine.JUnitDataReporter.prototype = {
reportRunnerStarting: function(runner) {
},
reportRunnerResults: function(runner) {
var suitesById = {},
suites = runner.suites().map(
function(suite)
{
var failures = 0,
data = {
topLevelSuiteId: getTopLevelSuiteId(suite),
name: getNestedSuiteName(suite),
time: suite.duration / 1000,
timestamp: suite.timestamp,
tests: suite.specs().length,
errors: 0, // TODO: These exist in the JUnit XML but not sure how they map to jasmine things
testcases: suite.specs().map(
function(spec)
{
var failureMessages = [];
if (spec.results().failedCount) {
failures ++;
spec.results().items_.forEach(
function(expectation)
{
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
}
);
}
return {
assertions: spec.results().items_.length,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
}
)
};
data.failures = failures;
suitesById[suite.id] = data;
return data;
}
);
console.log('Suites:', suites);
},
reportSuiteResults: function(suite) {
suite.timestamp = new Date();
suite.duration = suite.timestamp.getTime() - suite.specs()[0].startTime;
},
reportSpecStarting: function(spec) {
spec.startTime = (new Date()).getTime();
},
reportSpecResults: function(spec) {
spec.duration = (new Date()).getTime() - spec.startTime;
},
log: function(str) {
console.log(str);
}
};
}());

View File

@ -1,187 +0,0 @@
/* Based off of https://github.com/larrymyers/jasmine-reporters/ */
/*global phantom:false, jasmine:false*/
// Unused right now. Logic needs to be brought onto the grunt side, not on the phantom side
(function() {
"use strict";
function elapsed(startTime, endTime) {
return (endTime - startTime)/1000;
}
function ISODateString(d) {
function pad(n) { return n < 10 ? '0'+n : n; }
return d.getFullYear() + '-' +
pad(d.getMonth()+1) + '-' +
pad(d.getDate()) + 'T' +
pad(d.getHours()) + ':' +
pad(d.getMinutes()) + ':' +
pad(d.getSeconds());
}
function trim(str) {
return str.replace(/^\s+/, "" ).replace(/\s+$/, "" );
}
function escapeInvalidXmlChars(str) {
return str.replace(/\&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/\>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/\'/g, "&apos;");
}
/**
* Generates JUnit XML for the given spec run.
* Allows the test results to be used in java based CI
* systems like CruiseControl and Hudson.
*
* @param {string} savePath where to save the files
* @param {boolean} consolidate whether to save nested describes within the
* same file as their parent; default: true
* @param {boolean} useDotNotation whether to separate suite names with
* dots rather than spaces (ie "Class.init" not
* "Class init"); default: true
*/
function JUnitXmlReporter(savePath, consolidate, useDotNotation) {
this.savePath = savePath || '';
this.consolidate = typeof consolidate === 'undefined' ? true : consolidate;
this.useDotNotation = typeof useDotNotation === 'undefined' ? true : useDotNotation;
}
JUnitXmlReporter.finished_at = null; // will be updated after all files have been written
JUnitXmlReporter.prototype = {
reportSpecStarting: function(spec) {
spec.startTime = new Date();
if (!spec.suite.startTime) {
spec.suite.startTime = spec.startTime;
}
},
reportSpecResults: function(spec) {
var results = spec.results();
spec.didFail = !results.passed();
spec.duration = elapsed(spec.startTime, new Date());
spec.output = '<testcase classname="' + this.getFullName(spec.suite) +
'" name="' + escapeInvalidXmlChars(spec.description) + '" time="' + spec.duration + '">';
var failure = "";
var failures = 0;
var resultItems = results.getItems();
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type === 'expect' && result.passed && !result.passed()) {
failures += 1;
failure += (failures + ": " + escapeInvalidXmlChars(result.message) + " ");
}
}
if (failure) {
spec.output += "<failure>" + trim(failure) + "</failure>";
}
spec.output += "</testcase>";
},
reportSuiteResults: function(suite) {
var results = suite.results();
var specs = suite.specs();
var specOutput = "";
// for JUnit results, let's only include directly failed tests (not nested suites')
var failedCount = 0;
suite.status = results.passed() ? 'Passed.' : 'Failed.';
if (results.totalCount === 0) { // todo: change this to check results.skipped
suite.status = 'Skipped.';
}
// if a suite has no (active?) specs, reportSpecStarting is never called
// and thus the suite has no startTime -- account for that here
suite.startTime = suite.startTime || new Date();
suite.duration = elapsed(suite.startTime, new Date());
for (var i = 0; i < specs.length; i++) {
failedCount += specs[i].didFail ? 1 : 0;
specOutput += "\n " + specs[i].output;
}
suite.output = '\n<testsuite name="' + this.getFullName(suite) +
'" errors="0" tests="' + specs.length + '" failures="' + failedCount +
'" time="' + suite.duration + '" timestamp="' + new ISODateString(suite.startTime) + '">';
suite.output += specOutput;
suite.output += "\n</testsuite>";
},
reportRunnerResults: function(runner) {
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var fileName = 'TEST-' + this.getFullName(suite, true) + '.xml';
var output = '<?xml version="1.0" encoding="UTF-8" ?>';
// if we are consolidating, only write out top-level suites
if (this.consolidate && suite.parentSuite) {
continue;
}
else if (this.consolidate) {
output += "\n<testsuites>";
output += this.getNestedOutput(suite);
output += "\n</testsuites>";
this.writeFile(this.savePath + fileName, output);
}
else {
output += suite.output;
this.writeFile(this.savePath + fileName, output);
}
}
// When all done, make it known on JUnitXmlReporter
JUnitXmlReporter.finished_at = (new Date()).getTime();
phantom.sendMessage('jasmine.done.JUnitReporter');
},
getNestedOutput: function(suite) {
var output = suite.output;
for (var i = 0; i < suite.suites().length; i++) {
output += this.getNestedOutput(suite.suites()[i]);
}
return output;
},
writeFile: function(filename, text) {
phantom.sendMessage('jasmine.writeFile','junit',filename,text);
},
getFullName: function(suite, isFilename) {
var fullName;
if (this.useDotNotation) {
fullName = suite.description;
for (var parentSuite = suite.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
fullName = parentSuite.description + '.' + fullName;
}
}
else {
fullName = suite.getFullName();
}
// Either remove or escape invalid XML characters
if (isFilename) {
return fullName.replace(/[^\w]/g, "");
}
return escapeInvalidXmlChars(fullName);
},
log: function(str) {
var console = jasmine.getGlobal().console;
if (console && console.log) {
console.log(str);
}
}
};
jasmine.reporters = jasmine.reporters || {};
jasmine.reporters.JUnitXmlReporter = JUnitXmlReporter;
jasmine.getEnv().addReporter( new JUnitXmlReporter() );
}());

View File

@ -6,13 +6,17 @@
var phantom = {};
phantom.sendMessage = function(){
if (window._phantom) {
console.log = function(){
phantom.sendMessage('verbose',Array.prototype.slice.apply(arguments).join(', '));
};
}
phantom.sendMessage = function() {
var args = [].slice.call( arguments );
var payload = JSON.stringify( args );
if (window._phantom) {
alert( payload );
} else {
console.log(payload);
}
};
@ -24,10 +28,6 @@
this.buffer = '';
}
PhantomReporter.prototype.log = function(str) {
phantom.sendMessage('verbose',str);
};
PhantomReporter.prototype.reportRunnerStarting = function(runner) {
this.started = true;
@ -85,7 +85,7 @@
var specIds = runner.specs().map(function(a){return a.id;});
var summary = this.resultsForSpecs(specIds);
phantom.sendMessage('jasmine.reportRunnerResults',summary);
phantom.sendMessage('jasmine.reportJUnitResults', this.generateJUnitSummary_(runner));
phantom.sendMessage('jasmine.reportJUnitResults', this.generateJUnitSummary(runner));
phantom.sendMessage('jasmine.done.PhantomReporter');
};
@ -196,8 +196,7 @@
};
};
function getNestedSuiteName(suite)
{
function getNestedSuiteName(suite) {
var names = [];
while (suite) {
names.unshift(suite.description);
@ -206,8 +205,7 @@
return names.join(' ');
}
function getTopLevelSuiteId(suite)
{
function getTopLevelSuiteId(suite) {
var id;
while (suite) {
id = suite.id;
@ -216,52 +214,47 @@
return id;
}
PhantomReporter.prototype.generateJUnitSummary_ = function(runner) {
PhantomReporter.prototype.generateJUnitSummary = function(runner) {
var consolidatedSuites = {},
suites = runner.suites().map(
function(suite)
{
var failures = 0,
data = {
name: getNestedSuiteName(suite),
time: suite.duration / 1000,
timestamp: suite.timestamp,
tests: suite.specs().length,
errors: 0, // TODO: These exist in the JUnit XML but not sure how they map to jasmine things
testcases: suite.specs().map(
function(spec)
{
var failureMessages = [];
if (spec.results().failedCount) {
failures ++;
spec.results().items_.forEach(
function(expectation)
{
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
}
);
}
return {
assertions: spec.results().items_.length,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
}
)
};
data.failures = failures;
if (suite.parentSuite) {
consolidatedSuites[getTopLevelSuiteId(suite)].push(data);
} else {
consolidatedSuites[suite.id] = [data];
}
return data;
suites = runner.suites().map(function(suite) {
var failures = 0;
var testcases = suite.specs().map(function(spec) {
var failureMessages = [];
if (spec.results().failedCount) {
failures++;
spec.results().items_.forEach(function(expectation) {
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
});
}
);
return {
assertions: spec.results().items_.length,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
});
var data = {
name: getNestedSuiteName(suite),
time: suite.duration / 1000,
timestamp: suite.timestamp,
tests: suite.specs().length,
errors: 0, // TODO: These exist in the JUnit XML but not sure how they map to jasmine things
testcases: testcases,
failures: failures
};
if (suite.parentSuite) {
consolidatedSuites[getTopLevelSuiteId(suite)].push(data);
} else {
consolidatedSuites[suite.id] = [data];
}
return data;
});
return {
suites: suites,

View File

@ -1,40 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<script src="<%= options.requirejs %>"></script>
<script>
<% if (options.requireConfig) { %>
require.config(<%= JSON.stringify(options.requireConfig) %>);
<% } %>
</script>
<% css.forEach(function(style){ %>
<link rel="stylesheet" type="text/css" href="<%= style %>">
<% }) %>
<% with (scripts) { %>
<% [].concat(jasmine, vendor, helpers).forEach(function(script){ %>
<script src="<%= script %>"></script>
<% }) %>
<% }; %>
<script>
require([
<% scripts.src.forEach(function(script, i){ %>
<% if (options.requireConfig && options.requireConfig.baseUrl) script = script.replace(new RegExp('^' + options.requireConfig.baseUrl),""); %>
<% if (options.loaderPlugin) script = options.loaderPlugin + '!' + script %>
'<%= script.replace(/\.js$/,"") %>' <%= i < scripts.src.length-1 ? ',' : '' %>
<% }) %>
],
function(){
require(['<%= [].concat(scripts.specs,scripts.reporters).join("','") %>'], function(){
require(['<%= scripts.start.join("','") %>'], function(){
// good to go! Our tests should already be running.
})
})
}
)
</script>
</head>
<body>
</body>
</html>

View File

@ -1,67 +1,103 @@
/*jshint latedef:false, curly:false*/
'use strict';
var grunt = require('grunt'),
path = require('path');
exports.init = function(grunt) {
// node api
var fs = require('fs'),
path = require('path');
var baseDir = '.';
// npm
var rimraf = require('rimraf');
exports.buildSpecrunner = buildSpecrunner;
exports.getRelativeFileList = getRelativeFileList;
var baseDir = '.',
tempDir = '.grunt/grunt-contrib-jasmine';
function buildSpecrunner(src, options){
var reporters = [
__dirname + '/../jasmine/reporters/PhantomReporter.js'
];
var exports = {};
var jasmineCss = [
__dirname + '/../../vendor/jasmine-1.3.0/jasmine.css'
];
var jasmineCore = [
__dirname + '/../../vendor/jasmine-1.3.0/jasmine.js',
__dirname + '/../../vendor/jasmine-1.3.0/jasmine-html.js'
];
var jasmineHelper = __dirname + '/../jasmine/jasmine-helper.js';
var context = {
css : getRelativeFileList(jasmineCss),
scripts : {
jasmine : getRelativeFileList(jasmineCore),
helpers : getRelativeFileList(options.helpers),
specs : getRelativeFileList(options.specs),
src : getRelativeFileList(src),
vendor : getRelativeFileList(options.vendor),
reporters : getRelativeFileList(reporters),
start : getRelativeFileList(jasmineHelper)
},
options : options.templateOptions || {}
exports.writeTempFile = function(dest, contents) {
var file = path.join(tempDir,dest);
grunt.file.write(file, contents);
};
var source = '';
grunt.file.copy(options.template, path.join(baseDir,options.outfile), {
process : function(src) {
source = grunt.util._.template(src, context);
return source;
exports.copyTempFile = function(src, dest) {
var file = path.join(tempDir,dest);
grunt.file.copy(src, file);
};
exports.cleanTemp = function() {
rimraf.sync(tempDir);
};
exports.buildSpecrunner = function (src, options){
exports.copyTempFile(__dirname + '/../jasmine/reporters/PhantomReporter.js', 'reporter.js');
exports.copyTempFile(__dirname + '/../../vendor/jasmine-1.3.0/jasmine.css', 'jasmine.css');
exports.copyTempFile(__dirname + '/../../vendor/jasmine-1.3.0/jasmine.js', 'jasmine.js');
exports.copyTempFile(__dirname + '/../../vendor/jasmine-1.3.0/jasmine-html.js', 'jasmine-html.js');
exports.copyTempFile(__dirname + '/../jasmine/jasmine-helper.js', 'jasmine-helper.js');
var reporters = [
tempDir + '/reporter.js'
];
var jasmineCss = [
tempDir + '/jasmine.css'
];
var jasmineCore = [
tempDir + '/jasmine.js',
tempDir + '/jasmine-html.js'
];
var jasmineHelper = tempDir + '/jasmine-helper.js';
var context = {
temp : tempDir,
css : jasmineCss,
scripts : {
jasmine : exports.getRelativeFileList(jasmineCore),
helpers : exports.getRelativeFileList(options.helpers),
specs : exports.getRelativeFileList(options.specs),
src : exports.getRelativeFileList(src),
vendor : exports.getRelativeFileList(options.vendor),
reporters : exports.getRelativeFileList(reporters),
start : exports.getRelativeFileList(jasmineHelper)
},
options : options.templateOptions || {}
};
var source = '',
specrunner = path.join(baseDir,options.outfile);
if (options.template.process) {
source = options.template.process(grunt, exports, context);
grunt.file.write(specrunner, source);
} else {
grunt.file.copy(options.template, specrunner, {
process : function(src) {
source = grunt.util._.template(src, context);
return source;
}
});
}
});
return source;
}
return source;
};
function getRelativeFileList(/* args... */) {
exports.getRelativeFileList = function (/* args... */) {
var list = Array.prototype.slice.call(arguments);
var base = path.resolve(baseDir);
var files = [];
list.forEach(function(listItem){
if (listItem) files = files.concat(grunt.file.expandFiles(listItem));
});
files = grunt.util._(files).map(function(file){
return path.resolve(file).replace(base,'.').replace(/\\/g,'/');
});
return files;
}
var list = Array.prototype.slice.call(arguments);
var base = path.resolve(baseDir);
var files = [];
list.forEach(function(listItem){
if (listItem) files = files.concat(grunt.file.expandFiles(listItem));
});
files = grunt.util._(files).map(function(file){
return path.resolve(file).replace(base,'.').replace(/\\/g,'/');
});
return files;
};
return exports;
};

View File

@ -1,34 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<script src=""></script>
<script>
</script>
<link rel="stylesheet" type="text/css" href="css/a.css">
<script src="J1.js"></script>
<script src="J2.js"></script>
<script src="V1.js"></script>
<script src="V2.js"></script>
<script src="H1.js"></script>
<script src="H2.js"></script>
<script>
require([
'foo/bar/SRC1',
'foo/bar/SRC2'
],
function(){
require(['SPEC1.js','SPEC2.js','R1.js'], function(){
require(['START.js'], function(){
// good to go! Our tests should already be running.
})
})
}
)
</script>
</head>
<body>
</body>
</html>

View File

@ -1,40 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<script src="path/to/require.js"></script>
<script>
require.config({
"baseUrl" : "foo/bar/",
"paths" : {
"cs" : "plugins/cs"
}
});
</script>
<link rel="stylesheet" type="text/css" href="css/a.css">
<script src="J1.js"></script>
<script src="J2.js"></script>
<script src="V1.js"></script>
<script src="V2.js"></script>
<script src="H1.js"></script>
<script src="H2.js"></script>
<script>
require([
'cs!SRC1' ,
'cs!SRC2'
],
function(){
require(['SPEC1.js','SPEC2.js','R1.js'], function(){
require(['START.js'], function(){
// good to go! Our tests should already be running.
})
})
}
)
</script>
</head>
<body>
</body>
</html>

View File

@ -1,10 +0,0 @@
describe('Example AMD test', function(){
var math = require('math');
describe('Sum', function(){
it('Should add two numbers together', function(){
expect(math.sum(2,10)).toEqual(12);
})
})
});

View File

@ -1,5 +0,0 @@
define(['sum'],function(sum){
return {
sum : sum
}
})

View File

@ -1,5 +0,0 @@
define(function(){
return function(a,b){
return a + b;
}
})

View File

@ -3,7 +3,6 @@
var grunt = require('grunt');
// Majority of test benefit comes from running the task itself.
// This is kept around for future use.
function runTemplate(src,context) {
var source = grunt.file.read(src);
@ -39,64 +38,6 @@ exports.jasmine = {
test.equal(normalize(actual),normalize(expected), 'default test runner template');
test.done();
},
basicRequireJsTemplate: function(test) {
test.expect(1);
var context = {
css : ['css/a.css'],
scripts : {
jasmine : ['J1.js','J2.js'],
helpers : ['H1.js','H2.js'],
specs : ['SPEC1.js','SPEC2.js'],
src : ['foo/bar/SRC1.js','foo/bar/SRC2.js'],
vendor : ['V1.js','V2.js'],
reporters : ['R1.js'],
start : ['START.js']
},
options : {
}
};
var actual = runTemplate('./tasks/jasmine/templates/RequireJSRunner.tmpl', context);
var expected = grunt.file.read('./test/expected/basicRequireJSTemplate.html');
test.equal(normalize(actual),normalize(expected), 'default test runner template');
test.done();
},
requireJsTemplate: function(test) {
test.expect(1);
var context = {
css : ['css/a.css'],
scripts : {
jasmine : ['J1.js','J2.js'],
helpers : ['H1.js','H2.js'],
specs : ['SPEC1.js','SPEC2.js'],
src : ['foo/bar/SRC1.js','foo/bar/SRC2.js'],
vendor : ['V1.js','V2.js'],
reporters : ['R1.js'],
start : ['START.js']
},
options : {
requirejs : 'path/to/require.js',
loaderPlugin : 'cs',
requireConfig : {
baseUrl : 'foo/bar/',
paths : {
cs : 'plugins/cs'
}
}
}
};
var actual = runTemplate('./tasks/jasmine/templates/RequireJSRunner.tmpl', context);
var expected = grunt.file.read('./test/expected/requireJSTemplate.html');
test.equal(normalize(actual),normalize(expected), 'default test runner template');
test.done();
}
};