/*  Jsunittest-vs, version 1.0.0.0-vs
 *  (c) 2009 Sky Sanders (http://skysanders.net/subtext/)
 *
 *  Jsunittest-vs is freely distributable under 
 *  the terms of an MIT-style license.
 *  For details, see the web site: http://jsunittest.rubyforge.org
 *  
 *  Jsunittest-vs is based on codebase Jsunittest
 *  (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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="object" type="Object"></param>
      /// <returns type="String"></returns>
      
    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)
  {
      /// <summary>
      ///
      /// </summary>

      /// <param name="object" type="Object"></param>
      /// <returns type="String"></returns>

    return Object.prototype.toString.call(object)
     .match(/^\[object\s(.*)\]$/)[1]; 
  },

  $: function(element)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" type="String">Id(d) of dom elements</param>
      /// <returns domElement="true"></returns>
      
    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)
  {
      /// <summary>
      ///
      /// </summary>

      /// <param name="source" type=""></param>
      /// <param name="pattern" type=""></param>
      /// <param name="replacement" type=""></param>

      /// <returns type=""></returns>

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="source" type=""></param>
      /// <param name="pattern" type=""></param>
      /// <param name="iterator" type=""></param>

      /// <returns type=""></returns>

    this.gsub(source, pattern, iterator);
    return String(source);
  },
  escapeHTML: function(data)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="data" type=""></param>
      /// <returns type=""></returns>
    return data.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  toHexString: function(n)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="n" type="Number"></param>
      /// <returns type="String"></returns>
      
    var string = n.toString(16);
    return '00'.substring(string.length) + string;
  },
  arrayfromargs: function(args)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="args" type="arguments"></param>
      /// <returns type=""></returns>
  	var myarray = new Array();
  	var i;

  	for (i=0;i<args.length;i++) {
  	  myarray[i] = args[i];
  	}

  	return myarray;
  },
  
  // from now we recursively zip & compare nested arrays
  areArraysEqual: function(expected, actual)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="" type=""></param>
      /// <returns type=""></returns>
      
    var expected_array = JsUnitTest.flattenArray(expected);
    var actual_array   = JsUnitTest.flattenArray(actual);
    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;
  },

  areArraysNotEqual: function(expected, actual)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="" type=""></param>
      /// <returns type=""></returns>
      
    return !this.areArraysEqual(expected, actual);
  },

  areHashesEqual: function(expected, actual)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="" type=""></param>
      /// <returns type=""></returns>
      
    var expected_array = JsUnitTest.hashToSortedArray(expected);
    var actual_array   = JsUnitTest.hashToSortedArray(actual);
    return this.areArraysEqual(expected_array, actual_array);
  },

  areHashesNotEqual: function(expected, actual)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="" type=""></param>
      /// <returns type=""></returns>
      
    return !this.areHashesEqual(expected, actual);
  },

  hashToSortedArray: function(hash)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="hash" type="Object"></param>
      /// <returns type=""></returns>

    var results = [];
    for (key in hash) {
      results.push([key, hash[key]]);
    }
    return results.sort();
  },
  flattenArray: function(array)
  {
      /// <summary>
      /// 
      /// </summary>
      /// <param name="array" type="Array"></param>
      /// <returns type="Array"></returns>

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="expression" type=""></param>
      /// <param name="element" domElement="true"></param>
      /// <returns type=""></returns>

    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)
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="" type=""></param>
        /// <returns type=""></returns>

        return matches[1].toUpperCase() == element.tagName.toUpperCase();
      },

      className: function(element, matches)
      {
          /// <summary>
          ///
          /// </summary>
          /// <param name="" type=""></param>
          /// <returns type=""></returns>
        return Element.hasClassName(element, matches[1]);
      },

      id: function(element, matches)
      {
          /// <summary>
          ///
          /// </summary>
          /// <param name="" type=""></param>
          /// <returns type=""></returns>
        return element.id === matches[1];
      },

      attrPresence: function(element, matches)
      {
          /// <summary>
          ///
          /// </summary>
          /// <param name="" type=""></param>
          /// <returns type=""></returns>
        return Element.hasAttribute(element, matches[1]);
      },

      attr: function(element, matches)
      {
          /// <summary>
          ///
          /// </summary>
          /// <param name="" type=""></param>
          /// <returns type=""></returns>
        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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="query_in" type="String"></param>
      /// <param name="separator" type="String"></param>
      /// <returns type=""></returns>

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="value" type="Object"></param>
      /// <returns type="String"></returns>

      return value == null ? '' : String(value);
    }
  }
};

JsUnitTest.gsub.prepareReplacement = function(replacement)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="replacement" type="Object"></param>
    /// <returns type="Function"></returns>

  if (typeof replacement == "function") {return replacement;}
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match); };
};

JsUnitTest.Version = '1.0.0.0-vs';
JsUnitTest.Template = function(template, pattern)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="template" type=""></param>
    /// <param name="pattern" type=""></param>
    /// <returns type="JsUnitTest.Template"></returns>

  this.template = template; //template.toString();
  this.pattern = pattern || JsUnitTest.Template.Pattern;
};

JsUnitTest.Template.prototype.evaluate = function(object)
{

    /// <summary>
    ///
    /// </summary>
    /// <param name="object" type="Object"></param>
    /// <returns type="String"></returns>

  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 = {};

JsUnitTest.Event.addEvent = function(element, type, handler)
{
    /// <summary>
    /// 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/
    /// </summary>
    /// <param name="element" domElement="true"></param>
    /// <param name="type" type="String"></param>
    /// <param name="handler" type="Function"></param>

	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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="element" domElement="true"></param>
    /// <param name="type" type="String"></param>
    /// <param name="handler" type="Function"></param>

	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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="event" type=""></param>
    /// <returns type="Object"></returns>

	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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="event" type=""></param>
    /// <returns type=""></returns>

	// add W3C standard event methods
	event.preventDefault = this.fixEvent.preventDefault;
	event.stopPropagation = this.fixEvent.stopPropagation;
	return event;
};
JsUnitTest.Event.fixEvent.preventDefault = function()
{
    /// <summary>
    ///
    /// </summary>
	this.returnValue = false;
};
JsUnitTest.Event.fixEvent.stopPropagation = function()
{
    /// <summary>
    ///
    /// </summary>
	this.cancelBubble = true;
};

JsUnitTest.Unit.Logger = function(element, title, append)
{

    /// <summary>
    ///
    /// </summary>
    /// <param name="element" type="String">Logger element ID (or element reference)</param>
    /// <param name="title" type="String">Title for Logger</param>
    /// <param name="append" type="Boolean">if true, create child div for Logger.</param>
    /// <field name="element" domElement="true"></field>
    /// <returns type="JsUnitTest.Unit.Logger"></returns>
    /// <remarks>Sky added title and append args - see _createLogTable</remarks>

    /// <todo>abstract this class to enable multiple logger plug-ins for reporting results</todo>


  this.element = JsUnitTest.$(element);
  if (this.element) this._createLogTable(title, append);
};

JsUnitTest.Unit.Logger.prototype.start = function(testName)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="testName" type="String"></param>

  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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="status" type="String"></param>

  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)
{
    /// <summary>
    ///
    /// </summary>

    /// <param name="status" type="String"></param>
    /// <param name="summary" type="String"></param>
  if (!this.element) {return;}
  this.setStatus(status);
  this.message(summary);
};

JsUnitTest.Unit.Logger.prototype.message = function(message)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="message" type="String"></param>

  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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="summary" type="String"></param>

  if (!this.element) {return;}
  var div = this.element.getElementsByTagName('div')[0];
  div.innerHTML = this._toHTML(summary);
};

JsUnitTest.Unit.Logger.prototype.getLastLogLine = function()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns type="String"></returns>

  var tbody = this.element.getElementsByTagName('tbody')[0];
  var loglines = tbody.getElementsByTagName('tr');
  return loglines[loglines.length - 1];
};

JsUnitTest.Unit.Logger.prototype.getMessageCell = function()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns domElement="true">table cell</returns>

  var logline = this.getLastLogLine();
  return logline.getElementsByTagName('td')[2];
};

JsUnitTest.Unit.Logger.prototype._createLogTable = function(title, append)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="title" type="String">Title for Logger</param>
    /// <param name="append" type="Boolean">if true, create child div for Logger.</param>
    /// <remarks>Sky added title and append args</remarks>
    /// <todo>change h3 to a classified div</todo>
    if (append)
    {
        this.element = this.element.appendChild(top.document.createElement("div"));
    }

    var html = (title ? '<h3>' + title + '</h3>' : '') +
        '<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)
{
    /// <summary>
    /// Appears to be NOOP
    /// </summary>
    /// <param name="actions" type="Object"></param>

  // 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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="txt" type="String"></param>
    /// <returns type="String">HTML encoded txt</returns>

  return JsUnitTest.escapeHTML(txt).replace(/\n/g,"<br/>");
};


JsUnitTest.Unit.MessageTemplate = function(string)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="string" type="String"></param>
    /// <returns type="Array"></returns>

  var parts = [];
  var str = JsUnitTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
    parts.push(part[0]);
  });
  this.parts = parts;
};

JsUnitTest.Unit.MessageTemplate.prototype.evaluate = function(params)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="params" type="Array"></param>
    /// <returns type="String"></returns>

  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('');
};


JsUnitTest.ajax = function(options)
{
    /// <summary>
    /// 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
    /// </summary>
    /// <param name="options" type="Object"></param>


    // 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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="message" type="String"></param>
    /// <param name="template" type="">TODO</param>

    var args = JsUnitTest.arrayfromargs(arguments).slice(2);
    return (message ? message + '\n' : '') + 
      new JsUnitTest.Unit.MessageTemplate(template).evaluate(args);
  },

  flunk: function(message)
  {
      /// <summary>
      /// fails test with message.
      /// </summary>
      /// <param name="message" type="String"></param>

    this.assertBlock(message || 'Flunked', function() { return false; });
  },

  assertBlock: function(message, block)
  {
      /// <summary>
      /// calls block(this)
      /// </summary>
      /// <param name="message" type="String"></param>
      /// <param name="block" type="Function"></param>
    try {
      block.call(this) ? this.pass() : this.fail(message);
    } catch(e) { this.error(e); }
  },

  assert: function(expression, message)
  {
      /// <summary>
      /// 
      /// </summary>
      /// <param name="message" type="String"></param>
      /// <param name="expression" type="Boolean"></param>
      
    message = this.buildMessage(message || 'assert', 'got <?>', expression);
    this.assertBlock(message, function() { return expression; });
  },

  assertEqual: function(expected, actual, message)
  {
      /// <summary>
      /// applies '==' operator to operands.
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
    this.assertBlock(message, function() { return expected == actual; });
  },

  assertNotEqual: function(expected, actual, message)
  {
      /// <summary>
      /// applies '!=' operator to operands.
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
    this.assertBlock(message, function() { return expected != actual; });
  },

  assertEnumEqual: function(expected, actual, message)
  {
      /// <summary>
      /// Applies '==' to a pair of Numbers or a pair of Array of Numbers
      /// </summary>
      /// <param name="expected" type="Number">A Number or Array of Numbers</param>
      /// <param name="actual" type="Number">A Number or Array of Numbers</param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
    this.assertBlock(message, function() { return JsUnitTest.areArraysEqual(expected, actual); });
  },

  assertEnumNotEqual: function(expected, actual, message)
  {
      /// <summary>
      /// Applies '!=' to a pair of Numbers or a pair of Array of Numbers
      /// </summary>
      /// <param name="expected" type="Number">A Number or Array of Numbers</param>
      /// <param name="actual" type="Number">A Number or Array of Numbers</param>
      /// <param name="message" type="String"></param>

    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)
  {
      /// <summary>
      /// applies '==' to properties of operands
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>

    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)
  {
      /// <summary>
      /// applies '!=' to properties of operands
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>

    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)
  {
      /// <summary>
      /// applies '===' to operands
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>


    message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
    this.assertBlock(message, function() { return expected === actual; });
  },

  assertNotIdentical: function(expected, actual, message)
  {
      /// <summary>
      /// applies '!==' to operands
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
    this.assertBlock(message, function() { return expected !== actual; });
  },

  assertNull: function(obj, message)
  {
      /// <summary>
      /// applies '=== null' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
    this.assertBlock(message, function() { return obj === null; });
  },

  assertNotNull: function(obj, message)
  {
      /// <summary>
      /// applies '!== null' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
    this.assertBlock(message, function() { return obj !== null; });
  },

  assertUndefined: function(obj, message)
  {
      /// <summary>
      /// applies 'typeof obj == "undefined"' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>

    message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
    this.assertBlock(message, function() { return typeof obj == "undefined"; });
  },

  assertNotUndefined: function(obj, message)
  {
      /// <summary>
      /// applies 'typeof obj != "undefined"' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>    

    message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
    this.assertBlock(message, function() { return typeof obj != "undefined"; });
  },

  assertNullOrUndefined: function(obj, message)
  {
      /// <summary>
      /// applies '(typeof obj == "undefined") || obj == null' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>    

    message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
    this.assertBlock(message, function() { return obj == null; });
  },

  assertNotNullOrUndefined: function(obj, message)
  {
      /// <summary>
      /// applies '(typeof obj != "undefined")  && obj != null' to obj
      /// </summary>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>    

    message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
    this.assertBlock(message, function() { return obj != null; });
  },

  assertMatch: function(expected, actual, message)
  {
      /// <summary>
      /// Executes expected regex against actual string
      /// </summary>
      /// <param name="expected" type="String">Regular expression (or RegExp?)</param>
      /// <param name="actual" type="String">String to match</param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      /// Executes expected regex against actual string
      /// </summary>
      /// <param name="expected" type="String">Regular expression (or RegExp?)</param>
      /// <param name="actual" type="String">String to match</param>
      /// <param name="message" type="String"></param>    

    message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
    this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)); });
  },

  assertHasClass: function(element, klass, message)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="klass" type="String"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="klass" type="String"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="expected" type="Object"></param>
      /// <param name="actual" type="Object"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="method" type="String">the name of expected function</param>
      /// <param name="obj" type="Object"></param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="exceptionName" type="String"></param>
      /// <param name="method" type="Function">the function to call.</param>
      /// <param name="message" type="String"></param>    

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="method" type="Function">the function to call</param>
      /// <param name="message" type="String"></param>            

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>

    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)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="message" type="String"></param>            

    message = this.buildMessage(message, '? was not visible.', element);
    this.assertBlock(message, function() { return this._isVisible(element); });
  },

  assertNotVisible: function(element, message)
  {
      /// <summary>
      ///
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="message" type="String"></param>    

    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()
  {
      /// <summary>
      /// Compares elements pass as parameter array
      /// </summary>

    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)
  {
      /// <summary>
      /// TODO: not sure how this works yet
      /// </summary>
      /// <param name="element" domElement="true"></param>
      /// <param name="expression" type=""></param>
      /// <param name="message" type="String"></param>    
      
    this.assertElementsMatch([element], expression);
  }
};

JsUnitTest.Unit.Runner = function(testcases, options)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="testcases" type="Object"></param>
    /// <param name="options" type="JsUnitTest.Unit.Options" optional="true">Optional. options. see redundant.</param>
    /// <returns type="JsUnitTest.Unit.Runner"></returns>

    /// <field name="options" type="JsUnitTest.Unit.Options"></field>
    /// <field name="currentTest" type="Number" integer="true"></field>
    /// <field name="logger" type="JsUnitTest.Unit.Logger"></field>
    /// <field name="tests" type="Object"></field>
    /// <remarks>Sky added and options constructor and a few fields to options </remarks>

    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;

    //SKY
    this.logger = new JsUnitTest.Unit.Logger(options.testLog, options.title, options.append);

    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()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns type="Number" integer="true">Port number, if any, of the current request</returns>

    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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="testcases" type="Object">
    /// Associative array of functions passed to the constructor of JsUnitTest.Unit.Runner.
    /// </param>
    /// <returns type="Array"></returns>

    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()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns type="JsUnitTest.Unit.Results"></returns>
    /// <remarks>Sky added results constructor</remarks>

    var results = new JsUnitTest.Unit.Results(this.tests.length);

    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()
{
    /// <summary>
    ///
    /// </summary>

    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()
{
    /// <summary>
    ///
    /// </summary>

    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()
{
    /// <summary>
    ///
    /// </summary>

    this.postResults();
    this.logger.summary(this.summary());
};

JsUnitTest.Unit.Runner.prototype.summary = function()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns type="JsUnitTest.Template"></returns>

    return new JsUnitTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings').evaluate(this.getResult());
};


JsUnitTest.Unit.Results = function(tests)
{
    /// <summary>
    ///
    /// </summary>
    /// <field name="tests" type="Number" integer="true"></field>
    /// <field name="assertions" type="Number" integer="true"></field>
    /// <field name="failures" type="Number" integer="true"></field>
    /// <field name="errors" type="Number" integer="true"></field>
    /// <field name="warnings" type="Number" integer="true"></field>
    /// <returns type="JsUnitTest.Unit.Results"></returns>
    this.tests = tests || 0;
    this.assertions = 0;
    this.failures = 0;
    this.errors = 0;
    this.warnings = 0;
}

JsUnitTest.Unit.Options = function(testLog, title, append, resultsURL)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="testLog" domElement="true"></param>
    /// <param name="title" type="String"></param>
    /// <param name="append" type="Boolean"></param>
    /// <param name="resultsURL" type="String"></param>
    /// <field name="testLog" domElement="true"></field>
    /// <field name="title" type="String"></field>
    /// <field name="append" type="Boolean"></field>
    /// <field name="resultsURL" type="String"></field>
    /// <returns type="JsUnitTest.Unit.Options"></returns>
    /// <remarks>Sky added constructor for results</remarks>
    this.testLog = testLog;
    this.resultsURL = resultsURL;
    this.title = title;
    this.append = append;
}
JsUnitTest.Unit.Testcase = function(name, test, setup, teardown)
{
    /// <summary>
    /// Testcase constructor. hacked overload-->
    /// Invoked as a function with a function as the first arg (name) 
    /// behaves as a Visual Studio Intellisense casting helper wrapper 
    /// and forces VS to apply TestCase context to the test function.
    /// e.g.
    /// var that = Test.Unit.Testcase(this);
    /// </summary>
    /// <param name="name" type="String">The name of the test.</param>
    /// <param name="test" type="">function()</param>
    /// <param name="setup" type="">function()</param>
    /// <param name="teardown" type="Function">function()</param>

    /// <field name="name" type="String"></field>
    /// <field name="test" type="">function()</field>
    /// <field name="setup" type="">function()</field>
    /// <field name="teardown" type="Function">function()</field>
    /// <field name="messages" type="Array"></field>
    /// <field name="actions" type="Object"></field>
    /// <returns type="JsUnitTest.Unit.Testcase"></returns>

    if (name && (typeof (name) != 'string'))
    {
        // vs intellisense helper;
        return name;
    };
    
    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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="time" type="Number" integer="true">ms</param>
    /// <param name="nextPart" type="Function"></param>

    this.isWaiting = true;
    this.test = nextPart;
    this.timeToWait = time;
};

JsUnitTest.Unit.Testcase.prototype.run = function(rethrow)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="rethrow" type="Boolean"></param>

    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()
{
    /// <summary>
    ///
    /// </summary>

    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()
{
    /// <summary>
    ///
    /// </summary>

    this.assertions++;
};

JsUnitTest.Unit.Testcase.prototype.fail = function(message)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="message" type="String"></param>

    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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="message" type="String"></param>

    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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="message" type="String"></param>

    this.messages.push("Info: " + message);
};

JsUnitTest.Unit.Testcase.prototype.error = function(error, test)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="error" type="Error"></param>
    /// <param name="test" type="JsUnitTest.Unit.Testcase"></param>

    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()
{
    /// <summary>
    ///
    /// </summary>
    /// <returns type="String"></returns>

    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)
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="operation" type="Function"></param>
    /// <param name="iterations" type="Number" integer="true"></param>

    /// <field name="" type=""></field>
    /// <returns type="Date">TestCase duration</returns>

    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;

