diff --git a/README.md b/README.md index fc94955e..2d5deaef 100644 --- a/README.md +++ b/README.md @@ -175,20 +175,31 @@ command | description `grunt doc` | will build just the code documentation; `grunt lint` | will just lint your code; `grunt test` | will run your test suite; +`grunt test:dev` | will run your test suite and will keep monitoring it for changes, triggering re-runs; `grunt --help` | will show you all of the above and the kitchen sink; ## Unit tests -BAT comes with support for unit testing using [Mocha](http://mochajs.org/) and [Chai](http://chaijs.com/). The demo webapp has a basic (and admittedly, pretty unconnected) example of this. +BAT comes with support for unit testing using [Karma](http://karma-runner.github.io/1.0/), [Jasmine](http://jasmine.github.io/2.4/introduction.html) and [PhantomJS](http://phantomjs.org/). -You may already have guessed how to independently run your test suite; it's: +Unit testing is an integrated build step in both `default` and `debug` build runs, but can also be run independently as: ```shell grunt test ``` -The default and `debug` grunt tasks also include the `test` task. You may or may not want to add it to your `dev` task too, depending on your workflow. +And as watched, continuous test runs as: + +```shell +grunt test:dev +``` + +The latter invocation, while it is kept running, also offers the opportunity to launch a test suite run in any browser, simply by directing it to this url: + +[`http://localhost:9876/debug.html`](http://localhost:9876/debug.html) + +*Do not forget to open your dev tools and browser console there!* ## ChangeLog diff --git a/generators/app/index.js b/generators/app/index.js index 24b865b9..259f3135 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -347,6 +347,13 @@ var AppGenerator = generators.Base.extend( // Testing: , 'test' + , 'test/unit' + , 'test/unit/asset' + , 'test/unit/spec' + , 'test/unit/spec/collections' + , 'test/unit/spec/mixins' + , 'test/unit/spec/models' + , 'test/unit/spec/views' // Third-party, external libraries: @@ -420,6 +427,11 @@ var AppGenerator = generators.Base.extend( , [ 'settings/testing.json' ] , [ 'settings/local.json' ] + // Testing: + + , [ 'test/unit/init.coffee' ] + , 'test/unit/spec/trivial.spec.coffee' + ] ; @@ -486,7 +498,6 @@ var AppGenerator = generators.Base.extend( [ 'browserify' , 'browserify-shim' - , 'chai' , 'coffeeify' , 'coffee-script' , 'grunt' @@ -500,12 +511,18 @@ var AppGenerator = generators.Base.extend( , 'grunt-contrib-uglify' , 'grunt-contrib-watch' , 'grunt-contrib-yuidoc' - , 'grunt-mocha-test' + , 'grunt-karma' , 'grunt-template' , 'handlebars' , 'hbsfy' - , 'mocha' + , 'jasmine-core' + , 'karma' + , 'karma-browserify' + , 'karma-jasmine' + , 'karma-phantomjs-launcher' + , 'phantomjs-prebuilt' , 'standard-version' + , 'watchify' ] ; @@ -673,6 +690,9 @@ var AppGenerator = generators.Base.extend( + chalk.bold( ' * ' + chalk.yellow( 'grunt test ' )) + '- will run your test suite;\n' + + chalk.bold( ' * ' + chalk.yellow( 'grunt test:dev ' )) + + '- will run your test suite and will keep monitoring it for changes, triggering re-runs;\n' + + '\n' + chalk.bold( ' * ' + chalk.yellow( 'grunt --help ' )) + '- will show you all of the above and the kitchen sink;\n' diff --git a/generators/app/templates/@Gruntfile.coffee b/generators/app/templates/@Gruntfile.coffee index 7302054e..79c6ceb5 100644 --- a/generators/app/templates/@Gruntfile.coffee +++ b/generators/app/templates/@Gruntfile.coffee @@ -67,7 +67,7 @@ ## * Verification and testing: ## * coffeelint ## * coffee_jshint -## * mochaTest +## * karma & jasmine ## ## * Development support tools: ## * watch @@ -136,6 +136,7 @@ ## path = require( 'path' ) +_ = require( 'underscore' ) module.exports = ( grunt ) -> @@ -252,11 +253,39 @@ module.exports = ( grunt ) -> ## https://github.com/substack/node-browserify#browserifyfiles--opts ## ## https://github.com/substack/browserify-handbook#packagejson + ## ## file:./package.json - ## - browser : https://gist.github.com/defunctzombie/4339901 - ## - browserify-shim : https://github.com/thlorenz/browserify-shim#readme + ## + ## - browser : https://github.com/substack/browserify-handbook#browser-field + ## + ## You can define a "browser" field in the package.json of any package that will tell browserify to override lookups for the main field and + ## for individual modules. + ## + ## The browser field only applies to the current package. Any mappings you put will not propagate down to its dependencies or up to its + ## dependents. This isolation is designed to protect modules from each other so that when you require a module you won't need to worry about + ## any system-wide effects it might have. Likewise, you shouldn't need to wory about how your local configuration might adversely affect + ## modules far away deep into your dependency graph. + ## + ## See also: + ## - https://github.com/substack/node-browserify#browser-field + ## - https://gist.github.com/defunctzombie/4339901 + ## + ## ## - browserify.transform : https://github.com/substack/browserify-handbook#browserifytransform-field ## + ## You can configure transforms to be automatically applied when a module is loaded in a package's browserify.transform field. + ## + ## Like the "browser" field, transforms configured in package.json will only apply to the local package for the same reasons. + ## + ## See also: + ## - https://github.com/substack/node-browserify#browserifytransform + ## + ## - browserify-shim : https://github.com/substack/browserify-handbook#browserify-shim + ## + ## See also: + ## - https://github.com/thlorenz/browserify-shim#readme + ## + ## ## https://github.com/jnordberg/coffeeify#readme ## ## https://github.com/epeli/node-hbsfy#readme @@ -268,15 +297,22 @@ module.exports = ( grunt ) -> ## Transforms are ideally set in 'package.json' as 'browserify.transform'. ## Shadowed here as comments for easy reference. + ## + ## Browserify transforms are run in order and may modify your source code along the way. + ## You'll typically want to include browserify-shim last. + ## ### transform: [ - 'browserify-shim' 'coffeeify' 'hbsfy' + 'browserify-shim' ] ### - browserifyOptions: + ## Caveat: Using the extra variable `browserifyOptions` to share a common set between the different targets below. Afaict this can't be done + ## any other way. (Duplicating doesn't count). + ## + browserifyOptions: ( browserifyOptions = ## Scan all files for process, global, __filename, and __dirname, defining as necessary. ## With this option npm modules are more likely to work but bundling takes longer. @@ -298,6 +334,7 @@ module.exports = ( grunt ) -> noParse: [ 'jquery' ] + ) ## Non-debugging build ## @@ -312,11 +349,13 @@ module.exports = ( grunt ) -> app_debug: options: watch: true - browserifyOptions: - detectGlobals: '<%= browserify.options.browserifyOptions.detectGlobals %>' - extensions: '<%= browserify.options.browserifyOptions.extensions %>' - noParse: '<%= browserify.options.browserifyOptions.noParse %>' + browserifyOptions: _.extend( + {} + , + browserifyOptions + , debug: true + ) files: [ src: [ '<%= build.part.app.src.browserify %>', '<%= build.part.app.src.debug %>' ] @@ -474,7 +513,12 @@ module.exports = ( grunt ) -> files: '<%= coffeelint.gruntfile.files %>' test: - options: '<%= coffee_jshint.gruntfile.options %>' + options: + jshintOptions: jshintOptions.concat( [ + ## Environment options: + 'jasmine' + 'node' + ] ) files: '<%= coffeelint.test.files %>' @@ -603,25 +647,192 @@ module.exports = ( grunt ) -> ## - ## Test your build. + ## Test your code. + ## + ## https://github.com/karma-runner/grunt-karma#readme + ## + ## Karma: + ## https://github.com/karma-runner/karma#readme + ## http://karma-runner.github.io/1.0/ + ## + ## Browserify: + ## https://github.com/nikku/karma-browserify#readme + ## + ## See also the `browserify:` section in this config for more info on browserify and **its** preprocessors: + ## + ## coffeeify ## - ## https://github.com/pghalliday/grunt-mocha-test#readme + ## Jasmine: + ## https://github.com/karma-runner/karma-jasmine#readme + ## https://github.com/jasmine/jasmine#readme + ## http://jasmine.github.io/ + ## http://tryjasmine.com/ ## - ## https://github.com/mochajs/mocha#readme - ## http://mochajs.org/ + ## PhantomJS: + ## https://github.com/karma-runner/karma-phantomjs-launcher#readme + ## https://github.com/Medium/phantomjs#readme + ## http://phantomjs.org/ + ## + ## + ## The following combo of posts has been instrumental in getting this to work: + ## + ## http://nick.perfectedz.com/browserify-unit-testing-p1/ + ## http://nick.perfectedz.com/browserify-unit-testing-p2/ ## - mochaTest: + karma: - test: - options: - reporter: 'spec' - timeout: 30000 + ## https://karma-runner.github.io/1.0/config/configuration-file.html + ## + options: + basePath: '<%= build.test %>' - files: [ - src: '<%= build.test %>**/*.{coffee,js}' + ## https://karma-runner.github.io/1.0/config/browsers.html + ## + browsers: [ + 'PhantomJS' + ] + + ## https://karma-runner.github.io/1.0/config/files.html + ## + exclude: [] + files: [] + + frameworks: [ + ## https://github.com/nikku/karma-browserify#usage + ## + ## "Add browserify as a framework to your Karma configuration file." + ## + 'browserify' + 'jasmine' + ] + + hostname: 'localhost' + + httpServerOptions: {} + + logLevel: 'INFO' + + loggers: [ + + ## https://github.com/nomiddlename/log4js-node#readme + ## + type: 'console' + ] + + ## https://karma-runner.github.io/1.0/config/plugins.html + ## + ## By default, Karma loads all sibling NPM modules which have a name starting with karma-*. + ## We like to be explicit, so: + ## + plugins: [ + 'karma-browserify' + 'karma-jasmine' + 'karma-phantomjs-launcher' ] + port: 9876 + + ## https://karma-runner.github.io/1.0/config/preprocessors.html + ## + ## Note that there's no need for a `karma-coffee-preprocessor` because that's taken care of by browserify. + ## + preprocessors: + + 'unit/init.coffee': [ + 'browserify' + ] + + '**/spec/**/*': [ + 'browserify' + ] + + protocol: 'http:' + + ## https://karma-runner.github.io/1.0/config/files.html + ## + ## Section: Loading Assets + ## + proxies: {} + + ## Not related to `karma.options.proxies` setting above. + ## + ## Whether or not Karma or any browsers should raise an error when an inavlid SSL certificate is found. + ## + proxyValidateSSL: true + + reporters: [ + 'progress' + ] + + urlRoot: '/' + + + ## Continuous integration mode: + ## + autoWatch: false + background: false + colors: false + singleRun: true + + + ## + ## Plugin specific config: + ## + + ## Browserify: + ## + ## Reuse `browserify.options.browserifyOptions`. + ## + browserify: + _.extend( + {} + , + browserifyOptions + , + debug: true + ) + + + unit_ci: + options: + + ## Note that `files` is part of this task's extenstion of `karma.options` and its files are therefore relative to `karma.options.basePath`. + ## Despite appearance, this is **not** a grunt task's `files` declaration. + ## + ## https://karma-runner.github.io/1.0/config/files.html + ## + files: [ + ## Setup / initialization before all tests. + ## + 'unit/init.coffee' + , + ## The unit tests' specs. + ## + pattern: 'unit/spec/**/*' + , + ## Assets; non-code files. + ## See `proxies` section, below, to see how urls are mapped to these + ## + pattern: 'unit/asset/**/*' + + included: false + served: true + ] + + proxies: {} + + + unit_dev: + options: + + autoWatch: true + colors: true + singleRun: false + + files: '<%= karma.unit_ci.options.files %>' + proxies: '<%= karma.unit_ci.options.proxies %>' + ## ## Substitute build targets and - for cache-busting reasons - a build-run identifier into your app's main @@ -810,7 +1021,7 @@ module.exports = ( grunt ) -> grunt.loadNpmTasks( 'grunt-contrib-uglify' ) grunt.loadNpmTasks( 'grunt-contrib-watch' ) grunt.loadNpmTasks( 'grunt-contrib-yuidoc' ) - grunt.loadNpmTasks( 'grunt-mocha-test' ) + grunt.loadNpmTasks( 'grunt-karma' ) grunt.loadNpmTasks( 'grunt-template' ) @@ -972,6 +1183,14 @@ module.exports = ( grunt ) -> ) ) + grunt.registerTask( + 'test' + 'Unit test the app\'s code' + ( mode = 'ci' ) -> + grunt.task.run( + "karma:unit_#{mode}" + ) + ) ## ================================================ ## Command line tasks; the usual suspects anyway: @@ -988,7 +1207,7 @@ module.exports = ( grunt ) -> 'uglify:app' - 'test' + 'test:ci' 'compress:app_dist' @@ -1006,7 +1225,7 @@ module.exports = ( grunt ) -> 'app:debug' - 'test' + 'test:ci' 'compress:app_debug' ] @@ -1024,10 +1243,3 @@ module.exports = ( grunt ) -> 'watch' ] ) - - grunt.registerTask( - 'test' - [ - 'mochaTest' - ] - ) diff --git a/generators/app/templates/@README.md b/generators/app/templates/@README.md index c8a86d4c..0e44f9e8 100644 --- a/generators/app/templates/@README.md +++ b/generators/app/templates/@README.md @@ -61,9 +61,31 @@ command | description `grunt doc` | will build just the code documentation; `grunt lint` | will just lint your code; `grunt test` | will run your test suite; +`grunt test:dev` | will run your test suite and will keep monitoring it for changes, triggering re-runs; `grunt --help` | will show you all of the above and the kitchen sink; +### Test + +Unit testing is an integrated build step in both `default` and `debug` build runs, but can also be run independently as: + +```shell +grunt test +``` + +And as watched, continuous test runs as: + +```shell +grunt test:dev +``` + +The latter invocation, while it is kept running, also offers the opportunity to launch a test suite run in any browser, simply by directing it to this url: + +[`http://localhost:9876/debug.html`](http://localhost:9876/debug.html) + +*Do not forget to open your dev tools and browser console there!* + + ### Commit #### Branching Model diff --git a/generators/app/templates/@package.json b/generators/app/templates/@package.json index 222d3749..e1c2ae9f 100644 --- a/generators/app/templates/@package.json +++ b/generators/app/templates/@package.json @@ -9,8 +9,8 @@ "browserify": { "transform": [ "coffeeify", - "browserify-shim", - "hbsfy" + "hbsfy", + "browserify-shim" ] }, "browserify-shim": {}, diff --git a/generators/app/templates/test/unit/init.coffee b/generators/app/templates/test/unit/init.coffee new file mode 100644 index 00000000..a76550c9 --- /dev/null +++ b/generators/app/templates/test/unit/init.coffee @@ -0,0 +1,43 @@ + +## ============================================================================ +## +## [madlib] +## + +###* +# The app's globally sharable configuration settings. +# +# These are exposed through the `madlib-settings` singleton object. Simply `require(...)` it wherever you have a need for them. +# +# @class Settings +# @static +### + +## https://github.com/Qwerios/madlib-settings#readme +## +settings = require( 'madlib-settings' ) + + +###* +# The app's base url, so that resources can know what their origin is. +# +# Often the `document` and this app will share the same base url, but not necessarily so. +# +# @property appBaseUrl +# @type String +# @final +### + +appBaseUrl = '/' + +settings.init( 'appBaseUrl', appBaseUrl ) + + +## ============================================================================ +## +## [API] +## + +## `require()` the API services here to ensure their endpoints have been defined on the madlib-settings object before they are used anywhere else. +## +services = require( './../../src/collections/api-services.coffee' ) diff --git a/generators/app/templates/test/unit/spec/trivial.spec.coffee b/generators/app/templates/test/unit/spec/trivial.spec.coffee new file mode 100644 index 00000000..cbc9b4d6 --- /dev/null +++ b/generators/app/templates/test/unit/spec/trivial.spec.coffee @@ -0,0 +1,11 @@ +'use strict' + +## Trivial unit test that'll always pass. +## This is here because having zero tests will fail your test run. + +describe( 'A trivial unit test suite', () -> + it( 'should always pass', () -> + return + ) + return +)