devtools/data/qunit.js
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 /*!
       
     2  * QUnit 1.18.0
       
     3  * http://qunitjs.com/
       
     4  *
       
     5  * Copyright jQuery Foundation and other contributors
       
     6  * Released under the MIT license
       
     7  * http://jquery.org/license
       
     8  *
       
     9  * Date: 2015-04-03T10:23Z
       
    10  */
       
    11 
       
    12 (function( window ) {
       
    13 
       
    14 var QUnit,
       
    15 	config,
       
    16 	onErrorFnPrev,
       
    17 	loggingCallbacks = {},
       
    18 	fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
       
    19 	toString = Object.prototype.toString,
       
    20 	hasOwn = Object.prototype.hasOwnProperty,
       
    21 	// Keep a local reference to Date (GH-283)
       
    22 	Date = window.Date,
       
    23 	now = Date.now || function() {
       
    24 		return new Date().getTime();
       
    25 	},
       
    26 	globalStartCalled = false,
       
    27 	runStarted = false,
       
    28 	setTimeout = window.setTimeout,
       
    29 	clearTimeout = window.clearTimeout,
       
    30 	defined = {
       
    31 		document: window.document !== undefined,
       
    32 		setTimeout: window.setTimeout !== undefined,
       
    33 		sessionStorage: (function() {
       
    34 			var x = "qunit-test-string";
       
    35 			try {
       
    36 				sessionStorage.setItem( x, x );
       
    37 				sessionStorage.removeItem( x );
       
    38 				return true;
       
    39 			} catch ( e ) {
       
    40 				return false;
       
    41 			}
       
    42 		}())
       
    43 	},
       
    44 	/**
       
    45 	 * Provides a normalized error string, correcting an issue
       
    46 	 * with IE 7 (and prior) where Error.prototype.toString is
       
    47 	 * not properly implemented
       
    48 	 *
       
    49 	 * Based on http://es5.github.com/#x15.11.4.4
       
    50 	 *
       
    51 	 * @param {String|Error} error
       
    52 	 * @return {String} error message
       
    53 	 */
       
    54 	errorString = function( error ) {
       
    55 		var name, message,
       
    56 			errorString = error.toString();
       
    57 		if ( errorString.substring( 0, 7 ) === "[object" ) {
       
    58 			name = error.name ? error.name.toString() : "Error";
       
    59 			message = error.message ? error.message.toString() : "";
       
    60 			if ( name && message ) {
       
    61 				return name + ": " + message;
       
    62 			} else if ( name ) {
       
    63 				return name;
       
    64 			} else if ( message ) {
       
    65 				return message;
       
    66 			} else {
       
    67 				return "Error";
       
    68 			}
       
    69 		} else {
       
    70 			return errorString;
       
    71 		}
       
    72 	},
       
    73 	/**
       
    74 	 * Makes a clone of an object using only Array or Object as base,
       
    75 	 * and copies over the own enumerable properties.
       
    76 	 *
       
    77 	 * @param {Object} obj
       
    78 	 * @return {Object} New object with only the own properties (recursively).
       
    79 	 */
       
    80 	objectValues = function( obj ) {
       
    81 		var key, val,
       
    82 			vals = QUnit.is( "array", obj ) ? [] : {};
       
    83 		for ( key in obj ) {
       
    84 			if ( hasOwn.call( obj, key ) ) {
       
    85 				val = obj[ key ];
       
    86 				vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
       
    87 			}
       
    88 		}
       
    89 		return vals;
       
    90 	};
       
    91 
       
    92 QUnit = {};
       
    93 
       
    94 /**
       
    95  * Config object: Maintain internal state
       
    96  * Later exposed as QUnit.config
       
    97  * `config` initialized at top of scope
       
    98  */
       
    99 config = {
       
   100 	// The queue of tests to run
       
   101 	queue: [],
       
   102 
       
   103 	// block until document ready
       
   104 	blocking: true,
       
   105 
       
   106 	// by default, run previously failed tests first
       
   107 	// very useful in combination with "Hide passed tests" checked
       
   108 	reorder: true,
       
   109 
       
   110 	// by default, modify document.title when suite is done
       
   111 	altertitle: true,
       
   112 
       
   113 	// by default, scroll to top of the page when suite is done
       
   114 	scrolltop: true,
       
   115 
       
   116 	// when enabled, all tests must call expect()
       
   117 	requireExpects: false,
       
   118 
       
   119 	// depth up-to which object will be dumped
       
   120 	maxDepth: 5,
       
   121 
       
   122 	// add checkboxes that are persisted in the query-string
       
   123 	// when enabled, the id is set to `true` as a `QUnit.config` property
       
   124 	urlConfig: [
       
   125 		{
       
   126 			id: "hidepassed",
       
   127 			label: "Hide passed tests",
       
   128 			tooltip: "Only show tests and assertions that fail. Stored as query-strings."
       
   129 		},
       
   130 		{
       
   131 			id: "noglobals",
       
   132 			label: "Check for Globals",
       
   133 			tooltip: "Enabling this will test if any test introduces new properties on the " +
       
   134 				"`window` object. Stored as query-strings."
       
   135 		},
       
   136 		{
       
   137 			id: "notrycatch",
       
   138 			label: "No try-catch",
       
   139 			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
       
   140 				"exceptions in IE reasonable. Stored as query-strings."
       
   141 		}
       
   142 	],
       
   143 
       
   144 	// Set of all modules.
       
   145 	modules: [],
       
   146 
       
   147 	// The first unnamed module
       
   148 	currentModule: {
       
   149 		name: "",
       
   150 		tests: []
       
   151 	},
       
   152 
       
   153 	callbacks: {}
       
   154 };
       
   155 
       
   156 // Push a loose unnamed module to the modules collection
       
   157 config.modules.push( config.currentModule );
       
   158 
       
   159 // Initialize more QUnit.config and QUnit.urlParams
       
   160 (function() {
       
   161 	var i, current,
       
   162 		location = window.location || { search: "", protocol: "file:" },
       
   163 		params = location.search.slice( 1 ).split( "&" ),
       
   164 		length = params.length,
       
   165 		urlParams = {};
       
   166 
       
   167 	if ( params[ 0 ] ) {
       
   168 		for ( i = 0; i < length; i++ ) {
       
   169 			current = params[ i ].split( "=" );
       
   170 			current[ 0 ] = decodeURIComponent( current[ 0 ] );
       
   171 
       
   172 			// allow just a key to turn on a flag, e.g., test.html?noglobals
       
   173 			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
       
   174 			if ( urlParams[ current[ 0 ] ] ) {
       
   175 				urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
       
   176 			} else {
       
   177 				urlParams[ current[ 0 ] ] = current[ 1 ];
       
   178 			}
       
   179 		}
       
   180 	}
       
   181 
       
   182 	if ( urlParams.filter === true ) {
       
   183 		delete urlParams.filter;
       
   184 	}
       
   185 
       
   186 	QUnit.urlParams = urlParams;
       
   187 
       
   188 	// String search anywhere in moduleName+testName
       
   189 	config.filter = urlParams.filter;
       
   190 
       
   191 	if ( urlParams.maxDepth ) {
       
   192 		config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
       
   193 			Number.POSITIVE_INFINITY :
       
   194 			urlParams.maxDepth;
       
   195 	}
       
   196 
       
   197 	config.testId = [];
       
   198 	if ( urlParams.testId ) {
       
   199 
       
   200 		// Ensure that urlParams.testId is an array
       
   201 		urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
       
   202 		for ( i = 0; i < urlParams.testId.length; i++ ) {
       
   203 			config.testId.push( urlParams.testId[ i ] );
       
   204 		}
       
   205 	}
       
   206 
       
   207 	// Figure out if we're running the tests from a server or not
       
   208 	QUnit.isLocal = location.protocol === "file:";
       
   209 
       
   210 	// Expose the current QUnit version
       
   211 	QUnit.version = "1.18.0";
       
   212 }());
       
   213 
       
   214 // Root QUnit object.
       
   215 // `QUnit` initialized at top of scope
       
   216 extend( QUnit, {
       
   217 
       
   218 	// call on start of module test to prepend name to all tests
       
   219 	module: function( name, testEnvironment ) {
       
   220 		var currentModule = {
       
   221 			name: name,
       
   222 			testEnvironment: testEnvironment,
       
   223 			tests: []
       
   224 		};
       
   225 
       
   226 		// DEPRECATED: handles setup/teardown functions,
       
   227 		// beforeEach and afterEach should be used instead
       
   228 		if ( testEnvironment && testEnvironment.setup ) {
       
   229 			testEnvironment.beforeEach = testEnvironment.setup;
       
   230 			delete testEnvironment.setup;
       
   231 		}
       
   232 		if ( testEnvironment && testEnvironment.teardown ) {
       
   233 			testEnvironment.afterEach = testEnvironment.teardown;
       
   234 			delete testEnvironment.teardown;
       
   235 		}
       
   236 
       
   237 		config.modules.push( currentModule );
       
   238 		config.currentModule = currentModule;
       
   239 	},
       
   240 
       
   241 	// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
       
   242 	asyncTest: function( testName, expected, callback ) {
       
   243 		if ( arguments.length === 2 ) {
       
   244 			callback = expected;
       
   245 			expected = null;
       
   246 		}
       
   247 
       
   248 		QUnit.test( testName, expected, callback, true );
       
   249 	},
       
   250 
       
   251 	test: function( testName, expected, callback, async ) {
       
   252 		var test;
       
   253 
       
   254 		if ( arguments.length === 2 ) {
       
   255 			callback = expected;
       
   256 			expected = null;
       
   257 		}
       
   258 
       
   259 		test = new Test({
       
   260 			testName: testName,
       
   261 			expected: expected,
       
   262 			async: async,
       
   263 			callback: callback
       
   264 		});
       
   265 
       
   266 		test.queue();
       
   267 	},
       
   268 
       
   269 	skip: function( testName ) {
       
   270 		var test = new Test({
       
   271 			testName: testName,
       
   272 			skip: true
       
   273 		});
       
   274 
       
   275 		test.queue();
       
   276 	},
       
   277 
       
   278 	// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
       
   279 	// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
       
   280 	start: function( count ) {
       
   281 		var globalStartAlreadyCalled = globalStartCalled;
       
   282 
       
   283 		if ( !config.current ) {
       
   284 			globalStartCalled = true;
       
   285 
       
   286 			if ( runStarted ) {
       
   287 				throw new Error( "Called start() outside of a test context while already started" );
       
   288 			} else if ( globalStartAlreadyCalled || count > 1 ) {
       
   289 				throw new Error( "Called start() outside of a test context too many times" );
       
   290 			} else if ( config.autostart ) {
       
   291 				throw new Error( "Called start() outside of a test context when " +
       
   292 					"QUnit.config.autostart was true" );
       
   293 			} else if ( !config.pageLoaded ) {
       
   294 
       
   295 				// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
       
   296 				config.autostart = true;
       
   297 				return;
       
   298 			}
       
   299 		} else {
       
   300 
       
   301 			// If a test is running, adjust its semaphore
       
   302 			config.current.semaphore -= count || 1;
       
   303 
       
   304 			// Don't start until equal number of stop-calls
       
   305 			if ( config.current.semaphore > 0 ) {
       
   306 				return;
       
   307 			}
       
   308 
       
   309 			// throw an Error if start is called more often than stop
       
   310 			if ( config.current.semaphore < 0 ) {
       
   311 				config.current.semaphore = 0;
       
   312 
       
   313 				QUnit.pushFailure(
       
   314 					"Called start() while already started (test's semaphore was 0 already)",
       
   315 					sourceFromStacktrace( 2 )
       
   316 				);
       
   317 				return;
       
   318 			}
       
   319 		}
       
   320 
       
   321 		resumeProcessing();
       
   322 	},
       
   323 
       
   324 	// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
       
   325 	stop: function( count ) {
       
   326 
       
   327 		// If there isn't a test running, don't allow QUnit.stop() to be called
       
   328 		if ( !config.current ) {
       
   329 			throw new Error( "Called stop() outside of a test context" );
       
   330 		}
       
   331 
       
   332 		// If a test is running, adjust its semaphore
       
   333 		config.current.semaphore += count || 1;
       
   334 
       
   335 		pauseProcessing();
       
   336 	},
       
   337 
       
   338 	config: config,
       
   339 
       
   340 	// Safe object type checking
       
   341 	is: function( type, obj ) {
       
   342 		return QUnit.objectType( obj ) === type;
       
   343 	},
       
   344 
       
   345 	objectType: function( obj ) {
       
   346 		if ( typeof obj === "undefined" ) {
       
   347 			return "undefined";
       
   348 		}
       
   349 
       
   350 		// Consider: typeof null === object
       
   351 		if ( obj === null ) {
       
   352 			return "null";
       
   353 		}
       
   354 
       
   355 		var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
       
   356 			type = match && match[ 1 ] || "";
       
   357 
       
   358 		switch ( type ) {
       
   359 			case "Number":
       
   360 				if ( isNaN( obj ) ) {
       
   361 					return "nan";
       
   362 				}
       
   363 				return "number";
       
   364 			case "String":
       
   365 			case "Boolean":
       
   366 			case "Array":
       
   367 			case "Date":
       
   368 			case "RegExp":
       
   369 			case "Function":
       
   370 				return type.toLowerCase();
       
   371 		}
       
   372 		if ( typeof obj === "object" ) {
       
   373 			return "object";
       
   374 		}
       
   375 		return undefined;
       
   376 	},
       
   377 
       
   378 	extend: extend,
       
   379 
       
   380 	load: function() {
       
   381 		config.pageLoaded = true;
       
   382 
       
   383 		// Initialize the configuration options
       
   384 		extend( config, {
       
   385 			stats: { all: 0, bad: 0 },
       
   386 			moduleStats: { all: 0, bad: 0 },
       
   387 			started: 0,
       
   388 			updateRate: 1000,
       
   389 			autostart: true,
       
   390 			filter: ""
       
   391 		}, true );
       
   392 
       
   393 		config.blocking = false;
       
   394 
       
   395 		if ( config.autostart ) {
       
   396 			resumeProcessing();
       
   397 		}
       
   398 	}
       
   399 });
       
   400 
       
   401 // Register logging callbacks
       
   402 (function() {
       
   403 	var i, l, key,
       
   404 		callbacks = [ "begin", "done", "log", "testStart", "testDone",
       
   405 			"moduleStart", "moduleDone" ];
       
   406 
       
   407 	function registerLoggingCallback( key ) {
       
   408 		var loggingCallback = function( callback ) {
       
   409 			if ( QUnit.objectType( callback ) !== "function" ) {
       
   410 				throw new Error(
       
   411 					"QUnit logging methods require a callback function as their first parameters."
       
   412 				);
       
   413 			}
       
   414 
       
   415 			config.callbacks[ key ].push( callback );
       
   416 		};
       
   417 
       
   418 		// DEPRECATED: This will be removed on QUnit 2.0.0+
       
   419 		// Stores the registered functions allowing restoring
       
   420 		// at verifyLoggingCallbacks() if modified
       
   421 		loggingCallbacks[ key ] = loggingCallback;
       
   422 
       
   423 		return loggingCallback;
       
   424 	}
       
   425 
       
   426 	for ( i = 0, l = callbacks.length; i < l; i++ ) {
       
   427 		key = callbacks[ i ];
       
   428 
       
   429 		// Initialize key collection of logging callback
       
   430 		if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
       
   431 			config.callbacks[ key ] = [];
       
   432 		}
       
   433 
       
   434 		QUnit[ key ] = registerLoggingCallback( key );
       
   435 	}
       
   436 })();
       
   437 
       
   438 // `onErrorFnPrev` initialized at top of scope
       
   439 // Preserve other handlers
       
   440 onErrorFnPrev = window.onerror;
       
   441 
       
   442 // Cover uncaught exceptions
       
   443 // Returning true will suppress the default browser handler,
       
   444 // returning false will let it run.
       
   445 window.onerror = function( error, filePath, linerNr ) {
       
   446 	var ret = false;
       
   447 	if ( onErrorFnPrev ) {
       
   448 		ret = onErrorFnPrev( error, filePath, linerNr );
       
   449 	}
       
   450 
       
   451 	// Treat return value as window.onerror itself does,
       
   452 	// Only do our handling if not suppressed.
       
   453 	if ( ret !== true ) {
       
   454 		if ( QUnit.config.current ) {
       
   455 			if ( QUnit.config.current.ignoreGlobalErrors ) {
       
   456 				return true;
       
   457 			}
       
   458 			QUnit.pushFailure( error, filePath + ":" + linerNr );
       
   459 		} else {
       
   460 			QUnit.test( "global failure", extend(function() {
       
   461 				QUnit.pushFailure( error, filePath + ":" + linerNr );
       
   462 			}, { validTest: true } ) );
       
   463 		}
       
   464 		return false;
       
   465 	}
       
   466 
       
   467 	return ret;
       
   468 };
       
   469 
       
   470 function done() {
       
   471 	var runtime, passed;
       
   472 
       
   473 	config.autorun = true;
       
   474 
       
   475 	// Log the last module results
       
   476 	if ( config.previousModule ) {
       
   477 		runLoggingCallbacks( "moduleDone", {
       
   478 			name: config.previousModule.name,
       
   479 			tests: config.previousModule.tests,
       
   480 			failed: config.moduleStats.bad,
       
   481 			passed: config.moduleStats.all - config.moduleStats.bad,
       
   482 			total: config.moduleStats.all,
       
   483 			runtime: now() - config.moduleStats.started
       
   484 		});
       
   485 	}
       
   486 	delete config.previousModule;
       
   487 
       
   488 	runtime = now() - config.started;
       
   489 	passed = config.stats.all - config.stats.bad;
       
   490 
       
   491 	runLoggingCallbacks( "done", {
       
   492 		failed: config.stats.bad,
       
   493 		passed: passed,
       
   494 		total: config.stats.all,
       
   495 		runtime: runtime
       
   496 	});
       
   497 }
       
   498 
       
   499 // Doesn't support IE6 to IE9, it will return undefined on these browsers
       
   500 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
       
   501 function extractStacktrace( e, offset ) {
       
   502 	offset = offset === undefined ? 4 : offset;
       
   503 
       
   504 	var stack, include, i;
       
   505 
       
   506 	if ( e.stack ) {
       
   507 		stack = e.stack.split( "\n" );
       
   508 		if ( /^error$/i.test( stack[ 0 ] ) ) {
       
   509 			stack.shift();
       
   510 		}
       
   511 		if ( fileName ) {
       
   512 			include = [];
       
   513 			for ( i = offset; i < stack.length; i++ ) {
       
   514 				if ( stack[ i ].indexOf( fileName ) !== -1 ) {
       
   515 					break;
       
   516 				}
       
   517 				include.push( stack[ i ] );
       
   518 			}
       
   519 			if ( include.length ) {
       
   520 				return include.join( "\n" );
       
   521 			}
       
   522 		}
       
   523 		return stack[ offset ];
       
   524 
       
   525 	// Support: Safari <=6 only
       
   526 	} else if ( e.sourceURL ) {
       
   527 
       
   528 		// exclude useless self-reference for generated Error objects
       
   529 		if ( /qunit.js$/.test( e.sourceURL ) ) {
       
   530 			return;
       
   531 		}
       
   532 
       
   533 		// for actual exceptions, this is useful
       
   534 		return e.sourceURL + ":" + e.line;
       
   535 	}
       
   536 }
       
   537 
       
   538 function sourceFromStacktrace( offset ) {
       
   539 	var error = new Error();
       
   540 
       
   541 	// Support: Safari <=7 only, IE <=10 - 11 only
       
   542 	// Not all browsers generate the `stack` property for `new Error()`, see also #636
       
   543 	if ( !error.stack ) {
       
   544 		try {
       
   545 			throw error;
       
   546 		} catch ( err ) {
       
   547 			error = err;
       
   548 		}
       
   549 	}
       
   550 
       
   551 	return extractStacktrace( error, offset );
       
   552 }
       
   553 
       
   554 function synchronize( callback, last ) {
       
   555 	if ( QUnit.objectType( callback ) === "array" ) {
       
   556 		while ( callback.length ) {
       
   557 			synchronize( callback.shift() );
       
   558 		}
       
   559 		return;
       
   560 	}
       
   561 	config.queue.push( callback );
       
   562 
       
   563 	if ( config.autorun && !config.blocking ) {
       
   564 		process( last );
       
   565 	}
       
   566 }
       
   567 
       
   568 function process( last ) {
       
   569 	function next() {
       
   570 		process( last );
       
   571 	}
       
   572 	var start = now();
       
   573 	config.depth = ( config.depth || 0 ) + 1;
       
   574 
       
   575 	while ( config.queue.length && !config.blocking ) {
       
   576 		if ( !defined.setTimeout || config.updateRate <= 0 ||
       
   577 				( ( now() - start ) < config.updateRate ) ) {
       
   578 			if ( config.current ) {
       
   579 
       
   580 				// Reset async tracking for each phase of the Test lifecycle
       
   581 				config.current.usedAsync = false;
       
   582 			}
       
   583 			config.queue.shift()();
       
   584 		} else {
       
   585 			setTimeout( next, 13 );
       
   586 			break;
       
   587 		}
       
   588 	}
       
   589 	config.depth--;
       
   590 	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
       
   591 		done();
       
   592 	}
       
   593 }
       
   594 
       
   595 function begin() {
       
   596 	var i, l,
       
   597 		modulesLog = [];
       
   598 
       
   599 	// If the test run hasn't officially begun yet
       
   600 	if ( !config.started ) {
       
   601 
       
   602 		// Record the time of the test run's beginning
       
   603 		config.started = now();
       
   604 
       
   605 		verifyLoggingCallbacks();
       
   606 
       
   607 		// Delete the loose unnamed module if unused.
       
   608 		if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
       
   609 			config.modules.shift();
       
   610 		}
       
   611 
       
   612 		// Avoid unnecessary information by not logging modules' test environments
       
   613 		for ( i = 0, l = config.modules.length; i < l; i++ ) {
       
   614 			modulesLog.push({
       
   615 				name: config.modules[ i ].name,
       
   616 				tests: config.modules[ i ].tests
       
   617 			});
       
   618 		}
       
   619 
       
   620 		// The test run is officially beginning now
       
   621 		runLoggingCallbacks( "begin", {
       
   622 			totalTests: Test.count,
       
   623 			modules: modulesLog
       
   624 		});
       
   625 	}
       
   626 
       
   627 	config.blocking = false;
       
   628 	process( true );
       
   629 }
       
   630 
       
   631 function resumeProcessing() {
       
   632 	runStarted = true;
       
   633 
       
   634 	// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
       
   635 	if ( defined.setTimeout ) {
       
   636 		setTimeout(function() {
       
   637 			if ( config.current && config.current.semaphore > 0 ) {
       
   638 				return;
       
   639 			}
       
   640 			if ( config.timeout ) {
       
   641 				clearTimeout( config.timeout );
       
   642 			}
       
   643 
       
   644 			begin();
       
   645 		}, 13 );
       
   646 	} else {
       
   647 		begin();
       
   648 	}
       
   649 }
       
   650 
       
   651 function pauseProcessing() {
       
   652 	config.blocking = true;
       
   653 
       
   654 	if ( config.testTimeout && defined.setTimeout ) {
       
   655 		clearTimeout( config.timeout );
       
   656 		config.timeout = setTimeout(function() {
       
   657 			if ( config.current ) {
       
   658 				config.current.semaphore = 0;
       
   659 				QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
       
   660 			} else {
       
   661 				throw new Error( "Test timed out" );
       
   662 			}
       
   663 			resumeProcessing();
       
   664 		}, config.testTimeout );
       
   665 	}
       
   666 }
       
   667 
       
   668 function saveGlobal() {
       
   669 	config.pollution = [];
       
   670 
       
   671 	if ( config.noglobals ) {
       
   672 		for ( var key in window ) {
       
   673 			if ( hasOwn.call( window, key ) ) {
       
   674 				// in Opera sometimes DOM element ids show up here, ignore them
       
   675 				if ( /^qunit-test-output/.test( key ) ) {
       
   676 					continue;
       
   677 				}
       
   678 				config.pollution.push( key );
       
   679 			}
       
   680 		}
       
   681 	}
       
   682 }
       
   683 
       
   684 function checkPollution() {
       
   685 	var newGlobals,
       
   686 		deletedGlobals,
       
   687 		old = config.pollution;
       
   688 
       
   689 	saveGlobal();
       
   690 
       
   691 	newGlobals = diff( config.pollution, old );
       
   692 	if ( newGlobals.length > 0 ) {
       
   693 		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
       
   694 	}
       
   695 
       
   696 	deletedGlobals = diff( old, config.pollution );
       
   697 	if ( deletedGlobals.length > 0 ) {
       
   698 		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
       
   699 	}
       
   700 }
       
   701 
       
   702 // returns a new Array with the elements that are in a but not in b
       
   703 function diff( a, b ) {
       
   704 	var i, j,
       
   705 		result = a.slice();
       
   706 
       
   707 	for ( i = 0; i < result.length; i++ ) {
       
   708 		for ( j = 0; j < b.length; j++ ) {
       
   709 			if ( result[ i ] === b[ j ] ) {
       
   710 				result.splice( i, 1 );
       
   711 				i--;
       
   712 				break;
       
   713 			}
       
   714 		}
       
   715 	}
       
   716 	return result;
       
   717 }
       
   718 
       
   719 function extend( a, b, undefOnly ) {
       
   720 	for ( var prop in b ) {
       
   721 		if ( hasOwn.call( b, prop ) ) {
       
   722 
       
   723 			// Avoid "Member not found" error in IE8 caused by messing with window.constructor
       
   724 			if ( !( prop === "constructor" && a === window ) ) {
       
   725 				if ( b[ prop ] === undefined ) {
       
   726 					delete a[ prop ];
       
   727 				} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
       
   728 					a[ prop ] = b[ prop ];
       
   729 				}
       
   730 			}
       
   731 		}
       
   732 	}
       
   733 
       
   734 	return a;
       
   735 }
       
   736 
       
   737 function runLoggingCallbacks( key, args ) {
       
   738 	var i, l, callbacks;
       
   739 
       
   740 	callbacks = config.callbacks[ key ];
       
   741 	for ( i = 0, l = callbacks.length; i < l; i++ ) {
       
   742 		callbacks[ i ]( args );
       
   743 	}
       
   744 }
       
   745 
       
   746 // DEPRECATED: This will be removed on 2.0.0+
       
   747 // This function verifies if the loggingCallbacks were modified by the user
       
   748 // If so, it will restore it, assign the given callback and print a console warning
       
   749 function verifyLoggingCallbacks() {
       
   750 	var loggingCallback, userCallback;
       
   751 
       
   752 	for ( loggingCallback in loggingCallbacks ) {
       
   753 		if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
       
   754 
       
   755 			userCallback = QUnit[ loggingCallback ];
       
   756 
       
   757 			// Restore the callback function
       
   758 			QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
       
   759 
       
   760 			// Assign the deprecated given callback
       
   761 			QUnit[ loggingCallback ]( userCallback );
       
   762 
       
   763 			if ( window.console && window.console.warn ) {
       
   764 				window.console.warn(
       
   765 					"QUnit." + loggingCallback + " was replaced with a new value.\n" +
       
   766 					"Please, check out the documentation on how to apply logging callbacks.\n" +
       
   767 					"Reference: http://api.qunitjs.com/category/callbacks/"
       
   768 				);
       
   769 			}
       
   770 		}
       
   771 	}
       
   772 }
       
   773 
       
   774 // from jquery.js
       
   775 function inArray( elem, array ) {
       
   776 	if ( array.indexOf ) {
       
   777 		return array.indexOf( elem );
       
   778 	}
       
   779 
       
   780 	for ( var i = 0, length = array.length; i < length; i++ ) {
       
   781 		if ( array[ i ] === elem ) {
       
   782 			return i;
       
   783 		}
       
   784 	}
       
   785 
       
   786 	return -1;
       
   787 }
       
   788 
       
   789 function Test( settings ) {
       
   790 	var i, l;
       
   791 
       
   792 	++Test.count;
       
   793 
       
   794 	extend( this, settings );
       
   795 	this.assertions = [];
       
   796 	this.semaphore = 0;
       
   797 	this.usedAsync = false;
       
   798 	this.module = config.currentModule;
       
   799 	this.stack = sourceFromStacktrace( 3 );
       
   800 
       
   801 	// Register unique strings
       
   802 	for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
       
   803 		if ( this.module.tests[ i ].name === this.testName ) {
       
   804 			this.testName += " ";
       
   805 		}
       
   806 	}
       
   807 
       
   808 	this.testId = generateHash( this.module.name, this.testName );
       
   809 
       
   810 	this.module.tests.push({
       
   811 		name: this.testName,
       
   812 		testId: this.testId
       
   813 	});
       
   814 
       
   815 	if ( settings.skip ) {
       
   816 
       
   817 		// Skipped tests will fully ignore any sent callback
       
   818 		this.callback = function() {};
       
   819 		this.async = false;
       
   820 		this.expected = 0;
       
   821 	} else {
       
   822 		this.assert = new Assert( this );
       
   823 	}
       
   824 }
       
   825 
       
   826 Test.count = 0;
       
   827 
       
   828 Test.prototype = {
       
   829 	before: function() {
       
   830 		if (
       
   831 
       
   832 			// Emit moduleStart when we're switching from one module to another
       
   833 			this.module !== config.previousModule ||
       
   834 
       
   835 				// They could be equal (both undefined) but if the previousModule property doesn't
       
   836 				// yet exist it means this is the first test in a suite that isn't wrapped in a
       
   837 				// module, in which case we'll just emit a moduleStart event for 'undefined'.
       
   838 				// Without this, reporters can get testStart before moduleStart  which is a problem.
       
   839 				!hasOwn.call( config, "previousModule" )
       
   840 		) {
       
   841 			if ( hasOwn.call( config, "previousModule" ) ) {
       
   842 				runLoggingCallbacks( "moduleDone", {
       
   843 					name: config.previousModule.name,
       
   844 					tests: config.previousModule.tests,
       
   845 					failed: config.moduleStats.bad,
       
   846 					passed: config.moduleStats.all - config.moduleStats.bad,
       
   847 					total: config.moduleStats.all,
       
   848 					runtime: now() - config.moduleStats.started
       
   849 				});
       
   850 			}
       
   851 			config.previousModule = this.module;
       
   852 			config.moduleStats = { all: 0, bad: 0, started: now() };
       
   853 			runLoggingCallbacks( "moduleStart", {
       
   854 				name: this.module.name,
       
   855 				tests: this.module.tests
       
   856 			});
       
   857 		}
       
   858 
       
   859 		config.current = this;
       
   860 
       
   861 		this.testEnvironment = extend( {}, this.module.testEnvironment );
       
   862 		delete this.testEnvironment.beforeEach;
       
   863 		delete this.testEnvironment.afterEach;
       
   864 
       
   865 		this.started = now();
       
   866 		runLoggingCallbacks( "testStart", {
       
   867 			name: this.testName,
       
   868 			module: this.module.name,
       
   869 			testId: this.testId
       
   870 		});
       
   871 
       
   872 		if ( !config.pollution ) {
       
   873 			saveGlobal();
       
   874 		}
       
   875 	},
       
   876 
       
   877 	run: function() {
       
   878 		var promise;
       
   879 
       
   880 		config.current = this;
       
   881 
       
   882 		if ( this.async ) {
       
   883 			QUnit.stop();
       
   884 		}
       
   885 
       
   886 		this.callbackStarted = now();
       
   887 
       
   888 		if ( config.notrycatch ) {
       
   889 			promise = this.callback.call( this.testEnvironment, this.assert );
       
   890 			this.resolvePromise( promise );
       
   891 			return;
       
   892 		}
       
   893 
       
   894 		try {
       
   895 			promise = this.callback.call( this.testEnvironment, this.assert );
       
   896 			this.resolvePromise( promise );
       
   897 		} catch ( e ) {
       
   898 			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
       
   899 				this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
       
   900 
       
   901 			// else next test will carry the responsibility
       
   902 			saveGlobal();
       
   903 
       
   904 			// Restart the tests if they're blocking
       
   905 			if ( config.blocking ) {
       
   906 				QUnit.start();
       
   907 			}
       
   908 		}
       
   909 	},
       
   910 
       
   911 	after: function() {
       
   912 		checkPollution();
       
   913 	},
       
   914 
       
   915 	queueHook: function( hook, hookName ) {
       
   916 		var promise,
       
   917 			test = this;
       
   918 		return function runHook() {
       
   919 			config.current = test;
       
   920 			if ( config.notrycatch ) {
       
   921 				promise = hook.call( test.testEnvironment, test.assert );
       
   922 				test.resolvePromise( promise, hookName );
       
   923 				return;
       
   924 			}
       
   925 			try {
       
   926 				promise = hook.call( test.testEnvironment, test.assert );
       
   927 				test.resolvePromise( promise, hookName );
       
   928 			} catch ( error ) {
       
   929 				test.pushFailure( hookName + " failed on " + test.testName + ": " +
       
   930 					( error.message || error ), extractStacktrace( error, 0 ) );
       
   931 			}
       
   932 		};
       
   933 	},
       
   934 
       
   935 	// Currently only used for module level hooks, can be used to add global level ones
       
   936 	hooks: function( handler ) {
       
   937 		var hooks = [];
       
   938 
       
   939 		// Hooks are ignored on skipped tests
       
   940 		if ( this.skip ) {
       
   941 			return hooks;
       
   942 		}
       
   943 
       
   944 		if ( this.module.testEnvironment &&
       
   945 				QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
       
   946 			hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
       
   947 		}
       
   948 
       
   949 		return hooks;
       
   950 	},
       
   951 
       
   952 	finish: function() {
       
   953 		config.current = this;
       
   954 		if ( config.requireExpects && this.expected === null ) {
       
   955 			this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
       
   956 				"not called.", this.stack );
       
   957 		} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
       
   958 			this.pushFailure( "Expected " + this.expected + " assertions, but " +
       
   959 				this.assertions.length + " were run", this.stack );
       
   960 		} else if ( this.expected === null && !this.assertions.length ) {
       
   961 			this.pushFailure( "Expected at least one assertion, but none were run - call " +
       
   962 				"expect(0) to accept zero assertions.", this.stack );
       
   963 		}
       
   964 
       
   965 		var i,
       
   966 			bad = 0;
       
   967 
       
   968 		this.runtime = now() - this.started;
       
   969 		config.stats.all += this.assertions.length;
       
   970 		config.moduleStats.all += this.assertions.length;
       
   971 
       
   972 		for ( i = 0; i < this.assertions.length; i++ ) {
       
   973 			if ( !this.assertions[ i ].result ) {
       
   974 				bad++;
       
   975 				config.stats.bad++;
       
   976 				config.moduleStats.bad++;
       
   977 			}
       
   978 		}
       
   979 
       
   980 		runLoggingCallbacks( "testDone", {
       
   981 			name: this.testName,
       
   982 			module: this.module.name,
       
   983 			skipped: !!this.skip,
       
   984 			failed: bad,
       
   985 			passed: this.assertions.length - bad,
       
   986 			total: this.assertions.length,
       
   987 			runtime: this.runtime,
       
   988 
       
   989 			// HTML Reporter use
       
   990 			assertions: this.assertions,
       
   991 			testId: this.testId,
       
   992 
       
   993 			// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
       
   994 			duration: this.runtime
       
   995 		});
       
   996 
       
   997 		// QUnit.reset() is deprecated and will be replaced for a new
       
   998 		// fixture reset function on QUnit 2.0/2.1.
       
   999 		// It's still called here for backwards compatibility handling
       
  1000 		QUnit.reset();
       
  1001 
       
  1002 		config.current = undefined;
       
  1003 	},
       
  1004 
       
  1005 	queue: function() {
       
  1006 		var bad,
       
  1007 			test = this;
       
  1008 
       
  1009 		if ( !this.valid() ) {
       
  1010 			return;
       
  1011 		}
       
  1012 
       
  1013 		function run() {
       
  1014 
       
  1015 			// each of these can by async
       
  1016 			synchronize([
       
  1017 				function() {
       
  1018 					test.before();
       
  1019 				},
       
  1020 
       
  1021 				test.hooks( "beforeEach" ),
       
  1022 
       
  1023 				function() {
       
  1024 					test.run();
       
  1025 				},
       
  1026 
       
  1027 				test.hooks( "afterEach" ).reverse(),
       
  1028 
       
  1029 				function() {
       
  1030 					test.after();
       
  1031 				},
       
  1032 				function() {
       
  1033 					test.finish();
       
  1034 				}
       
  1035 			]);
       
  1036 		}
       
  1037 
       
  1038 		// `bad` initialized at top of scope
       
  1039 		// defer when previous test run passed, if storage is available
       
  1040 		bad = QUnit.config.reorder && defined.sessionStorage &&
       
  1041 				+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
       
  1042 
       
  1043 		if ( bad ) {
       
  1044 			run();
       
  1045 		} else {
       
  1046 			synchronize( run, true );
       
  1047 		}
       
  1048 	},
       
  1049 
       
  1050 	push: function( result, actual, expected, message ) {
       
  1051 		var source,
       
  1052 			details = {
       
  1053 				module: this.module.name,
       
  1054 				name: this.testName,
       
  1055 				result: result,
       
  1056 				message: message,
       
  1057 				actual: actual,
       
  1058 				expected: expected,
       
  1059 				testId: this.testId,
       
  1060 				runtime: now() - this.started
       
  1061 			};
       
  1062 
       
  1063 		if ( !result ) {
       
  1064 			source = sourceFromStacktrace();
       
  1065 
       
  1066 			if ( source ) {
       
  1067 				details.source = source;
       
  1068 			}
       
  1069 		}
       
  1070 
       
  1071 		runLoggingCallbacks( "log", details );
       
  1072 
       
  1073 		this.assertions.push({
       
  1074 			result: !!result,
       
  1075 			message: message
       
  1076 		});
       
  1077 	},
       
  1078 
       
  1079 	pushFailure: function( message, source, actual ) {
       
  1080 		if ( !this instanceof Test ) {
       
  1081 			throw new Error( "pushFailure() assertion outside test context, was " +
       
  1082 				sourceFromStacktrace( 2 ) );
       
  1083 		}
       
  1084 
       
  1085 		var details = {
       
  1086 				module: this.module.name,
       
  1087 				name: this.testName,
       
  1088 				result: false,
       
  1089 				message: message || "error",
       
  1090 				actual: actual || null,
       
  1091 				testId: this.testId,
       
  1092 				runtime: now() - this.started
       
  1093 			};
       
  1094 
       
  1095 		if ( source ) {
       
  1096 			details.source = source;
       
  1097 		}
       
  1098 
       
  1099 		runLoggingCallbacks( "log", details );
       
  1100 
       
  1101 		this.assertions.push({
       
  1102 			result: false,
       
  1103 			message: message
       
  1104 		});
       
  1105 	},
       
  1106 
       
  1107 	resolvePromise: function( promise, phase ) {
       
  1108 		var then, message,
       
  1109 			test = this;
       
  1110 		if ( promise != null ) {
       
  1111 			then = promise.then;
       
  1112 			if ( QUnit.objectType( then ) === "function" ) {
       
  1113 				QUnit.stop();
       
  1114 				then.call(
       
  1115 					promise,
       
  1116 					QUnit.start,
       
  1117 					function( error ) {
       
  1118 						message = "Promise rejected " +
       
  1119 							( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
       
  1120 							" " + test.testName + ": " + ( error.message || error );
       
  1121 						test.pushFailure( message, extractStacktrace( error, 0 ) );
       
  1122 
       
  1123 						// else next test will carry the responsibility
       
  1124 						saveGlobal();
       
  1125 
       
  1126 						// Unblock
       
  1127 						QUnit.start();
       
  1128 					}
       
  1129 				);
       
  1130 			}
       
  1131 		}
       
  1132 	},
       
  1133 
       
  1134 	valid: function() {
       
  1135 		var include,
       
  1136 			filter = config.filter && config.filter.toLowerCase(),
       
  1137 			module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
       
  1138 			fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
       
  1139 
       
  1140 		// Internally-generated tests are always valid
       
  1141 		if ( this.callback && this.callback.validTest ) {
       
  1142 			return true;
       
  1143 		}
       
  1144 
       
  1145 		if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
       
  1146 			return false;
       
  1147 		}
       
  1148 
       
  1149 		if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
       
  1150 			return false;
       
  1151 		}
       
  1152 
       
  1153 		if ( !filter ) {
       
  1154 			return true;
       
  1155 		}
       
  1156 
       
  1157 		include = filter.charAt( 0 ) !== "!";
       
  1158 		if ( !include ) {
       
  1159 			filter = filter.slice( 1 );
       
  1160 		}
       
  1161 
       
  1162 		// If the filter matches, we need to honour include
       
  1163 		if ( fullName.indexOf( filter ) !== -1 ) {
       
  1164 			return include;
       
  1165 		}
       
  1166 
       
  1167 		// Otherwise, do the opposite
       
  1168 		return !include;
       
  1169 	}
       
  1170 
       
  1171 };
       
  1172 
       
  1173 // Resets the test setup. Useful for tests that modify the DOM.
       
  1174 /*
       
  1175 DEPRECATED: Use multiple tests instead of resetting inside a test.
       
  1176 Use testStart or testDone for custom cleanup.
       
  1177 This method will throw an error in 2.0, and will be removed in 2.1
       
  1178 */
       
  1179 QUnit.reset = function() {
       
  1180 
       
  1181 	// Return on non-browser environments
       
  1182 	// This is necessary to not break on node tests
       
  1183 	if ( typeof window === "undefined" ) {
       
  1184 		return;
       
  1185 	}
       
  1186 
       
  1187 	var fixture = defined.document && document.getElementById &&
       
  1188 			document.getElementById( "qunit-fixture" );
       
  1189 
       
  1190 	if ( fixture ) {
       
  1191 		fixture.innerHTML = config.fixture;
       
  1192 	}
       
  1193 };
       
  1194 
       
  1195 QUnit.pushFailure = function() {
       
  1196 	if ( !QUnit.config.current ) {
       
  1197 		throw new Error( "pushFailure() assertion outside test context, in " +
       
  1198 			sourceFromStacktrace( 2 ) );
       
  1199 	}
       
  1200 
       
  1201 	// Gets current test obj
       
  1202 	var currentTest = QUnit.config.current;
       
  1203 
       
  1204 	return currentTest.pushFailure.apply( currentTest, arguments );
       
  1205 };
       
  1206 
       
  1207 // Based on Java's String.hashCode, a simple but not
       
  1208 // rigorously collision resistant hashing function
       
  1209 function generateHash( module, testName ) {
       
  1210 	var hex,
       
  1211 		i = 0,
       
  1212 		hash = 0,
       
  1213 		str = module + "\x1C" + testName,
       
  1214 		len = str.length;
       
  1215 
       
  1216 	for ( ; i < len; i++ ) {
       
  1217 		hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
       
  1218 		hash |= 0;
       
  1219 	}
       
  1220 
       
  1221 	// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
       
  1222 	// strictly necessary but increases user understanding that the id is a SHA-like hash
       
  1223 	hex = ( 0x100000000 + hash ).toString( 16 );
       
  1224 	if ( hex.length < 8 ) {
       
  1225 		hex = "0000000" + hex;
       
  1226 	}
       
  1227 
       
  1228 	return hex.slice( -8 );
       
  1229 }
       
  1230 
       
  1231 function Assert( testContext ) {
       
  1232 	this.test = testContext;
       
  1233 }
       
  1234 
       
  1235 // Assert helpers
       
  1236 QUnit.assert = Assert.prototype = {
       
  1237 
       
  1238 	// Specify the number of expected assertions to guarantee that failed test
       
  1239 	// (no assertions are run at all) don't slip through.
       
  1240 	expect: function( asserts ) {
       
  1241 		if ( arguments.length === 1 ) {
       
  1242 			this.test.expected = asserts;
       
  1243 		} else {
       
  1244 			return this.test.expected;
       
  1245 		}
       
  1246 	},
       
  1247 
       
  1248 	// Increment this Test's semaphore counter, then return a single-use function that
       
  1249 	// decrements that counter a maximum of once.
       
  1250 	async: function() {
       
  1251 		var test = this.test,
       
  1252 			popped = false;
       
  1253 
       
  1254 		test.semaphore += 1;
       
  1255 		test.usedAsync = true;
       
  1256 		pauseProcessing();
       
  1257 
       
  1258 		return function done() {
       
  1259 			if ( !popped ) {
       
  1260 				test.semaphore -= 1;
       
  1261 				popped = true;
       
  1262 				resumeProcessing();
       
  1263 			} else {
       
  1264 				test.pushFailure( "Called the callback returned from `assert.async` more than once",
       
  1265 					sourceFromStacktrace( 2 ) );
       
  1266 			}
       
  1267 		};
       
  1268 	},
       
  1269 
       
  1270 	// Exports test.push() to the user API
       
  1271 	push: function( /* result, actual, expected, message */ ) {
       
  1272 		var assert = this,
       
  1273 			currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
       
  1274 
       
  1275 		// Backwards compatibility fix.
       
  1276 		// Allows the direct use of global exported assertions and QUnit.assert.*
       
  1277 		// Although, it's use is not recommended as it can leak assertions
       
  1278 		// to other tests from async tests, because we only get a reference to the current test,
       
  1279 		// not exactly the test where assertion were intended to be called.
       
  1280 		if ( !currentTest ) {
       
  1281 			throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
       
  1282 		}
       
  1283 
       
  1284 		if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
       
  1285 			currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
       
  1286 				sourceFromStacktrace( 2 ) );
       
  1287 
       
  1288 			// Allow this assertion to continue running anyway...
       
  1289 		}
       
  1290 
       
  1291 		if ( !( assert instanceof Assert ) ) {
       
  1292 			assert = currentTest.assert;
       
  1293 		}
       
  1294 		return assert.test.push.apply( assert.test, arguments );
       
  1295 	},
       
  1296 
       
  1297 	ok: function( result, message ) {
       
  1298 		message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
       
  1299 			QUnit.dump.parse( result ) );
       
  1300 		this.push( !!result, result, true, message );
       
  1301 	},
       
  1302 
       
  1303 	notOk: function( result, message ) {
       
  1304 		message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
       
  1305 			QUnit.dump.parse( result ) );
       
  1306 		this.push( !result, result, false, message );
       
  1307 	},
       
  1308 
       
  1309 	equal: function( actual, expected, message ) {
       
  1310 		/*jshint eqeqeq:false */
       
  1311 		this.push( expected == actual, actual, expected, message );
       
  1312 	},
       
  1313 
       
  1314 	notEqual: function( actual, expected, message ) {
       
  1315 		/*jshint eqeqeq:false */
       
  1316 		this.push( expected != actual, actual, expected, message );
       
  1317 	},
       
  1318 
       
  1319 	propEqual: function( actual, expected, message ) {
       
  1320 		actual = objectValues( actual );
       
  1321 		expected = objectValues( expected );
       
  1322 		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
       
  1323 	},
       
  1324 
       
  1325 	notPropEqual: function( actual, expected, message ) {
       
  1326 		actual = objectValues( actual );
       
  1327 		expected = objectValues( expected );
       
  1328 		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
       
  1329 	},
       
  1330 
       
  1331 	deepEqual: function( actual, expected, message ) {
       
  1332 		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
       
  1333 	},
       
  1334 
       
  1335 	notDeepEqual: function( actual, expected, message ) {
       
  1336 		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
       
  1337 	},
       
  1338 
       
  1339 	strictEqual: function( actual, expected, message ) {
       
  1340 		this.push( expected === actual, actual, expected, message );
       
  1341 	},
       
  1342 
       
  1343 	notStrictEqual: function( actual, expected, message ) {
       
  1344 		this.push( expected !== actual, actual, expected, message );
       
  1345 	},
       
  1346 
       
  1347 	"throws": function( block, expected, message ) {
       
  1348 		var actual, expectedType,
       
  1349 			expectedOutput = expected,
       
  1350 			ok = false,
       
  1351 			currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
       
  1352 
       
  1353 		// 'expected' is optional unless doing string comparison
       
  1354 		if ( message == null && typeof expected === "string" ) {
       
  1355 			message = expected;
       
  1356 			expected = null;
       
  1357 		}
       
  1358 
       
  1359 		currentTest.ignoreGlobalErrors = true;
       
  1360 		try {
       
  1361 			block.call( currentTest.testEnvironment );
       
  1362 		} catch (e) {
       
  1363 			actual = e;
       
  1364 		}
       
  1365 		currentTest.ignoreGlobalErrors = false;
       
  1366 
       
  1367 		if ( actual ) {
       
  1368 			expectedType = QUnit.objectType( expected );
       
  1369 
       
  1370 			// we don't want to validate thrown error
       
  1371 			if ( !expected ) {
       
  1372 				ok = true;
       
  1373 				expectedOutput = null;
       
  1374 
       
  1375 			// expected is a regexp
       
  1376 			} else if ( expectedType === "regexp" ) {
       
  1377 				ok = expected.test( errorString( actual ) );
       
  1378 
       
  1379 			// expected is a string
       
  1380 			} else if ( expectedType === "string" ) {
       
  1381 				ok = expected === errorString( actual );
       
  1382 
       
  1383 			// expected is a constructor, maybe an Error constructor
       
  1384 			} else if ( expectedType === "function" && actual instanceof expected ) {
       
  1385 				ok = true;
       
  1386 
       
  1387 			// expected is an Error object
       
  1388 			} else if ( expectedType === "object" ) {
       
  1389 				ok = actual instanceof expected.constructor &&
       
  1390 					actual.name === expected.name &&
       
  1391 					actual.message === expected.message;
       
  1392 
       
  1393 			// expected is a validation function which returns true if validation passed
       
  1394 			} else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
       
  1395 				expectedOutput = null;
       
  1396 				ok = true;
       
  1397 			}
       
  1398 		}
       
  1399 
       
  1400 		currentTest.assert.push( ok, actual, expectedOutput, message );
       
  1401 	}
       
  1402 };
       
  1403 
       
  1404 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
       
  1405 // Known to us are: Closure Compiler, Narwhal
       
  1406 (function() {
       
  1407 	/*jshint sub:true */
       
  1408 	Assert.prototype.raises = Assert.prototype[ "throws" ];
       
  1409 }());
       
  1410 
       
  1411 // Test for equality any JavaScript type.
       
  1412 // Author: Philippe Rathé <prathe@gmail.com>
       
  1413 QUnit.equiv = (function() {
       
  1414 
       
  1415 	// Call the o related callback with the given arguments.
       
  1416 	function bindCallbacks( o, callbacks, args ) {
       
  1417 		var prop = QUnit.objectType( o );
       
  1418 		if ( prop ) {
       
  1419 			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
       
  1420 				return callbacks[ prop ].apply( callbacks, args );
       
  1421 			} else {
       
  1422 				return callbacks[ prop ]; // or undefined
       
  1423 			}
       
  1424 		}
       
  1425 	}
       
  1426 
       
  1427 	// the real equiv function
       
  1428 	var innerEquiv,
       
  1429 
       
  1430 		// stack to decide between skip/abort functions
       
  1431 		callers = [],
       
  1432 
       
  1433 		// stack to avoiding loops from circular referencing
       
  1434 		parents = [],
       
  1435 		parentsB = [],
       
  1436 
       
  1437 		getProto = Object.getPrototypeOf || function( obj ) {
       
  1438 			/* jshint camelcase: false, proto: true */
       
  1439 			return obj.__proto__;
       
  1440 		},
       
  1441 		callbacks = (function() {
       
  1442 
       
  1443 			// for string, boolean, number and null
       
  1444 			function useStrictEquality( b, a ) {
       
  1445 
       
  1446 				/*jshint eqeqeq:false */
       
  1447 				if ( b instanceof a.constructor || a instanceof b.constructor ) {
       
  1448 
       
  1449 					// to catch short annotation VS 'new' annotation of a
       
  1450 					// declaration
       
  1451 					// e.g. var i = 1;
       
  1452 					// var j = new Number(1);
       
  1453 					return a == b;
       
  1454 				} else {
       
  1455 					return a === b;
       
  1456 				}
       
  1457 			}
       
  1458 
       
  1459 			return {
       
  1460 				"string": useStrictEquality,
       
  1461 				"boolean": useStrictEquality,
       
  1462 				"number": useStrictEquality,
       
  1463 				"null": useStrictEquality,
       
  1464 				"undefined": useStrictEquality,
       
  1465 
       
  1466 				"nan": function( b ) {
       
  1467 					return isNaN( b );
       
  1468 				},
       
  1469 
       
  1470 				"date": function( b, a ) {
       
  1471 					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
       
  1472 				},
       
  1473 
       
  1474 				"regexp": function( b, a ) {
       
  1475 					return QUnit.objectType( b ) === "regexp" &&
       
  1476 
       
  1477 						// the regex itself
       
  1478 						a.source === b.source &&
       
  1479 
       
  1480 						// and its modifiers
       
  1481 						a.global === b.global &&
       
  1482 
       
  1483 						// (gmi) ...
       
  1484 						a.ignoreCase === b.ignoreCase &&
       
  1485 						a.multiline === b.multiline &&
       
  1486 						a.sticky === b.sticky;
       
  1487 				},
       
  1488 
       
  1489 				// - skip when the property is a method of an instance (OOP)
       
  1490 				// - abort otherwise,
       
  1491 				// initial === would have catch identical references anyway
       
  1492 				"function": function() {
       
  1493 					var caller = callers[ callers.length - 1 ];
       
  1494 					return caller !== Object && typeof caller !== "undefined";
       
  1495 				},
       
  1496 
       
  1497 				"array": function( b, a ) {
       
  1498 					var i, j, len, loop, aCircular, bCircular;
       
  1499 
       
  1500 					// b could be an object literal here
       
  1501 					if ( QUnit.objectType( b ) !== "array" ) {
       
  1502 						return false;
       
  1503 					}
       
  1504 
       
  1505 					len = a.length;
       
  1506 					if ( len !== b.length ) {
       
  1507 						// safe and faster
       
  1508 						return false;
       
  1509 					}
       
  1510 
       
  1511 					// track reference to avoid circular references
       
  1512 					parents.push( a );
       
  1513 					parentsB.push( b );
       
  1514 					for ( i = 0; i < len; i++ ) {
       
  1515 						loop = false;
       
  1516 						for ( j = 0; j < parents.length; j++ ) {
       
  1517 							aCircular = parents[ j ] === a[ i ];
       
  1518 							bCircular = parentsB[ j ] === b[ i ];
       
  1519 							if ( aCircular || bCircular ) {
       
  1520 								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
       
  1521 									loop = true;
       
  1522 								} else {
       
  1523 									parents.pop();
       
  1524 									parentsB.pop();
       
  1525 									return false;
       
  1526 								}
       
  1527 							}
       
  1528 						}
       
  1529 						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
       
  1530 							parents.pop();
       
  1531 							parentsB.pop();
       
  1532 							return false;
       
  1533 						}
       
  1534 					}
       
  1535 					parents.pop();
       
  1536 					parentsB.pop();
       
  1537 					return true;
       
  1538 				},
       
  1539 
       
  1540 				"object": function( b, a ) {
       
  1541 
       
  1542 					/*jshint forin:false */
       
  1543 					var i, j, loop, aCircular, bCircular,
       
  1544 						// Default to true
       
  1545 						eq = true,
       
  1546 						aProperties = [],
       
  1547 						bProperties = [];
       
  1548 
       
  1549 					// comparing constructors is more strict than using
       
  1550 					// instanceof
       
  1551 					if ( a.constructor !== b.constructor ) {
       
  1552 
       
  1553 						// Allow objects with no prototype to be equivalent to
       
  1554 						// objects with Object as their constructor.
       
  1555 						if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
       
  1556 							( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
       
  1557 							return false;
       
  1558 						}
       
  1559 					}
       
  1560 
       
  1561 					// stack constructor before traversing properties
       
  1562 					callers.push( a.constructor );
       
  1563 
       
  1564 					// track reference to avoid circular references
       
  1565 					parents.push( a );
       
  1566 					parentsB.push( b );
       
  1567 
       
  1568 					// be strict: don't ensure hasOwnProperty and go deep
       
  1569 					for ( i in a ) {
       
  1570 						loop = false;
       
  1571 						for ( j = 0; j < parents.length; j++ ) {
       
  1572 							aCircular = parents[ j ] === a[ i ];
       
  1573 							bCircular = parentsB[ j ] === b[ i ];
       
  1574 							if ( aCircular || bCircular ) {
       
  1575 								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
       
  1576 									loop = true;
       
  1577 								} else {
       
  1578 									eq = false;
       
  1579 									break;
       
  1580 								}
       
  1581 							}
       
  1582 						}
       
  1583 						aProperties.push( i );
       
  1584 						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
       
  1585 							eq = false;
       
  1586 							break;
       
  1587 						}
       
  1588 					}
       
  1589 
       
  1590 					parents.pop();
       
  1591 					parentsB.pop();
       
  1592 					callers.pop(); // unstack, we are done
       
  1593 
       
  1594 					for ( i in b ) {
       
  1595 						bProperties.push( i ); // collect b's properties
       
  1596 					}
       
  1597 
       
  1598 					// Ensures identical properties name
       
  1599 					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
       
  1600 				}
       
  1601 			};
       
  1602 		}());
       
  1603 
       
  1604 	innerEquiv = function() { // can take multiple arguments
       
  1605 		var args = [].slice.apply( arguments );
       
  1606 		if ( args.length < 2 ) {
       
  1607 			return true; // end transition
       
  1608 		}
       
  1609 
       
  1610 		return ( (function( a, b ) {
       
  1611 			if ( a === b ) {
       
  1612 				return true; // catch the most you can
       
  1613 			} else if ( a === null || b === null || typeof a === "undefined" ||
       
  1614 					typeof b === "undefined" ||
       
  1615 					QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
       
  1616 
       
  1617 				// don't lose time with error prone cases
       
  1618 				return false;
       
  1619 			} else {
       
  1620 				return bindCallbacks( a, callbacks, [ b, a ] );
       
  1621 			}
       
  1622 
       
  1623 			// apply transition with (1..n) arguments
       
  1624 		}( args[ 0 ], args[ 1 ] ) ) &&
       
  1625 			innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
       
  1626 	};
       
  1627 
       
  1628 	return innerEquiv;
       
  1629 }());
       
  1630 
       
  1631 // Based on jsDump by Ariel Flesler
       
  1632 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
       
  1633 QUnit.dump = (function() {
       
  1634 	function quote( str ) {
       
  1635 		return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
       
  1636 	}
       
  1637 	function literal( o ) {
       
  1638 		return o + "";
       
  1639 	}
       
  1640 	function join( pre, arr, post ) {
       
  1641 		var s = dump.separator(),
       
  1642 			base = dump.indent(),
       
  1643 			inner = dump.indent( 1 );
       
  1644 		if ( arr.join ) {
       
  1645 			arr = arr.join( "," + s + inner );
       
  1646 		}
       
  1647 		if ( !arr ) {
       
  1648 			return pre + post;
       
  1649 		}
       
  1650 		return [ pre, inner + arr, base + post ].join( s );
       
  1651 	}
       
  1652 	function array( arr, stack ) {
       
  1653 		var i = arr.length,
       
  1654 			ret = new Array( i );
       
  1655 
       
  1656 		if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
       
  1657 			return "[object Array]";
       
  1658 		}
       
  1659 
       
  1660 		this.up();
       
  1661 		while ( i-- ) {
       
  1662 			ret[ i ] = this.parse( arr[ i ], undefined, stack );
       
  1663 		}
       
  1664 		this.down();
       
  1665 		return join( "[", ret, "]" );
       
  1666 	}
       
  1667 
       
  1668 	var reName = /^function (\w+)/,
       
  1669 		dump = {
       
  1670 
       
  1671 			// objType is used mostly internally, you can fix a (custom) type in advance
       
  1672 			parse: function( obj, objType, stack ) {
       
  1673 				stack = stack || [];
       
  1674 				var res, parser, parserType,
       
  1675 					inStack = inArray( obj, stack );
       
  1676 
       
  1677 				if ( inStack !== -1 ) {
       
  1678 					return "recursion(" + ( inStack - stack.length ) + ")";
       
  1679 				}
       
  1680 
       
  1681 				objType = objType || this.typeOf( obj  );
       
  1682 				parser = this.parsers[ objType ];
       
  1683 				parserType = typeof parser;
       
  1684 
       
  1685 				if ( parserType === "function" ) {
       
  1686 					stack.push( obj );
       
  1687 					res = parser.call( this, obj, stack );
       
  1688 					stack.pop();
       
  1689 					return res;
       
  1690 				}
       
  1691 				return ( parserType === "string" ) ? parser : this.parsers.error;
       
  1692 			},
       
  1693 			typeOf: function( obj ) {
       
  1694 				var type;
       
  1695 				if ( obj === null ) {
       
  1696 					type = "null";
       
  1697 				} else if ( typeof obj === "undefined" ) {
       
  1698 					type = "undefined";
       
  1699 				} else if ( QUnit.is( "regexp", obj ) ) {
       
  1700 					type = "regexp";
       
  1701 				} else if ( QUnit.is( "date", obj ) ) {
       
  1702 					type = "date";
       
  1703 				} else if ( QUnit.is( "function", obj ) ) {
       
  1704 					type = "function";
       
  1705 				} else if ( obj.setInterval !== undefined &&
       
  1706 						obj.document !== undefined &&
       
  1707 						obj.nodeType === undefined ) {
       
  1708 					type = "window";
       
  1709 				} else if ( obj.nodeType === 9 ) {
       
  1710 					type = "document";
       
  1711 				} else if ( obj.nodeType ) {
       
  1712 					type = "node";
       
  1713 				} else if (
       
  1714 
       
  1715 					// native arrays
       
  1716 					toString.call( obj ) === "[object Array]" ||
       
  1717 
       
  1718 					// NodeList objects
       
  1719 					( typeof obj.length === "number" && obj.item !== undefined &&
       
  1720 					( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
       
  1721 					obj[ 0 ] === undefined ) ) )
       
  1722 				) {
       
  1723 					type = "array";
       
  1724 				} else if ( obj.constructor === Error.prototype.constructor ) {
       
  1725 					type = "error";
       
  1726 				} else {
       
  1727 					type = typeof obj;
       
  1728 				}
       
  1729 				return type;
       
  1730 			},
       
  1731 			separator: function() {
       
  1732 				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
       
  1733 			},
       
  1734 			// extra can be a number, shortcut for increasing-calling-decreasing
       
  1735 			indent: function( extra ) {
       
  1736 				if ( !this.multiline ) {
       
  1737 					return "";
       
  1738 				}
       
  1739 				var chr = this.indentChar;
       
  1740 				if ( this.HTML ) {
       
  1741 					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
       
  1742 				}
       
  1743 				return new Array( this.depth + ( extra || 0 ) ).join( chr );
       
  1744 			},
       
  1745 			up: function( a ) {
       
  1746 				this.depth += a || 1;
       
  1747 			},
       
  1748 			down: function( a ) {
       
  1749 				this.depth -= a || 1;
       
  1750 			},
       
  1751 			setParser: function( name, parser ) {
       
  1752 				this.parsers[ name ] = parser;
       
  1753 			},
       
  1754 			// The next 3 are exposed so you can use them
       
  1755 			quote: quote,
       
  1756 			literal: literal,
       
  1757 			join: join,
       
  1758 			//
       
  1759 			depth: 1,
       
  1760 			maxDepth: QUnit.config.maxDepth,
       
  1761 
       
  1762 			// This is the list of parsers, to modify them, use dump.setParser
       
  1763 			parsers: {
       
  1764 				window: "[Window]",
       
  1765 				document: "[Document]",
       
  1766 				error: function( error ) {
       
  1767 					return "Error(\"" + error.message + "\")";
       
  1768 				},
       
  1769 				unknown: "[Unknown]",
       
  1770 				"null": "null",
       
  1771 				"undefined": "undefined",
       
  1772 				"function": function( fn ) {
       
  1773 					var ret = "function",
       
  1774 
       
  1775 						// functions never have name in IE
       
  1776 						name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
       
  1777 
       
  1778 					if ( name ) {
       
  1779 						ret += " " + name;
       
  1780 					}
       
  1781 					ret += "( ";
       
  1782 
       
  1783 					ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
       
  1784 					return join( ret, dump.parse( fn, "functionCode" ), "}" );
       
  1785 				},
       
  1786 				array: array,
       
  1787 				nodelist: array,
       
  1788 				"arguments": array,
       
  1789 				object: function( map, stack ) {
       
  1790 					var keys, key, val, i, nonEnumerableProperties,
       
  1791 						ret = [];
       
  1792 
       
  1793 					if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
       
  1794 						return "[object Object]";
       
  1795 					}
       
  1796 
       
  1797 					dump.up();
       
  1798 					keys = [];
       
  1799 					for ( key in map ) {
       
  1800 						keys.push( key );
       
  1801 					}
       
  1802 
       
  1803 					// Some properties are not always enumerable on Error objects.
       
  1804 					nonEnumerableProperties = [ "message", "name" ];
       
  1805 					for ( i in nonEnumerableProperties ) {
       
  1806 						key = nonEnumerableProperties[ i ];
       
  1807 						if ( key in map && inArray( key, keys ) < 0 ) {
       
  1808 							keys.push( key );
       
  1809 						}
       
  1810 					}
       
  1811 					keys.sort();
       
  1812 					for ( i = 0; i < keys.length; i++ ) {
       
  1813 						key = keys[ i ];
       
  1814 						val = map[ key ];
       
  1815 						ret.push( dump.parse( key, "key" ) + ": " +
       
  1816 							dump.parse( val, undefined, stack ) );
       
  1817 					}
       
  1818 					dump.down();
       
  1819 					return join( "{", ret, "}" );
       
  1820 				},
       
  1821 				node: function( node ) {
       
  1822 					var len, i, val,
       
  1823 						open = dump.HTML ? "&lt;" : "<",
       
  1824 						close = dump.HTML ? "&gt;" : ">",
       
  1825 						tag = node.nodeName.toLowerCase(),
       
  1826 						ret = open + tag,
       
  1827 						attrs = node.attributes;
       
  1828 
       
  1829 					if ( attrs ) {
       
  1830 						for ( i = 0, len = attrs.length; i < len; i++ ) {
       
  1831 							val = attrs[ i ].nodeValue;
       
  1832 
       
  1833 							// IE6 includes all attributes in .attributes, even ones not explicitly
       
  1834 							// set. Those have values like undefined, null, 0, false, "" or
       
  1835 							// "inherit".
       
  1836 							if ( val && val !== "inherit" ) {
       
  1837 								ret += " " + attrs[ i ].nodeName + "=" +
       
  1838 									dump.parse( val, "attribute" );
       
  1839 							}
       
  1840 						}
       
  1841 					}
       
  1842 					ret += close;
       
  1843 
       
  1844 					// Show content of TextNode or CDATASection
       
  1845 					if ( node.nodeType === 3 || node.nodeType === 4 ) {
       
  1846 						ret += node.nodeValue;
       
  1847 					}
       
  1848 
       
  1849 					return ret + open + "/" + tag + close;
       
  1850 				},
       
  1851 
       
  1852 				// function calls it internally, it's the arguments part of the function
       
  1853 				functionArgs: function( fn ) {
       
  1854 					var args,
       
  1855 						l = fn.length;
       
  1856 
       
  1857 					if ( !l ) {
       
  1858 						return "";
       
  1859 					}
       
  1860 
       
  1861 					args = new Array( l );
       
  1862 					while ( l-- ) {
       
  1863 
       
  1864 						// 97 is 'a'
       
  1865 						args[ l ] = String.fromCharCode( 97 + l );
       
  1866 					}
       
  1867 					return " " + args.join( ", " ) + " ";
       
  1868 				},
       
  1869 				// object calls it internally, the key part of an item in a map
       
  1870 				key: quote,
       
  1871 				// function calls it internally, it's the content of the function
       
  1872 				functionCode: "[code]",
       
  1873 				// node calls it internally, it's an html attribute value
       
  1874 				attribute: quote,
       
  1875 				string: quote,
       
  1876 				date: quote,
       
  1877 				regexp: literal,
       
  1878 				number: literal,
       
  1879 				"boolean": literal
       
  1880 			},
       
  1881 			// if true, entities are escaped ( <, >, \t, space and \n )
       
  1882 			HTML: false,
       
  1883 			// indentation unit
       
  1884 			indentChar: "  ",
       
  1885 			// if true, items in a collection, are separated by a \n, else just a space.
       
  1886 			multiline: true
       
  1887 		};
       
  1888 
       
  1889 	return dump;
       
  1890 }());
       
  1891 
       
  1892 // back compat
       
  1893 QUnit.jsDump = QUnit.dump;
       
  1894 
       
  1895 // For browser, export only select globals
       
  1896 if ( typeof window !== "undefined" ) {
       
  1897 
       
  1898 	// Deprecated
       
  1899 	// Extend assert methods to QUnit and Global scope through Backwards compatibility
       
  1900 	(function() {
       
  1901 		var i,
       
  1902 			assertions = Assert.prototype;
       
  1903 
       
  1904 		function applyCurrent( current ) {
       
  1905 			return function() {
       
  1906 				var assert = new Assert( QUnit.config.current );
       
  1907 				current.apply( assert, arguments );
       
  1908 			};
       
  1909 		}
       
  1910 
       
  1911 		for ( i in assertions ) {
       
  1912 			QUnit[ i ] = applyCurrent( assertions[ i ] );
       
  1913 		}
       
  1914 	})();
       
  1915 
       
  1916 	(function() {
       
  1917 		var i, l,
       
  1918 			keys = [
       
  1919 				"test",
       
  1920 				"module",
       
  1921 				"expect",
       
  1922 				"asyncTest",
       
  1923 				"start",
       
  1924 				"stop",
       
  1925 				"ok",
       
  1926 				"notOk",
       
  1927 				"equal",
       
  1928 				"notEqual",
       
  1929 				"propEqual",
       
  1930 				"notPropEqual",
       
  1931 				"deepEqual",
       
  1932 				"notDeepEqual",
       
  1933 				"strictEqual",
       
  1934 				"notStrictEqual",
       
  1935 				"throws"
       
  1936 			];
       
  1937 
       
  1938 		for ( i = 0, l = keys.length; i < l; i++ ) {
       
  1939 			window[ keys[ i ] ] = QUnit[ keys[ i ] ];
       
  1940 		}
       
  1941 	})();
       
  1942 
       
  1943 	window.QUnit = QUnit;
       
  1944 }
       
  1945 
       
  1946 // For nodejs
       
  1947 if ( typeof module !== "undefined" && module && module.exports ) {
       
  1948 	module.exports = QUnit;
       
  1949 
       
  1950 	// For consistency with CommonJS environments' exports
       
  1951 	module.exports.QUnit = QUnit;
       
  1952 }
       
  1953 
       
  1954 // For CommonJS with exports, but without module.exports, like Rhino
       
  1955 if ( typeof exports !== "undefined" && exports ) {
       
  1956 	exports.QUnit = QUnit;
       
  1957 }
       
  1958 
       
  1959 if ( typeof define === "function" && define.amd ) {
       
  1960 	define( function() {
       
  1961 		return QUnit;
       
  1962 	} );
       
  1963 	QUnit.config.autostart = false;
       
  1964 }
       
  1965 
       
  1966 // Get a reference to the global object, like window in browsers
       
  1967 }( (function() {
       
  1968 	return this;
       
  1969 })() ));
       
  1970 
       
  1971 /*istanbul ignore next */
       
  1972 // jscs:disable maximumLineLength
       
  1973 /*
       
  1974  * This file is a modified version of google-diff-match-patch's JavaScript implementation
       
  1975  * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
       
  1976  * modifications are licensed as more fully set forth in LICENSE.txt.
       
  1977  *
       
  1978  * The original source of google-diff-match-patch is attributable and licensed as follows:
       
  1979  *
       
  1980  * Copyright 2006 Google Inc.
       
  1981  * http://code.google.com/p/google-diff-match-patch/
       
  1982  *
       
  1983  * Licensed under the Apache License, Version 2.0 (the "License");
       
  1984  * you may not use this file except in compliance with the License.
       
  1985  * You may obtain a copy of the License at
       
  1986  *
       
  1987  * http://www.apache.org/licenses/LICENSE-2.0
       
  1988  *
       
  1989  * Unless required by applicable law or agreed to in writing, software
       
  1990  * distributed under the License is distributed on an "AS IS" BASIS,
       
  1991  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
  1992  * See the License for the specific language governing permissions and
       
  1993  * limitations under the License.
       
  1994  *
       
  1995  * More Info:
       
  1996  *  https://code.google.com/p/google-diff-match-patch/
       
  1997  *
       
  1998  * Usage: QUnit.diff(expected, actual)
       
  1999  *
       
  2000  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the  quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
       
  2001  */
       
  2002 QUnit.diff = (function() {
       
  2003 
       
  2004     function DiffMatchPatch() {
       
  2005 
       
  2006         // Defaults.
       
  2007         // Redefine these in your program to override the defaults.
       
  2008 
       
  2009         // Number of seconds to map a diff before giving up (0 for infinity).
       
  2010         this.DiffTimeout = 1.0;
       
  2011         // Cost of an empty edit operation in terms of edit characters.
       
  2012         this.DiffEditCost = 4;
       
  2013     }
       
  2014 
       
  2015     //  DIFF FUNCTIONS
       
  2016 
       
  2017     /**
       
  2018      * The data structure representing a diff is an array of tuples:
       
  2019      * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
       
  2020      * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
       
  2021      */
       
  2022     var DIFF_DELETE = -1,
       
  2023 		DIFF_INSERT = 1,
       
  2024 		DIFF_EQUAL = 0;
       
  2025 
       
  2026     /**
       
  2027      * Find the differences between two texts.  Simplifies the problem by stripping
       
  2028      * any common prefix or suffix off the texts before diffing.
       
  2029      * @param {string} text1 Old string to be diffed.
       
  2030      * @param {string} text2 New string to be diffed.
       
  2031      * @param {boolean=} optChecklines Optional speedup flag. If present and false,
       
  2032      *     then don't run a line-level diff first to identify the changed areas.
       
  2033      *     Defaults to true, which does a faster, slightly less optimal diff.
       
  2034      * @param {number} optDeadline Optional time when the diff should be complete
       
  2035      *     by.  Used internally for recursive calls.  Users should set DiffTimeout
       
  2036      *     instead.
       
  2037      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
       
  2038      */
       
  2039     DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
       
  2040         var deadline, checklines, commonlength,
       
  2041 			commonprefix, commonsuffix, diffs;
       
  2042         // Set a deadline by which time the diff must be complete.
       
  2043         if ( typeof optDeadline === "undefined" ) {
       
  2044             if ( this.DiffTimeout <= 0 ) {
       
  2045                 optDeadline = Number.MAX_VALUE;
       
  2046             } else {
       
  2047                 optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
       
  2048             }
       
  2049         }
       
  2050         deadline = optDeadline;
       
  2051 
       
  2052         // Check for null inputs.
       
  2053         if ( text1 === null || text2 === null ) {
       
  2054             throw new Error( "Null input. (DiffMain)" );
       
  2055         }
       
  2056 
       
  2057         // Check for equality (speedup).
       
  2058         if ( text1 === text2 ) {
       
  2059             if ( text1 ) {
       
  2060                 return [
       
  2061                     [ DIFF_EQUAL, text1 ]
       
  2062                 ];
       
  2063             }
       
  2064             return [];
       
  2065         }
       
  2066 
       
  2067         if ( typeof optChecklines === "undefined" ) {
       
  2068             optChecklines = true;
       
  2069         }
       
  2070 
       
  2071         checklines = optChecklines;
       
  2072 
       
  2073         // Trim off common prefix (speedup).
       
  2074         commonlength = this.diffCommonPrefix( text1, text2 );
       
  2075         commonprefix = text1.substring( 0, commonlength );
       
  2076         text1 = text1.substring( commonlength );
       
  2077         text2 = text2.substring( commonlength );
       
  2078 
       
  2079         // Trim off common suffix (speedup).
       
  2080         /////////
       
  2081         commonlength = this.diffCommonSuffix( text1, text2 );
       
  2082         commonsuffix = text1.substring( text1.length - commonlength );
       
  2083         text1 = text1.substring( 0, text1.length - commonlength );
       
  2084         text2 = text2.substring( 0, text2.length - commonlength );
       
  2085 
       
  2086         // Compute the diff on the middle block.
       
  2087         diffs = this.diffCompute( text1, text2, checklines, deadline );
       
  2088 
       
  2089         // Restore the prefix and suffix.
       
  2090         if ( commonprefix ) {
       
  2091             diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
       
  2092         }
       
  2093         if ( commonsuffix ) {
       
  2094             diffs.push( [ DIFF_EQUAL, commonsuffix ] );
       
  2095         }
       
  2096         this.diffCleanupMerge( diffs );
       
  2097         return diffs;
       
  2098     };
       
  2099 
       
  2100     /**
       
  2101      * Reduce the number of edits by eliminating operationally trivial equalities.
       
  2102      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
       
  2103      */
       
  2104     DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
       
  2105         var changes, equalities, equalitiesLength, lastequality,
       
  2106 			pointer, preIns, preDel, postIns, postDel;
       
  2107         changes = false;
       
  2108         equalities = []; // Stack of indices where equalities are found.
       
  2109         equalitiesLength = 0; // Keeping our own length var is faster in JS.
       
  2110         /** @type {?string} */
       
  2111         lastequality = null;
       
  2112         // Always equal to diffs[equalities[equalitiesLength - 1]][1]
       
  2113         pointer = 0; // Index of current position.
       
  2114         // Is there an insertion operation before the last equality.
       
  2115         preIns = false;
       
  2116         // Is there a deletion operation before the last equality.
       
  2117         preDel = false;
       
  2118         // Is there an insertion operation after the last equality.
       
  2119         postIns = false;
       
  2120         // Is there a deletion operation after the last equality.
       
  2121         postDel = false;
       
  2122         while ( pointer < diffs.length ) {
       
  2123             if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
       
  2124                 if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
       
  2125                     // Candidate found.
       
  2126                     equalities[ equalitiesLength++ ] = pointer;
       
  2127                     preIns = postIns;
       
  2128                     preDel = postDel;
       
  2129                     lastequality = diffs[ pointer ][ 1 ];
       
  2130                 } else {
       
  2131                     // Not a candidate, and can never become one.
       
  2132                     equalitiesLength = 0;
       
  2133                     lastequality = null;
       
  2134                 }
       
  2135                 postIns = postDel = false;
       
  2136             } else { // An insertion or deletion.
       
  2137                 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
       
  2138                     postDel = true;
       
  2139                 } else {
       
  2140                     postIns = true;
       
  2141                 }
       
  2142                 /*
       
  2143                  * Five types to be split:
       
  2144                  * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
       
  2145                  * <ins>A</ins>X<ins>C</ins><del>D</del>
       
  2146                  * <ins>A</ins><del>B</del>X<ins>C</ins>
       
  2147                  * <ins>A</del>X<ins>C</ins><del>D</del>
       
  2148                  * <ins>A</ins><del>B</del>X<del>C</del>
       
  2149                  */
       
  2150                 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
       
  2151                         ( ( lastequality.length < this.DiffEditCost / 2 ) &&
       
  2152                             ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
       
  2153                     // Duplicate record.
       
  2154                     diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
       
  2155                     // Change second copy to insert.
       
  2156                     diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
       
  2157                     equalitiesLength--; // Throw away the equality we just deleted;
       
  2158                     lastequality = null;
       
  2159                     if (preIns && preDel) {
       
  2160                         // No changes made which could affect previous entry, keep going.
       
  2161                         postIns = postDel = true;
       
  2162                         equalitiesLength = 0;
       
  2163                     } else {
       
  2164                         equalitiesLength--; // Throw away the previous equality.
       
  2165                         pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
       
  2166                         postIns = postDel = false;
       
  2167                     }
       
  2168                     changes = true;
       
  2169                 }
       
  2170             }
       
  2171             pointer++;
       
  2172         }
       
  2173 
       
  2174         if ( changes ) {
       
  2175             this.diffCleanupMerge( diffs );
       
  2176         }
       
  2177     };
       
  2178 
       
  2179     /**
       
  2180      * Convert a diff array into a pretty HTML report.
       
  2181      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
       
  2182      * @param {integer} string to be beautified.
       
  2183      * @return {string} HTML representation.
       
  2184      */
       
  2185     DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
       
  2186         var op, data, x, html = [];
       
  2187         for ( x = 0; x < diffs.length; x++ ) {
       
  2188             op = diffs[x][0]; // Operation (insert, delete, equal)
       
  2189             data = diffs[x][1]; // Text of change.
       
  2190             switch ( op ) {
       
  2191                 case DIFF_INSERT:
       
  2192                     html[x] = "<ins>" + data + "</ins>";
       
  2193                     break;
       
  2194                 case DIFF_DELETE:
       
  2195                     html[x] = "<del>" + data + "</del>";
       
  2196                     break;
       
  2197                 case DIFF_EQUAL:
       
  2198                     html[x] = "<span>" + data + "</span>";
       
  2199                     break;
       
  2200             }
       
  2201         }
       
  2202         return html.join("");
       
  2203     };
       
  2204 
       
  2205     /**
       
  2206      * Determine the common prefix of two strings.
       
  2207      * @param {string} text1 First string.
       
  2208      * @param {string} text2 Second string.
       
  2209      * @return {number} The number of characters common to the start of each
       
  2210      *     string.
       
  2211      */
       
  2212     DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
       
  2213         var pointermid, pointermax, pointermin, pointerstart;
       
  2214         // Quick check for common null cases.
       
  2215         if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
       
  2216             return 0;
       
  2217         }
       
  2218         // Binary search.
       
  2219         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
       
  2220         pointermin = 0;
       
  2221         pointermax = Math.min( text1.length, text2.length );
       
  2222         pointermid = pointermax;
       
  2223         pointerstart = 0;
       
  2224         while ( pointermin < pointermid ) {
       
  2225             if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
       
  2226                 pointermin = pointermid;
       
  2227                 pointerstart = pointermin;
       
  2228             } else {
       
  2229                 pointermax = pointermid;
       
  2230             }
       
  2231             pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
       
  2232         }
       
  2233         return pointermid;
       
  2234     };
       
  2235 
       
  2236     /**
       
  2237      * Determine the common suffix of two strings.
       
  2238      * @param {string} text1 First string.
       
  2239      * @param {string} text2 Second string.
       
  2240      * @return {number} The number of characters common to the end of each string.
       
  2241      */
       
  2242     DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
       
  2243         var pointermid, pointermax, pointermin, pointerend;
       
  2244         // Quick check for common null cases.
       
  2245         if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
       
  2246             return 0;
       
  2247         }
       
  2248         // Binary search.
       
  2249         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
       
  2250         pointermin = 0;
       
  2251         pointermax = Math.min(text1.length, text2.length);
       
  2252         pointermid = pointermax;
       
  2253         pointerend = 0;
       
  2254         while ( pointermin < pointermid ) {
       
  2255             if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
       
  2256                 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
       
  2257                 pointermin = pointermid;
       
  2258                 pointerend = pointermin;
       
  2259             } else {
       
  2260                 pointermax = pointermid;
       
  2261             }
       
  2262             pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
       
  2263         }
       
  2264         return pointermid;
       
  2265     };
       
  2266 
       
  2267     /**
       
  2268      * Find the differences between two texts.  Assumes that the texts do not
       
  2269      * have any common prefix or suffix.
       
  2270      * @param {string} text1 Old string to be diffed.
       
  2271      * @param {string} text2 New string to be diffed.
       
  2272      * @param {boolean} checklines Speedup flag.  If false, then don't run a
       
  2273      *     line-level diff first to identify the changed areas.
       
  2274      *     If true, then run a faster, slightly less optimal diff.
       
  2275      * @param {number} deadline Time when the diff should be complete by.
       
  2276      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
       
  2277      * @private
       
  2278      */
       
  2279     DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
       
  2280         var diffs, longtext, shorttext, i, hm,
       
  2281 			text1A, text2A, text1B, text2B,
       
  2282 			midCommon, diffsA, diffsB;
       
  2283 
       
  2284         if ( !text1 ) {
       
  2285             // Just add some text (speedup).
       
  2286             return [
       
  2287                 [ DIFF_INSERT, text2 ]
       
  2288             ];
       
  2289         }
       
  2290 
       
  2291         if (!text2) {
       
  2292             // Just delete some text (speedup).
       
  2293             return [
       
  2294                 [ DIFF_DELETE, text1 ]
       
  2295             ];
       
  2296         }
       
  2297 
       
  2298         longtext = text1.length > text2.length ? text1 : text2;
       
  2299         shorttext = text1.length > text2.length ? text2 : text1;
       
  2300         i = longtext.indexOf( shorttext );
       
  2301         if ( i !== -1 ) {
       
  2302             // Shorter text is inside the longer text (speedup).
       
  2303             diffs = [
       
  2304                 [ DIFF_INSERT, longtext.substring( 0, i ) ],
       
  2305                 [ DIFF_EQUAL, shorttext ],
       
  2306                 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
       
  2307             ];
       
  2308             // Swap insertions for deletions if diff is reversed.
       
  2309             if ( text1.length > text2.length ) {
       
  2310                 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
       
  2311             }
       
  2312             return diffs;
       
  2313         }
       
  2314 
       
  2315         if ( shorttext.length === 1 ) {
       
  2316             // Single character string.
       
  2317             // After the previous speedup, the character can't be an equality.
       
  2318             return [
       
  2319                 [ DIFF_DELETE, text1 ],
       
  2320                 [ DIFF_INSERT, text2 ]
       
  2321             ];
       
  2322         }
       
  2323 
       
  2324         // Check to see if the problem can be split in two.
       
  2325         hm = this.diffHalfMatch(text1, text2);
       
  2326         if (hm) {
       
  2327             // A half-match was found, sort out the return data.
       
  2328             text1A = hm[0];
       
  2329             text1B = hm[1];
       
  2330             text2A = hm[2];
       
  2331             text2B = hm[3];
       
  2332             midCommon = hm[4];
       
  2333             // Send both pairs off for separate processing.
       
  2334             diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
       
  2335             diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
       
  2336             // Merge the results.
       
  2337             return diffsA.concat([
       
  2338                 [ DIFF_EQUAL, midCommon ]
       
  2339             ], diffsB);
       
  2340         }
       
  2341 
       
  2342         if (checklines && text1.length > 100 && text2.length > 100) {
       
  2343             return this.diffLineMode(text1, text2, deadline);
       
  2344         }
       
  2345 
       
  2346         return this.diffBisect(text1, text2, deadline);
       
  2347     };
       
  2348 
       
  2349     /**
       
  2350      * Do the two texts share a substring which is at least half the length of the
       
  2351      * longer text?
       
  2352      * This speedup can produce non-minimal diffs.
       
  2353      * @param {string} text1 First string.
       
  2354      * @param {string} text2 Second string.
       
  2355      * @return {Array.<string>} Five element Array, containing the prefix of
       
  2356      *     text1, the suffix of text1, the prefix of text2, the suffix of
       
  2357      *     text2 and the common middle.  Or null if there was no match.
       
  2358      * @private
       
  2359      */
       
  2360     DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
       
  2361         var longtext, shorttext, dmp,
       
  2362 			text1A, text2B, text2A, text1B, midCommon,
       
  2363 			hm1, hm2, hm;
       
  2364         if (this.DiffTimeout <= 0) {
       
  2365             // Don't risk returning a non-optimal diff if we have unlimited time.
       
  2366             return null;
       
  2367         }
       
  2368         longtext = text1.length > text2.length ? text1 : text2;
       
  2369         shorttext = text1.length > text2.length ? text2 : text1;
       
  2370         if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
       
  2371             return null; // Pointless.
       
  2372         }
       
  2373         dmp = this; // 'this' becomes 'window' in a closure.
       
  2374 
       
  2375         /**
       
  2376          * Does a substring of shorttext exist within longtext such that the substring
       
  2377          * is at least half the length of longtext?
       
  2378          * Closure, but does not reference any external variables.
       
  2379          * @param {string} longtext Longer string.
       
  2380          * @param {string} shorttext Shorter string.
       
  2381          * @param {number} i Start index of quarter length substring within longtext.
       
  2382          * @return {Array.<string>} Five element Array, containing the prefix of
       
  2383          *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
       
  2384          *     of shorttext and the common middle.  Or null if there was no match.
       
  2385          * @private
       
  2386          */
       
  2387         function diffHalfMatchI(longtext, shorttext, i) {
       
  2388             var seed, j, bestCommon, prefixLength, suffixLength,
       
  2389 				bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
       
  2390             // Start with a 1/4 length substring at position i as a seed.
       
  2391             seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
       
  2392             j = -1;
       
  2393             bestCommon = "";
       
  2394             while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
       
  2395                 prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
       
  2396                     shorttext.substring(j));
       
  2397                 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
       
  2398                     shorttext.substring(0, j));
       
  2399                 if (bestCommon.length < suffixLength + prefixLength) {
       
  2400                     bestCommon = shorttext.substring(j - suffixLength, j) +
       
  2401                         shorttext.substring(j, j + prefixLength);
       
  2402                     bestLongtextA = longtext.substring(0, i - suffixLength);
       
  2403                     bestLongtextB = longtext.substring(i + prefixLength);
       
  2404                     bestShorttextA = shorttext.substring(0, j - suffixLength);
       
  2405                     bestShorttextB = shorttext.substring(j + prefixLength);
       
  2406                 }
       
  2407             }
       
  2408             if (bestCommon.length * 2 >= longtext.length) {
       
  2409                 return [ bestLongtextA, bestLongtextB,
       
  2410                     bestShorttextA, bestShorttextB, bestCommon
       
  2411                 ];
       
  2412             } else {
       
  2413                 return null;
       
  2414             }
       
  2415         }
       
  2416 
       
  2417         // First check if the second quarter is the seed for a half-match.
       
  2418         hm1 = diffHalfMatchI(longtext, shorttext,
       
  2419             Math.ceil(longtext.length / 4));
       
  2420         // Check again based on the third quarter.
       
  2421         hm2 = diffHalfMatchI(longtext, shorttext,
       
  2422             Math.ceil(longtext.length / 2));
       
  2423         if (!hm1 && !hm2) {
       
  2424             return null;
       
  2425         } else if (!hm2) {
       
  2426             hm = hm1;
       
  2427         } else if (!hm1) {
       
  2428             hm = hm2;
       
  2429         } else {
       
  2430             // Both matched.  Select the longest.
       
  2431             hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
       
  2432         }
       
  2433 
       
  2434         // A half-match was found, sort out the return data.
       
  2435         text1A, text1B, text2A, text2B;
       
  2436         if (text1.length > text2.length) {
       
  2437             text1A = hm[0];
       
  2438             text1B = hm[1];
       
  2439             text2A = hm[2];
       
  2440             text2B = hm[3];
       
  2441         } else {
       
  2442             text2A = hm[0];
       
  2443             text2B = hm[1];
       
  2444             text1A = hm[2];
       
  2445             text1B = hm[3];
       
  2446         }
       
  2447         midCommon = hm[4];
       
  2448         return [ text1A, text1B, text2A, text2B, midCommon ];
       
  2449     };
       
  2450 
       
  2451     /**
       
  2452      * Do a quick line-level diff on both strings, then rediff the parts for
       
  2453      * greater accuracy.
       
  2454      * This speedup can produce non-minimal diffs.
       
  2455      * @param {string} text1 Old string to be diffed.
       
  2456      * @param {string} text2 New string to be diffed.
       
  2457      * @param {number} deadline Time when the diff should be complete by.
       
  2458      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
       
  2459      * @private
       
  2460      */
       
  2461     DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
       
  2462         var a, diffs, linearray, pointer, countInsert,
       
  2463 			countDelete, textInsert, textDelete, j;
       
  2464         // Scan the text on a line-by-line basis first.
       
  2465         a = this.diffLinesToChars(text1, text2);
       
  2466         text1 = a.chars1;
       
  2467         text2 = a.chars2;
       
  2468         linearray = a.lineArray;
       
  2469 
       
  2470         diffs = this.DiffMain(text1, text2, false, deadline);
       
  2471 
       
  2472         // Convert the diff back to original text.
       
  2473         this.diffCharsToLines(diffs, linearray);
       
  2474         // Eliminate freak matches (e.g. blank lines)
       
  2475         this.diffCleanupSemantic(diffs);
       
  2476 
       
  2477         // Rediff any replacement blocks, this time character-by-character.
       
  2478         // Add a dummy entry at the end.
       
  2479         diffs.push( [ DIFF_EQUAL, "" ] );
       
  2480         pointer = 0;
       
  2481         countDelete = 0;
       
  2482         countInsert = 0;
       
  2483         textDelete = "";
       
  2484         textInsert = "";
       
  2485         while (pointer < diffs.length) {
       
  2486             switch ( diffs[pointer][0] ) {
       
  2487                 case DIFF_INSERT:
       
  2488                     countInsert++;
       
  2489                     textInsert += diffs[pointer][1];
       
  2490                     break;
       
  2491                 case DIFF_DELETE:
       
  2492                     countDelete++;
       
  2493                     textDelete += diffs[pointer][1];
       
  2494                     break;
       
  2495                 case DIFF_EQUAL:
       
  2496                     // Upon reaching an equality, check for prior redundancies.
       
  2497                     if (countDelete >= 1 && countInsert >= 1) {
       
  2498                         // Delete the offending records and add the merged ones.
       
  2499                         diffs.splice(pointer - countDelete - countInsert,
       
  2500                             countDelete + countInsert);
       
  2501                         pointer = pointer - countDelete - countInsert;
       
  2502                         a = this.DiffMain(textDelete, textInsert, false, deadline);
       
  2503                         for (j = a.length - 1; j >= 0; j--) {
       
  2504                             diffs.splice( pointer, 0, a[j] );
       
  2505                         }
       
  2506                         pointer = pointer + a.length;
       
  2507                     }
       
  2508                     countInsert = 0;
       
  2509                     countDelete = 0;
       
  2510                     textDelete = "";
       
  2511                     textInsert = "";
       
  2512                     break;
       
  2513             }
       
  2514             pointer++;
       
  2515         }
       
  2516         diffs.pop(); // Remove the dummy entry at the end.
       
  2517 
       
  2518         return diffs;
       
  2519     };
       
  2520 
       
  2521     /**
       
  2522      * Find the 'middle snake' of a diff, split the problem in two
       
  2523      * and return the recursively constructed diff.
       
  2524      * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
       
  2525      * @param {string} text1 Old string to be diffed.
       
  2526      * @param {string} text2 New string to be diffed.
       
  2527      * @param {number} deadline Time at which to bail if not yet complete.
       
  2528      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
       
  2529      * @private
       
  2530      */
       
  2531     DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
       
  2532         var text1Length, text2Length, maxD, vOffset, vLength,
       
  2533 			v1, v2, x, delta, front, k1start, k1end, k2start,
       
  2534 			k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
       
  2535         // Cache the text lengths to prevent multiple calls.
       
  2536         text1Length = text1.length;
       
  2537         text2Length = text2.length;
       
  2538         maxD = Math.ceil((text1Length + text2Length) / 2);
       
  2539         vOffset = maxD;
       
  2540         vLength = 2 * maxD;
       
  2541         v1 = new Array(vLength);
       
  2542         v2 = new Array(vLength);
       
  2543         // Setting all elements to -1 is faster in Chrome & Firefox than mixing
       
  2544         // integers and undefined.
       
  2545         for (x = 0; x < vLength; x++) {
       
  2546             v1[x] = -1;
       
  2547             v2[x] = -1;
       
  2548         }
       
  2549         v1[vOffset + 1] = 0;
       
  2550         v2[vOffset + 1] = 0;
       
  2551         delta = text1Length - text2Length;
       
  2552         // If the total number of characters is odd, then the front path will collide
       
  2553         // with the reverse path.
       
  2554         front = (delta % 2 !== 0);
       
  2555         // Offsets for start and end of k loop.
       
  2556         // Prevents mapping of space beyond the grid.
       
  2557         k1start = 0;
       
  2558         k1end = 0;
       
  2559         k2start = 0;
       
  2560         k2end = 0;
       
  2561         for (d = 0; d < maxD; d++) {
       
  2562             // Bail out if deadline is reached.
       
  2563             if ((new Date()).getTime() > deadline) {
       
  2564                 break;
       
  2565             }
       
  2566 
       
  2567             // Walk the front path one step.
       
  2568             for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
       
  2569                 k1Offset = vOffset + k1;
       
  2570                 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
       
  2571                     x1 = v1[k1Offset + 1];
       
  2572                 } else {
       
  2573                     x1 = v1[k1Offset - 1] + 1;
       
  2574                 }
       
  2575                 y1 = x1 - k1;
       
  2576                 while (x1 < text1Length && y1 < text2Length &&
       
  2577                     text1.charAt(x1) === text2.charAt(y1)) {
       
  2578                     x1++;
       
  2579                     y1++;
       
  2580                 }
       
  2581                 v1[k1Offset] = x1;
       
  2582                 if (x1 > text1Length) {
       
  2583                     // Ran off the right of the graph.
       
  2584                     k1end += 2;
       
  2585                 } else if (y1 > text2Length) {
       
  2586                     // Ran off the bottom of the graph.
       
  2587                     k1start += 2;
       
  2588                 } else if (front) {
       
  2589                     k2Offset = vOffset + delta - k1;
       
  2590                     if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
       
  2591                         // Mirror x2 onto top-left coordinate system.
       
  2592                         x2 = text1Length - v2[k2Offset];
       
  2593                         if (x1 >= x2) {
       
  2594                             // Overlap detected.
       
  2595                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
       
  2596                         }
       
  2597                     }
       
  2598                 }
       
  2599             }
       
  2600 
       
  2601             // Walk the reverse path one step.
       
  2602             for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
       
  2603                 k2Offset = vOffset + k2;
       
  2604                 if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
       
  2605                     x2 = v2[k2Offset + 1];
       
  2606                 } else {
       
  2607                     x2 = v2[k2Offset - 1] + 1;
       
  2608                 }
       
  2609                 y2 = x2 - k2;
       
  2610                 while (x2 < text1Length && y2 < text2Length &&
       
  2611                     text1.charAt(text1Length - x2 - 1) ===
       
  2612                     text2.charAt(text2Length - y2 - 1)) {
       
  2613                     x2++;
       
  2614                     y2++;
       
  2615                 }
       
  2616                 v2[k2Offset] = x2;
       
  2617                 if (x2 > text1Length) {
       
  2618                     // Ran off the left of the graph.
       
  2619                     k2end += 2;
       
  2620                 } else if (y2 > text2Length) {
       
  2621                     // Ran off the top of the graph.
       
  2622                     k2start += 2;
       
  2623                 } else if (!front) {
       
  2624                     k1Offset = vOffset + delta - k2;
       
  2625                     if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
       
  2626                         x1 = v1[k1Offset];
       
  2627                         y1 = vOffset + x1 - k1Offset;
       
  2628                         // Mirror x2 onto top-left coordinate system.
       
  2629                         x2 = text1Length - x2;
       
  2630                         if (x1 >= x2) {
       
  2631                             // Overlap detected.
       
  2632                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
       
  2633                         }
       
  2634                     }
       
  2635                 }
       
  2636             }
       
  2637         }
       
  2638         // Diff took too long and hit the deadline or
       
  2639         // number of diffs equals number of characters, no commonality at all.
       
  2640         return [
       
  2641             [ DIFF_DELETE, text1 ],
       
  2642             [ DIFF_INSERT, text2 ]
       
  2643         ];
       
  2644     };
       
  2645 
       
  2646     /**
       
  2647      * Given the location of the 'middle snake', split the diff in two parts
       
  2648      * and recurse.
       
  2649      * @param {string} text1 Old string to be diffed.
       
  2650      * @param {string} text2 New string to be diffed.
       
  2651      * @param {number} x Index of split point in text1.
       
  2652      * @param {number} y Index of split point in text2.
       
  2653      * @param {number} deadline Time at which to bail if not yet complete.
       
  2654      * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
       
  2655      * @private
       
  2656      */
       
  2657     DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
       
  2658         var text1a, text1b, text2a, text2b, diffs, diffsb;
       
  2659         text1a = text1.substring(0, x);
       
  2660         text2a = text2.substring(0, y);
       
  2661         text1b = text1.substring(x);
       
  2662         text2b = text2.substring(y);
       
  2663 
       
  2664         // Compute both diffs serially.
       
  2665         diffs = this.DiffMain(text1a, text2a, false, deadline);
       
  2666         diffsb = this.DiffMain(text1b, text2b, false, deadline);
       
  2667 
       
  2668         return diffs.concat(diffsb);
       
  2669     };
       
  2670 
       
  2671     /**
       
  2672      * Reduce the number of edits by eliminating semantically trivial equalities.
       
  2673      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
       
  2674      */
       
  2675     DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
       
  2676         var changes, equalities, equalitiesLength, lastequality,
       
  2677 			pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
       
  2678 			lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
       
  2679         changes = false;
       
  2680         equalities = []; // Stack of indices where equalities are found.
       
  2681         equalitiesLength = 0; // Keeping our own length var is faster in JS.
       
  2682         /** @type {?string} */
       
  2683         lastequality = null;
       
  2684         // Always equal to diffs[equalities[equalitiesLength - 1]][1]
       
  2685         pointer = 0; // Index of current position.
       
  2686         // Number of characters that changed prior to the equality.
       
  2687         lengthInsertions1 = 0;
       
  2688         lengthDeletions1 = 0;
       
  2689         // Number of characters that changed after the equality.
       
  2690         lengthInsertions2 = 0;
       
  2691         lengthDeletions2 = 0;
       
  2692         while (pointer < diffs.length) {
       
  2693             if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
       
  2694                 equalities[equalitiesLength++] = pointer;
       
  2695                 lengthInsertions1 = lengthInsertions2;
       
  2696                 lengthDeletions1 = lengthDeletions2;
       
  2697                 lengthInsertions2 = 0;
       
  2698                 lengthDeletions2 = 0;
       
  2699                 lastequality = diffs[pointer][1];
       
  2700             } else { // An insertion or deletion.
       
  2701                 if (diffs[pointer][0] === DIFF_INSERT) {
       
  2702                     lengthInsertions2 += diffs[pointer][1].length;
       
  2703                 } else {
       
  2704                     lengthDeletions2 += diffs[pointer][1].length;
       
  2705                 }
       
  2706                 // Eliminate an equality that is smaller or equal to the edits on both
       
  2707                 // sides of it.
       
  2708                 if (lastequality && (lastequality.length <=
       
  2709                         Math.max(lengthInsertions1, lengthDeletions1)) &&
       
  2710                     (lastequality.length <= Math.max(lengthInsertions2,
       
  2711                         lengthDeletions2))) {
       
  2712                     // Duplicate record.
       
  2713                     diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
       
  2714                     // Change second copy to insert.
       
  2715                     diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
       
  2716                     // Throw away the equality we just deleted.
       
  2717                     equalitiesLength--;
       
  2718                     // Throw away the previous equality (it needs to be reevaluated).
       
  2719                     equalitiesLength--;
       
  2720                     pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
       
  2721                     lengthInsertions1 = 0; // Reset the counters.
       
  2722                     lengthDeletions1 = 0;
       
  2723                     lengthInsertions2 = 0;
       
  2724                     lengthDeletions2 = 0;
       
  2725                     lastequality = null;
       
  2726                     changes = true;
       
  2727                 }
       
  2728             }
       
  2729             pointer++;
       
  2730         }
       
  2731 
       
  2732         // Normalize the diff.
       
  2733         if (changes) {
       
  2734             this.diffCleanupMerge(diffs);
       
  2735         }
       
  2736 
       
  2737         // Find any overlaps between deletions and insertions.
       
  2738         // e.g: <del>abcxxx</del><ins>xxxdef</ins>
       
  2739         //   -> <del>abc</del>xxx<ins>def</ins>
       
  2740         // e.g: <del>xxxabc</del><ins>defxxx</ins>
       
  2741         //   -> <ins>def</ins>xxx<del>abc</del>
       
  2742         // Only extract an overlap if it is as big as the edit ahead or behind it.
       
  2743         pointer = 1;
       
  2744         while (pointer < diffs.length) {
       
  2745             if (diffs[pointer - 1][0] === DIFF_DELETE &&
       
  2746                 diffs[pointer][0] === DIFF_INSERT) {
       
  2747                 deletion = diffs[pointer - 1][1];
       
  2748                 insertion = diffs[pointer][1];
       
  2749                 overlapLength1 = this.diffCommonOverlap(deletion, insertion);
       
  2750                 overlapLength2 = this.diffCommonOverlap(insertion, deletion);
       
  2751                 if (overlapLength1 >= overlapLength2) {
       
  2752                     if (overlapLength1 >= deletion.length / 2 ||
       
  2753                         overlapLength1 >= insertion.length / 2) {
       
  2754                         // Overlap found.  Insert an equality and trim the surrounding edits.
       
  2755                         diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
       
  2756                         diffs[pointer - 1][1] =
       
  2757                             deletion.substring(0, deletion.length - overlapLength1);
       
  2758                         diffs[pointer + 1][1] = insertion.substring(overlapLength1);
       
  2759                         pointer++;
       
  2760                     }
       
  2761                 } else {
       
  2762                     if (overlapLength2 >= deletion.length / 2 ||
       
  2763                         overlapLength2 >= insertion.length / 2) {
       
  2764                         // Reverse overlap found.
       
  2765                         // Insert an equality and swap and trim the surrounding edits.
       
  2766                         diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
       
  2767                         diffs[pointer - 1][0] = DIFF_INSERT;
       
  2768                         diffs[pointer - 1][1] =
       
  2769                             insertion.substring(0, insertion.length - overlapLength2);
       
  2770                         diffs[pointer + 1][0] = DIFF_DELETE;
       
  2771                         diffs[pointer + 1][1] =
       
  2772                             deletion.substring(overlapLength2);
       
  2773                         pointer++;
       
  2774                     }
       
  2775                 }
       
  2776                 pointer++;
       
  2777             }
       
  2778             pointer++;
       
  2779         }
       
  2780     };
       
  2781 
       
  2782     /**
       
  2783      * Determine if the suffix of one string is the prefix of another.
       
  2784      * @param {string} text1 First string.
       
  2785      * @param {string} text2 Second string.
       
  2786      * @return {number} The number of characters common to the end of the first
       
  2787      *     string and the start of the second string.
       
  2788      * @private
       
  2789      */
       
  2790     DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
       
  2791         var text1Length, text2Length, textLength,
       
  2792 			best, length, pattern, found;
       
  2793         // Cache the text lengths to prevent multiple calls.
       
  2794         text1Length = text1.length;
       
  2795         text2Length = text2.length;
       
  2796         // Eliminate the null case.
       
  2797         if (text1Length === 0 || text2Length === 0) {
       
  2798             return 0;
       
  2799         }
       
  2800         // Truncate the longer string.
       
  2801         if (text1Length > text2Length) {
       
  2802             text1 = text1.substring(text1Length - text2Length);
       
  2803         } else if (text1Length < text2Length) {
       
  2804             text2 = text2.substring(0, text1Length);
       
  2805         }
       
  2806         textLength = Math.min(text1Length, text2Length);
       
  2807         // Quick check for the worst case.
       
  2808         if (text1 === text2) {
       
  2809             return textLength;
       
  2810         }
       
  2811 
       
  2812         // Start by looking for a single character match
       
  2813         // and increase length until no match is found.
       
  2814         // Performance analysis: http://neil.fraser.name/news/2010/11/04/
       
  2815         best = 0;
       
  2816         length = 1;
       
  2817         while (true) {
       
  2818             pattern = text1.substring(textLength - length);
       
  2819             found = text2.indexOf(pattern);
       
  2820             if (found === -1) {
       
  2821                 return best;
       
  2822             }
       
  2823             length += found;
       
  2824             if (found === 0 || text1.substring(textLength - length) ===
       
  2825                 text2.substring(0, length)) {
       
  2826                 best = length;
       
  2827                 length++;
       
  2828             }
       
  2829         }
       
  2830     };
       
  2831 
       
  2832     /**
       
  2833      * Split two texts into an array of strings.  Reduce the texts to a string of
       
  2834      * hashes where each Unicode character represents one line.
       
  2835      * @param {string} text1 First string.
       
  2836      * @param {string} text2 Second string.
       
  2837      * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
       
  2838      *     An object containing the encoded text1, the encoded text2 and
       
  2839      *     the array of unique strings.
       
  2840      *     The zeroth element of the array of unique strings is intentionally blank.
       
  2841      * @private
       
  2842      */
       
  2843     DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
       
  2844         var lineArray, lineHash, chars1, chars2;
       
  2845         lineArray = []; // e.g. lineArray[4] === 'Hello\n'
       
  2846         lineHash = {}; // e.g. lineHash['Hello\n'] === 4
       
  2847 
       
  2848         // '\x00' is a valid character, but various debuggers don't like it.
       
  2849         // So we'll insert a junk entry to avoid generating a null character.
       
  2850         lineArray[0] = "";
       
  2851 
       
  2852         /**
       
  2853          * Split a text into an array of strings.  Reduce the texts to a string of
       
  2854          * hashes where each Unicode character represents one line.
       
  2855          * Modifies linearray and linehash through being a closure.
       
  2856          * @param {string} text String to encode.
       
  2857          * @return {string} Encoded string.
       
  2858          * @private
       
  2859          */
       
  2860         function diffLinesToCharsMunge(text) {
       
  2861             var chars, lineStart, lineEnd, lineArrayLength, line;
       
  2862             chars = "";
       
  2863             // Walk the text, pulling out a substring for each line.
       
  2864             // text.split('\n') would would temporarily double our memory footprint.
       
  2865             // Modifying text would create many large strings to garbage collect.
       
  2866             lineStart = 0;
       
  2867             lineEnd = -1;
       
  2868             // Keeping our own length variable is faster than looking it up.
       
  2869             lineArrayLength = lineArray.length;
       
  2870             while (lineEnd < text.length - 1) {
       
  2871                 lineEnd = text.indexOf("\n", lineStart);
       
  2872                 if (lineEnd === -1) {
       
  2873                     lineEnd = text.length - 1;
       
  2874                 }
       
  2875                 line = text.substring(lineStart, lineEnd + 1);
       
  2876                 lineStart = lineEnd + 1;
       
  2877 
       
  2878                 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
       
  2879                     (lineHash[line] !== undefined)) {
       
  2880                     chars += String.fromCharCode( lineHash[ line ] );
       
  2881                 } else {
       
  2882                     chars += String.fromCharCode(lineArrayLength);
       
  2883                     lineHash[line] = lineArrayLength;
       
  2884                     lineArray[lineArrayLength++] = line;
       
  2885                 }
       
  2886             }
       
  2887             return chars;
       
  2888         }
       
  2889 
       
  2890         chars1 = diffLinesToCharsMunge(text1);
       
  2891         chars2 = diffLinesToCharsMunge(text2);
       
  2892         return {
       
  2893             chars1: chars1,
       
  2894             chars2: chars2,
       
  2895             lineArray: lineArray
       
  2896         };
       
  2897     };
       
  2898 
       
  2899     /**
       
  2900      * Rehydrate the text in a diff from a string of line hashes to real lines of
       
  2901      * text.
       
  2902      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
       
  2903      * @param {!Array.<string>} lineArray Array of unique strings.
       
  2904      * @private
       
  2905      */
       
  2906     DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
       
  2907         var x, chars, text, y;
       
  2908         for ( x = 0; x < diffs.length; x++ ) {
       
  2909             chars = diffs[x][1];
       
  2910             text = [];
       
  2911             for ( y = 0; y < chars.length; y++ ) {
       
  2912                 text[y] = lineArray[chars.charCodeAt(y)];
       
  2913             }
       
  2914             diffs[x][1] = text.join("");
       
  2915         }
       
  2916     };
       
  2917 
       
  2918     /**
       
  2919      * Reorder and merge like edit sections.  Merge equalities.
       
  2920      * Any edit section can move as long as it doesn't cross an equality.
       
  2921      * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
       
  2922      */
       
  2923     DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
       
  2924         var pointer, countDelete, countInsert, textInsert, textDelete,
       
  2925 			commonlength, changes;
       
  2926         diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
       
  2927         pointer = 0;
       
  2928         countDelete = 0;
       
  2929         countInsert = 0;
       
  2930         textDelete = "";
       
  2931         textInsert = "";
       
  2932         commonlength;
       
  2933         while (pointer < diffs.length) {
       
  2934             switch ( diffs[ pointer ][ 0 ] ) {
       
  2935                 case DIFF_INSERT:
       
  2936                     countInsert++;
       
  2937                     textInsert += diffs[pointer][1];
       
  2938                     pointer++;
       
  2939                     break;
       
  2940                 case DIFF_DELETE:
       
  2941                     countDelete++;
       
  2942                     textDelete += diffs[pointer][1];
       
  2943                     pointer++;
       
  2944                     break;
       
  2945                 case DIFF_EQUAL:
       
  2946                     // Upon reaching an equality, check for prior redundancies.
       
  2947                     if (countDelete + countInsert > 1) {
       
  2948                         if (countDelete !== 0 && countInsert !== 0) {
       
  2949                             // Factor out any common prefixies.
       
  2950                             commonlength = this.diffCommonPrefix(textInsert, textDelete);
       
  2951                             if (commonlength !== 0) {
       
  2952                                 if ((pointer - countDelete - countInsert) > 0 &&
       
  2953                                     diffs[pointer - countDelete - countInsert - 1][0] ===
       
  2954                                     DIFF_EQUAL) {
       
  2955                                     diffs[pointer - countDelete - countInsert - 1][1] +=
       
  2956                                         textInsert.substring(0, commonlength);
       
  2957                                 } else {
       
  2958                                     diffs.splice( 0, 0, [ DIFF_EQUAL,
       
  2959                                         textInsert.substring( 0, commonlength )
       
  2960                                      ] );
       
  2961                                     pointer++;
       
  2962                                 }
       
  2963                                 textInsert = textInsert.substring(commonlength);
       
  2964                                 textDelete = textDelete.substring(commonlength);
       
  2965                             }
       
  2966                             // Factor out any common suffixies.
       
  2967                             commonlength = this.diffCommonSuffix(textInsert, textDelete);
       
  2968                             if (commonlength !== 0) {
       
  2969                                 diffs[pointer][1] = textInsert.substring(textInsert.length -
       
  2970                                     commonlength) + diffs[pointer][1];
       
  2971                                 textInsert = textInsert.substring(0, textInsert.length -
       
  2972                                     commonlength);
       
  2973                                 textDelete = textDelete.substring(0, textDelete.length -
       
  2974                                     commonlength);
       
  2975                             }
       
  2976                         }
       
  2977                         // Delete the offending records and add the merged ones.
       
  2978                         if (countDelete === 0) {
       
  2979                             diffs.splice( pointer - countInsert,
       
  2980                                 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
       
  2981                         } else if (countInsert === 0) {
       
  2982                             diffs.splice( pointer - countDelete,
       
  2983                                 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
       
  2984                         } else {
       
  2985                             diffs.splice( pointer - countDelete - countInsert,
       
  2986                                 countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
       
  2987                         }
       
  2988                         pointer = pointer - countDelete - countInsert +
       
  2989                             (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
       
  2990                     } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
       
  2991                         // Merge this equality with the previous one.
       
  2992                         diffs[pointer - 1][1] += diffs[pointer][1];
       
  2993                         diffs.splice(pointer, 1);
       
  2994                     } else {
       
  2995                         pointer++;
       
  2996                     }
       
  2997                     countInsert = 0;
       
  2998                     countDelete = 0;
       
  2999                     textDelete = "";
       
  3000                     textInsert = "";
       
  3001                     break;
       
  3002             }
       
  3003         }
       
  3004         if (diffs[diffs.length - 1][1] === "") {
       
  3005             diffs.pop(); // Remove the dummy entry at the end.
       
  3006         }
       
  3007 
       
  3008         // Second pass: look for single edits surrounded on both sides by equalities
       
  3009         // which can be shifted sideways to eliminate an equality.
       
  3010         // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
       
  3011         changes = false;
       
  3012         pointer = 1;
       
  3013         // Intentionally ignore the first and last element (don't need checking).
       
  3014         while (pointer < diffs.length - 1) {
       
  3015             if (diffs[pointer - 1][0] === DIFF_EQUAL &&
       
  3016                 diffs[pointer + 1][0] === DIFF_EQUAL) {
       
  3017                 // This is a single edit surrounded by equalities.
       
  3018                 if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
       
  3019                         diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
       
  3020                     // Shift the edit over the previous equality.
       
  3021                     diffs[pointer][1] = diffs[pointer - 1][1] +
       
  3022                         diffs[pointer][1].substring(0, diffs[pointer][1].length -
       
  3023                             diffs[pointer - 1][1].length);
       
  3024                     diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
       
  3025                     diffs.splice(pointer - 1, 1);
       
  3026                     changes = true;
       
  3027                 } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
       
  3028                     diffs[ pointer + 1 ][ 1 ] ) {
       
  3029                     // Shift the edit over the next equality.
       
  3030                     diffs[pointer - 1][1] += diffs[pointer + 1][1];
       
  3031                     diffs[pointer][1] =
       
  3032                         diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
       
  3033                         diffs[pointer + 1][1];
       
  3034                     diffs.splice(pointer + 1, 1);
       
  3035                     changes = true;
       
  3036                 }
       
  3037             }
       
  3038             pointer++;
       
  3039         }
       
  3040         // If shifts were made, the diff needs reordering and another shift sweep.
       
  3041         if (changes) {
       
  3042             this.diffCleanupMerge(diffs);
       
  3043         }
       
  3044     };
       
  3045 
       
  3046     return function(o, n) {
       
  3047 		var diff, output, text;
       
  3048         diff = new DiffMatchPatch();
       
  3049         output = diff.DiffMain(o, n);
       
  3050         //console.log(output);
       
  3051         diff.diffCleanupEfficiency(output);
       
  3052         text = diff.diffPrettyHtml(output);
       
  3053 
       
  3054         return text;
       
  3055     };
       
  3056 }());
       
  3057 // jscs:enable
       
  3058 
       
  3059 (function() {
       
  3060 
       
  3061 // Deprecated QUnit.init - Ref #530
       
  3062 // Re-initialize the configuration options
       
  3063 QUnit.init = function() {
       
  3064 	var tests, banner, result, qunit,
       
  3065 		config = QUnit.config;
       
  3066 
       
  3067 	config.stats = { all: 0, bad: 0 };
       
  3068 	config.moduleStats = { all: 0, bad: 0 };
       
  3069 	config.started = 0;
       
  3070 	config.updateRate = 1000;
       
  3071 	config.blocking = false;
       
  3072 	config.autostart = true;
       
  3073 	config.autorun = false;
       
  3074 	config.filter = "";
       
  3075 	config.queue = [];
       
  3076 
       
  3077 	// Return on non-browser environments
       
  3078 	// This is necessary to not break on node tests
       
  3079 	if ( typeof window === "undefined" ) {
       
  3080 		return;
       
  3081 	}
       
  3082 
       
  3083 	qunit = id( "qunit" );
       
  3084 	if ( qunit ) {
       
  3085 		qunit.innerHTML =
       
  3086 			"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
       
  3087 			"<h2 id='qunit-banner'></h2>" +
       
  3088 			"<div id='qunit-testrunner-toolbar'></div>" +
       
  3089 			"<h2 id='qunit-userAgent'></h2>" +
       
  3090 			"<ol id='qunit-tests'></ol>";
       
  3091 	}
       
  3092 
       
  3093 	tests = id( "qunit-tests" );
       
  3094 	banner = id( "qunit-banner" );
       
  3095 	result = id( "qunit-testresult" );
       
  3096 
       
  3097 	if ( tests ) {
       
  3098 		tests.innerHTML = "";
       
  3099 	}
       
  3100 
       
  3101 	if ( banner ) {
       
  3102 		banner.className = "";
       
  3103 	}
       
  3104 
       
  3105 	if ( result ) {
       
  3106 		result.parentNode.removeChild( result );
       
  3107 	}
       
  3108 
       
  3109 	if ( tests ) {
       
  3110 		result = document.createElement( "p" );
       
  3111 		result.id = "qunit-testresult";
       
  3112 		result.className = "result";
       
  3113 		tests.parentNode.insertBefore( result, tests );
       
  3114 		result.innerHTML = "Running...<br />&#160;";
       
  3115 	}
       
  3116 };
       
  3117 
       
  3118 // Don't load the HTML Reporter on non-Browser environments
       
  3119 if ( typeof window === "undefined" ) {
       
  3120 	return;
       
  3121 }
       
  3122 
       
  3123 var config = QUnit.config,
       
  3124 	hasOwn = Object.prototype.hasOwnProperty,
       
  3125 	defined = {
       
  3126 		document: window.document !== undefined,
       
  3127 		sessionStorage: (function() {
       
  3128 			var x = "qunit-test-string";
       
  3129 			try {
       
  3130 				sessionStorage.setItem( x, x );
       
  3131 				sessionStorage.removeItem( x );
       
  3132 				return true;
       
  3133 			} catch ( e ) {
       
  3134 				return false;
       
  3135 			}
       
  3136 		}())
       
  3137 	},
       
  3138 	modulesList = [];
       
  3139 
       
  3140 /**
       
  3141 * Escape text for attribute or text content.
       
  3142 */
       
  3143 function escapeText( s ) {
       
  3144 	if ( !s ) {
       
  3145 		return "";
       
  3146 	}
       
  3147 	s = s + "";
       
  3148 
       
  3149 	// Both single quotes and double quotes (for attributes)
       
  3150 	return s.replace( /['"<>&]/g, function( s ) {
       
  3151 		switch ( s ) {
       
  3152 		case "'":
       
  3153 			return "&#039;";
       
  3154 		case "\"":
       
  3155 			return "&quot;";
       
  3156 		case "<":
       
  3157 			return "&lt;";
       
  3158 		case ">":
       
  3159 			return "&gt;";
       
  3160 		case "&":
       
  3161 			return "&amp;";
       
  3162 		}
       
  3163 	});
       
  3164 }
       
  3165 
       
  3166 /**
       
  3167  * @param {HTMLElement} elem
       
  3168  * @param {string} type
       
  3169  * @param {Function} fn
       
  3170  */
       
  3171 function addEvent( elem, type, fn ) {
       
  3172 	if ( elem.addEventListener ) {
       
  3173 
       
  3174 		// Standards-based browsers
       
  3175 		elem.addEventListener( type, fn, false );
       
  3176 	} else if ( elem.attachEvent ) {
       
  3177 
       
  3178 		// support: IE <9
       
  3179 		elem.attachEvent( "on" + type, function() {
       
  3180 			var event = window.event;
       
  3181 			if ( !event.target ) {
       
  3182 				event.target = event.srcElement || document;
       
  3183 			}
       
  3184 
       
  3185 			fn.call( elem, event );
       
  3186 		});
       
  3187 	}
       
  3188 }
       
  3189 
       
  3190 /**
       
  3191  * @param {Array|NodeList} elems
       
  3192  * @param {string} type
       
  3193  * @param {Function} fn
       
  3194  */
       
  3195 function addEvents( elems, type, fn ) {
       
  3196 	var i = elems.length;
       
  3197 	while ( i-- ) {
       
  3198 		addEvent( elems[ i ], type, fn );
       
  3199 	}
       
  3200 }
       
  3201 
       
  3202 function hasClass( elem, name ) {
       
  3203 	return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
       
  3204 }
       
  3205 
       
  3206 function addClass( elem, name ) {
       
  3207 	if ( !hasClass( elem, name ) ) {
       
  3208 		elem.className += ( elem.className ? " " : "" ) + name;
       
  3209 	}
       
  3210 }
       
  3211 
       
  3212 function toggleClass( elem, name ) {
       
  3213 	if ( hasClass( elem, name ) ) {
       
  3214 		removeClass( elem, name );
       
  3215 	} else {
       
  3216 		addClass( elem, name );
       
  3217 	}
       
  3218 }
       
  3219 
       
  3220 function removeClass( elem, name ) {
       
  3221 	var set = " " + elem.className + " ";
       
  3222 
       
  3223 	// Class name may appear multiple times
       
  3224 	while ( set.indexOf( " " + name + " " ) >= 0 ) {
       
  3225 		set = set.replace( " " + name + " ", " " );
       
  3226 	}
       
  3227 
       
  3228 	// trim for prettiness
       
  3229 	elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
       
  3230 }
       
  3231 
       
  3232 function id( name ) {
       
  3233 	return defined.document && document.getElementById && document.getElementById( name );
       
  3234 }
       
  3235 
       
  3236 function getUrlConfigHtml() {
       
  3237 	var i, j, val,
       
  3238 		escaped, escapedTooltip,
       
  3239 		selection = false,
       
  3240 		len = config.urlConfig.length,
       
  3241 		urlConfigHtml = "";
       
  3242 
       
  3243 	for ( i = 0; i < len; i++ ) {
       
  3244 		val = config.urlConfig[ i ];
       
  3245 		if ( typeof val === "string" ) {
       
  3246 			val = {
       
  3247 				id: val,
       
  3248 				label: val
       
  3249 			};
       
  3250 		}
       
  3251 
       
  3252 		escaped = escapeText( val.id );
       
  3253 		escapedTooltip = escapeText( val.tooltip );
       
  3254 
       
  3255 		if ( config[ val.id ] === undefined ) {
       
  3256 			config[ val.id ] = QUnit.urlParams[ val.id ];
       
  3257 		}
       
  3258 
       
  3259 		if ( !val.value || typeof val.value === "string" ) {
       
  3260 			urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
       
  3261 				"' name='" + escaped + "' type='checkbox'" +
       
  3262 				( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
       
  3263 				( config[ val.id ] ? " checked='checked'" : "" ) +
       
  3264 				" title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
       
  3265 				"' title='" + escapedTooltip + "'>" + val.label + "</label>";
       
  3266 		} else {
       
  3267 			urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
       
  3268 				"' title='" + escapedTooltip + "'>" + val.label +
       
  3269 				": </label><select id='qunit-urlconfig-" + escaped +
       
  3270 				"' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
       
  3271 
       
  3272 			if ( QUnit.is( "array", val.value ) ) {
       
  3273 				for ( j = 0; j < val.value.length; j++ ) {
       
  3274 					escaped = escapeText( val.value[ j ] );
       
  3275 					urlConfigHtml += "<option value='" + escaped + "'" +
       
  3276 						( config[ val.id ] === val.value[ j ] ?
       
  3277 							( selection = true ) && " selected='selected'" : "" ) +
       
  3278 						">" + escaped + "</option>";
       
  3279 				}
       
  3280 			} else {
       
  3281 				for ( j in val.value ) {
       
  3282 					if ( hasOwn.call( val.value, j ) ) {
       
  3283 						urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
       
  3284 							( config[ val.id ] === j ?
       
  3285 								( selection = true ) && " selected='selected'" : "" ) +
       
  3286 							">" + escapeText( val.value[ j ] ) + "</option>";
       
  3287 					}
       
  3288 				}
       
  3289 			}
       
  3290 			if ( config[ val.id ] && !selection ) {
       
  3291 				escaped = escapeText( config[ val.id ] );
       
  3292 				urlConfigHtml += "<option value='" + escaped +
       
  3293 					"' selected='selected' disabled='disabled'>" + escaped + "</option>";
       
  3294 			}
       
  3295 			urlConfigHtml += "</select>";
       
  3296 		}
       
  3297 	}
       
  3298 
       
  3299 	return urlConfigHtml;
       
  3300 }
       
  3301 
       
  3302 // Handle "click" events on toolbar checkboxes and "change" for select menus.
       
  3303 // Updates the URL with the new state of `config.urlConfig` values.
       
  3304 function toolbarChanged() {
       
  3305 	var updatedUrl, value,
       
  3306 		field = this,
       
  3307 		params = {};
       
  3308 
       
  3309 	// Detect if field is a select menu or a checkbox
       
  3310 	if ( "selectedIndex" in field ) {
       
  3311 		value = field.options[ field.selectedIndex ].value || undefined;
       
  3312 	} else {
       
  3313 		value = field.checked ? ( field.defaultValue || true ) : undefined;
       
  3314 	}
       
  3315 
       
  3316 	params[ field.name ] = value;
       
  3317 	updatedUrl = setUrl( params );
       
  3318 
       
  3319 	if ( "hidepassed" === field.name && "replaceState" in window.history ) {
       
  3320 		config[ field.name ] = value || false;
       
  3321 		if ( value ) {
       
  3322 			addClass( id( "qunit-tests" ), "hidepass" );
       
  3323 		} else {
       
  3324 			removeClass( id( "qunit-tests" ), "hidepass" );
       
  3325 		}
       
  3326 
       
  3327 		// It is not necessary to refresh the whole page
       
  3328 		window.history.replaceState( null, "", updatedUrl );
       
  3329 	} else {
       
  3330 		window.location = updatedUrl;
       
  3331 	}
       
  3332 }
       
  3333 
       
  3334 function setUrl( params ) {
       
  3335 	var key,
       
  3336 		querystring = "?";
       
  3337 
       
  3338 	params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
       
  3339 
       
  3340 	for ( key in params ) {
       
  3341 		if ( hasOwn.call( params, key ) ) {
       
  3342 			if ( params[ key ] === undefined ) {
       
  3343 				continue;
       
  3344 			}
       
  3345 			querystring += encodeURIComponent( key );
       
  3346 			if ( params[ key ] !== true ) {
       
  3347 				querystring += "=" + encodeURIComponent( params[ key ] );
       
  3348 			}
       
  3349 			querystring += "&";
       
  3350 		}
       
  3351 	}
       
  3352 	return location.protocol + "//" + location.host +
       
  3353 		location.pathname + querystring.slice( 0, -1 );
       
  3354 }
       
  3355 
       
  3356 function applyUrlParams() {
       
  3357 	var selectedModule,
       
  3358 		modulesList = id( "qunit-modulefilter" ),
       
  3359 		filter = id( "qunit-filter-input" ).value;
       
  3360 
       
  3361 	selectedModule = modulesList ?
       
  3362 		decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
       
  3363 		undefined;
       
  3364 
       
  3365 	window.location = setUrl({
       
  3366 		module: ( selectedModule === "" ) ? undefined : selectedModule,
       
  3367 		filter: ( filter === "" ) ? undefined : filter,
       
  3368 
       
  3369 		// Remove testId filter
       
  3370 		testId: undefined
       
  3371 	});
       
  3372 }
       
  3373 
       
  3374 function toolbarUrlConfigContainer() {
       
  3375 	var urlConfigContainer = document.createElement( "span" );
       
  3376 
       
  3377 	urlConfigContainer.innerHTML = getUrlConfigHtml();
       
  3378 	addClass( urlConfigContainer, "qunit-url-config" );
       
  3379 
       
  3380 	// For oldIE support:
       
  3381 	// * Add handlers to the individual elements instead of the container
       
  3382 	// * Use "click" instead of "change" for checkboxes
       
  3383 	addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
       
  3384 	addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
       
  3385 
       
  3386 	return urlConfigContainer;
       
  3387 }
       
  3388 
       
  3389 function toolbarLooseFilter() {
       
  3390 	var filter = document.createElement( "form" ),
       
  3391 		label = document.createElement( "label" ),
       
  3392 		input = document.createElement( "input" ),
       
  3393 		button = document.createElement( "button" );
       
  3394 
       
  3395 	addClass( filter, "qunit-filter" );
       
  3396 
       
  3397 	label.innerHTML = "Filter: ";
       
  3398 
       
  3399 	input.type = "text";
       
  3400 	input.value = config.filter || "";
       
  3401 	input.name = "filter";
       
  3402 	input.id = "qunit-filter-input";
       
  3403 
       
  3404 	button.innerHTML = "Go";
       
  3405 
       
  3406 	label.appendChild( input );
       
  3407 
       
  3408 	filter.appendChild( label );
       
  3409 	filter.appendChild( button );
       
  3410 	addEvent( filter, "submit", function( ev ) {
       
  3411 		applyUrlParams();
       
  3412 
       
  3413 		if ( ev && ev.preventDefault ) {
       
  3414 			ev.preventDefault();
       
  3415 		}
       
  3416 
       
  3417 		return false;
       
  3418 	});
       
  3419 
       
  3420 	return filter;
       
  3421 }
       
  3422 
       
  3423 function toolbarModuleFilterHtml() {
       
  3424 	var i,
       
  3425 		moduleFilterHtml = "";
       
  3426 
       
  3427 	if ( !modulesList.length ) {
       
  3428 		return false;
       
  3429 	}
       
  3430 
       
  3431 	modulesList.sort(function( a, b ) {
       
  3432 		return a.localeCompare( b );
       
  3433 	});
       
  3434 
       
  3435 	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
       
  3436 		"<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
       
  3437 		( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
       
  3438 		">< All Modules ></option>";
       
  3439 
       
  3440 	for ( i = 0; i < modulesList.length; i++ ) {
       
  3441 		moduleFilterHtml += "<option value='" +
       
  3442 			escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
       
  3443 			( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
       
  3444 			">" + escapeText( modulesList[ i ] ) + "</option>";
       
  3445 	}
       
  3446 	moduleFilterHtml += "</select>";
       
  3447 
       
  3448 	return moduleFilterHtml;
       
  3449 }
       
  3450 
       
  3451 function toolbarModuleFilter() {
       
  3452 	var toolbar = id( "qunit-testrunner-toolbar" ),
       
  3453 		moduleFilter = document.createElement( "span" ),
       
  3454 		moduleFilterHtml = toolbarModuleFilterHtml();
       
  3455 
       
  3456 	if ( !toolbar || !moduleFilterHtml ) {
       
  3457 		return false;
       
  3458 	}
       
  3459 
       
  3460 	moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
       
  3461 	moduleFilter.innerHTML = moduleFilterHtml;
       
  3462 
       
  3463 	addEvent( moduleFilter.lastChild, "change", applyUrlParams );
       
  3464 
       
  3465 	toolbar.appendChild( moduleFilter );
       
  3466 }
       
  3467 
       
  3468 function appendToolbar() {
       
  3469 	var toolbar = id( "qunit-testrunner-toolbar" );
       
  3470 
       
  3471 	if ( toolbar ) {
       
  3472 		toolbar.appendChild( toolbarUrlConfigContainer() );
       
  3473 		toolbar.appendChild( toolbarLooseFilter() );
       
  3474 	}
       
  3475 }
       
  3476 
       
  3477 function appendHeader() {
       
  3478 	var header = id( "qunit-header" );
       
  3479 
       
  3480 	if ( header ) {
       
  3481 		header.innerHTML = "<a href='" +
       
  3482 			setUrl({ filter: undefined, module: undefined, testId: undefined }) +
       
  3483 			"'>" + header.innerHTML + "</a> ";
       
  3484 	}
       
  3485 }
       
  3486 
       
  3487 function appendBanner() {
       
  3488 	var banner = id( "qunit-banner" );
       
  3489 
       
  3490 	if ( banner ) {
       
  3491 		banner.className = "";
       
  3492 	}
       
  3493 }
       
  3494 
       
  3495 function appendTestResults() {
       
  3496 	var tests = id( "qunit-tests" ),
       
  3497 		result = id( "qunit-testresult" );
       
  3498 
       
  3499 	if ( result ) {
       
  3500 		result.parentNode.removeChild( result );
       
  3501 	}
       
  3502 
       
  3503 	if ( tests ) {
       
  3504 		tests.innerHTML = "";
       
  3505 		result = document.createElement( "p" );
       
  3506 		result.id = "qunit-testresult";
       
  3507 		result.className = "result";
       
  3508 		tests.parentNode.insertBefore( result, tests );
       
  3509 		result.innerHTML = "Running...<br />&#160;";
       
  3510 	}
       
  3511 }
       
  3512 
       
  3513 function storeFixture() {
       
  3514 	var fixture = id( "qunit-fixture" );
       
  3515 	if ( fixture ) {
       
  3516 		config.fixture = fixture.innerHTML;
       
  3517 	}
       
  3518 }
       
  3519 
       
  3520 function appendUserAgent() {
       
  3521 	var userAgent = id( "qunit-userAgent" );
       
  3522 
       
  3523 	if ( userAgent ) {
       
  3524 		userAgent.innerHTML = "";
       
  3525 		userAgent.appendChild(
       
  3526 			document.createTextNode(
       
  3527 				"QUnit " + QUnit.version  + "; " + navigator.userAgent
       
  3528 			)
       
  3529 		);
       
  3530 	}
       
  3531 }
       
  3532 
       
  3533 function appendTestsList( modules ) {
       
  3534 	var i, l, x, z, test, moduleObj;
       
  3535 
       
  3536 	for ( i = 0, l = modules.length; i < l; i++ ) {
       
  3537 		moduleObj = modules[ i ];
       
  3538 
       
  3539 		if ( moduleObj.name ) {
       
  3540 			modulesList.push( moduleObj.name );
       
  3541 		}
       
  3542 
       
  3543 		for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
       
  3544 			test = moduleObj.tests[ x ];
       
  3545 
       
  3546 			appendTest( test.name, test.testId, moduleObj.name );
       
  3547 		}
       
  3548 	}
       
  3549 }
       
  3550 
       
  3551 function appendTest( name, testId, moduleName ) {
       
  3552 	var title, rerunTrigger, testBlock, assertList,
       
  3553 		tests = id( "qunit-tests" );
       
  3554 
       
  3555 	if ( !tests ) {
       
  3556 		return;
       
  3557 	}
       
  3558 
       
  3559 	title = document.createElement( "strong" );
       
  3560 	title.innerHTML = getNameHtml( name, moduleName );
       
  3561 
       
  3562 	rerunTrigger = document.createElement( "a" );
       
  3563 	rerunTrigger.innerHTML = "Rerun";
       
  3564 	rerunTrigger.href = setUrl({ testId: testId });
       
  3565 
       
  3566 	testBlock = document.createElement( "li" );
       
  3567 	testBlock.appendChild( title );
       
  3568 	testBlock.appendChild( rerunTrigger );
       
  3569 	testBlock.id = "qunit-test-output-" + testId;
       
  3570 
       
  3571 	assertList = document.createElement( "ol" );
       
  3572 	assertList.className = "qunit-assert-list";
       
  3573 
       
  3574 	testBlock.appendChild( assertList );
       
  3575 
       
  3576 	tests.appendChild( testBlock );
       
  3577 }
       
  3578 
       
  3579 // HTML Reporter initialization and load
       
  3580 QUnit.begin(function( details ) {
       
  3581 	var qunit = id( "qunit" );
       
  3582 
       
  3583 	// Fixture is the only one necessary to run without the #qunit element
       
  3584 	storeFixture();
       
  3585 
       
  3586 	if ( qunit ) {
       
  3587 		qunit.innerHTML =
       
  3588 			"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
       
  3589 			"<h2 id='qunit-banner'></h2>" +
       
  3590 			"<div id='qunit-testrunner-toolbar'></div>" +
       
  3591 			"<h2 id='qunit-userAgent'></h2>" +
       
  3592 			"<ol id='qunit-tests'></ol>";
       
  3593 	}
       
  3594 
       
  3595 	appendHeader();
       
  3596 	appendBanner();
       
  3597 	appendTestResults();
       
  3598 	appendUserAgent();
       
  3599 	appendToolbar();
       
  3600 	appendTestsList( details.modules );
       
  3601 	toolbarModuleFilter();
       
  3602 
       
  3603 	if ( qunit && config.hidepassed ) {
       
  3604 		addClass( qunit.lastChild, "hidepass" );
       
  3605 	}
       
  3606 });
       
  3607 
       
  3608 QUnit.done(function( details ) {
       
  3609 	var i, key,
       
  3610 		banner = id( "qunit-banner" ),
       
  3611 		tests = id( "qunit-tests" ),
       
  3612 		html = [
       
  3613 			"Tests completed in ",
       
  3614 			details.runtime,
       
  3615 			" milliseconds.<br />",
       
  3616 			"<span class='passed'>",
       
  3617 			details.passed,
       
  3618 			"</span> assertions of <span class='total'>",
       
  3619 			details.total,
       
  3620 			"</span> passed, <span class='failed'>",
       
  3621 			details.failed,
       
  3622 			"</span> failed."
       
  3623 		].join( "" );
       
  3624 
       
  3625 	if ( banner ) {
       
  3626 		banner.className = details.failed ? "qunit-fail" : "qunit-pass";
       
  3627 	}
       
  3628 
       
  3629 	if ( tests ) {
       
  3630 		id( "qunit-testresult" ).innerHTML = html;
       
  3631 	}
       
  3632 
       
  3633 	if ( config.altertitle && defined.document && document.title ) {
       
  3634 
       
  3635 		// show ✖ for good, ✔ for bad suite result in title
       
  3636 		// use escape sequences in case file gets loaded with non-utf-8-charset
       
  3637 		document.title = [
       
  3638 			( details.failed ? "\u2716" : "\u2714" ),
       
  3639 			document.title.replace( /^[\u2714\u2716] /i, "" )
       
  3640 		].join( " " );
       
  3641 	}
       
  3642 
       
  3643 	// clear own sessionStorage items if all tests passed
       
  3644 	if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
       
  3645 		for ( i = 0; i < sessionStorage.length; i++ ) {
       
  3646 			key = sessionStorage.key( i++ );
       
  3647 			if ( key.indexOf( "qunit-test-" ) === 0 ) {
       
  3648 				sessionStorage.removeItem( key );
       
  3649 			}
       
  3650 		}
       
  3651 	}
       
  3652 
       
  3653 	// scroll back to top to show results
       
  3654 	if ( config.scrolltop && window.scrollTo ) {
       
  3655 		window.scrollTo( 0, 0 );
       
  3656 	}
       
  3657 });
       
  3658 
       
  3659 function getNameHtml( name, module ) {
       
  3660 	var nameHtml = "";
       
  3661 
       
  3662 	if ( module ) {
       
  3663 		nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
       
  3664 	}
       
  3665 
       
  3666 	nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
       
  3667 
       
  3668 	return nameHtml;
       
  3669 }
       
  3670 
       
  3671 QUnit.testStart(function( details ) {
       
  3672 	var running, testBlock, bad;
       
  3673 
       
  3674 	testBlock = id( "qunit-test-output-" + details.testId );
       
  3675 	if ( testBlock ) {
       
  3676 		testBlock.className = "running";
       
  3677 	} else {
       
  3678 
       
  3679 		// Report later registered tests
       
  3680 		appendTest( details.name, details.testId, details.module );
       
  3681 	}
       
  3682 
       
  3683 	running = id( "qunit-testresult" );
       
  3684 	if ( running ) {
       
  3685 		bad = QUnit.config.reorder && defined.sessionStorage &&
       
  3686 			+sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
       
  3687 
       
  3688 		running.innerHTML = ( bad ?
       
  3689 			"Rerunning previously failed test: <br />" :
       
  3690 			"Running: <br />" ) +
       
  3691 			getNameHtml( details.name, details.module );
       
  3692 	}
       
  3693 
       
  3694 });
       
  3695 
       
  3696 QUnit.log(function( details ) {
       
  3697 	var assertList, assertLi,
       
  3698 		message, expected, actual,
       
  3699 		testItem = id( "qunit-test-output-" + details.testId );
       
  3700 
       
  3701 	if ( !testItem ) {
       
  3702 		return;
       
  3703 	}
       
  3704 
       
  3705 	message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
       
  3706 	message = "<span class='test-message'>" + message + "</span>";
       
  3707 	message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
       
  3708 
       
  3709 	// pushFailure doesn't provide details.expected
       
  3710 	// when it calls, it's implicit to also not show expected and diff stuff
       
  3711 	// Also, we need to check details.expected existence, as it can exist and be undefined
       
  3712 	if ( !details.result && hasOwn.call( details, "expected" ) ) {
       
  3713 		expected = escapeText( QUnit.dump.parse( details.expected ) );
       
  3714 		actual = escapeText( QUnit.dump.parse( details.actual ) );
       
  3715 		message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
       
  3716 			expected +
       
  3717 			"</pre></td></tr>";
       
  3718 
       
  3719 		if ( actual !== expected ) {
       
  3720 			message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
       
  3721 				actual + "</pre></td></tr>" +
       
  3722 				"<tr class='test-diff'><th>Diff: </th><td><pre>" +
       
  3723 				QUnit.diff( expected, actual ) + "</pre></td></tr>";
       
  3724 		} else {
       
  3725 			if ( expected.indexOf( "[object Array]" ) !== -1 ||
       
  3726 					expected.indexOf( "[object Object]" ) !== -1 ) {
       
  3727 				message += "<tr class='test-message'><th>Message: </th><td>" +
       
  3728 					"Diff suppressed as the depth of object is more than current max depth (" +
       
  3729 					QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
       
  3730 					" run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
       
  3731 					"Rerun</a> without max depth.</p></td></tr>";
       
  3732 			}
       
  3733 		}
       
  3734 
       
  3735 		if ( details.source ) {
       
  3736 			message += "<tr class='test-source'><th>Source: </th><td><pre>" +
       
  3737 				escapeText( details.source ) + "</pre></td></tr>";
       
  3738 		}
       
  3739 
       
  3740 		message += "</table>";
       
  3741 
       
  3742 	// this occours when pushFailure is set and we have an extracted stack trace
       
  3743 	} else if ( !details.result && details.source ) {
       
  3744 		message += "<table>" +
       
  3745 			"<tr class='test-source'><th>Source: </th><td><pre>" +
       
  3746 			escapeText( details.source ) + "</pre></td></tr>" +
       
  3747 			"</table>";
       
  3748 	}
       
  3749 
       
  3750 	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
       
  3751 
       
  3752 	assertLi = document.createElement( "li" );
       
  3753 	assertLi.className = details.result ? "pass" : "fail";
       
  3754 	assertLi.innerHTML = message;
       
  3755 	assertList.appendChild( assertLi );
       
  3756 });
       
  3757 
       
  3758 QUnit.testDone(function( details ) {
       
  3759 	var testTitle, time, testItem, assertList,
       
  3760 		good, bad, testCounts, skipped,
       
  3761 		tests = id( "qunit-tests" );
       
  3762 
       
  3763 	if ( !tests ) {
       
  3764 		return;
       
  3765 	}
       
  3766 
       
  3767 	testItem = id( "qunit-test-output-" + details.testId );
       
  3768 
       
  3769 	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
       
  3770 
       
  3771 	good = details.passed;
       
  3772 	bad = details.failed;
       
  3773 
       
  3774 	// store result when possible
       
  3775 	if ( config.reorder && defined.sessionStorage ) {
       
  3776 		if ( bad ) {
       
  3777 			sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
       
  3778 		} else {
       
  3779 			sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
       
  3780 		}
       
  3781 	}
       
  3782 
       
  3783 	if ( bad === 0 ) {
       
  3784 		addClass( assertList, "qunit-collapsed" );
       
  3785 	}
       
  3786 
       
  3787 	// testItem.firstChild is the test name
       
  3788 	testTitle = testItem.firstChild;
       
  3789 
       
  3790 	testCounts = bad ?
       
  3791 		"<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
       
  3792 		"";
       
  3793 
       
  3794 	testTitle.innerHTML += " <b class='counts'>(" + testCounts +
       
  3795 		details.assertions.length + ")</b>";
       
  3796 
       
  3797 	if ( details.skipped ) {
       
  3798 		testItem.className = "skipped";
       
  3799 		skipped = document.createElement( "em" );
       
  3800 		skipped.className = "qunit-skipped-label";
       
  3801 		skipped.innerHTML = "skipped";
       
  3802 		testItem.insertBefore( skipped, testTitle );
       
  3803 	} else {
       
  3804 		addEvent( testTitle, "click", function() {
       
  3805 			toggleClass( assertList, "qunit-collapsed" );
       
  3806 		});
       
  3807 
       
  3808 		testItem.className = bad ? "fail" : "pass";
       
  3809 
       
  3810 		time = document.createElement( "span" );
       
  3811 		time.className = "runtime";
       
  3812 		time.innerHTML = details.runtime + " ms";
       
  3813 		testItem.insertBefore( time, assertList );
       
  3814 	}
       
  3815 });
       
  3816 
       
  3817 if ( defined.document ) {
       
  3818 	if ( document.readyState === "complete" ) {
       
  3819 		QUnit.load();
       
  3820 	} else {
       
  3821 		addEvent( window, "load", QUnit.load );
       
  3822 	}
       
  3823 } else {
       
  3824 	config.pageLoaded = true;
       
  3825 	config.autorun = true;
       
  3826 }
       
  3827 
       
  3828 })();