Yeoman + AMD (RequireJS) + Mocha/Chai
Assuming you like the setup I described here (and why wouldn't you), this will describe how to add Mocha and Chai for testing in a way that will work with RequireJS modules that live in your app/
directory.
Create a config.js
file for use by both app/
and test/
- Split
app/scripts/main.js
file out into two files,app/scripts/main.js
andapp/scripts/config.js
. Theconfig.js
file hasmain
as a dependency, somain
,app
andbootstrap
files are loaded as before. - Setup
index.html
to point atconfig.js
, and remove theusemin
comments, like this. Note: the commented out script block pointing atscripts/app.min.js
will be useful later, so be sure to include it.
Update test/
to use the generated CSS and testing libraries installed with Bower
-
Install Mocha and Chai as dev dependencies:
bower install mocha --save-dev
bower install chai --save-dev Remove the
test/lib
directory.Replace the entire contents of
test/index.html
with the contents ofapp/index.html
.-
In the
head
of the newtest/index.html
, add references to the Mocha and Chai libraries installed with Bower:<link rel="stylesheet" href="../app/bowercomponents/mocha/mocha.css">
<script src="../app/bowercomponents/mocha/mocha.js"></script>
<script src="../app/bower_components/chai/chai.js"></script> Update the reference to the main stylesheet, to point at the generated CSS file. The
Gruntfile.js
will be updated to serve.tmp
and.
, so the relative path will be../styles/main.css
.
Update test/
to use the shared config.js
.
- At the bottom of
test/index.html
(right before the closingbody
tag), remove the Chrome Frame conditional comment, the Google Analytics script tag, and the commented out script block referencingscripts/app.min.js
(it won't be needed for tests). - Update the reference to RequireJS to point to
../app/bower_components/requirejs/require.js
and set it'sdata-main
attribute tospec/config
. -
Create
spec/config.js
, something like this:'use strict';
require.config({
baseUrl: '../../app/scripts/', // non-pathed dependencies should come from the app/scripts directory
deps: ['runner'], // load spec/runner.js by default
paths: {
spec: '../../test/spec', // path tests to this directory
runner: '../../test/spec/runner', // the main test runner, load all tests as dependencies
appConfig: '../../app/scripts/config' // the app's config file
},
shim: {
runner: ['appConfig'] // make runner depend on the app's config file
}
}); -
Create
spec/runner.js
something like this:/* global define */
define([
// All your tests go here.
'spec/app.test' // maybe it makes sense to add tests as dependencies in spec/config?
], function () {
'use strict';window.console = window.console || function() {}; // protect from barfs window.notrack = true; // don't track window.mocha.run(); // kickoff the runner
});
-
Create
spec/app.test.js
something like this:/* global define, describe, it, should */
define(['app'], function (app) {
'use strict';// whatever tests are in here will run as soon as this module is loaded describe('app', function () { it('should exist', function() { should.exist(app); }); });
});
Update Gruntfile.js
to serve and test everything
Make these changes to Gruntfile.js
. The most important of these are:
- Add
mountFolder(connect, '.'),
to theconnect:livereload
task. This adds the project root to files served atlocalhost:9000
. Tests will be available when the server is running atlocalhost:9000/test
. - Replace
mountFolder(connect, 'test')
withmountFolder(connect, '.')
in theconnect:test
task. This is required in order fortest/index.html
to have relative path access toapp
-level dependencies. - Update the
mocha:all
task options torun: false
andurls: ['http://localhost:<%= connect.options.port %>/test/index.html']
. Optionally set the reporter option:reporter: 'Spec'
. Run is now triggered bytest/spec/runner.js
when it (and it's dependencies) have loaded. The tests are now located intest/index.html
because we are serving the project root. -
Update the
requirejs:dist
task by adding to the options hash:include: '../bower_components/requirejs/require',
mainConfigFile: yeomanConfig.app + '/scripts/config.js',
out: yeomanConfig.dist + '/scripts/app.min.js'
Nearly done!
If all went well, you should have passing tests at both http://localhost:9000/test
(while running grunt server
), and via the command line by running grunt test
. Assuming that's true, commit whatever's not committed, and push up to GitHub. When the Travis hook fires, you should also have a successful build in your gh-pages
branch!
But wait! What's this!?
The built files don't quite work yet. Because we're not using usemin
to rewrite our script tags, dist/index.html
has a script tag referencing bower_components/requirejs/require.js
… but no such file exists in the dist
directory.
However, that little commented out script tag referencing scripts/app.min.js
does get updated to point to the generated (RequireJS included, and rev
d) JavaScript. So how do we get dist
to use it?
Add cleanup.js
, and update .travis.yml
to use it in the after_success
hook.
Here's app/scripts/util/cleanup.js
in it's entirety (I'm not super familiar with Node, so if there's a better way to do this, please do tell). Also, please note: This script is really specific to how I have things set up. I accept no responsibility if this destroys your project and ruins your life. USE AT YOUR OWN RISK!:
'use strict';
var fs = require('fs');
var filename = process.argv[2];
// Make sure we got a filename on the command line.
if (process.argv.length < 3) {
console.log('Usage: node ' + process.argv[1] + ' FILENAME');
process.exit(1);
}
// Read the file.
fs.readFile(filename, 'utf8', function(error, data) {
var file = data;
// Find the old RequireJS script tag, and remove it.
var tag = ' <script data-main="scripts/config" src="bower_components/requirejs/require.js"></script>';
var tagIndex = file.indexOf(tag);
file = file.substring(0, tagIndex) + file.substring(tagIndex + tag.length + 1);
// Assume the last comment in the HTML file is the one you want to remove.
// TODO: Make it a comment like build:uncomment or build:remove like `usemin` does.
var beginComment = file.lastIndexOf('<!-- ');
file = file.substring(0, beginComment) + file.substring(beginComment + 5);
var endComment = file.lastIndexOf(' -->');
file = file.substring(0, endComment) + file.substring(endComment + 4);
// Write the edited file back into place.
fs.writeFile(filename, file, 'utf8', function(error) {
if (error) { throw error; }
});
});
Create this file, then add the following line to .travis.yml
between after line 45 (like here):
- node ../app/scripts/util/cleanup.js ../dist/index.html
This way, when Travis successfully builds your project, it will remove the script tag pointing at the Bower-loaded RequireJS library, and un-comment script tag pointing to the r.js
optimized, and rev
d JavaScript.
This has been a really long Pro Tip, but I hope you find it useful. If anyone knows how to improve upon cleanup.js
I'm all ears. Further, if there's a way to have Grunt run cleanup.js
on dist/index.html
during grunt:build
that'd be good (better) too.
Also, I don't really know what's going on with this formatting … for a code-specific community to not get code formatting right is a pretty huge bummer. If you have any questions you can check out the whole, un-touched after this demo project here:
https://github.com/mysterycommand/my-new-everything
http://mysterycommand.github.io/my-new-everything/
Written by Matt Hayes
Related protips
2 Responses
when exactly was this published? there's been some recent shift in RequireJS support in Yeoman and i'm looking for the solution. is this recent?
@theOther, the last time I updated the repo was 7 months ago, but I've been using a setup very like it since then with only minor tweaks for Yeoman 1.0 … I don't know of any breaking changes in RequireJS.
Looks like the webapp generator removed RequireJS a couple months ago … you could probably just reverse engineer the RequireJS-enabled Gruntfile? I'll update this post as soon as I get a chance (it might not be that soon).