text: // Based on jsDump by Ariel Flesler
  // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
  QUnit.dump = ( function() {
  	function quote( str ) {
  		return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
  	}
  	function literal( o ) {
  		return o + "";
  	}
  	function join( pre, arr, post ) {
  		var s = dump.separator(),
  			base = dump.indent(),
  			inner = dump.indent( 1 );
  		if ( arr.join ) {
  			arr = arr.join( "," + s + inner );
  		}
  		if ( !arr ) {
  			return pre + post;
  		}
  		return [ pre, inner + arr, base + post ].join( s );
  	}
  	function array( arr, stack ) {
  		var i = arr.length,
  			ret = new Array( i );
  
  		if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
  			return "[object Array]";
  		}
  
  		this.up();
  		while ( i-- ) {
  			ret[ i ] = this.parse( arr[ i ], undefined, stack );
  		}
  		this.down();
  		return join( "[", ret, "]" );
  	}
  
  	var reName = /^function (\w+)/,
  		dump = {
  
  			// The objType is used mostly internally, you can fix a (custom) type in advance
  			parse: function( obj, objType, stack ) {
  				stack = stack || [];
  				var res, parser, parserType,
  					inStack = inArray( obj, stack );
  
  				if ( inStack !== -1 ) {
  					return "recursion(" + ( inStack - stack.length ) + ")";
  				}
  
  				objType = objType || this.typeOf( obj  );
  				parser = this.parsers[ objType ];
  				parserType = typeof parser;
  
  				if ( parserType === "function" ) {
  					stack.push( obj );
  					res = parser.call( this, obj, stack );
  					stack.pop();
  					return res;
  				}
  				return ( parserType === "string" ) ? parser : this.parsers.error;
  			},
  			typeOf: function( obj ) {
  				var type;
  				if ( obj === null ) {
  					type = "null";
  				} else if ( typeof obj === "undefined" ) {
  					type = "undefined";
  				} else if ( QUnit.is( "regexp", obj ) ) {
  					type = "regexp";
  				} else if ( QUnit.is( "date", obj ) ) {
  					type = "date";
  				} else if ( QUnit.is( "function", obj ) ) {
  					type = "function";
  				} else if ( obj.setInterval !== undefined &&
  						obj.document !== undefined &&
  						obj.nodeType === undefined ) {
  					type = "window";
  				} else if ( obj.nodeType === 9 ) {
  					type = "document";
  				} else if ( obj.nodeType ) {
  					type = "node";
  				} else if (
  
  					// Native arrays
  					toString.call( obj ) === "[object Array]" ||
  
  					// NodeList objects
  					( typeof obj.length === "number" && obj.item !== undefined &&
  					( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
  					obj[ 0 ] === undefined ) ) )
  				) {
  					type = "array";
  				} else if ( obj.constructor === Error.prototype.constructor ) {
  					type = "error";
  				} else {
  					type = typeof obj;
  				}
  				return type;
  			},
  
  			separator: function() {
  				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
  			},
  
  			// Extra can be a number, shortcut for increasing-calling-decreasing
  			indent: function( extra ) {
  				if ( !this.multiline ) {
  					return "";
  				}
  				var chr = this.indentChar;
  				if ( this.HTML ) {
  					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
  				}
  				return new Array( this.depth + ( extra || 0 ) ).join( chr );
  			},
  			up: function( a ) {
  				this.depth += a || 1;
  			},
  			down: function( a ) {
  				this.depth -= a || 1;
  			},
  			setParser: function( name, parser ) {
  				this.parsers[ name ] = parser;
  			},
  
  			// The next 3 are exposed so you can use them
  			quote: quote,
  			literal: literal,
  			join: join,
  			depth: 1,
  			maxDepth: QUnit.config.maxDepth,
  
  			// This is the list of parsers, to modify them, use dump.setParser
  			parsers: {
  				window: "[Window]",
  				document: "[Document]",
  				error: function( error ) {
  					return "Error(\"" + error.message + "\")";
  				},
  				unknown: "[Unknown]",
  				"null": "null",
  				"undefined": "undefined",
  				"function": function( fn ) {
  					var ret = "function",
  
  						// Functions never have name in IE
  						name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
  
  					if ( name ) {
  						ret += " " + name;
  					}
  					ret += "(";
  
  					ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
  					return join( ret, dump.parse( fn, "functionCode" ), "}" );
  				},
  				array: array,
  				nodelist: array,
  				"arguments": array,
  				object: function( map, stack ) {
  					var keys, key, val, i, nonEnumerableProperties,
  						ret = [];
  
  					if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
  						return "[object Object]";
  					}
  
  					dump.up();
  					keys = [];
  					for ( key in map ) {
  						keys.push( key );
  					}
  
  					// Some properties are not always enumerable on Error objects.
  					nonEnumerableProperties = [ "message", "name" ];
  					for ( i in nonEnumerableProperties ) {
  						key = nonEnumerableProperties[ i ];
  						if ( key in map && inArray( key, keys ) < 0 ) {
  							keys.push( key );
  						}
  					}
  					keys.sort();
  					for ( i = 0; i < keys.length; i++ ) {
  						key = keys[ i ];
  						val = map[ key ];
  						ret.push( dump.parse( key, "key" ) + ": " +
  							dump.parse( val, undefined, stack ) );
  					}
  					dump.down();
  					return join( "{", ret, "}" );
  				},
  				node: function( node ) {
  					var len, i, val,
  						open = dump.HTML ? "&lt;" : "<",
  						close = dump.HTML ? "&gt;" : ">",
  						tag = node.nodeName.toLowerCase(),
  						ret = open + tag,
  						attrs = node.attributes;
  
  					if ( attrs ) {
  						for ( i = 0, len = attrs.length; i < len; i++ ) {
  							val = attrs[ i ].nodeValue;
  
  							// IE6 includes all attributes in .attributes, even ones not explicitly
  							// set. Those have values like undefined, null, 0, false, "" or
  							// "inherit".
  							if ( val && val !== "inherit" ) {
  								ret += " " + attrs[ i ].nodeName + "=" +
  									dump.parse( val, "attribute" );
  							}
  						}
  					}
  					ret += close;
  
  					// Show content of TextNode or CDATASection
  					if ( node.nodeType === 3 || node.nodeType === 4 ) {
  						ret += node.nodeValue;
  					}
  
  					return ret + open + "/" + tag + close;
  				},
  
  				// Function calls it internally, it's the arguments part of the function
  				functionArgs: function( fn ) {
  					var args,
  						l = fn.length;
  
  					if ( !l ) {
  						return "";
  					}
  
  					args = new Array( l );
  					while ( l-- ) {
  
  						// 97 is 'a'
  						args[ l ] = String.fromCharCode( 97 + l );
  					}
  					return " " + args.join( ", " ) + " ";
  				},
  
  				// Object calls it internally, the key part of an item in a map
  				key: quote,
  
  				// Function calls it internally, it's the content of the function
  				functionCode: "[code]",
  
  				// Node calls it internally, it's a html attribute value
  				attribute: quote,
  				string: quote,
  				date: quote,
  				regexp: literal,
  				number: literal,
  				"boolean": literal
  			},
  
  			// If true, entities are escaped ( <, >, \t, space and \n )
  			HTML: false,
  
  			// Indentation unit
  			indentChar: "  ",
  
  			// If true, items in a collection, are separated by a \n, else just a space.
  			multiline: true
  		};
  
  	return dump;
  }() );
  
  // Back compat
  QUnit.jsDump = QUnit.dump;
  