Skip to content

Instantly share code, notes, and snippets.

@garth
Created December 1, 2011 12:06
Show Gist options
  • Save garth/1416238 to your computer and use it in GitHub Desktop.
Save garth/1416238 to your computer and use it in GitHub Desktop.
Example Jakefile from ViennaJS meetup
# This is some example code that executes in the browser and communicates with the jake dev process
# to re-run tests and report testing status
# The code is taken from a larger application, so just ignore the other bits. If you're not using QUnit
# replace the QUnit references with your favourite javascript testing/bdd framework.
define ['lib/browserinfo', 'template!testsuite', 'socket.io'], (browserInfo) ->
#connect to the test server
MyApp.testSocket = io.connect '/'
#get info about the current browser to send to the server
currentBrowser = "#{browserInfo.name} #{browserInfo.version} (#{browserInfo.OS})"
#replace the debug function with one that also notifies the test server
MyApp.debug = (message) ->
console.log message
MyApp.testSocket.emit 'debug', { browserInfo: currentBrowser, message: message }
#wait for notification from the server to re-run the test
MyApp.testSocket.on 'codeChanged', () ->
location.reload()
#render the qunit placeholders
view = SC.View.create templateName: 'testsuite'
#wait until the view is rendered before initialising QUnit
view.onReady = () ->
#load qunit
require ['qunit'], () ->
#configure QUnit
QUnit.config.urlConfig = []
#send test results to the server
currentTest = ''
QUnit.testStart = (name) -> currentTest = name
QUnit.log = (data) ->
if !data.result
data.browserInfo = currentBrowser
data.test = currentTest
MyApp.testSocket.emit 'testFailed', data
QUnit.done = (data) ->
data.browserInfo = currentBrowser
MyApp.testSocket.emit 'testingComplete', data
#set test modules to run
require [
'test/sometests'
'test/moretests'
], () ->
#run the tests
QUnit.load()
#return the view to to the router
return view
// Jake is like Rake for nodejs https://github.com/mde/jake
//
// Assumes that Jake will be run in the root of the web app with coffee files in /js/ and
// a single app.less file in /css/ that can include references to other .less files
//
//requires
var sys = require('util');
var execute = require('child_process').exec;
var fs = require('fs');
var colors = require('colors');
//tasks
desc('Build all files');
task({ 'default': ['coffee', 'less'] }, function (params) {
});
desc('Watch .coffee and .less files for changes and auto compile on change');
task({ 'dev': ['coffee', 'less'] }, function (params) {
watchDir('js', compileCoffeeScript, /\.coffee$/);
watchDir('css', compileLessFiles, /\.less$/);
console.log('Watching .coffee and .less files for changes...');
startTestServer();
});
desc('Compile all .coffee files');
task('coffee', function (params) {
console.log('Compiling all .coffee files');
execOnDirFiles('js', compileCoffeeScript, /\.coffee$/);
});
desc('Compile all .less files');
task('less', function (params) {
console.log('Compiling all .less files');
compileLessFiles('css/app.less');
});
function compileCoffeeScript(file) {
exec("coffee -l -b -c " + file);
notifyTestClients();
}
function compileLessFiles(file) {
//ignore the file and always compile app.less which links in all other files
exec('lessc -x css/app.less', function(css) {
//replace the image urls with inline base64 image data
var files = {};
css = css.replace(/url\((\S*)\.(png|jpg|jpeg|gif)\)/g, function(match, file, type)
{
var fileName = file + '.' + type;
var base64 = fs.readFileSync(fileName).toString('base64');
if (typeof(files[fileName]) !== 'undefined') {
console.log(('Warning: ' + fileName + ' has already been base64 encoded in the css').red);
}
else {
files[fileName] = true;
}
return 'url("data:image/' + (type === 'jpg' ? 'jpeg' : type) + ';base64,' + base64 + '")';
});
fs.writeFileSync('css/app.css', css, 'utf-8');
});
notifyTestClients();
}
function execOnDirFiles(path, callback, match) {
fs.readdir(path, function (err, files) {
for (var i = 0; i < files.length; i++) {
if (!match || match.test(files[i])) {
callback(path + '/' + files[i]);
}
}
});
}
function watchDir(path, callback, match) {
fs.readdir(path, function (err, files) {
for (var i = 0; i < files.length; i++) {
if (!match || match.test(files[i])) {
watchFile(path + '/' + files[i], callback);
}
}
});
}
function watchFile(file, callback) {
var timeoutId = 0;
fs.watch(file, { persistent: true }, function () {
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
console.log(('File chaged: ' + file).yellow);
callback(file);
}, 500);
});
}
function exec(command, onComplete) {
execute(command, function (error, stdout, stderr) {
if (typeof(onComplete) !== "function" && stdout) {
console.log(stdout);
}
if (stderr) {
console.log(stderr);
}
if (typeof(onComplete) === "function") {
onComplete(stdout);
}
});
}
//start a web server with test fixtures for development
var testClients = [];
function startTestServer() {
var express = require('express');
var rewriter = require('express-rewrite');
var app = express.createServer();
var io = require('socket.io').listen(app);
io.set('log level', 1);
io.set('transports', ['websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
app.configure(function() {
app.use(rewriter);
app.use(app.router);
app.use(express.static(__dirname));
});
app.configure('development', function() {
return app.use(express.errorHandler({
dumpExceptions: true,
showStack: true
}));
});
//load the index.html as root/default
app.get('/', rewriter.rewrite('/index.html'));
//reroute static test fixtures to emulate the production layout
app.get('/data/:a.json', rewriter.rewrite('/js/test/fixture/$1.json'));
app.get('/data/:a/:b.json', rewriter.rewrite('/js/test/fixture/$1/$2.json'));
app.get('/data/:a/:b/:c.json', rewriter.rewrite('/js/test/fixture/$1/$2/$3.json'));
app.get('/data/:a/:b/:c/:d.json', rewriter.rewrite('/js/test/fixture/$1/$2/$3/$4.json'));
app.get('/data/:a/:b/:c/:d/:e.json', rewriter.rewrite('/js/test/fixture/$1/$2/$3/$4/$5.json'));
app.get('/data/:a/:b/:c/:d/:e/:f.json', rewriter.rewrite('/js/test/fixture/$1/$2/$3/$4/$5/$6.json'));
//start the test web server
app.listen(3000);
console.log("HTTP test server listening on port %d... [ctrl+c to exit]", app.address().port);
//keep in contact with the test clients
io.sockets.on('connection', function (socket) {
testClients.push(socket);
//output messages from the test clients
socket.on('testFailed', function (data) {
var output = data.browserInfo + ' Test Failed'.red + ': ' + data.test.module + ' > ' + data.test.name;
if (!data.expected || !data.actual) {
if (data.message.indexOf('\n') == -1) { console.log(output + ' > ' + data.message); }
else { console.log(output + '\n' + data.message.cyan); }
}
else {
console.log(output + ' > ' + data.message +
' ('+ data.expected.toString().green + ':' + data.actual.toString().red + ')');
}
if (data.source) { console.log(' ' + data.source.trim().cyan); }
});
socket.on('testingComplete', function (data) {
var failed = data.failed > 0 ? data.failed.toString().red : data.failed.toString().green
console.log(data.browserInfo + ' Testing Complete - failed: ' + failed +
', passed: ' + data.passed.toString().green + ', total: ' + data.total +
', runtime: ' + data.runtime + 'ms');
});
socket.on('debug', function (data) {
console.log(data.browserInfo + ' Debug: ' + data.message.cyan);
});
});
}
//send change notification messages to connect clients
var timeoutId = 0;
function notifyTestClients() {
if (testClients.length > 0) {
//use a timeout so that we don't send multiple messages
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
console.log('Running client tests...'.yellow);
for (var i = 0; i < testClients.length; i++) {
testClients[i].emit('codeChanged');
}
}, 2000);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment