/* Jsunittest, version 0.7.3 * (c) 2008 Dr Nic Williams * * Jsunittest is freely distributable under * the terms of an MIT-style license. * For details, see the web site: http://jsunittest.rubyforge.org * *--------------------------------------------------------------------------*/ var JsUnitTest = { Unit: {}, inspect: function(object) { try { if (typeof object == "undefined") {return 'undefined';} if (object === null) {return 'null';} if (typeof object == "string") { var useDoubleQuotes = arguments[1]; var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) { var character = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' }[match[0]]; return character ? character : '\\u00' + JsUnitTest.toHexString(match[0].charCodeAt()); }); if (useDoubleQuotes) {return '"' + escapedString.replace(/"/g, '\\"') + '"';} return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } if (JsUnitTest.getClass(object) === 'Object') { var keys_values = new Array(), prefix = 'Object: { '; for (property in object) { keys_values.push(property + ': ' + object[property]); } return (prefix + keys_values.join(', ') + ' }'); } return String(object); } catch (e) { if (e instanceof RangeError) {return '...';} throw e; } }, getClass: function(object) { return Object.prototype.toString.call(object) .match(/^\[object\s(.*)\]$/)[1]; }, $: function(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) { elements.push(this.$(arguments[i])); } return elements; } if (typeof element == "string") { element = document.getElementById(element); } return element; }, gsub: function(source, pattern, replacement) { var result = '', match; replacement = arguments.callee.prepareReplacement(replacement); while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += JsUnitTest.String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; }, scan: function(source, pattern, iterator) { this.gsub(source, pattern, iterator); return String(source); }, escapeHTML: function(data) { return data.replace(/&/g,'&').replace(//g,'>'); }, toHexString : function(n) { var string = n.toString(16); return '00'.substring(string.length) + string; }, arrayfromargs: function(args) { var myarray = new Array(); var i; for (i=0;i\s*/, adjacent: /^\s*\+\s*/, descendant: /^\s/, // selectors follow tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }; var assertions = { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); }, className: function(element, matches) { return Element.hasClassName(element, matches[1]); }, id: function(element, matches) { return element.id === matches[1]; }, attrPresence: function(element, matches) { return Element.hasAttribute(element, matches[1]); }, attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]); } }; var e = this.expression, ps = patterns, as = assertions; var le, p, m; while (e && le !== e && (/\S/).test(e)) { le = e; for (var i in ps) { p = ps[i]; if (m = e.match(p)) { // use the Selector.assertions methods unless the selector // is too complex. if (as[i]) { tokens.push([i, Object.clone(m)]); e = e.replace(m[0], ''); } } } } var match = true, name, matches; for (var i = 0, token; token = tokens[i]; i++) { name = token[0]; matches = token[1]; if (!assertions[name](element, matches)) { match = false; break; } } return match; }, toQueryParams: function(query, separator) { var query = query || window.location.search; var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/); if (!match) {return { };} var hash = {}; var parts = match[1].split(separator || '&'); for (var i=0; i < parts.length; i++) { var pair = parts[i].split('='); if (pair[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) {value = decodeURIComponent(value);} if (key in hash) { var object = hash[key]; var isArray = object != null && typeof object == "object" && 'splice' in object && 'join' in object; if (!isArray) {hash[key] = [hash[key]];} hash[key].push(value); } else { hash[key] = value; } } } return hash; }, String: { interpret: function(value) { return value == null ? '' : String(value); } } }; JsUnitTest.gsub.prepareReplacement = function(replacement) { if (typeof replacement == "function") {return replacement;} var template = new Template(replacement); return function(match) { return template.evaluate(match); }; }; JsUnitTest.Version = '0.7.3'; JsUnitTest.Template = function(template, pattern) { this.template = template; //template.toString(); this.pattern = pattern || JsUnitTest.Template.Pattern; }; JsUnitTest.Template.prototype.evaluate = function(object) { if (typeof object.toTemplateReplacements == "function") { object = object.toTemplateReplacements(); } return JsUnitTest.gsub(this.template, this.pattern, function(match) { if (object == null) {return '';} var before = match[1] || ''; if (before == '\\') {return match[2];} var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) {return before;} while (match != null) { var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) {break;} expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + JsUnitTest.String.interpret(ctx); }); }; JsUnitTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; JsUnitTest.Event = {}; // written by Dean Edwards, 2005 // with input from Tino Zijdel, Matthias Miller, Diego Perini // namespaced by Dr Nic Williams 2008 // http://dean.edwards.name/weblog/2005/10/add-event/ // http://dean.edwards.name/weblog/2005/10/add-event2/ JsUnitTest.Event.addEvent = function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else { // assign each event handler a unique ID if (!handler.$$guid) {handler.$$guid = JsUnitTest.Event.addEvent.guid++;} // create a hash table of event types for the element if (!element.events) {element.events = {};} // create a hash table of event handlers for each element/event pair var handlers = element.events[type]; if (!handlers) { handlers = element.events[type] = {}; // store the existing event handler (if there is one) if (element["on" + type]) { handlers[0] = element["on" + type]; } } // store the event handler in the hash table handlers[handler.$$guid] = handler; // assign a global event handler to do all the work element["on" + type] = this.handleEvent; } }; // a counter used to create unique IDs JsUnitTest.Event.addEvent.guid = 1; JsUnitTest.Event.removeEvent = function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else { // delete the event handler from the hash table if (element.events && element.events[type]) { delete element.events[type][handler.$$guid]; } } }; JsUnitTest.Event.handleEvent = function(event) { var returnValue = true; // grab the event object (IE uses a global event object) event = event || JsUnitTest.Event.fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event); // get a reference to the hash table of event handlers var handlers = this.events[event.type]; // execute each event handler for (var i in handlers) { this.$$handleEvent = handlers[i]; if (this.$$handleEvent(event) === false) { returnValue = false; } } return returnValue; }; JsUnitTest.Event.fixEvent = function(event) { // add W3C standard event methods event.preventDefault = this.fixEvent.preventDefault; event.stopPropagation = this.fixEvent.stopPropagation; return event; }; JsUnitTest.Event.fixEvent.preventDefault = function() { this.returnValue = false; }; JsUnitTest.Event.fixEvent.stopPropagation = function() { this.cancelBubble = true; }; JsUnitTest.Unit.Logger = function(element) { this.element = JsUnitTest.$(element); if (this.element) {this._createLogTable();} }; JsUnitTest.Unit.Logger.prototype.start = function(testName) { if (!this.element) {return;} var tbody = this.element.getElementsByTagName('tbody')[0]; var tr = document.createElement('tr'); var td; //testname td = document.createElement('td'); td.appendChild(document.createTextNode(testName)); tr.appendChild(td); tr.appendChild(document.createElement('td'));//status tr.appendChild(document.createElement('td'));//message tbody.appendChild(tr); }; JsUnitTest.Unit.Logger.prototype.setStatus = function(status) { var logline = this.getLastLogLine(); logline.className = status; var statusCell = logline.getElementsByTagName('td')[1]; statusCell.appendChild(document.createTextNode(status)); }; JsUnitTest.Unit.Logger.prototype.finish = function(status, summary) { if (!this.element) {return;} this.setStatus(status); this.message(summary); }; JsUnitTest.Unit.Logger.prototype.message = function(message) { if (!this.element) {return;} var cell = this.getMessageCell(); // cell.appendChild(document.createTextNode(this._toHTML(message))); cell.innerHTML = this._toHTML(message); }; JsUnitTest.Unit.Logger.prototype.summary = function(summary) { if (!this.element) {return;} var div = this.element.getElementsByTagName('div')[0]; div.innerHTML = this._toHTML(summary); }; JsUnitTest.Unit.Logger.prototype.getLastLogLine = function() { var tbody = this.element.getElementsByTagName('tbody')[0]; var loglines = tbody.getElementsByTagName('tr'); return loglines[loglines.length - 1]; }; JsUnitTest.Unit.Logger.prototype.getMessageCell = function() { var logline = this.getLastLogLine(); return logline.getElementsByTagName('td')[2]; }; JsUnitTest.Unit.Logger.prototype._createLogTable = function() { var html = '
running...
' + '' + '' + '' + '
StatusTestMessage
'; this.element.innerHTML = html; }; JsUnitTest.Unit.Logger.prototype.appendActionButtons = function(actions) { // actions = $H(actions); // if (!actions.any()) return; // var div = new Element("div", {className: 'action_buttons'}); // actions.inject(div, function(container, action) { // var button = new Element("input").setValue(action.key).observe("click", action.value); // button.type = "button"; // return container.insert(button); // }); // this.getMessageCell().insert(div); }; JsUnitTest.Unit.Logger.prototype._toHTML = function(txt) { return JsUnitTest.escapeHTML(txt).replace(/\n/g,"
"); }; JsUnitTest.Unit.MessageTemplate = function(string) { var parts = []; var str = JsUnitTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) { parts.push(part[0]); }); this.parts = parts; }; JsUnitTest.Unit.MessageTemplate.prototype.evaluate = function(params) { var results = []; for (var i=0; i < this.parts.length; i++) { var part = this.parts[i]; var result = (part == '?') ? JsUnitTest.inspect(params.shift()) : part.replace(/\\\?/, '?'); results.push(result); } return results.join(''); }; // A generic function for performming AJAX requests // It takes one argument, which is an object that contains a set of options // All of which are outline in the comments, below // From John Resig's book Pro JavaScript Techniques // published by Apress, 2006-8 JsUnitTest.ajax = function( options ) { // Load the options object with defaults, if no // values were provided by the user options = { // The type of HTTP Request type: options.type || "POST", // The URL the request will be made to url: options.url || "", // How long to wait before considering the request to be a timeout timeout: options.timeout || 5000, // Functions to call when the request fails, succeeds, // or completes (either fail or succeed) onComplete: options.onComplete || function(){}, onError: options.onError || function(){}, onSuccess: options.onSuccess || function(){}, // The data type that'll be returned from the server // the default is simply to determine what data was returned from the // and act accordingly. data: options.data || "" }; // Create the request object var xml = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); // Open the asynchronous POST request xml.open(options.type, options.url, true); // We're going to wait for a request for 5 seconds, before giving up var timeoutLength = 5000; // Keep track of when the request has been succesfully completed var requestDone = false; // Initalize a callback which will fire 5 seconds from now, cancelling // the request (if it has not already occurred). setTimeout(function(){ requestDone = true; }, timeoutLength); // Watch for when the state of the document gets updated xml.onreadystatechange = function(){ // Wait until the data is fully loaded, // and make sure that the request hasn't already timed out if ( xml.readyState == 4 && !requestDone ) { // Check to see if the request was successful if ( httpSuccess( xml ) ) { // Execute the success callback with the data returned from the server options.onSuccess( httpData( xml, options.type ) ); // Otherwise, an error occurred, so execute the error callback } else { options.onError(); } // Call the completion callback options.onComplete(); // Clean up after ourselves, to avoid memory leaks xml = null; } }; // Establish the connection to the server xml.send(null); // Determine the success of the HTTP response function httpSuccess(r) { try { // If no server status is provided, and we're actually // requesting a local file, then it was successful return !r.status && location.protocol == "file:" || // Any status in the 200 range is good ( r.status >= 200 && r.status < 300 ) || // Successful if the document has not been modified r.status == 304 || // Safari returns an empty status if the file has not been modified navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined"; } catch(e){} // If checking the status failed, then assume that the request failed too return false; } // Extract the correct data from the HTTP response function httpData(r,type) { // Get the content-type header var ct = r.getResponseHeader("content-type"); // If no default type was provided, determine if some // form of XML was returned from the server var data = !type && ct && ct.indexOf("xml") >= 0; // Get the XML Document object if XML was returned from // the server, otherwise return the text contents returned by the server data = type == "xml" || data ? r.responseXML : r.responseText; // If the specified type is "script", execute the returned text // response as if it was JavaScript if ( type == "script" ) { eval.call( window, data ); } // Return the response data (either an XML Document or a text string) return data; } }; JsUnitTest.Unit.Assertions = { buildMessage: function(message, template) { var args = JsUnitTest.arrayfromargs(arguments).slice(2); return (message ? message + '\n' : '') + new JsUnitTest.Unit.MessageTemplate(template).evaluate(args); }, flunk: function(message) { this.assertBlock(message || 'Flunked', function() { return false; }); }, assertBlock: function(message, block) { try { block.call(this) ? this.pass() : this.fail(message); } catch(e) { this.error(e); } }, assert: function(expression, message) { message = this.buildMessage(message || 'assert', 'got ', expression); this.assertBlock(message, function() { return expression; }); }, assertEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertEqual', 'expected , actual: ', expected, actual); this.assertBlock(message, function() { return expected == actual; }); }, assertNotEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertNotEqual', 'expected , actual: ', expected, actual); this.assertBlock(message, function() { return expected != actual; }); }, assertEnumEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertEnumEqual', 'expected , actual: ', expected, actual); this.assertBlock(message, function() { return JsUnitTest.areArraysEqual(expected, actual); }); }, assertEnumNotEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertEnumNotEqual', ' was the same as ', expected, actual); this.assertBlock(message, function() { return JsUnitTest.areArraysNotEqual(expected, actual); }); }, assertHashEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertHashEqual', 'expected , actual: ', JsUnitTest.inspect(expected), JsUnitTest.inspect(actual)); this.assertBlock(message, function() { return JsUnitTest.areHashesEqual(expected, actual); }); }, assertHashNotEqual: function(expected, actual, message) { message = this.buildMessage(message || 'assertHashNotEqual', ' was the same as ', JsUnitTest.inspect(expected), JsUnitTest.inspect(actual)); this.assertBlock(message, function() { return JsUnitTest.areHashesNotEqual(expected, actual); }); }, assertIdentical: function(expected, actual, message) { message = this.buildMessage(message || 'assertIdentical', 'expected , actual: ', expected, actual); this.assertBlock(message, function() { return expected === actual; }); }, assertNotIdentical: function(expected, actual, message) { message = this.buildMessage(message || 'assertNotIdentical', 'expected , actual: ', expected, actual); this.assertBlock(message, function() { return expected !== actual; }); }, assertNull: function(obj, message) { message = this.buildMessage(message || 'assertNull', 'got ', obj); this.assertBlock(message, function() { return obj === null; }); }, assertNotNull: function(obj, message) { message = this.buildMessage(message || 'assertNotNull', 'got ', obj); this.assertBlock(message, function() { return obj !== null; }); }, assertUndefined: function(obj, message) { message = this.buildMessage(message || 'assertUndefined', 'got ', obj); this.assertBlock(message, function() { return typeof obj == "undefined"; }); }, assertNotUndefined: function(obj, message) { message = this.buildMessage(message || 'assertNotUndefined', 'got ', obj); this.assertBlock(message, function() { return typeof obj != "undefined"; }); }, assertNullOrUndefined: function(obj, message) { message = this.buildMessage(message || 'assertNullOrUndefined', 'got ', obj); this.assertBlock(message, function() { return obj == null; }); }, assertNotNullOrUndefined: function(obj, message) { message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got ', obj); this.assertBlock(message, function() { return obj != null; }); }, assertMatch: function(expected, actual, message) { message = this.buildMessage(message || 'assertMatch', 'regex did not match ', expected, actual); this.assertBlock(message, function() { return new RegExp(expected).exec(actual); }); }, assertNoMatch: function(expected, actual, message) { message = this.buildMessage(message || 'assertNoMatch', 'regex matched ', expected, actual); this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)); }); }, assertHasClass: function(element, klass, message) { element = JsUnitTest.$(element); message = this.buildMessage(message || 'assertHasClass', '? doesn\'t have class .', element, klass); this.assertBlock(message, function() { var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == klass || new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName))); // return !!element.className.match(new RegExp(klass)) }); }, assertNotHasClass: function(element, klass, message) { element = JsUnitTest.$(element); message = this.buildMessage(message || 'assertNotHasClass', '? does have class .', element, klass); this.assertBlock(message, function() { var elementClassName = element.className; return !(elementClassName.length > 0 && (elementClassName == klass || new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName))); }); }, assertHidden: function(element, message) { element = JsUnitTest.$(element); message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element); this.assertBlock(message, function() { return !element.style.display || element.style.display == 'none'; }); }, assertInstanceOf: function(expected, actual, message) { message = this.buildMessage(message || 'assertInstanceOf', ' was not an instance of the expected type', actual); this.assertBlock(message, function() { return actual instanceof expected; }); }, assertNotInstanceOf: function(expected, actual, message) { message = this.buildMessage(message || 'assertNotInstanceOf', ' was an instance of the expected type', actual); this.assertBlock(message, function() { return !(actual instanceof expected); }); }, assertRespondsTo: function(method, obj, message) { message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to ', method); this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function'); }); }, assertRaise: function(exceptionName, method, message) { message = this.buildMessage(message || 'assertRaise', ' exception expected but none was raised', exceptionName); var block = function() { try { method(); return false; } catch(e) { if (e.name == exceptionName) {return true;} else {throw e;} } }; this.assertBlock(message, block); }, assertNothingRaised: function(method, message) { try { method(); this.assert(true, "Expected nothing to be thrown"); } catch(e) { message = this.buildMessage(message || 'assertNothingRaised', ' was thrown when nothing was expected.', e); this.flunk(message); } }, _isVisible: function(element) { element = JsUnitTest.$(element); if(!element.parentNode) {return true;} this.assertNotNull(element); if(element.style && (element.style.display == 'none')) { return false; } return arguments.callee.call(this, element.parentNode); }, assertVisible: function(element, message) { message = this.buildMessage(message, '? was not visible.', element); this.assertBlock(message, function() { return this._isVisible(element); }); }, assertNotVisible: function(element, message) { message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element); this.assertBlock(message, function() { return !this._isVisible(element); }); }, assertElementsMatch: function() { var pass = true, expressions = JsUnitTest.arrayfromargs(arguments); var elements = expressions.shift(); if (elements.length != expressions.length) { message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions); this.flunk(message); pass = false; } for (var i=0; i < expressions.length; i++) { var expression = expressions[i]; var element = JsUnitTest.$(elements[i]); if (JsUnitTest.selectorMatch(expression, element)) { pass = true; break; } message = this.buildMessage('assertElementsMatch', 'In index : expected but got ?', index, expression, element); this.flunk(message); pass = false; } this.assert(pass, "Expected all elements to match."); }, assertElementMatches: function(element, expression, message) { this.assertElementsMatch([element], expression); } }; JsUnitTest.Unit.Runner = function(testcases) { var argumentOptions = arguments[1] || {}; var options = this.options = {}; options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog'; options.resultsURL = this.queryParams.resultsURL; options.testLog = JsUnitTest.$(options.testLog); this.tests = this.getTests(testcases); this.currentTest = 0; this.logger = new JsUnitTest.Unit.Logger(options.testLog); var self = this; JsUnitTest.Event.addEvent(window, "load", function() { setTimeout(function() { self.runTests(); }, 0.1); }); }; JsUnitTest.Unit.Runner.prototype.queryParams = JsUnitTest.toQueryParams(); JsUnitTest.Unit.Runner.prototype.portNumber = function() { if (window.location.search.length > 0) { var matches = window.location.search.match(/\:(\d{3,5})\//); if (matches) { return parseInt(matches[1], 10); } } return null; }; JsUnitTest.Unit.Runner.prototype.getTests = function(testcases) { var tests = [], options = this.options; if (this.queryParams.tests) {tests = this.queryParams.tests.split(',');} else if (options.tests) {tests = options.tests;} else if (options.test) {tests = [option.test];} else { for (testname in testcases) { if (testname.match(/^test/)) {tests.push(testname);} } } var results = []; for (var i=0; i < tests.length; i++) { var test = tests[i]; if (testcases[test]) { results.push( new JsUnitTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown) ); } } return results; }; JsUnitTest.Unit.Runner.prototype.getResult = function() { var results = { tests: this.tests.length, assertions: 0, failures: 0, errors: 0, warnings: 0 }; for (var i=0; i < this.tests.length; i++) { var test = this.tests[i]; results.assertions += test.assertions; results.failures += test.failures; results.errors += test.errors; results.warnings += test.warnings; } return results; }; JsUnitTest.Unit.Runner.prototype.postResults = function() { if (this.options.resultsURL) { // new Ajax.Request(this.options.resultsURL, // { method: 'get', parameters: this.getResult(), asynchronous: false }); var results = this.getResult(); var url = this.options.resultsURL + "?"; url += "tests="+ this.tests.length + "&"; url += "assertions="+ results.assertions + "&"; url += "warnings=" + results.warnings + "&"; url += "failures=" + results.failures + "&"; url += "errors=" + results.errors; JsUnitTest.ajax({ url: url, type: 'GET' }); } }; JsUnitTest.Unit.Runner.prototype.runTests = function() { var test = this.tests[this.currentTest], actions; if (!test) {return this.finish();} if (!test.isWaiting) {this.logger.start(test.name);} test.run(); var self = this; if(test.isWaiting) { this.logger.message("Waiting for " + test.timeToWait + "ms"); // setTimeout(this.runTests.bind(this), test.timeToWait || 1000); setTimeout(function() { self.runTests(); }, test.timeToWait || 1000); return; } this.logger.finish(test.status(), test.summary()); if (actions = test.actions) {this.logger.appendActionButtons(actions);} this.currentTest++; // tail recursive, hopefully the browser will skip the stackframe this.runTests(); }; JsUnitTest.Unit.Runner.prototype.finish = function() { this.postResults(); this.logger.summary(this.summary()); }; JsUnitTest.Unit.Runner.prototype.summary = function() { return new JsUnitTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings').evaluate(this.getResult()); }; JsUnitTest.Unit.Testcase = function(name, test, setup, teardown) { this.name = name; this.test = test || function() {}; this.setup = setup || function() {}; this.teardown = teardown || function() {}; this.messages = []; this.actions = {}; }; // import JsUnitTest.Unit.Assertions for (method in JsUnitTest.Unit.Assertions) { JsUnitTest.Unit.Testcase.prototype[method] = JsUnitTest.Unit.Assertions[method]; } JsUnitTest.Unit.Testcase.prototype.isWaiting = false; JsUnitTest.Unit.Testcase.prototype.timeToWait = 1000; JsUnitTest.Unit.Testcase.prototype.assertions = 0; JsUnitTest.Unit.Testcase.prototype.failures = 0; JsUnitTest.Unit.Testcase.prototype.errors = 0; JsUnitTest.Unit.Testcase.prototype.warnings = 0; JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port; // JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711; JsUnitTest.Unit.Testcase.prototype.wait = function(time, nextPart) { this.isWaiting = true; this.test = nextPart; this.timeToWait = time; }; JsUnitTest.Unit.Testcase.prototype.run = function(rethrow) { try { try { if (!this.isWaiting) {this.setup();} this.isWaiting = false; this.test(); } finally { if(!this.isWaiting) { this.teardown(); } } } catch(e) { if (rethrow) {throw e;} this.error(e, this); } }; JsUnitTest.Unit.Testcase.prototype.summary = function() { var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings\n'; return new JsUnitTest.Template(msg).evaluate(this) + this.messages.join("\n"); }; JsUnitTest.Unit.Testcase.prototype.pass = function() { this.assertions++; }; JsUnitTest.Unit.Testcase.prototype.fail = function(message) { this.failures++; var line = ""; try { throw new Error("stack"); } catch(e){ line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1]; } this.messages.push("Failure: " + message + (line ? " Line #" + line : "")); }; JsUnitTest.Unit.Testcase.prototype.warning = function(message) { this.warnings++; var line = ""; try { throw new Error("stack"); } catch(e){ line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1]; } this.messages.push("Warning: " + message + (line ? " Line #" + line : "")); }; JsUnitTest.Unit.Testcase.prototype.warn = JsUnitTest.Unit.Testcase.prototype.warning; JsUnitTest.Unit.Testcase.prototype.info = function(message) { this.messages.push("Info: " + message); }; JsUnitTest.Unit.Testcase.prototype.error = function(error, test) { this.errors++; this.actions['retry with throw'] = function() { test.run(true); }; this.messages.push(error.name + ": "+ error.message + "(" + JsUnitTest.inspect(error) + ")"); if( typeof console != "undefined" && console.error) { console.error("Test '" + test.name + "' died, exception and test follows"); console.error(error); console.error(test.test.toString()); } }; JsUnitTest.Unit.Testcase.prototype.status = function() { if (this.failures > 0) {return 'failed';} if (this.errors > 0) {return 'error';} if (this.warnings > 0) {return 'warning';} return 'passed'; }; JsUnitTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) { var startAt = new Date(); (iterations || 1).times(operation); var timeTaken = ((new Date())-startAt); this.info((arguments[2] || 'Operation') + ' finished ' + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); return timeTaken; }; Test = JsUnitTest