michael@0: var _config = {
michael@0: fixture: null,
michael@0: Test: [],
michael@0: stats: {
michael@0: all: 0,
michael@0: bad: 0
michael@0: },
michael@0: queue: [],
michael@0: blocking: true,
michael@0: timeout: null,
michael@0: expected: null,
michael@0: currentModule: null,
michael@0: asyncTimeout: 2 // seconds for async timeout
michael@0: };
michael@0:
michael@0: _config.filters = location.search.length > 1 && //restrict modules/tests by get parameters
michael@0: $.map( location.search.slice(1).split('&'), decodeURIComponent );
michael@0:
michael@0: var isLocal = !!(window.location.protocol == 'file:');
michael@0:
michael@0: $(function() {
michael@0: $('#userAgent').html(navigator.userAgent);
michael@0: runTest();
michael@0: });
michael@0:
michael@0: function synchronize(callback) {
michael@0: _config.queue[_config.queue.length] = callback;
michael@0: if(!_config.blocking) {
michael@0: process();
michael@0: }
michael@0: }
michael@0:
michael@0: function process() {
michael@0: while(_config.queue.length && !_config.blocking) {
michael@0: var call = _config.queue[0];
michael@0: _config.queue = _config.queue.slice(1);
michael@0: call();
michael@0: }
michael@0: }
michael@0:
michael@0: function stop(allowFailure) {
michael@0: _config.blocking = true;
michael@0: var handler = allowFailure ? start : function() {
michael@0: ok( false, "Test timed out" );
michael@0: start();
michael@0: };
michael@0: // Disabled, caused too many random errors
michael@0: //_config.timeout = setTimeout(handler, _config.asyncTimeout * 1000);
michael@0: }
michael@0: function start() {
michael@0: // A slight delay, to avoid any current callbacks
michael@0: setTimeout(function(){
michael@0: if(_config.timeout)
michael@0: clearTimeout(_config.timeout);
michael@0: _config.blocking = false;
michael@0: process();
michael@0: }, 13);
michael@0: }
michael@0:
michael@0: function validTest( name ) {
michael@0: var filters = _config.filters;
michael@0: if( !filters )
michael@0: return true;
michael@0:
michael@0: var i = filters.length,
michael@0: run = false;
michael@0: while( i-- ){
michael@0: var filter = filters[i],
michael@0: not = filter.charAt(0) == '!';
michael@0: if( not )
michael@0: filter = filter.slice(1);
michael@0: if( name.indexOf(filter) != -1 )
michael@0: return !not;
michael@0: if( not )
michael@0: run = true;
michael@0: }
michael@0: return run;
michael@0: }
michael@0:
michael@0: function runTest() {
michael@0: _config.blocking = false;
michael@0: var time = new Date();
michael@0: _config.fixture = document.getElementById('main').innerHTML;
michael@0: _config.ajaxSettings = $.ajaxSettings;
michael@0: synchronize(function() {
michael@0: time = new Date() - time;
michael@0: $("
").html(['
Tests completed in ',
michael@0: time, ' milliseconds.
',
michael@0: _config.stats.bad, ' tests of ', _config.stats.all, ' failed.
']
michael@0: .join(''))
michael@0: .appendTo("body");
michael@0: $("#banner").addClass(_config.stats.bad ? "fail" : "pass");
michael@0: if ( parent.runAJAXTest )
michael@0: parent.runAJAXTest();
michael@0:
michael@0: });
michael@0: }
michael@0:
michael@0: function test(name, callback, nowait) {
michael@0: if(_config.currentModule)
michael@0: name = _config.currentModule + " module: " + name;
michael@0:
michael@0: if ( !validTest(name) )
michael@0: return;
michael@0:
michael@0: synchronize(function() {
michael@0: _config.Test = [];
michael@0: try {
michael@0: callback();
michael@0: } catch(e) {
michael@0: if( typeof console != "undefined" && console.error && console.warn ) {
michael@0: console.error("Test " + name + " died, exception and test follows");
michael@0: console.error(e);
michael@0: console.warn(callback.toString());
michael@0: }
michael@0: _config.Test.push( [ false, "Died on test #" + (_config.Test.length+1) + ": " + e.message ] );
michael@0: }
michael@0: });
michael@0: synchronize(function() {
michael@0: reset();
michael@0:
michael@0: // don't output pause tests
michael@0: if(nowait) return;
michael@0:
michael@0: if(_config.expected && _config.expected != _config.Test.length) {
michael@0: _config.Test.push( [ false, "Expected " + _config.expected + " assertions, but " + _config.Test.length + " were run" ] );
michael@0: }
michael@0: _config.expected = null;
michael@0:
michael@0: var good = 0, bad = 0;
michael@0: var ol = document.createElement("ol");
michael@0: ol.style.display = "none";
michael@0: var li = "", state = "pass";
michael@0: for ( var i = 0; i < _config.Test.length; i++ ) {
michael@0: var li = document.createElement("li");
michael@0: li.className = _config.Test[i][0] ? "pass" : "fail";
michael@0: li.innerHTML = _config.Test[i][1];
michael@0: ol.appendChild( li );
michael@0:
michael@0: _config.stats.all++;
michael@0: if ( !_config.Test[i][0] ) {
michael@0: state = "fail";
michael@0: bad++;
michael@0: _config.stats.bad++;
michael@0: } else good++;
michael@0:
michael@0: if ( parent.SimpleTest ){
michael@0: parent.SimpleTest.ok( _config.Test[i][0], name, _config.Test[i][1] );}
michael@0: }
michael@0:
michael@0: var li = document.createElement("li");
michael@0: li.className = state;
michael@0:
michael@0: var b = document.createElement("strong");
michael@0: b.innerHTML = name + "
(" + bad + ", " + good + ", " + _config.Test.length + ")";
michael@0: b.onclick = function(){
michael@0: var n = this.nextSibling;
michael@0: if ( jQuery.css( n, "display" ) == "none" )
michael@0: n.style.display = "block";
michael@0: else
michael@0: n.style.display = "none";
michael@0: };
michael@0: $(b).dblclick(function(event) {
michael@0: var target = jQuery(event.target).filter("strong").clone();
michael@0: if ( target.length ) {
michael@0: target.children().remove();
michael@0: location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent($.trim(target.text()));
michael@0: }
michael@0: });
michael@0: li.appendChild( b );
michael@0: li.appendChild( ol );
michael@0:
michael@0: document.getElementById("tests").appendChild( li );
michael@0: });
michael@0: }
michael@0:
michael@0: // call on start of module test to prepend name to all tests
michael@0: function module(moduleName) {
michael@0: _config.currentModule = moduleName;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
michael@0: */
michael@0: function expect(asserts) {
michael@0: _config.expected = asserts;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Resets the test setup. Useful for tests that modify the DOM.
michael@0: */
michael@0: function reset() {
michael@0: $("#main").html( _config.fixture );
michael@0: $.event.global = {};
michael@0: $.ajaxSettings = $.extend({}, _config.ajaxSettings);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Asserts true.
michael@0: * @example ok( $("a").size() > 5, "There must be at least 5 anchors" );
michael@0: */
michael@0: function ok(a, msg) {
michael@0: _config.Test.push( [ !!a, msg ] );
michael@0: }
michael@0:
michael@0: /**
michael@0: * Asserts that two arrays are the same
michael@0: */
michael@0: function isSet(a, b, msg) {
michael@0: var ret = true;
michael@0: if ( a && b && a.length != undefined && a.length == b.length ) {
michael@0: for ( var i = 0; i < a.length; i++ )
michael@0: if ( a[i] != b[i] )
michael@0: ret = false;
michael@0: } else
michael@0: ret = false;
michael@0: if ( !ret )
michael@0: _config.Test.push( [ ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) ] );
michael@0: else
michael@0: _config.Test.push( [ ret, msg ] );
michael@0: }
michael@0:
michael@0: /**
michael@0: * Asserts that two objects are equivalent
michael@0: */
michael@0: function isObj(a, b, msg) {
michael@0: var ret = true;
michael@0:
michael@0: if ( a && b ) {
michael@0: for ( var i in a )
michael@0: if ( a[i] != b[i] )
michael@0: ret = false;
michael@0:
michael@0: for ( i in b )
michael@0: if ( a[i] != b[i] )
michael@0: ret = false;
michael@0: } else
michael@0: ret = false;
michael@0:
michael@0: _config.Test.push( [ ret, msg ] );
michael@0: }
michael@0:
michael@0: function serialArray( a ) {
michael@0: var r = [];
michael@0:
michael@0: if ( a && a.length )
michael@0: for ( var i = 0; i < a.length; i++ ) {
michael@0: var str = a[i].nodeName;
michael@0: if ( str ) {
michael@0: str = str.toLowerCase();
michael@0: if ( a[i].id )
michael@0: str += "#" + a[i].id;
michael@0: } else
michael@0: str = a[i];
michael@0: r.push( str );
michael@0: }
michael@0:
michael@0: return "[ " + r.join(", ") + " ]";
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns an array of elements with the given IDs, eg.
michael@0: * @example q("main", "foo", "bar")
michael@0: * @result [
, , ]
michael@0: */
michael@0: function q() {
michael@0: var r = [];
michael@0: for ( var i = 0; i < arguments.length; i++ )
michael@0: r.push( document.getElementById( arguments[i] ) );
michael@0: return r;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Asserts that a select matches the given IDs
michael@0: * @example t("Check for something", "//[a]", ["foo", "baar"]);
michael@0: * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
michael@0: */
michael@0: function t(a,b,c) {
michael@0: var f = jQuery(b);
michael@0: var s = "";
michael@0: for ( var i = 0; i < f.length; i++ )
michael@0: s += (s && ",") + '"' + f[i].id + '"';
michael@0: isSet(f, q.apply(q,c), a + " (" + b + ")");
michael@0: }
michael@0:
michael@0: /**
michael@0: * Add random number to url to stop IE from caching
michael@0: *
michael@0: * @example url("data/test.html")
michael@0: * @result "data/test.html?10538358428943"
michael@0: *
michael@0: * @example url("data/test.php?foo=bar")
michael@0: * @result "data/test.php?foo=bar&10538358345554"
michael@0: */
michael@0: function url(value) {
michael@0: return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Checks that the first two arguments are equal, with an optional message.
michael@0: * Prints out both expected and actual values on failure.
michael@0: *
michael@0: * Prefered to ok( expected == actual, message )
michael@0: *
michael@0: * @example equals( "Expected 2 characters.", v.formatMessage("Expected {0} characters.", 2) );
michael@0: *
michael@0: * @param Object actual
michael@0: * @param Object expected
michael@0: * @param String message (optional)
michael@0: */
michael@0: function equals(actual, expected, message) {
michael@0: var result = expected == actual;
michael@0: message = message || (result ? "okay" : "failed");
michael@0: _config.Test.push( [ result, result ? message + ": " + expected : message + " expected: " + expected + " actual: " + actual ] );
michael@0: }
michael@0:
michael@0: /**
michael@0: * Trigger an event on an element.
michael@0: *
michael@0: * @example triggerEvent( document.body, "click" );
michael@0: *
michael@0: * @param DOMElement elem
michael@0: * @param String type
michael@0: */
michael@0: function triggerEvent( elem, type, event ) {
michael@0: if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
michael@0: event = document.createEvent("MouseEvents");
michael@0: event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
michael@0: 0, 0, 0, 0, 0, false, false, false, false, 0, null);
michael@0: elem.dispatchEvent( event );
michael@0: } else if ( jQuery.browser.msie ) {
michael@0: elem.fireEvent("on"+type);
michael@0: }
michael@0: }