| /* 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 = String.specialChar[match[0]]; |
| return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); |
| }); |
| if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; |
| return "'" + escapedString.replace(/'/g, '\\\'') + "'"; |
| }; |
| return String(object); |
| } catch (e) { |
| if (e instanceof RangeError) return '...'; |
| throw e; |
| } |
| }, |
| $: 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,'<').replace(/>/g,'>'); |
| }, |
| arrayfromargs: function(args) { |
| var myarray = new Array(); |
| var i; |
| |
| for (i=0;i<args.length;i++) |
| myarray[i] = args[i]; |
| |
| return myarray; |
| }, |
| hashToSortedArray: function(hash) { |
| var results = []; |
| for (key in hash) { |
| results.push([key, hash[key]]); |
| } |
| return results.sort(); |
| }, |
| flattenArray: function(array) { |
| var results = arguments[1] || []; |
| for (var i=0; i < array.length; i++) { |
| var object = array[i]; |
| if (object != null && typeof object == "object" && |
| 'splice' in object && 'join' in object) { |
| this.flattenArray(object, results); |
| } else { |
| results.push(object); |
| } |
| }; |
| return results; |
| }, |
| selectorMatch: function(expression, element) { |
| var tokens = []; |
| var patterns = { |
| // combinators must be listed first |
| // (and descendant needs to be last combinator) |
| laterSibling: /^\s*~\s*/, |
| child: /^\s*>\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 = '<div class="logsummary">running...</div>' + |
| '<table class="logtable">' + |
| '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' + |
| '<tbody class="loglines"></tbody>' + |
| '</table>'; |
| 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,"<br/>"); |
| }; |
| 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); |
| var expected_array = JsUnitTest.flattenArray(expected); |
| var actual_array = JsUnitTest.flattenArray(actual); |
| this.assertBlock(message, function() { |
| if (expected_array.length == actual_array.length) { |
| for (var i=0; i < expected_array.length; i++) { |
| if (expected_array[i] != actual_array[i]) return false; |
| }; |
| return true; |
| } |
| return false; |
| }); |
| }, |
| |
| assertEnumNotEqual: function(expected, actual, message) { |
| message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual); |
| var expected_array = JsUnitTest.flattenArray(expected); |
| var actual_array = JsUnitTest.flattenArray(actual); |
| this.assertBlock(message, function() { |
| if (expected_array.length == actual_array.length) { |
| for (var i=0; i < expected_array.length; i++) { |
| if (expected_array[i] != actual_array[i]) return true; |
| }; |
| return false; |
| } |
| return true; |
| }); |
| }, |
| |
| assertHashEqual: function(expected, actual, message) { |
| message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual); |
| var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected)); |
| var actual_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual)); |
| var block = function() { |
| if (expected_array.length == actual_array.length) { |
| for (var i=0; i < expected_array.length; i++) { |
| if (expected_array[i] != actual_array[i]) return false; |
| }; |
| return true; |
| } |
| return false; |
| }; |
| this.assertBlock(message, block); |
| }, |
| |
| assertHashNotEqual: function(expected, actual, message) { |
| message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual); |
| var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected)); |
| var actual_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual)); |
| // from now we recursively zip & compare nested arrays |
| var block = function() { |
| if (expected_array.length == actual_array.length) { |
| for (var i=0; i < expected_array.length; i++) { |
| if (expected_array[i] != actual_array[i]) return true; |
| }; |
| return false; |
| } |
| return true; |
| }; |
| this.assertBlock(message, block); |
| }, |
| |
| 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]); |
| } |
| } |
| 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) + ")"); |
| }; |
| |
| 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 |