From 7fd976a2a14a1b0552dee03e3eb305ba33dae972 Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Tue, 8 Jan 2013 11:28:26 -0800 Subject: [PATCH] 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 --- .jshintrc | 2 +- CHANGELOG | 9 +- Gruntfile.js | 27 +-- README.md | 107 +++-------- docs/jasmine-examples.md | 48 +---- docs/jasmine-options.md | 60 ++---- docs/jasmine-overview.md | 4 +- package.json | 3 +- tasks/jasmine.js | 79 ++------ tasks/jasmine/reporters/JUnitDataReporter.js | 94 ---------- tasks/jasmine/reporters/JUnitReporter.js | 187 ------------------- tasks/jasmine/reporters/PhantomReporter.js | 105 +++++------ tasks/jasmine/templates/RequireJSRunner.tmpl | 40 ---- tasks/lib/jasmine.js | 142 ++++++++------ test/expected/basicRequireJSTemplate.html | 34 ---- test/expected/requireJSTemplate.html | 40 ---- test/fixtures/requirejs/spec/SumSpec.js | 10 - test/fixtures/requirejs/src/math.js | 5 - test/fixtures/requirejs/src/sum.js | 5 - test/jasmine_test.js | 59 ------ 20 files changed, 222 insertions(+), 838 deletions(-) delete mode 100644 tasks/jasmine/reporters/JUnitDataReporter.js delete mode 100644 tasks/jasmine/reporters/JUnitReporter.js delete mode 100644 tasks/jasmine/templates/RequireJSRunner.tmpl delete mode 100644 test/expected/basicRequireJSTemplate.html delete mode 100644 test/expected/requireJSTemplate.html delete mode 100644 test/fixtures/requirejs/spec/SumSpec.js delete mode 100644 test/fixtures/requirejs/src/math.js delete mode 100644 test/fixtures/requirejs/src/sum.js diff --git a/.jshintrc b/.jshintrc index 6b4c1a9..68d4c0a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,5 +1,5 @@ { - "curly": true, + "curly": false, "eqeqeq": true, "immed": true, "latedef": true, diff --git a/CHANGELOG b/CHANGELOG index 6e20b16..688bb3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/Gruntfile.js b/Gruntfile.js index 60776c4..1c8dbb3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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']); }; diff --git a/README.md b/README.md index 9a66d81..9a2cb1a 100644 --- a/README.md +++ b/README.md @@ -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-07   v0.3.0   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) * 2012-12-02   v0.2.0   Generalized requirejs template config Added loader plugin Tests for templates Updated jasmine to 1.3.0 * 2012-11-23   v0.1.2   Updated for new grunt/grunt-contrib apis * 2012-11-06   v0.1.1   Fixed 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.* diff --git a/docs/jasmine-examples.md b/docs/jasmine-examples.md index 00868cf..5aeb5c3 100644 --- a/docs/jasmine-examples.md +++ b/docs/jasmine-examples.md @@ -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) diff --git a/docs/jasmine-options.md b/docs/jasmine-options.md index f7fc016..1686402 100644 --- a/docs/jasmine-options.md +++ b/docs/jasmine-options.md @@ -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()` - diff --git a/docs/jasmine-overview.md b/docs/jasmine-overview.md index 184783a..90164d3 100644 --- a/docs/jasmine-overview.md +++ b/docs/jasmine-overview.md @@ -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 diff --git a/package.json b/package.json index 42402a6..99d4ba3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tasks/jasmine.js b/tasks/jasmine.js index 5dfd034..4b7e5a3 100644 --- a/tasks/jasmine.js +++ b/tasks/jasmine.js @@ -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] })); + }); } } }); diff --git a/tasks/jasmine/reporters/JUnitDataReporter.js b/tasks/jasmine/reporters/JUnitDataReporter.js deleted file mode 100644 index 8ca899a..0000000 --- a/tasks/jasmine/reporters/JUnitDataReporter.js +++ /dev/null @@ -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); - } - }; - -}()); \ No newline at end of file diff --git a/tasks/jasmine/reporters/JUnitReporter.js b/tasks/jasmine/reporters/JUnitReporter.js deleted file mode 100644 index b8703e5..0000000 --- a/tasks/jasmine/reporters/JUnitReporter.js +++ /dev/null @@ -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, "&") - .replace(//g, ">") - .replace(/\"/g, """) - .replace(/\'/g, "'"); - } - - /** - * 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 = ''; - - 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 += "" + trim(failure) + ""; - } - spec.output += ""; - }, - - 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'; - suite.output += specOutput; - suite.output += "\n"; - }, - - 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 = ''; - // if we are consolidating, only write out top-level suites - if (this.consolidate && suite.parentSuite) { - continue; - } - else if (this.consolidate) { - output += "\n"; - output += this.getNestedOutput(suite); - output += "\n"; - 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() ); -}()); diff --git a/tasks/jasmine/reporters/PhantomReporter.js b/tasks/jasmine/reporters/PhantomReporter.js index a953f61..4b85e41 100644 --- a/tasks/jasmine/reporters/PhantomReporter.js +++ b/tasks/jasmine/reporters/PhantomReporter.js @@ -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, diff --git a/tasks/jasmine/templates/RequireJSRunner.tmpl b/tasks/jasmine/templates/RequireJSRunner.tmpl deleted file mode 100644 index a0fbbcd..0000000 --- a/tasks/jasmine/templates/RequireJSRunner.tmpl +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Jasmine Spec Runner - - - <% css.forEach(function(style){ %> - - <% }) %> - <% with (scripts) { %> - <% [].concat(jasmine, vendor, helpers).forEach(function(script){ %> - - <% }) %> - <% }; %> - - - - - diff --git a/tasks/lib/jasmine.js b/tasks/lib/jasmine.js index 693db85..ee282f4 100644 --- a/tasks/lib/jasmine.js +++ b/tasks/lib/jasmine.js @@ -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; +}; diff --git a/test/expected/basicRequireJSTemplate.html b/test/expected/basicRequireJSTemplate.html deleted file mode 100644 index 5413563..0000000 --- a/test/expected/basicRequireJSTemplate.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Jasmine Spec Runner - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/expected/requireJSTemplate.html b/test/expected/requireJSTemplate.html deleted file mode 100644 index 3bfe71a..0000000 --- a/test/expected/requireJSTemplate.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Jasmine Spec Runner - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/fixtures/requirejs/spec/SumSpec.js b/test/fixtures/requirejs/spec/SumSpec.js deleted file mode 100644 index 359b5bc..0000000 --- a/test/fixtures/requirejs/spec/SumSpec.js +++ /dev/null @@ -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); - }) - }) -}); \ No newline at end of file diff --git a/test/fixtures/requirejs/src/math.js b/test/fixtures/requirejs/src/math.js deleted file mode 100644 index 8a61159..0000000 --- a/test/fixtures/requirejs/src/math.js +++ /dev/null @@ -1,5 +0,0 @@ -define(['sum'],function(sum){ - return { - sum : sum - } -}) \ No newline at end of file diff --git a/test/fixtures/requirejs/src/sum.js b/test/fixtures/requirejs/src/sum.js deleted file mode 100644 index 139cce6..0000000 --- a/test/fixtures/requirejs/src/sum.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function(){ - return function(a,b){ - return a + b; - } -}) \ No newline at end of file diff --git a/test/jasmine_test.js b/test/jasmine_test.js index 85be4e9..42ff1f5 100644 --- a/test/jasmine_test.js +++ b/test/jasmine_test.js @@ -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(); } };