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: }