Skip to content

Commit

Permalink
Add phantomjs support to unit tests (CLI-capable!)
Browse files Browse the repository at this point in the history
  • Loading branch information
eternicode committed May 5, 2012
1 parent 619a0c2 commit e4128ee
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
39 changes: 39 additions & 0 deletions TESTS.md
@@ -0,0 +1,39 @@
Unit tests, written with [QUnit](http://docs.jquery.com/QUnit), are used to
expose bugs for squashing, prevent bugs from respawning, and suppress new
bugs when adding new features and making changes.

# Running the tests

The simplest way to run the tests is to open `tests/tests.html` in your browser.
The test suites will automatically run themselves and present their results.

To run the tests from the command line, download and install
[PhantomJS](http://phantomjs.org/), and run `run-qunit.js` with it:

$ cd tests/
$ phantomjs run-qunit.js tests.html

Failed tests and their failed assertions will be printed to the console. A
results summary will be printed at the end.

# Shout-out

Thanks to Rod @ While One Fork for the
[CIS guide](http://whileonefork.blogspot.com/2011/10/integrating-javascript-tests-into-cli.html)
on putting the above together.

# Adding tests

Tests go in js files in the `tests/suites/` directory tree. QUnit organizes
tests into suites called "modules"; there is one module per js file. If the
tests you are adding do not fit into an existing module, create a new one at
`tests/suites/<new module>.js`, where `<new module>` is a broad yet
descriptive name for the suite. If tests have many year-specific cases (ie,
behave differently in leap years vs normal years, or have specific buggy
behavior in a certain year), create the module in a new directory,
`tests/suites/<new module>/<year>.js`, where `<new module>` is the decriptive
name and `<year>` is the four-digit year the tests pertain to.

In order for new tests to be run, they must be imported into `tests/tests.html`.
Find the script includes headed by the html comment `<!-- Test suites -->`, and
add a new one to the list which includes the new js files.
28 changes: 28 additions & 0 deletions tests/assets/qunit-logging.js
@@ -0,0 +1,28 @@
// Dummy logging calls (ie, if tests are run in IE)
window.console = window.console || {};
window.console.log = window.console.log || function(){};
window.console.debug = window.console.debug || function(){};
window.console.info = window.console.info || function(){};
window.console.warn = window.console.warn || function(){};
window.console.error = window.console.error || function(){};

(function() {
var testName;

//arg: { name }
QUnit.testStart = function(t) {
testName = t.name;
};

//arg: { name, failed, passed, total }
QUnit.testDone = function(t) {
if (t.failed)
console.log('Test "' + t.name + '" completed: ' + (0 === t.failed ? 'pass' : 'FAIL') + '\n')
};

//{ result, actual, expected, message }
QUnit.log = function(t) {
if (!t.result)
console.log('Test "' + testName + '" assertion failed. Expected <' + t.expected + '> Actual <' + t.actual + '>' + (t.message ? ': \'' + t.message + '\'' : ''));
};
}());
76 changes: 76 additions & 0 deletions tests/run-qunit.js
@@ -0,0 +1,76 @@
var system = require('system');

/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
//console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};


if (system.args.length !== 2) {
console.log('Usage: run-qunit.js URL');
phantom.exit(1);
}

var page = require('webpage').create();

// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg);
};

page.open(system.args[1], function(status){
if (status !== "success") {
console.log("Unable to access network");
phantom.exit(1);
} else {
waitFor(function(){
return page.evaluate(function(){
var el = document.getElementById('qunit-testresult');
if (el && el.innerText.match('completed')) {
return true;
}
return false;
});
}, function(){
var failedNum = page.evaluate(function(){
var el = document.getElementById('qunit-testresult');
console.log(el.innerText);
try {
return el.getElementsByClassName('failed')[0].innerHTML;
} catch (e) { }
return 10000;
});
phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0);
});
}
});
1 change: 1 addition & 0 deletions tests/tests.html
Expand Up @@ -3,6 +3,7 @@
<head>
<link rel="stylesheet" href="assets/qunit.css" />
<script src="assets/qunit.js"></script>
<script src="assets/qunit-logging.js"></script> <!-- console.log for test failures -->
<script src="assets/jquery-1.7.1.min.js"></script>
<script src="../js/bootstrap-datepicker.js"></script>
<style>
Expand Down

0 comments on commit e4128ee

Please sign in to comment.