diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..164ecb478
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1 @@
+script: cd test && phantomjs lib/run_jasmine_test.coffee index.html
diff --git a/test/index.html b/test/index.html
index 9d7da2254..20fe70185 100644
--- a/test/index.html
+++ b/test/index.html
@@ -7,6 +7,8 @@
+
+
@@ -18,31 +20,31 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -66,9 +68,11 @@
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
-
jasmineEnv.addReporter(htmlReporter);
+ window.console_reporter = new jasmine.ConsoleReporter();
+ jasmineEnv.addReporter(console_reporter);
+
jasmineEnv.specFilter = function (spec) {
return htmlReporter.specFilter(spec);
};
diff --git a/test/lib/bind-shim.js b/test/lib/bind-shim.js
new file mode 100644
index 000000000..14d90b768
--- /dev/null
+++ b/test/lib/bind-shim.js
@@ -0,0 +1,26 @@
+// PhantomJS is missing Function.prototype.bind:
+// http://code.google.com/p/phantomjs/issues/detail?id=522
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function (oThis) {
+ if (typeof this !== "function") {
+ // closest thing possible to the ECMAScript 5 internal IsCallable function
+ throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP && oThis
+ ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+
+ return fBound;
+ };
+}
diff --git a/test/lib/console-runner.js b/test/lib/console-runner.js
new file mode 100644
index 000000000..fcd4a6d47
--- /dev/null
+++ b/test/lib/console-runner.js
@@ -0,0 +1,104 @@
+/**
+ Jasmine Reporter that outputs test results to the browser console.
+ Useful for running in a headless environment such as PhantomJs, ZombieJs etc.
+
+ Usage:
+ // From your html file that loads jasmine:
+ jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
+ jasmine.getEnv().execute();
+*/
+
+(function(jasmine, console) {
+ if (!jasmine) {
+ throw "jasmine library isn't loaded!";
+ }
+
+ var ANSI = {}
+ ANSI.color_map = {
+ "green" : 32,
+ "red" : 31
+ }
+
+ ANSI.colorize_text = function(text, color) {
+ var color_code = this.color_map[color];
+ return "\033[" + color_code + "m" + text + "\033[0m";
+ }
+
+ var ConsoleReporter = function() {
+ if (!console || !console.log) { throw "console isn't present!"; }
+ this.status = this.statuses.stopped;
+ };
+
+ var proto = ConsoleReporter.prototype;
+ proto.statuses = {
+ stopped : "stopped",
+ running : "running",
+ fail : "fail",
+ success : "success"
+ };
+
+ proto.reportRunnerStarting = function(runner) {
+ this.status = this.statuses.running;
+ this.start_time = (new Date()).getTime();
+ this.executed_specs = 0;
+ this.passed_specs = 0;
+ this.log("Starting...");
+ };
+
+ proto.reportRunnerResults = function(runner) {
+ var failed = this.executed_specs - this.passed_specs;
+ var spec_str = this.executed_specs + (this.executed_specs === 1 ? " spec, " : " specs, ");
+ var fail_str = failed + (failed === 1 ? " failure in " : " failures in ");
+ var color = (failed > 0)? "red" : "green";
+ var dur = (new Date()).getTime() - this.start_time;
+
+ this.log("");
+ this.log("Finished");
+ this.log("-----------------");
+ this.log(spec_str + fail_str + (dur/1000) + "s.", color);
+
+ this.status = (failed > 0)? this.statuses.fail : this.statuses.success;
+
+ /* Print something that signals that testing is over so that headless browsers
+ like PhantomJs know when to terminate. */
+ this.log("");
+ this.log("ConsoleReporter finished");
+ };
+
+
+ proto.reportSpecStarting = function(spec) {
+ this.executed_specs++;
+ };
+
+ proto.reportSpecResults = function(spec) {
+ if (spec.results().passed()) {
+ this.passed_specs++;
+ return;
+ }
+
+ var resultText = spec.suite.description + " : " + spec.description;
+ this.log(resultText, "red");
+
+ var items = spec.results().getItems()
+ for (var i = 0; i < items.length; i++) {
+ var trace = items[i].trace.stack || items[i].trace;
+ this.log(trace, "red");
+ }
+ };
+
+ proto.reportSuiteResults = function(suite) {
+ if (!suite.parentSuite) { return; }
+ var results = suite.results();
+ var failed = results.totalCount - results.passedCount;
+ var color = (failed > 0)? "red" : "green";
+ this.log(suite.description + ": " + results.passedCount + " of " + results.totalCount + " passed.", color);
+ };
+
+ proto.log = function(str, color) {
+ var text = (color != undefined)? ANSI.colorize_text(str, color) : str;
+ console.log(text)
+ };
+
+ jasmine.ConsoleReporter = ConsoleReporter;
+})(jasmine, console);
+
diff --git a/test/lib/run_jasmine_test.coffee b/test/lib/run_jasmine_test.coffee
new file mode 100644
index 000000000..3c276aeab
--- /dev/null
+++ b/test/lib/run_jasmine_test.coffee
@@ -0,0 +1,46 @@
+#!/usr/local/bin/phantomjs
+
+# Runs a Jasmine Suite from an html page
+# @page is a PhantomJs page object
+# @exit_func is the function to call in order to exit the script
+
+class PhantomJasmineRunner
+ constructor: (@page, @exit_func = phantom.exit) ->
+ @tries = 0
+ @max_tries = 10
+
+ get_status: -> @page.evaluate(-> console_reporter.status)
+
+ terminate: ->
+ switch @get_status()
+ when "success" then @exit_func 0
+ when "fail" then @exit_func 1
+ else @exit_func 2
+
+# Script Begin
+if phantom.args.length == 0
+ console.log "Need a url as the argument"
+ phantom.exit 1
+
+page = new WebPage()
+
+runner = new PhantomJasmineRunner(page)
+
+# Don't supress console output
+page.onConsoleMessage = (msg) ->
+ console.log msg
+
+ # Terminate when the reporter singals that testing is over.
+ # We cannot use a callback function for this (because page.evaluate is sandboxed),
+ # so we have to *observe* the website.
+ if msg == "ConsoleReporter finished"
+ runner.terminate()
+
+address = phantom.args[0]
+
+page.open address, (status) ->
+ if status != "success"
+ console.log "can't load the address!"
+ phantom.exit 1
+
+ # Now we wait until onConsoleMessage reads the termination signal from the log.