1 /* Jsunittest, version 0.6.0
2 * (c) 2008 Dr Nic Williams
4 * Jsunittest is freely distributable under
5 * the terms of an MIT-style license.
6 * For details, see the web site: http://jsunittest.rubyforge.org
8 *--------------------------------------------------------------------------*/
16 inspect: function(object) {
18 if (typeof object == "undefined") return 'undefined';
19 if (object === null) return 'null';
20 if (typeof object == "string") {
21 var useDoubleQuotes = arguments[1];
22 var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) {
23 var character = String.specialChar[match[0]];
24 return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
26 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
27 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
29 return String(object);
31 if (e instanceof RangeError) return '...';
35 $: function(element) {
36 if (arguments.length > 1) {
37 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
38 elements.push(this.$(arguments[i]));
41 if (typeof element == "string")
42 element = document.getElementById(element);
45 gsub: function(source, pattern, replacement) {
46 var result = '', match;
47 replacement = arguments.callee.prepareReplacement(replacement);
49 while (source.length > 0) {
50 if (match = source.match(pattern)) {
51 result += source.slice(0, match.index);
52 result += DrNicTest.String.interpret(replacement(match));
53 source = source.slice(match.index + match[0].length);
55 result += source, source = '';
60 scan: function(source, pattern, iterator) {
61 this.gsub(source, pattern, iterator);
62 return String(source);
64 escapeHTML: function(data) {
65 return data.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
67 arrayfromargs: function(args) {
68 var myarray = new Array();
71 for (i=0;i<args.length;i++)
76 hashToSortedArray: function(hash) {
79 results.push([key, hash[key]]);
81 return results.sort();
83 flattenArray: function(array) {
84 var results = arguments[1] || [];
85 for (var i=0; i < array.length; i++) {
86 var object = array[i];
87 if (object != null && typeof object == "object" &&
88 'splice' in object && 'join' in object) {
89 this.flattenArray(object, results);
96 selectorMatch: function(expression, element) {
99 // combinators must be listed first
100 // (and descendant needs to be last combinator)
101 laterSibling: /^\s*~\s*/,
103 adjacent: /^\s*\+\s*/,
107 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
108 id: /^#([\w\-\*]+)(\b|$)/,
109 className: /^\.([\w\-\*]+)(\b|$)/,
111 /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
112 attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
113 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
117 tagName: function(element, matches) {
118 return matches[1].toUpperCase() == element.tagName.toUpperCase();
121 className: function(element, matches) {
122 return Element.hasClassName(element, matches[1]);
125 id: function(element, matches) {
126 return element.id === matches[1];
129 attrPresence: function(element, matches) {
130 return Element.hasAttribute(element, matches[1]);
133 attr: function(element, matches) {
134 var nodeValue = Element.readAttribute(element, matches[1]);
135 return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]);
138 var e = this.expression, ps = patterns, as = assertions;
141 while (e && le !== e && (/\S/).test(e)) {
145 if (m = e.match(p)) {
146 // use the Selector.assertions methods unless the selector
149 tokens.push([i, Object.clone(m)]);
150 e = e.replace(m[0], '');
156 var match = true, name, matches;
157 for (var i = 0, token; token = tokens[i]; i++) {
158 name = token[0], matches = token[1];
159 if (!assertions[name](element, matches)) {
160 match = false; break;
166 toQueryParams: function(query, separator) {
167 var query = query || window.location.search;
168 var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/);
169 if (!match) return { };
172 var parts = match[1].split(separator || '&');
173 for (var i=0; i < parts.length; i++) {
174 var pair = parts[i].split('=');
176 var key = decodeURIComponent(pair.shift());
177 var value = pair.length > 1 ? pair.join('=') : pair[0];
178 if (value != undefined) value = decodeURIComponent(value);
181 var object = hash[key];
182 var isArray = object != null && typeof object == "object" &&
183 'splice' in object && 'join' in object
184 if (!isArray) hash[key] = [hash[key]];
185 hash[key].push(value);
187 else hash[key] = value;
194 interpret: function(value) {
195 return value == null ? '' : String(value);
200 DrNicTest.gsub.prepareReplacement = function(replacement) {
201 if (typeof replacement == "function") return replacement;
202 var template = new Template(replacement);
203 return function(match) { return template.evaluate(match) };
206 DrNicTest.Template = function(template, pattern) {
207 this.template = template; //template.toString();
208 this.pattern = pattern || DrNicTest.Template.Pattern;
211 DrNicTest.Template.prototype.evaluate = function(object) {
212 if (typeof object.toTemplateReplacements == "function")
213 object = object.toTemplateReplacements();
215 return DrNicTest.gsub(this.template, this.pattern, function(match) {
216 if (object == null) return '';
218 var before = match[1] || '';
219 if (before == '\\') return match[2];
221 var ctx = object, expr = match[3];
222 var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
223 match = pattern.exec(expr);
224 if (match == null) return before;
226 while (match != null) {
227 var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1];
229 if (null == ctx || '' == match[3]) break;
230 expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
231 match = pattern.exec(expr);
234 return before + DrNicTest.String.interpret(ctx);
238 DrNicTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
239 DrNicTest.Event = {};
240 // written by Dean Edwards, 2005
241 // with input from Tino Zijdel, Matthias Miller, Diego Perini
242 // namespaced by Dr Nic Williams 2008
244 // http://dean.edwards.name/weblog/2005/10/add-event/
245 // http://dean.edwards.name/weblog/2005/10/add-event2/
246 DrNicTest.Event.addEvent = function(element, type, handler) {
247 if (element.addEventListener) {
248 element.addEventListener(type, handler, false);
250 // assign each event handler a unique ID
251 if (!handler.$$guid) handler.$$guid = addEvent.guid++;
252 // create a hash table of event types for the element
253 if (!element.events) element.events = {};
254 // create a hash table of event handlers for each element/event pair
255 var handlers = element.events[type];
257 handlers = element.events[type] = {};
258 // store the existing event handler (if there is one)
259 if (element["on" + type]) {
260 handlers[0] = element["on" + type];
263 // store the event handler in the hash table
264 handlers[handler.$$guid] = handler;
265 // assign a global event handler to do all the work
266 element["on" + type] = handleEvent;
269 // a counter used to create unique IDs
270 DrNicTest.Event.addEvent.guid = 1;
272 DrNicTest.Event.removeEvent = function(element, type, handler) {
273 if (element.removeEventListener) {
274 element.removeEventListener(type, handler, false);
276 // delete the event handler from the hash table
277 if (element.events && element.events[type]) {
278 delete element.events[type][handler.$$guid];
283 DrNicTest.Event.handleEvent = function(event) {
284 var returnValue = true;
285 // grab the event object (IE uses a global event object)
286 event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
287 // get a reference to the hash table of event handlers
288 var handlers = this.events[event.type];
289 // execute each event handler
290 for (var i in handlers) {
291 this.$$handleEvent = handlers[i];
292 if (this.$$handleEvent(event) === false) {
299 DrNicTest.Event.fixEvent = function(event) {
300 // add W3C standard event methods
301 event.preventDefault = fixEvent.preventDefault;
302 event.stopPropagation = fixEvent.stopPropagation;
305 DrNicTest.Event.fixEvent.preventDefault = function() {
306 this.returnValue = false;
308 DrNicTest.Event.fixEvent.stopPropagation = function() {
309 this.cancelBubble = true;
312 DrNicTest.Unit.Logger = function(element) {
313 this.element = DrNicTest.$(element);
314 if (this.element) this._createLogTable();
317 DrNicTest.Unit.Logger.prototype.start = function(testName) {
318 if (!this.element) return;
319 var tbody = this.element.getElementsByTagName('tbody')[0];
320 tbody.innerHTML = tbody.innerHTML + '<tr><td>' + testName + '</td><td></td><td></td></tr>';
323 DrNicTest.Unit.Logger.prototype.setStatus = function(status) {
324 var logline = this.getLastLogLine();
325 logline.className = status;
326 var statusCell = logline.getElementsByTagName('td')[1];
327 statusCell.innerHTML = status;
330 DrNicTest.Unit.Logger.prototype.finish = function(status, summary) {
331 if (!this.element) return;
332 this.setStatus(status);
333 this.message(summary);
336 DrNicTest.Unit.Logger.prototype.message = function(message) {
337 if (!this.element) return;
338 var cell = this.getMessageCell();
339 cell.innerHTML = this._toHTML(message);
342 DrNicTest.Unit.Logger.prototype.summary = function(summary) {
343 if (!this.element) return;
344 var div = this.element.getElementsByTagName('div')[0];
345 div.innerHTML = this._toHTML(summary);
348 DrNicTest.Unit.Logger.prototype.getLastLogLine = function() {
349 var tbody = this.element.getElementsByTagName('tbody')[0];
350 var loglines = tbody.getElementsByTagName('tr');
351 return loglines[loglines.length - 1];
354 DrNicTest.Unit.Logger.prototype.getMessageCell = function() {
355 var logline = this.getLastLogLine();
356 return logline.getElementsByTagName('td')[2];
359 DrNicTest.Unit.Logger.prototype._createLogTable = function() {
360 var html = '<div class="logsummary">running...</div>' +
361 '<table class="logtable">' +
362 '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
363 '<tbody class="loglines"></tbody>' +
365 this.element.innerHTML = html;
368 DrNicTest.Unit.Logger.prototype.appendActionButtons = function(actions) {
369 // actions = $H(actions);
370 // if (!actions.any()) return;
371 // var div = new Element("div", {className: 'action_buttons'});
372 // actions.inject(div, function(container, action) {
373 // var button = new Element("input").setValue(action.key).observe("click", action.value);
374 // button.type = "button";
375 // return container.insert(button);
377 // this.getMessageCell().insert(div);
380 DrNicTest.Unit.Logger.prototype._toHTML = function(txt) {
381 return DrNicTest.escapeHTML(txt).replace(/\n/g,"<br/>");
383 DrNicTest.Unit.MessageTemplate = function(string) {
385 var str = DrNicTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
391 DrNicTest.Unit.MessageTemplate.prototype.evaluate = function(params) {
393 for (var i=0; i < this.parts.length; i++) {
394 var part = this.parts[i];
395 var result = (part == '?') ? DrNicTest.inspect(params.shift()) : part.replace(/\\\?/, '?');
396 results.push(result);
398 return results.join('');
400 // A generic function for performming AJAX requests
401 // It takes one argument, which is an object that contains a set of options
402 // All of which are outline in the comments, below
403 // From John Resig's book Pro JavaScript Techniques
404 // published by Apress, 2006-8
405 DrNicTest.ajax = function( options ) {
407 // Load the options object with defaults, if no
408 // values were provided by the user
410 // The type of HTTP Request
411 type: options.type || "POST",
413 // The URL the request will be made to
414 url: options.url || "",
416 // How long to wait before considering the request to be a timeout
417 timeout: options.timeout || 5000,
419 // Functions to call when the request fails, succeeds,
420 // or completes (either fail or succeed)
421 onComplete: options.onComplete || function(){},
422 onError: options.onError || function(){},
423 onSuccess: options.onSuccess || function(){},
425 // The data type that'll be returned from the server
426 // the default is simply to determine what data was returned from the
427 // and act accordingly.
428 data: options.data || ""
431 // Create the request object
432 var xml = new XMLHttpRequest();
434 // Open the asynchronous POST request
435 xml.open(options.type, options.url, true);
437 // We're going to wait for a request for 5 seconds, before giving up
438 var timeoutLength = 5000;
440 // Keep track of when the request has been succesfully completed
441 var requestDone = false;
443 // Initalize a callback which will fire 5 seconds from now, cancelling
444 // the request (if it has not already occurred).
445 setTimeout(function(){
449 // Watch for when the state of the document gets updated
450 xml.onreadystatechange = function(){
451 // Wait until the data is fully loaded,
452 // and make sure that the request hasn't already timed out
453 if ( xml.readyState == 4 && !requestDone ) {
455 // Check to see if the request was successful
456 if ( httpSuccess( xml ) ) {
458 // Execute the success callback with the data returned from the server
459 options.onSuccess( httpData( xml, options.type ) );
461 // Otherwise, an error occurred, so execute the error callback
466 // Call the completion callback
467 options.onComplete();
469 // Clean up after ourselves, to avoid memory leaks
474 // Establish the connection to the server
477 // Determine the success of the HTTP response
478 function httpSuccess(r) {
480 // If no server status is provided, and we're actually
481 // requesting a local file, then it was successful
482 return !r.status && location.protocol == "file:" ||
484 // Any status in the 200 range is good
485 ( r.status >= 200 && r.status < 300 ) ||
487 // Successful if the document has not been modified
490 // Safari returns an empty status if the file has not been modified
491 navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined";
494 // If checking the status failed, then assume that the request failed too
498 // Extract the correct data from the HTTP response
499 function httpData(r,type) {
500 // Get the content-type header
501 var ct = r.getResponseHeader("content-type");
503 // If no default type was provided, determine if some
504 // form of XML was returned from the server
505 var data = !type && ct && ct.indexOf("xml") >= 0;
507 // Get the XML Document object if XML was returned from
508 // the server, otherwise return the text contents returned by the server
509 data = type == "xml" || data ? r.responseXML : r.responseText;
511 // If the specified type is "script", execute the returned text
512 // response as if it was JavaScript
513 if ( type == "script" )
514 eval.call( window, data );
516 // Return the response data (either an XML Document or a text string)
521 DrNicTest.Unit.Assertions = {
522 buildMessage: function(message, template) {
523 var args = DrNicTest.arrayfromargs(arguments).slice(2);
524 return (message ? message + '\n' : '') +
525 new DrNicTest.Unit.MessageTemplate(template).evaluate(args);
528 flunk: function(message) {
529 this.assertBlock(message || 'Flunked', function() { return false });
532 assertBlock: function(message, block) {
534 block.call(this) ? this.pass() : this.fail(message);
535 } catch(e) { this.error(e) }
538 assert: function(expression, message) {
539 message = this.buildMessage(message || 'assert', 'got <?>', expression);
540 this.assertBlock(message, function() { return expression });
543 assertEqual: function(expected, actual, message) {
544 message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
545 this.assertBlock(message, function() { return expected == actual });
548 assertNotEqual: function(expected, actual, message) {
549 message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
550 this.assertBlock(message, function() { return expected != actual });
553 assertEnumEqual: function(expected, actual, message) {
554 message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
555 var expected_array = DrNicTest.flattenArray(expected);
556 var actual_array = DrNicTest.flattenArray(actual);
557 this.assertBlock(message, function() {
558 if (expected_array.length == actual_array.length) {
559 for (var i=0; i < expected_array.length; i++) {
560 if (expected_array[i] != actual_array[i]) return false;
568 assertEnumNotEqual: function(expected, actual, message) {
569 message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual);
570 var expected_array = DrNicTest.flattenArray(expected);
571 var actual_array = DrNicTest.flattenArray(actual);
572 this.assertBlock(message, function() {
573 if (expected_array.length == actual_array.length) {
574 for (var i=0; i < expected_array.length; i++) {
575 if (expected_array[i] != actual_array[i]) return true;
583 assertHashEqual: function(expected, actual, message) {
584 message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual);
585 var expected_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(expected));
586 var actual_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(actual));
587 var block = function() {
588 if (expected_array.length == actual_array.length) {
589 for (var i=0; i < expected_array.length; i++) {
590 if (expected_array[i] != actual_array[i]) return false;
596 this.assertBlock(message, block);
599 assertHashNotEqual: function(expected, actual, message) {
600 message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual);
601 var expected_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(expected));
602 var actual_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(actual));
603 // from now we recursively zip & compare nested arrays
604 var block = function() {
605 if (expected_array.length == actual_array.length) {
606 for (var i=0; i < expected_array.length; i++) {
607 if (expected_array[i] != actual_array[i]) return true;
613 this.assertBlock(message, block);
616 assertIdentical: function(expected, actual, message) {
617 message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
618 this.assertBlock(message, function() { return expected === actual });
621 assertNotIdentical: function(expected, actual, message) {
622 message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
623 this.assertBlock(message, function() { return expected !== actual });
626 assertNull: function(obj, message) {
627 message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
628 this.assertBlock(message, function() { return obj === null });
631 assertNotNull: function(obj, message) {
632 message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
633 this.assertBlock(message, function() { return obj !== null });
636 assertUndefined: function(obj, message) {
637 message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
638 this.assertBlock(message, function() { return typeof obj == "undefined" });
641 assertNotUndefined: function(obj, message) {
642 message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
643 this.assertBlock(message, function() { return typeof obj != "undefined" });
646 assertNullOrUndefined: function(obj, message) {
647 message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
648 this.assertBlock(message, function() { return obj == null });
651 assertNotNullOrUndefined: function(obj, message) {
652 message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
653 this.assertBlock(message, function() { return obj != null });
656 assertMatch: function(expected, actual, message) {
657 message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
658 this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
661 assertNoMatch: function(expected, actual, message) {
662 message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
663 this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
666 assertHidden: function(element, message) {
667 message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
668 this.assertBlock(message, function() { return element.style.display == 'none' });
671 assertInstanceOf: function(expected, actual, message) {
672 message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
673 this.assertBlock(message, function() { return actual instanceof expected });
676 assertNotInstanceOf: function(expected, actual, message) {
677 message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
678 this.assertBlock(message, function() { return !(actual instanceof expected) });
681 assertRespondsTo: function(method, obj, message) {
682 message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
683 this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
686 assertRaise: function(exceptionName, method, message) {
687 message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName);
688 var block = function() {
693 if (e.name == exceptionName) return true;
697 this.assertBlock(message, block);
700 assertNothingRaised: function(method, message) {
703 this.assert(true, "Expected nothing to be thrown");
705 message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
710 _isVisible: function(element) {
711 element = DrNicTest.$(element);
712 if(!element.parentNode) return true;
713 this.assertNotNull(element);
714 if(element.style && element.style.display == 'none')
717 return arguments.callee.call(this, element.parentNode);
720 assertVisible: function(element, message) {
721 message = this.buildMessage(message, '? was not visible.', element);
722 this.assertBlock(message, function() { return this._isVisible(element) });
725 assertNotVisible: function(element, message) {
726 message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
727 this.assertBlock(message, function() { return !this._isVisible(element) });
730 assertElementsMatch: function() {
731 var pass = true, expressions = DrNicTest.arrayfromargs(arguments);
732 var elements = expressions.shift();
733 if (elements.length != expressions.length) {
734 message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
738 for (var i=0; i < expressions.length; i++) {
739 var expression = expressions[i];
740 var element = DrNicTest.$(elements[i]);
741 if (DrNicTest.selectorMatch(expression, element)) {
745 message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element);
749 this.assert(pass, "Expected all elements to match.");
752 assertElementMatches: function(element, expression, message) {
753 this.assertElementsMatch([element], expression);
756 DrNicTest.Unit.Runner = function(testcases) {
757 var argumentOptions = arguments[1] || {};
758 var options = this.options = {};
759 options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog';
760 options.resultsURL = this.queryParams.resultsURL;
761 options.testLog = DrNicTest.$(options.testLog);
763 this.tests = this.getTests(testcases);
764 this.currentTest = 0;
765 this.logger = new DrNicTest.Unit.Logger(options.testLog);
768 DrNicTest.Event.addEvent(window, "load", function() {
769 setTimeout(function() {
775 DrNicTest.Unit.Runner.prototype.queryParams = DrNicTest.toQueryParams();
777 DrNicTest.Unit.Runner.prototype.portNumber = function() {
778 if (window.location.search.length > 0) {
779 var matches = window.location.search.match(/\:(\d{3,5})\//);
781 return parseInt(matches[1]);
787 DrNicTest.Unit.Runner.prototype.getTests = function(testcases) {
788 var tests = [], options = this.options;
789 if (this.queryParams.tests) tests = this.queryParams.tests.split(',');
790 else if (options.tests) tests = options.tests;
791 else if (options.test) tests = [option.test];
793 for (testname in testcases) {
794 if (testname.match(/^test/)) tests.push(testname);
798 for (var i=0; i < tests.length; i++) {
802 new DrNicTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown)
808 DrNicTest.Unit.Runner.prototype.getResult = function() {
810 tests: this.tests.length,
816 for (var i=0; i < this.tests.length; i++) {
817 var test = this.tests[i];
818 results.assertions += test.assertions;
819 results.failures += test.failures;
820 results.errors += test.errors;
825 DrNicTest.Unit.Runner.prototype.postResults = function() {
826 if (this.options.resultsURL) {
827 // new Ajax.Request(this.options.resultsURL,
828 // { method: 'get', parameters: this.getResult(), asynchronous: false });
829 var results = this.getResult();
830 var url = this.options.resultsURL + "?";
831 url += "assertions="+ results.assertions + "&";
832 url += "failures=" + results.failures + "&";
833 url += "errors=" + results.errors;
841 DrNicTest.Unit.Runner.prototype.runTests = function() {
842 var test = this.tests[this.currentTest], actions;
844 if (!test) return this.finish();
845 if (!test.isWaiting) this.logger.start(test.name);
849 this.logger.message("Waiting for " + test.timeToWait + "ms");
850 // setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
851 setTimeout(function() {
853 }, test.timeToWait || 1000);
857 this.logger.finish(test.status(), test.summary());
858 if (actions = test.actions) this.logger.appendActionButtons(actions);
860 // tail recursive, hopefully the browser will skip the stackframe
864 DrNicTest.Unit.Runner.prototype.finish = function() {
866 this.logger.summary(this.summary());
869 DrNicTest.Unit.Runner.prototype.summary = function() {
870 return new DrNicTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors').evaluate(this.getResult());
872 DrNicTest.Unit.Testcase = function(name, test, setup, teardown) {
874 this.test = test || function() {};
875 this.setup = setup || function() {};
876 this.teardown = teardown || function() {};
880 // import DrNicTest.Unit.Assertions
882 for (method in DrNicTest.Unit.Assertions) {
883 DrNicTest.Unit.Testcase.prototype[method] = DrNicTest.Unit.Assertions[method];
886 DrNicTest.Unit.Testcase.prototype.isWaiting = false;
887 DrNicTest.Unit.Testcase.prototype.timeToWait = 1000;
888 DrNicTest.Unit.Testcase.prototype.assertions = 0;
889 DrNicTest.Unit.Testcase.prototype.failures = 0;
890 DrNicTest.Unit.Testcase.prototype.errors = 0;
891 // DrNicTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711;
892 DrNicTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port;
894 DrNicTest.Unit.Testcase.prototype.wait = function(time, nextPart) {
895 this.isWaiting = true;
896 this.test = nextPart;
897 this.timeToWait = time;
900 DrNicTest.Unit.Testcase.prototype.run = function(rethrow) {
903 if (!this.isWaiting) this.setup();
904 this.isWaiting = false;
907 if(!this.isWaiting) {
913 if (rethrow) throw e;
918 DrNicTest.Unit.Testcase.prototype.summary = function() {
919 var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors\n';
920 return new DrNicTest.Template(msg).evaluate(this) +
921 this.messages.join("\n");
924 DrNicTest.Unit.Testcase.prototype.pass = function() {
928 DrNicTest.Unit.Testcase.prototype.fail = function(message) {
932 throw new Error("stack");
934 line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
936 this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
939 DrNicTest.Unit.Testcase.prototype.info = function(message) {
940 this.messages.push("Info: " + message);
943 DrNicTest.Unit.Testcase.prototype.error = function(error, test) {
945 this.actions['retry with throw'] = function() { test.run(true) };
946 this.messages.push(error.name + ": "+ error.message + "(" + DrNicTest.inspect(error) + ")");
949 DrNicTest.Unit.Testcase.prototype.status = function() {
950 if (this.failures > 0) return 'failed';
951 if (this.errors > 0) return 'error';
955 DrNicTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) {
956 var startAt = new Date();
957 (iterations || 1).times(operation);
958 var timeTaken = ((new Date())-startAt);
959 this.info((arguments[2] || 'Operation') + ' finished ' +
960 iterations + ' iterations in ' + (timeTaken/1000)+'s' );