2014-01-16 22:33:09 +01:00
/ * !
2017-03-20 06:07:18 +01:00
* QUnit 2.2 . 0
2016-10-06 15:21:05 +02:00
* https : //qunitjs.com/
2011-09-10 07:47:49 +02:00
*
2015-01-19 18:05:24 +01:00
* Copyright jQuery Foundation and other contributors
2014-01-16 22:33:09 +01:00
* Released under the MIT license
2016-10-06 15:21:05 +02:00
* https : //jquery.org/license
2011-09-10 07:47:49 +02:00
*
2017-03-20 06:07:18 +01:00
* Date : 2017 - 03 - 11 T16 : 19 Z
2011-09-10 07:47:49 +02:00
* /
2017-02-05 13:39:16 +01:00
( function ( global$1 ) {
'use strict' ;
2011-09-10 07:47:49 +02:00
2017-02-05 13:39:16 +01:00
global$1 = 'default' in global$1 ? global$1 [ 'default' ] : global$1 ;
2016-10-06 15:21:05 +02:00
2017-02-05 13:39:16 +01:00
var window = global$1 . window ;
var console = global$1 . console ;
var setTimeout = global$1 . setTimeout ;
var clearTimeout = global$1 . clearTimeout ;
2011-09-10 07:47:49 +02:00
2017-02-05 13:39:16 +01:00
var document = window && window . document ;
var navigator = window && window . navigator ;
2017-03-20 06:07:18 +01:00
var sessionStorage = function ( ) {
var x = "qunit-test-string" ;
try {
sessionStorage . setItem ( x , x ) ;
sessionStorage . removeItem ( x ) ;
return sessionStorage ;
} catch ( e ) {
return undefined ;
}
} ( ) ;
2011-09-10 07:47:49 +02:00
2017-02-05 13:39:16 +01:00
var _typeof = typeof Symbol === "function" && typeof Symbol . iterator === "symbol" ? function ( obj ) {
return typeof obj ;
} : function ( obj ) {
return obj && typeof Symbol === "function" && obj . constructor === Symbol && obj !== Symbol . prototype ? "symbol" : typeof obj ;
} ;
var classCallCheck = function ( instance , Constructor ) {
if ( ! ( instance instanceof Constructor ) ) {
throw new TypeError ( "Cannot call a class as a function" ) ;
}
} ;
var createClass = function ( ) {
function defineProperties ( target , props ) {
for ( var i = 0 ; i < props . length ; i ++ ) {
var descriptor = props [ i ] ;
descriptor . enumerable = descriptor . enumerable || false ;
descriptor . configurable = true ;
if ( "value" in descriptor ) descriptor . writable = true ;
Object . defineProperty ( target , descriptor . key , descriptor ) ;
}
}
return function ( Constructor , protoProps , staticProps ) {
if ( protoProps ) defineProperties ( Constructor . prototype , protoProps ) ;
if ( staticProps ) defineProperties ( Constructor , staticProps ) ;
return Constructor ;
} ;
} ( ) ;
var toConsumableArray = function ( arr ) {
if ( Array . isArray ( arr ) ) {
for ( var i = 0 , arr2 = Array ( arr . length ) ; i < arr . length ; i ++ ) arr2 [ i ] = arr [ i ] ;
return arr2 ;
} else {
return Array . from ( arr ) ;
}
} ;
var toString = Object . prototype . toString ;
var hasOwn = Object . prototype . hasOwnProperty ;
var now = Date . now || function ( ) {
return new Date ( ) . getTime ( ) ;
} ;
var defined = {
document : window && window . document !== undefined ,
setTimeout : setTimeout !== undefined
} ;
// Returns a new Array with the elements that are in a but not in b
function diff ( a , b ) {
var i ,
j ,
result = a . slice ( ) ;
for ( i = 0 ; i < result . length ; i ++ ) {
for ( j = 0 ; j < b . length ; j ++ ) {
if ( result [ i ] === b [ j ] ) {
result . splice ( i , 1 ) ;
i -- ;
break ;
}
}
}
return result ;
}
2017-03-20 06:07:18 +01:00
/ * *
* Determines whether an element exists in a given array or not .
*
* @ method inArray
* @ param { Any } elem
* @ param { Array } array
* @ return { Boolean }
* /
2017-02-05 13:39:16 +01:00
function inArray ( elem , array ) {
2017-03-20 06:07:18 +01:00
return array . indexOf ( elem ) !== - 1 ;
2017-02-05 13:39:16 +01:00
}
/ * *
* Makes a clone of an object using only Array or Object as base ,
* and copies over the own enumerable properties .
*
* @ param { Object } obj
* @ return { Object } New object with only the own properties ( recursively ) .
* /
function objectValues ( obj ) {
var key ,
val ,
vals = is ( "array" , obj ) ? [ ] : { } ;
for ( key in obj ) {
if ( hasOwn . call ( obj , key ) ) {
val = obj [ key ] ;
vals [ key ] = val === Object ( val ) ? objectValues ( val ) : val ;
}
}
return vals ;
}
function extend ( a , b , undefOnly ) {
for ( var prop in b ) {
if ( hasOwn . call ( b , prop ) ) {
if ( b [ prop ] === undefined ) {
delete a [ prop ] ;
} else if ( ! ( undefOnly && typeof a [ prop ] !== "undefined" ) ) {
a [ prop ] = b [ prop ] ;
}
}
}
return a ;
}
function objectType ( obj ) {
if ( typeof obj === "undefined" ) {
return "undefined" ;
}
// Consider: typeof null === object
if ( obj === null ) {
return "null" ;
}
var match = toString . call ( obj ) . match ( /^\[object\s(.*)\]$/ ) ,
type = match && match [ 1 ] ;
switch ( type ) {
case "Number" :
if ( isNaN ( obj ) ) {
return "nan" ;
}
return "number" ;
case "String" :
case "Boolean" :
case "Array" :
case "Set" :
case "Map" :
case "Date" :
case "RegExp" :
case "Function" :
case "Symbol" :
return type . toLowerCase ( ) ;
}
if ( ( typeof obj === "undefined" ? "undefined" : _typeof ( obj ) ) === "object" ) {
return "object" ;
}
}
// Safe object type checking
function is ( type , obj ) {
return objectType ( obj ) === type ;
}
// Test for equality any JavaScript type.
2017-03-20 06:07:18 +01:00
// Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
2017-02-05 13:39:16 +01:00
var equiv = ( function ( ) {
2017-03-20 06:07:18 +01:00
// Value pairs queued for comparison. Used for breadth-first processing order, recursion
// detection and avoiding repeated comparison (see below for details).
// Elements are { a: val, b: val }.
var pairs = [ ] ;
2017-02-05 13:39:16 +01:00
var getProto = Object . getPrototypeOf || function ( obj ) {
return obj . _ _proto _ _ ;
} ;
2017-03-20 06:07:18 +01:00
function useStrictEquality ( a , b ) {
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
// This only gets called if a and b are not strict equal, and is used to compare on
// the primitive values inside object wrappers. For example:
2017-02-05 13:39:16 +01:00
// `var i = 1;`
// `var j = new Number(1);`
2017-03-20 06:07:18 +01:00
// Neither a nor b can be null, as a !== b and they have the same type.
2017-02-05 13:39:16 +01:00
if ( ( typeof a === "undefined" ? "undefined" : _typeof ( a ) ) === "object" ) {
a = a . valueOf ( ) ;
}
if ( ( typeof b === "undefined" ? "undefined" : _typeof ( b ) ) === "object" ) {
b = b . valueOf ( ) ;
}
return a === b ;
}
function compareConstructors ( a , b ) {
var protoA = getProto ( a ) ;
var protoB = getProto ( b ) ;
// Comparing constructors is more strict than using `instanceof`
if ( a . constructor === b . constructor ) {
return true ;
}
// Ref #851
// If the obj prototype descends from a null constructor, treat it
// as a null prototype.
if ( protoA && protoA . constructor === null ) {
protoA = null ;
}
if ( protoB && protoB . constructor === null ) {
protoB = null ;
}
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if ( protoA === null && protoB === Object . prototype || protoB === null && protoA === Object . prototype ) {
return true ;
}
return false ;
}
function getRegExpFlags ( regexp ) {
return "flags" in regexp ? regexp . flags : regexp . toString ( ) . match ( /[gimuy]*$/ ) [ 0 ] ;
}
2017-03-20 06:07:18 +01:00
function isContainer ( val ) {
return [ "object" , "array" , "map" , "set" ] . indexOf ( objectType ( val ) ) !== - 1 ;
}
function breadthFirstCompareChild ( a , b ) {
// If a is a container not reference-equal to b, postpone the comparison to the
// end of the pairs queue -- unless (a, b) has been seen before, in which case skip
// over the pair.
if ( a === b ) {
return true ;
}
if ( ! isContainer ( a ) ) {
return typeEquiv ( a , b ) ;
}
if ( pairs . every ( function ( pair ) {
return pair . a !== a || pair . b !== b ;
} ) ) {
// Not yet started comparing this pair
pairs . push ( { a : a , b : b } ) ;
}
return true ;
}
2017-02-05 13:39:16 +01:00
var callbacks = {
"string" : useStrictEquality ,
"boolean" : useStrictEquality ,
"number" : useStrictEquality ,
"null" : useStrictEquality ,
"undefined" : useStrictEquality ,
"symbol" : useStrictEquality ,
"date" : useStrictEquality ,
"nan" : function nan ( ) {
return true ;
} ,
2017-03-20 06:07:18 +01:00
"regexp" : function regexp ( a , b ) {
2017-02-05 13:39:16 +01:00
return a . source === b . source &&
// Include flags in the comparison
getRegExpFlags ( a ) === getRegExpFlags ( b ) ;
} ,
2017-03-20 06:07:18 +01:00
// abort (identical references / instance methods were skipped earlier)
"function" : function _function ( ) {
return false ;
2017-02-05 13:39:16 +01:00
} ,
2017-03-20 06:07:18 +01:00
"array" : function array ( a , b ) {
var i , len ;
2017-02-05 13:39:16 +01:00
len = a . length ;
if ( len !== b . length ) {
// Safe and faster
return false ;
}
for ( i = 0 ; i < len ; i ++ ) {
2017-03-20 06:07:18 +01:00
// Compare non-containers; queue non-reference-equal containers
if ( ! breadthFirstCompareChild ( a [ i ] , b [ i ] ) ) {
2017-02-05 13:39:16 +01:00
return false ;
}
}
return true ;
} ,
2017-03-20 06:07:18 +01:00
// Define sets a and b to be equivalent if for each element aVal in a, there
// is some element bVal in b such that aVal and bVal are equivalent. Element
// repetitions are not counted, so these are equivalent:
// a = new Set( [ {}, [], [] ] );
// b = new Set( [ {}, {}, [] ] );
"set" : function set$$1 ( a , b ) {
2017-02-05 13:39:16 +01:00
var innerEq ,
outerEq = true ;
if ( a . size !== b . size ) {
2017-03-20 06:07:18 +01:00
// This optimization has certain quirks because of the lack of
// repetition counting. For instance, adding the same
// (reference-identical) element to two equivalent sets can
// make them non-equivalent.
2017-02-05 13:39:16 +01:00
return false ;
}
a . forEach ( function ( aVal ) {
2017-03-20 06:07:18 +01:00
// Short-circuit if the result is already known. (Using for...of
// with a break clause would be cleaner here, but it would cause
// a syntax error on older Javascript implementations even if
// Set is unused)
if ( ! outerEq ) {
return ;
}
2017-02-05 13:39:16 +01:00
innerEq = false ;
b . forEach ( function ( bVal ) {
2017-03-20 06:07:18 +01:00
var parentPairs ;
// Likewise, short-circuit if the result is already known
if ( innerEq ) {
return ;
}
// Swap out the global pairs list, as the nested call to
// innerEquiv will clobber its contents
parentPairs = pairs ;
2017-02-05 13:39:16 +01:00
if ( innerEquiv ( bVal , aVal ) ) {
innerEq = true ;
}
2017-03-20 06:07:18 +01:00
// Replace the global pairs list
pairs = parentPairs ;
2017-02-05 13:39:16 +01:00
} ) ;
if ( ! innerEq ) {
outerEq = false ;
}
} ) ;
return outerEq ;
} ,
2017-03-20 06:07:18 +01:00
// Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
// in a, there is some key-value pair (bKey, bVal) in b such that
// [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
// counted, so these are equivalent:
// a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
// b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
"map" : function map ( a , b ) {
2017-02-05 13:39:16 +01:00
var innerEq ,
outerEq = true ;
if ( a . size !== b . size ) {
2017-03-20 06:07:18 +01:00
// This optimization has certain quirks because of the lack of
// repetition counting. For instance, adding the same
// (reference-identical) key-value pair to two equivalent maps
// can make them non-equivalent.
2017-02-05 13:39:16 +01:00
return false ;
}
a . forEach ( function ( aVal , aKey ) {
2017-03-20 06:07:18 +01:00
// Short-circuit if the result is already known. (Using for...of
// with a break clause would be cleaner here, but it would cause
// a syntax error on older Javascript implementations even if
// Map is unused)
if ( ! outerEq ) {
return ;
}
2017-02-05 13:39:16 +01:00
innerEq = false ;
b . forEach ( function ( bVal , bKey ) {
2017-03-20 06:07:18 +01:00
var parentPairs ;
// Likewise, short-circuit if the result is already known
if ( innerEq ) {
return ;
}
// Swap out the global pairs list, as the nested call to
// innerEquiv will clobber its contents
parentPairs = pairs ;
2017-02-05 13:39:16 +01:00
if ( innerEquiv ( [ bVal , bKey ] , [ aVal , aKey ] ) ) {
innerEq = true ;
}
2017-03-20 06:07:18 +01:00
// Replace the global pairs list
pairs = parentPairs ;
2017-02-05 13:39:16 +01:00
} ) ;
if ( ! innerEq ) {
outerEq = false ;
}
} ) ;
return outerEq ;
} ,
2017-03-20 06:07:18 +01:00
"object" : function object ( a , b ) {
var i ,
aProperties = [ ] ,
bProperties = [ ] ;
2017-02-05 13:39:16 +01:00
if ( compareConstructors ( a , b ) === false ) {
return false ;
}
// Be strict: don't ensure hasOwnProperty and go deep
for ( i in a ) {
2017-03-20 06:07:18 +01:00
// Collect a's properties
2017-02-05 13:39:16 +01:00
aProperties . push ( i ) ;
2017-03-20 06:07:18 +01:00
// Skip OOP methods that look the same
if ( a . constructor !== Object && typeof a . constructor !== "undefined" && typeof a [ i ] === "function" && typeof b [ i ] === "function" && a [ i ] . toString ( ) === b [ i ] . toString ( ) ) {
continue ;
}
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
// Compare non-containers; queue non-reference-equal containers
if ( ! breadthFirstCompareChild ( a [ i ] , b [ i ] ) ) {
return false ;
}
}
2017-02-05 13:39:16 +01:00
for ( i in b ) {
// Collect b's properties
bProperties . push ( i ) ;
}
// Ensures identical properties name
2017-03-20 06:07:18 +01:00
return typeEquiv ( aProperties . sort ( ) , bProperties . sort ( ) ) ;
2017-02-05 13:39:16 +01:00
}
} ;
function typeEquiv ( a , b ) {
var type = objectType ( a ) ;
2017-03-20 06:07:18 +01:00
// Callbacks for containers will append to the pairs queue to achieve breadth-first
// search order. The pairs queue is also used to avoid reprocessing any pair of
// containers that are reference-equal to a previously visited pair (a special case
// this being recursion detection).
//
// Because of this approach, once typeEquiv returns a false value, it should not be
// called again without clearing the pair queue else it may wrongly report a visited
// pair as being equivalent.
return objectType ( b ) === type && callbacks [ type ] ( a , b ) ;
2017-02-05 13:39:16 +01:00
}
function innerEquiv ( a , b ) {
2017-03-20 06:07:18 +01:00
var i , pair ;
2017-02-05 13:39:16 +01:00
// We're done when there's nothing more to compare
if ( arguments . length < 2 ) {
return true ;
}
2017-03-20 06:07:18 +01:00
// Clear the global pair queue and add the top-level values being compared
pairs = [ { a : a , b : b } ] ;
for ( i = 0 ; i < pairs . length ; i ++ ) {
pair = pairs [ i ] ;
// Perform type-specific comparison on any pairs that are not strictly
// equal. For container types, that comparison will postpone comparison
// of any sub-container pair to the end of the pair queue. This gives
// breadth-first search order. It also avoids the reprocessing of
// reference-equal siblings, cousins etc, which can have a significant speed
// impact when comparing a container of small objects each of which has a
// reference to the same (singleton) large object.
if ( pair . a !== pair . b && ! typeEquiv ( pair . a , pair . b ) ) {
return false ;
}
}
2017-02-05 13:39:16 +01:00
// ...across all consecutive argument pairs
2017-03-20 06:07:18 +01:00
return arguments . length === 2 || innerEquiv . apply ( this , [ ] . slice . call ( arguments , 1 ) ) ;
2017-02-05 13:39:16 +01:00
}
return innerEquiv ;
} ) ( ) ;
/ * *
* Config object : Maintain internal state
* Later exposed as QUnit . config
* ` config ` initialized at top of scope
* /
var config = {
// The queue of tests to run
queue : [ ] ,
// Block until document ready
blocking : true ,
// By default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder : true ,
// By default, modify document.title when suite is done
altertitle : true ,
// HTML Reporter: collapse every test except the first failing test
// If false, all failing tests will be expanded
collapse : true ,
// By default, scroll to top of the page when suite is done
scrolltop : true ,
// Depth up-to which object will be dumped
maxDepth : 5 ,
// When enabled, all tests must call expect()
requireExpects : false ,
// Placeholder for user-configurable form-exposed URL parameters
urlConfig : [ ] ,
// Set of all modules.
modules : [ ] ,
// Stack of nested modules
moduleStack : [ ] ,
// The first unnamed module
currentModule : {
name : "" ,
tests : [ ] ,
childModules : [ ] ,
testsRun : 0
} ,
callbacks : { } ,
// The storage module to use for reordering tests
storage : sessionStorage
} ;
// take a predefined QUnit.config and extend the defaults
var globalConfig = window && window . QUnit && window . QUnit . config ;
// only extend the global config if there is no QUnit overload
if ( window && window . QUnit && ! window . QUnit . version ) {
extend ( config , globalConfig ) ;
}
// Push a loose unnamed module to the modules collection
config . modules . push ( config . currentModule ) ;
// Based on jsDump by Ariel Flesler
// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
var dump = ( function ( ) {
function quote ( str ) {
return "\"" + str . toString ( ) . replace ( /\\/g , "\\\\" ) . replace ( /"/g , "\\\"" ) + "\"" ;
}
function literal ( o ) {
return o + "" ;
}
function join ( pre , arr , post ) {
var s = dump . separator ( ) ,
base = dump . indent ( ) ,
inner = dump . indent ( 1 ) ;
if ( arr . join ) {
arr = arr . join ( "," + s + inner ) ;
}
if ( ! arr ) {
return pre + post ;
}
return [ pre , inner + arr , base + post ] . join ( s ) ;
}
function array ( arr , stack ) {
var i = arr . length ,
ret = new Array ( i ) ;
if ( dump . maxDepth && dump . depth > dump . maxDepth ) {
return "[object Array]" ;
}
this . up ( ) ;
while ( i -- ) {
ret [ i ] = this . parse ( arr [ i ] , undefined , stack ) ;
}
this . down ( ) ;
return join ( "[" , ret , "]" ) ;
}
function isArray ( obj ) {
return (
//Native Arrays
toString . call ( obj ) === "[object Array]" ||
// NodeList objects
typeof obj . length === "number" && obj . item !== undefined && ( obj . length ? obj . item ( 0 ) === obj [ 0 ] : obj . item ( 0 ) === null && obj [ 0 ] === undefined )
) ;
}
var reName = /^function (\w+)/ ,
dump = {
// The objType is used mostly internally, you can fix a (custom) type in advance
parse : function parse ( obj , objType , stack ) {
stack = stack || [ ] ;
var res ,
parser ,
parserType ,
2017-03-20 06:07:18 +01:00
objIndex = stack . indexOf ( obj ) ;
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
if ( objIndex !== - 1 ) {
return "recursion(" + ( objIndex - stack . length ) + ")" ;
2017-02-05 13:39:16 +01:00
}
objType = objType || this . typeOf ( obj ) ;
parser = this . parsers [ objType ] ;
parserType = typeof parser === "undefined" ? "undefined" : _typeof ( parser ) ;
if ( parserType === "function" ) {
stack . push ( obj ) ;
res = parser . call ( this , obj , stack ) ;
stack . pop ( ) ;
return res ;
}
return parserType === "string" ? parser : this . parsers . error ;
} ,
typeOf : function typeOf ( obj ) {
var type ;
if ( obj === null ) {
type = "null" ;
} else if ( typeof obj === "undefined" ) {
type = "undefined" ;
} else if ( is ( "regexp" , obj ) ) {
type = "regexp" ;
} else if ( is ( "date" , obj ) ) {
type = "date" ;
} else if ( is ( "function" , obj ) ) {
type = "function" ;
} else if ( obj . setInterval !== undefined && obj . document !== undefined && obj . nodeType === undefined ) {
type = "window" ;
} else if ( obj . nodeType === 9 ) {
type = "document" ;
} else if ( obj . nodeType ) {
type = "node" ;
} else if ( isArray ( obj ) ) {
type = "array" ;
} else if ( obj . constructor === Error . prototype . constructor ) {
type = "error" ;
} else {
type = typeof obj === "undefined" ? "undefined" : _typeof ( obj ) ;
}
return type ;
} ,
separator : function separator ( ) {
if ( this . multiline ) {
return this . HTML ? "<br />" : "\n" ;
} else {
return this . HTML ? " " : " " ;
}
} ,
// Extra can be a number, shortcut for increasing-calling-decreasing
indent : function indent ( extra ) {
if ( ! this . multiline ) {
return "" ;
}
var chr = this . indentChar ;
if ( this . HTML ) {
chr = chr . replace ( /\t/g , " " ) . replace ( / /g , " " ) ;
}
return new Array ( this . depth + ( extra || 0 ) ) . join ( chr ) ;
} ,
up : function up ( a ) {
this . depth += a || 1 ;
} ,
down : function down ( a ) {
this . depth -= a || 1 ;
} ,
setParser : function setParser ( name , parser ) {
this . parsers [ name ] = parser ;
} ,
// The next 3 are exposed so you can use them
quote : quote ,
literal : literal ,
join : join ,
depth : 1 ,
maxDepth : config . maxDepth ,
// This is the list of parsers, to modify them, use dump.setParser
parsers : {
window : "[Window]" ,
document : "[Document]" ,
error : function error ( _error ) {
return "Error(\"" + _error . message + "\")" ;
} ,
unknown : "[Unknown]" ,
"null" : "null" ,
"undefined" : "undefined" ,
"function" : function _function ( fn ) {
var ret = "function" ,
// Functions never have name in IE
name = "name" in fn ? fn . name : ( reName . exec ( fn ) || [ ] ) [ 1 ] ;
if ( name ) {
ret += " " + name ;
}
ret += "(" ;
ret = [ ret , dump . parse ( fn , "functionArgs" ) , "){" ] . join ( "" ) ;
return join ( ret , dump . parse ( fn , "functionCode" ) , "}" ) ;
} ,
array : array ,
nodelist : array ,
"arguments" : array ,
object : function object ( map , stack ) {
var keys ,
key ,
val ,
i ,
nonEnumerableProperties ,
ret = [ ] ;
if ( dump . maxDepth && dump . depth > dump . maxDepth ) {
return "[object Object]" ;
}
dump . up ( ) ;
keys = [ ] ;
for ( key in map ) {
keys . push ( key ) ;
}
// Some properties are not always enumerable on Error objects.
nonEnumerableProperties = [ "message" , "name" ] ;
for ( i in nonEnumerableProperties ) {
key = nonEnumerableProperties [ i ] ;
2017-03-20 06:07:18 +01:00
if ( key in map && ! inArray ( key , keys ) ) {
2017-02-05 13:39:16 +01:00
keys . push ( key ) ;
}
}
keys . sort ( ) ;
for ( i = 0 ; i < keys . length ; i ++ ) {
key = keys [ i ] ;
val = map [ key ] ;
ret . push ( dump . parse ( key , "key" ) + ": " + dump . parse ( val , undefined , stack ) ) ;
}
dump . down ( ) ;
return join ( "{" , ret , "}" ) ;
} ,
node : function node ( _node ) {
var len ,
i ,
val ,
open = dump . HTML ? "<" : "<" ,
close = dump . HTML ? ">" : ">" ,
tag = _node . nodeName . toLowerCase ( ) ,
ret = open + tag ,
attrs = _node . attributes ;
if ( attrs ) {
for ( i = 0 , len = attrs . length ; i < len ; i ++ ) {
val = attrs [ i ] . nodeValue ;
// IE6 includes all attributes in .attributes, even ones not explicitly
// set. Those have values like undefined, null, 0, false, "" or
// "inherit".
if ( val && val !== "inherit" ) {
ret += " " + attrs [ i ] . nodeName + "=" + dump . parse ( val , "attribute" ) ;
}
}
}
ret += close ;
// Show content of TextNode or CDATASection
if ( _node . nodeType === 3 || _node . nodeType === 4 ) {
ret += _node . nodeValue ;
}
return ret + open + "/" + tag + close ;
} ,
// Function calls it internally, it's the arguments part of the function
functionArgs : function functionArgs ( fn ) {
var args ,
l = fn . length ;
if ( ! l ) {
return "" ;
}
args = new Array ( l ) ;
while ( l -- ) {
// 97 is 'a'
args [ l ] = String . fromCharCode ( 97 + l ) ;
}
return " " + args . join ( ", " ) + " " ;
} ,
// Object calls it internally, the key part of an item in a map
key : quote ,
// Function calls it internally, it's the content of the function
functionCode : "[code]" ,
// Node calls it internally, it's a html attribute value
attribute : quote ,
string : quote ,
date : quote ,
regexp : literal ,
number : literal ,
"boolean" : literal ,
symbol : function symbol ( sym ) {
return sym . toString ( ) ;
}
} ,
// If true, entities are escaped ( <, >, \t, space and \n )
HTML : false ,
// Indentation unit
indentChar : " " ,
// If true, items in a collection, are separated by a \n, else just a space.
multiline : true
} ;
return dump ;
} ) ( ) ;
2017-03-20 06:07:18 +01:00
var LISTENERS = Object . create ( null ) ;
var SUPPORTED _EVENTS = [ "runStart" , "suiteStart" , "testStart" , "assertion" , "testEnd" , "suiteEnd" , "runEnd" ] ;
/ * *
* Emits an event with the specified data to all currently registered listeners .
* Callbacks will fire in the order in which they are registered ( FIFO ) . This
* function is not exposed publicly ; it is used by QUnit internals to emit
* logging events .
*
* @ private
* @ method emit
* @ param { String } eventName
* @ param { Object } data
* @ return { Void }
* /
function emit ( eventName , data ) {
if ( objectType ( eventName ) !== "string" ) {
throw new TypeError ( "eventName must be a string when emitting an event" ) ;
}
// Clone the callbacks in case one of them registers a new callback
var originalCallbacks = LISTENERS [ eventName ] ;
var callbacks = originalCallbacks ? [ ] . concat ( toConsumableArray ( originalCallbacks ) ) : [ ] ;
for ( var i = 0 ; i < callbacks . length ; i ++ ) {
callbacks [ i ] ( data ) ;
}
}
/ * *
* Registers a callback as a listener to the specified event .
*
* @ public
* @ method on
* @ param { String } eventName
* @ param { Function } callback
* @ return { Void }
* /
function on ( eventName , callback ) {
if ( objectType ( eventName ) !== "string" ) {
throw new TypeError ( "eventName must be a string when registering a listener" ) ;
} else if ( ! inArray ( eventName , SUPPORTED _EVENTS ) ) {
var events = SUPPORTED _EVENTS . join ( ", " ) ;
throw new Error ( "\"" + eventName + "\" is not a valid event; must be one of: " + events + "." ) ;
} else if ( objectType ( callback ) !== "function" ) {
throw new TypeError ( "callback must be a function when registering a listener" ) ;
}
if ( ! LISTENERS [ eventName ] ) {
LISTENERS [ eventName ] = [ ] ;
}
// Don't register the same callback more than once
if ( ! inArray ( callback , LISTENERS [ eventName ] ) ) {
LISTENERS [ eventName ] . push ( callback ) ;
}
}
2017-02-05 13:39:16 +01:00
// Register logging callbacks
function registerLoggingCallbacks ( obj ) {
var i ,
l ,
key ,
callbackNames = [ "begin" , "done" , "log" , "testStart" , "testDone" , "moduleStart" , "moduleDone" ] ;
function registerLoggingCallback ( key ) {
var loggingCallback = function loggingCallback ( callback ) {
if ( objectType ( callback ) !== "function" ) {
throw new Error ( "QUnit logging methods require a callback function as their first parameters." ) ;
}
config . callbacks [ key ] . push ( callback ) ;
} ;
return loggingCallback ;
}
for ( i = 0 , l = callbackNames . length ; i < l ; i ++ ) {
key = callbackNames [ i ] ;
// Initialize key collection of logging callback
if ( objectType ( config . callbacks [ key ] ) === "undefined" ) {
config . callbacks [ key ] = [ ] ;
}
obj [ key ] = registerLoggingCallback ( key ) ;
}
}
function runLoggingCallbacks ( key , args ) {
var i , l , callbacks ;
callbacks = config . callbacks [ key ] ;
for ( i = 0 , l = callbacks . length ; i < l ; i ++ ) {
callbacks [ i ] ( args ) ;
}
}
// Doesn't support IE9, it will return undefined on these browsers
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
var fileName = ( sourceFromStacktrace ( 0 ) || "" ) . replace ( /(:\d+)+\)?/ , "" ) . replace ( /.+\// , "" ) ;
function extractStacktrace ( e , offset ) {
offset = offset === undefined ? 4 : offset ;
var stack , include , i ;
if ( e && e . stack ) {
stack = e . stack . split ( "\n" ) ;
if ( /^error$/i . test ( stack [ 0 ] ) ) {
stack . shift ( ) ;
}
if ( fileName ) {
include = [ ] ;
for ( i = offset ; i < stack . length ; i ++ ) {
if ( stack [ i ] . indexOf ( fileName ) !== - 1 ) {
break ;
}
include . push ( stack [ i ] ) ;
}
if ( include . length ) {
return include . join ( "\n" ) ;
}
}
return stack [ offset ] ;
}
}
function sourceFromStacktrace ( offset ) {
var error = new Error ( ) ;
// Support: Safari <=7 only, IE <=10 - 11 only
// Not all browsers generate the `stack` property for `new Error()`, see also #636
if ( ! error . stack ) {
try {
throw error ;
} catch ( err ) {
error = err ;
}
}
return extractStacktrace ( error , offset ) ;
}
2017-03-20 06:07:18 +01:00
var TestReport = function ( ) {
function TestReport ( name , suite , options ) {
classCallCheck ( this , TestReport ) ;
this . name = name ;
this . suiteName = suite . name ;
this . fullName = suite . fullName . concat ( name ) ;
this . runtime = 0 ;
this . assertions = [ ] ;
this . skipped = ! ! options . skip ;
this . todo = ! ! options . todo ;
this . _startTime = 0 ;
this . _endTime = 0 ;
suite . pushTest ( this ) ;
}
createClass ( TestReport , [ {
key : "start" ,
value : function start ( recordTime ) {
if ( recordTime ) {
this . _startTime = Date . now ( ) ;
}
return {
name : this . name ,
suiteName : this . suiteName ,
fullName : this . fullName . slice ( )
} ;
}
} , {
key : "end" ,
value : function end ( recordTime ) {
if ( recordTime ) {
this . _endTime = Date . now ( ) ;
}
return extend ( this . start ( ) , {
runtime : this . getRuntime ( ) ,
status : this . getStatus ( ) ,
errors : this . getFailedAssertions ( ) ,
assertions : this . getAssertions ( )
} ) ;
}
} , {
key : "pushAssertion" ,
value : function pushAssertion ( assertion ) {
this . assertions . push ( assertion ) ;
}
} , {
key : "getRuntime" ,
value : function getRuntime ( ) {
return this . _endTime - this . _startTime ;
}
} , {
key : "getStatus" ,
value : function getStatus ( ) {
if ( this . skipped ) {
return "skipped" ;
}
var testPassed = this . getFailedAssertions ( ) . length > 0 ? this . todo : ! this . todo ;
if ( ! testPassed ) {
return "failed" ;
} else if ( this . todo ) {
return "todo" ;
} else {
return "passed" ;
}
}
} , {
key : "getFailedAssertions" ,
value : function getFailedAssertions ( ) {
return this . assertions . filter ( function ( assertion ) {
return ! assertion . passed ;
} ) ;
}
} , {
key : "getAssertions" ,
value : function getAssertions ( ) {
return this . assertions . slice ( ) ;
}
} ] ) ;
return TestReport ;
} ( ) ;
2017-02-05 13:39:16 +01:00
var unitSampler ;
var focused = false ;
var priorityCount = 0 ;
function Test ( settings ) {
var i , l ;
++ Test . count ;
this . expected = null ;
extend ( this , settings ) ;
this . assertions = [ ] ;
this . semaphore = 0 ;
this . usedAsync = false ;
this . module = config . currentModule ;
this . stack = sourceFromStacktrace ( 3 ) ;
2017-03-20 06:07:18 +01:00
this . steps = [ ] ;
this . testReport = new TestReport ( settings . testName , this . module . suiteReport , {
todo : settings . todo ,
skip : settings . skip
} ) ;
2017-02-05 13:39:16 +01:00
// Register unique strings
for ( i = 0 , l = this . module . tests ; i < l . length ; i ++ ) {
if ( this . module . tests [ i ] . name === this . testName ) {
this . testName += " " ;
}
}
this . testId = generateHash ( this . module . name , this . testName ) ;
this . module . tests . push ( {
name : this . testName ,
testId : this . testId
} ) ;
if ( settings . skip ) {
// Skipped tests will fully ignore any sent callback
this . callback = function ( ) { } ;
this . async = false ;
this . expected = 0 ;
} else {
this . assert = new Assert ( this ) ;
}
}
Test . count = 0 ;
function getNotStartedModules ( startModule ) {
var module = startModule ,
modules = [ ] ;
while ( module && module . testsRun === 0 ) {
modules . push ( module ) ;
module = module . parentModule ;
}
return modules ;
}
Test . prototype = {
before : function before ( ) {
var i ,
startModule ,
module = this . module ,
notStartedModules = getNotStartedModules ( module ) ;
for ( i = notStartedModules . length - 1 ; i >= 0 ; i -- ) {
startModule = notStartedModules [ i ] ;
startModule . stats = { all : 0 , bad : 0 , started : now ( ) } ;
2017-03-20 06:07:18 +01:00
emit ( "suiteStart" , startModule . suiteReport . start ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "moduleStart" , {
name : startModule . name ,
tests : startModule . tests
} ) ;
}
config . current = this ;
if ( module . testEnvironment ) {
delete module . testEnvironment . before ;
delete module . testEnvironment . beforeEach ;
delete module . testEnvironment . afterEach ;
delete module . testEnvironment . after ;
}
this . testEnvironment = extend ( { } , module . testEnvironment ) ;
this . started = now ( ) ;
2017-03-20 06:07:18 +01:00
emit ( "testStart" , this . testReport . start ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "testStart" , {
name : this . testName ,
module : module . name ,
testId : this . testId ,
previousFailure : this . previousFailure
} ) ;
if ( ! config . pollution ) {
saveGlobal ( ) ;
}
} ,
run : function run ( ) {
var promise ;
config . current = this ;
this . callbackStarted = now ( ) ;
if ( config . notrycatch ) {
runTest ( this ) ;
return ;
}
try {
runTest ( this ) ;
} catch ( e ) {
this . pushFailure ( "Died on test #" + ( this . assertions . length + 1 ) + " " + this . stack + ": " + ( e . message || e ) , extractStacktrace ( e , 0 ) ) ;
// Else next test will carry the responsibility
saveGlobal ( ) ;
// Restart the tests if they're blocking
if ( config . blocking ) {
internalRecover ( this ) ;
}
}
function runTest ( test ) {
promise = test . callback . call ( test . testEnvironment , test . assert ) ;
test . resolvePromise ( promise ) ;
}
} ,
after : function after ( ) {
checkPollution ( ) ;
} ,
queueHook : function queueHook ( hook , hookName , hookOwner ) {
var promise ,
test = this ;
return function runHook ( ) {
if ( hookName === "before" ) {
if ( hookOwner . testsRun !== 0 ) {
return ;
}
test . preserveEnvironment = true ;
}
if ( hookName === "after" && hookOwner . testsRun !== numberOfTests ( hookOwner ) - 1 ) {
return ;
}
config . current = test ;
if ( config . notrycatch ) {
callHook ( ) ;
return ;
}
try {
callHook ( ) ;
} catch ( error ) {
test . pushFailure ( hookName + " failed on " + test . testName + ": " + ( error . message || error ) , extractStacktrace ( error , 0 ) ) ;
}
function callHook ( ) {
promise = hook . call ( test . testEnvironment , test . assert ) ;
test . resolvePromise ( promise , hookName ) ;
}
} ;
} ,
// Currently only used for module level hooks, can be used to add global level ones
hooks : function hooks ( handler ) {
var hooks = [ ] ;
function processHooks ( test , module ) {
if ( module . parentModule ) {
processHooks ( test , module . parentModule ) ;
}
if ( module . testEnvironment && objectType ( module . testEnvironment [ handler ] ) === "function" ) {
hooks . push ( test . queueHook ( module . testEnvironment [ handler ] , handler , module ) ) ;
}
}
// Hooks are ignored on skipped tests
if ( ! this . skip ) {
processHooks ( this , this . module ) ;
}
return hooks ;
} ,
finish : function finish ( ) {
config . current = this ;
if ( config . requireExpects && this . expected === null ) {
this . pushFailure ( "Expected number of assertions to be defined, but expect() was " + "not called." , this . stack ) ;
} else if ( this . expected !== null && this . expected !== this . assertions . length ) {
this . pushFailure ( "Expected " + this . expected + " assertions, but " + this . assertions . length + " were run" , this . stack ) ;
} else if ( this . expected === null && ! this . assertions . length ) {
this . pushFailure ( "Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions." , this . stack ) ;
}
var i ,
module = this . module ,
moduleName = module . name ,
testName = this . testName ,
skipped = ! ! this . skip ,
2017-03-20 06:07:18 +01:00
todo = ! ! this . todo ,
2017-02-05 13:39:16 +01:00
bad = 0 ,
storage = config . storage ;
this . runtime = now ( ) - this . started ;
config . stats . all += this . assertions . length ;
module . stats . all += this . assertions . length ;
for ( i = 0 ; i < this . assertions . length ; i ++ ) {
if ( ! this . assertions [ i ] . result ) {
bad ++ ;
config . stats . bad ++ ;
module . stats . bad ++ ;
}
}
notifyTestsRan ( module ) ;
// Store result when possible
if ( storage ) {
if ( bad ) {
storage . setItem ( "qunit-test-" + moduleName + "-" + testName , bad ) ;
} else {
storage . removeItem ( "qunit-test-" + moduleName + "-" + testName ) ;
}
}
2017-03-20 06:07:18 +01:00
emit ( "testEnd" , this . testReport . end ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "testDone" , {
name : testName ,
module : moduleName ,
skipped : skipped ,
2017-03-20 06:07:18 +01:00
todo : todo ,
2017-02-05 13:39:16 +01:00
failed : bad ,
passed : this . assertions . length - bad ,
total : this . assertions . length ,
runtime : skipped ? 0 : this . runtime ,
// HTML Reporter use
assertions : this . assertions ,
testId : this . testId ,
// Source of Test
source : this . stack
} ) ;
if ( module . testsRun === numberOfTests ( module ) ) {
2017-03-20 06:07:18 +01:00
emit ( "suiteEnd" , module . suiteReport . end ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "moduleDone" , {
name : module . name ,
tests : module . tests ,
failed : module . stats . bad ,
passed : module . stats . all - module . stats . bad ,
total : module . stats . all ,
runtime : now ( ) - module . stats . started
} ) ;
}
config . current = undefined ;
} ,
preserveTestEnvironment : function preserveTestEnvironment ( ) {
if ( this . preserveEnvironment ) {
this . module . testEnvironment = this . testEnvironment ;
this . testEnvironment = extend ( { } , this . module . testEnvironment ) ;
}
} ,
queue : function queue ( ) {
var priority ,
previousFailCount ,
test = this ;
if ( ! this . valid ( ) ) {
return ;
}
function run ( ) {
// Each of these can by async
synchronize ( [ function ( ) {
test . before ( ) ;
} , test . hooks ( "before" ) , function ( ) {
test . preserveTestEnvironment ( ) ;
} , test . hooks ( "beforeEach" ) , function ( ) {
test . run ( ) ;
} , test . hooks ( "afterEach" ) . reverse ( ) , test . hooks ( "after" ) . reverse ( ) , function ( ) {
test . after ( ) ;
} , function ( ) {
test . finish ( ) ;
} ] ) ;
}
previousFailCount = config . storage && + config . storage . getItem ( "qunit-test-" + this . module . name + "-" + this . testName ) ;
// Prioritize previously failed tests, detected from storage
priority = config . reorder && previousFailCount ;
this . previousFailure = ! ! previousFailCount ;
return synchronize ( run , priority , config . seed ) ;
} ,
pushResult : function pushResult ( resultInfo ) {
// Destructure of resultInfo = { result, actual, expected, message, negative }
var source ,
details = {
module : this . module . name ,
name : this . testName ,
result : resultInfo . result ,
message : resultInfo . message ,
actual : resultInfo . actual ,
expected : resultInfo . expected ,
testId : this . testId ,
negative : resultInfo . negative || false ,
2017-03-20 06:07:18 +01:00
runtime : now ( ) - this . started ,
todo : ! ! this . todo
2017-02-05 13:39:16 +01:00
} ;
if ( ! resultInfo . result ) {
2017-03-20 06:07:18 +01:00
source = resultInfo . source || sourceFromStacktrace ( ) ;
2017-02-05 13:39:16 +01:00
if ( source ) {
details . source = source ;
}
}
2017-03-20 06:07:18 +01:00
this . logAssertion ( details ) ;
2017-02-05 13:39:16 +01:00
this . assertions . push ( {
result : ! ! resultInfo . result ,
message : resultInfo . message
} ) ;
} ,
pushFailure : function pushFailure ( message , source , actual ) {
if ( ! ( this instanceof Test ) ) {
throw new Error ( "pushFailure() assertion outside test context, was " + sourceFromStacktrace ( 2 ) ) ;
}
2017-03-20 06:07:18 +01:00
this . assert . pushResult ( {
2017-02-05 13:39:16 +01:00
result : false ,
message : message || "error" ,
actual : actual || null ,
2017-03-20 06:07:18 +01:00
expected : null ,
source : source
} ) ;
} ,
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
/ * *
* Log assertion details using both the old QUnit . log interface and
* QUnit . on ( "assertion" ) interface .
*
* @ private
* /
logAssertion : function logAssertion ( details ) {
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "log" , details ) ;
2017-03-20 06:07:18 +01:00
var assertion = {
passed : details . result ,
actual : details . actual ,
expected : details . expected ,
message : details . message ,
stack : details . source ,
todo : details . todo
} ;
this . testReport . pushAssertion ( assertion ) ;
emit ( "assertion" , assertion ) ;
2017-02-05 13:39:16 +01:00
} ,
2017-03-20 06:07:18 +01:00
2017-02-05 13:39:16 +01:00
resolvePromise : function resolvePromise ( promise , phase ) {
var then ,
resume ,
message ,
test = this ;
if ( promise != null ) {
then = promise . then ;
if ( objectType ( then ) === "function" ) {
resume = internalStop ( test ) ;
then . call ( promise , function ( ) {
resume ( ) ;
} , function ( error ) {
message = "Promise rejected " + ( ! phase ? "during" : phase . replace ( /Each$/ , "" ) ) + " \"" + test . testName + "\": " + ( error && error . message || error ) ;
test . pushFailure ( message , extractStacktrace ( error , 0 ) ) ;
// Else next test will carry the responsibility
saveGlobal ( ) ;
// Unblock
resume ( ) ;
} ) ;
}
}
} ,
valid : function valid ( ) {
var filter = config . filter ,
regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/ . exec ( filter ) ,
module = config . module && config . module . toLowerCase ( ) ,
fullName = this . module . name + ": " + this . testName ;
function moduleChainNameMatch ( testModule ) {
var testModuleName = testModule . name ? testModule . name . toLowerCase ( ) : null ;
if ( testModuleName === module ) {
return true ;
} else if ( testModule . parentModule ) {
return moduleChainNameMatch ( testModule . parentModule ) ;
} else {
return false ;
}
}
function moduleChainIdMatch ( testModule ) {
2017-03-20 06:07:18 +01:00
return inArray ( testModule . moduleId , config . moduleId ) || testModule . parentModule && moduleChainIdMatch ( testModule . parentModule ) ;
2017-02-05 13:39:16 +01:00
}
// Internally-generated tests are always valid
if ( this . callback && this . callback . validTest ) {
return true ;
}
if ( config . moduleId && config . moduleId . length > 0 && ! moduleChainIdMatch ( this . module ) ) {
return false ;
}
2017-03-20 06:07:18 +01:00
if ( config . testId && config . testId . length > 0 && ! inArray ( this . testId , config . testId ) ) {
2017-02-05 13:39:16 +01:00
return false ;
}
if ( module && ! moduleChainNameMatch ( this . module ) ) {
return false ;
}
if ( ! filter ) {
return true ;
}
return regexFilter ? this . regexFilter ( ! ! regexFilter [ 1 ] , regexFilter [ 2 ] , regexFilter [ 3 ] , fullName ) : this . stringFilter ( filter , fullName ) ;
} ,
regexFilter : function regexFilter ( exclude , pattern , flags , fullName ) {
var regex = new RegExp ( pattern , flags ) ;
var match = regex . test ( fullName ) ;
return match !== exclude ;
} ,
stringFilter : function stringFilter ( filter , fullName ) {
filter = filter . toLowerCase ( ) ;
fullName = fullName . toLowerCase ( ) ;
var include = filter . charAt ( 0 ) !== "!" ;
if ( ! include ) {
filter = filter . slice ( 1 ) ;
}
// If the filter matches, we need to honour include
if ( fullName . indexOf ( filter ) !== - 1 ) {
return include ;
}
// Otherwise, do the opposite
return ! include ;
}
} ;
function pushFailure ( ) {
if ( ! config . current ) {
throw new Error ( "pushFailure() assertion outside test context, in " + sourceFromStacktrace ( 2 ) ) ;
}
// Gets current test obj
var currentTest = config . current ;
return currentTest . pushFailure . apply ( currentTest , arguments ) ;
}
// Based on Java's String.hashCode, a simple but not
// rigorously collision resistant hashing function
function generateHash ( module , testName ) {
var hex ,
i = 0 ,
hash = 0 ,
str = module + "\x1C" + testName ,
len = str . length ;
for ( ; i < len ; i ++ ) {
hash = ( hash << 5 ) - hash + str . charCodeAt ( i ) ;
hash |= 0 ;
}
// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
// strictly necessary but increases user understanding that the id is a SHA-like hash
hex = ( 0x100000000 + hash ) . toString ( 16 ) ;
if ( hex . length < 8 ) {
hex = "0000000" + hex ;
}
return hex . slice ( - 8 ) ;
}
function synchronize ( callback , priority , seed ) {
var last = ! priority ,
index ;
if ( objectType ( callback ) === "array" ) {
while ( callback . length ) {
synchronize ( callback . shift ( ) ) ;
}
return ;
}
if ( priority ) {
config . queue . splice ( priorityCount ++ , 0 , callback ) ;
} else if ( seed ) {
if ( ! unitSampler ) {
unitSampler = unitSamplerGenerator ( seed ) ;
}
// Insert into a random position after all priority items
index = Math . floor ( unitSampler ( ) * ( config . queue . length - priorityCount + 1 ) ) ;
config . queue . splice ( priorityCount + index , 0 , callback ) ;
} else {
config . queue . push ( callback ) ;
}
if ( internalState . autorun && ! config . blocking ) {
process ( last ) ;
}
}
function unitSamplerGenerator ( seed ) {
// 32-bit xorshift, requires only a nonzero seed
// http://excamera.com/sphinx/article-xorshift.html
var sample = parseInt ( generateHash ( seed ) , 16 ) || - 1 ;
return function ( ) {
sample ^= sample << 13 ;
sample ^= sample >>> 17 ;
sample ^= sample << 5 ;
// ECMAScript has no unsigned number type
if ( sample < 0 ) {
sample += 0x100000000 ;
}
return sample / 0x100000000 ;
} ;
}
function saveGlobal ( ) {
config . pollution = [ ] ;
if ( config . noglobals ) {
for ( var key in global$1 ) {
if ( hasOwn . call ( global$1 , key ) ) {
// In Opera sometimes DOM element ids show up here, ignore them
if ( /^qunit-test-output/ . test ( key ) ) {
continue ;
}
config . pollution . push ( key ) ;
}
}
}
}
function checkPollution ( ) {
var newGlobals ,
deletedGlobals ,
old = config . pollution ;
saveGlobal ( ) ;
newGlobals = diff ( config . pollution , old ) ;
if ( newGlobals . length > 0 ) {
pushFailure ( "Introduced global variable(s): " + newGlobals . join ( ", " ) ) ;
}
deletedGlobals = diff ( old , config . pollution ) ;
if ( deletedGlobals . length > 0 ) {
pushFailure ( "Deleted global variable(s): " + deletedGlobals . join ( ", " ) ) ;
}
}
// Will be exposed as QUnit.test
function test ( testName , callback ) {
if ( focused ) {
return ;
}
2017-03-20 06:07:18 +01:00
var newTest = new Test ( {
2017-02-05 13:39:16 +01:00
testName : testName ,
callback : callback
} ) ;
newTest . queue ( ) ;
}
2017-03-20 06:07:18 +01:00
function todo ( testName , callback ) {
if ( focused ) {
return ;
}
var newTest = new Test ( {
testName : testName ,
callback : callback ,
todo : true
} ) ;
newTest . queue ( ) ;
}
2017-02-05 13:39:16 +01:00
// Will be exposed as QUnit.skip
function skip ( testName ) {
if ( focused ) {
return ;
}
var test = new Test ( {
testName : testName ,
skip : true
} ) ;
test . queue ( ) ;
}
// Will be exposed as QUnit.only
function only ( testName , callback ) {
if ( focused ) {
return ;
}
config . queue . length = 0 ;
focused = true ;
2017-03-20 06:07:18 +01:00
var newTest = new Test ( {
2017-02-05 13:39:16 +01:00
testName : testName ,
callback : callback
} ) ;
newTest . queue ( ) ;
}
// Put a hold on processing and return a function that will release it.
function internalStop ( test ) {
var released = false ;
test . semaphore += 1 ;
config . blocking = true ;
// Set a recovery timeout, if so configured.
if ( config . testTimeout && defined . setTimeout ) {
clearTimeout ( config . timeout ) ;
config . timeout = setTimeout ( function ( ) {
pushFailure ( "Test timed out" , sourceFromStacktrace ( 2 ) ) ;
internalRecover ( test ) ;
} , config . testTimeout ) ;
}
return function resume ( ) {
if ( released ) {
return ;
}
released = true ;
test . semaphore -= 1 ;
internalStart ( test ) ;
} ;
}
// Forcefully release all processing holds.
function internalRecover ( test ) {
test . semaphore = 0 ;
internalStart ( test ) ;
}
// Release a processing hold, scheduling a resumption attempt if no holds remain.
function internalStart ( test ) {
// If semaphore is non-numeric, throw error
if ( isNaN ( test . semaphore ) ) {
test . semaphore = 0 ;
pushFailure ( "Invalid value on test.semaphore" , sourceFromStacktrace ( 2 ) ) ;
return ;
}
// Don't start until equal number of stop-calls
if ( test . semaphore > 0 ) {
return ;
}
// Throw an Error if start is called more often than stop
if ( test . semaphore < 0 ) {
test . semaphore = 0 ;
pushFailure ( "Tried to restart test while already started (test's semaphore was 0 already)" , sourceFromStacktrace ( 2 ) ) ;
return ;
}
// Add a slight delay to allow more assertions etc.
if ( defined . setTimeout ) {
if ( config . timeout ) {
clearTimeout ( config . timeout ) ;
}
config . timeout = setTimeout ( function ( ) {
if ( test . semaphore > 0 ) {
return ;
}
if ( config . timeout ) {
clearTimeout ( config . timeout ) ;
}
begin ( ) ;
} , 13 ) ;
} else {
begin ( ) ;
}
}
function numberOfTests ( module ) {
var count = module . tests . length ,
modules = [ ] . concat ( toConsumableArray ( module . childModules ) ) ;
// Do a breadth-first traversal of the child modules
while ( modules . length ) {
var nextModule = modules . shift ( ) ;
count += nextModule . tests . length ;
modules . push . apply ( modules , toConsumableArray ( nextModule . childModules ) ) ;
}
return count ;
}
function notifyTestsRan ( module ) {
module . testsRun ++ ;
while ( module = module . parentModule ) {
module . testsRun ++ ;
}
}
2017-03-20 06:07:18 +01:00
/ * *
* Returns a function that proxies to the given method name on the globals
* console object . The proxy will also detect if the console doesn ' t exist and
* will appropriately no - op . This allows support for IE9 , which doesn ' t have a
* console if the developer tools are not open .
* /
function consoleProxy ( method ) {
return function ( ) {
if ( console ) {
console [ method ] . apply ( console , arguments ) ;
}
} ;
}
var Logger = {
warn : consoleProxy ( "warn" )
} ;
2017-02-05 13:39:16 +01:00
var Assert = function ( ) {
function Assert ( testContext ) {
classCallCheck ( this , Assert ) ;
this . test = testContext ;
}
// Assert helpers
2017-03-20 06:07:18 +01:00
// Documents a "step", which is a string value, in a test as a passing assertion
2017-02-05 13:39:16 +01:00
createClass ( Assert , [ {
2017-03-20 06:07:18 +01:00
key : "step" ,
value : function step ( message ) {
var result = ! ! message ;
this . test . steps . push ( message ) ;
return this . pushResult ( {
result : result ,
message : message || "You must provide a message to assert.step"
} ) ;
}
// Verifies the steps in a test match a given array of string values
} , {
key : "verifySteps" ,
value : function verifySteps ( steps , message ) {
this . deepEqual ( this . test . steps , steps , message ) ;
}
// Specify the number of expected assertions to guarantee that failed test
// (no assertions are run at all) don't slip through.
} , {
2017-02-05 13:39:16 +01:00
key : "expect" ,
value : function expect ( asserts ) {
if ( arguments . length === 1 ) {
this . test . expected = asserts ;
} else {
return this . test . expected ;
}
}
// Put a hold on processing and return a function that will release it a maximum of once.
} , {
key : "async" ,
value : function async ( count ) {
var test$$1 = this . test ,
popped = false ,
acceptCallCount = count ;
if ( typeof acceptCallCount === "undefined" ) {
acceptCallCount = 1 ;
}
test$$1 . usedAsync = true ;
var resume = internalStop ( test$$1 ) ;
return function done ( ) {
if ( popped ) {
test$$1 . pushFailure ( "Too many calls to the `assert.async` callback" , sourceFromStacktrace ( 2 ) ) ;
return ;
}
acceptCallCount -= 1 ;
if ( acceptCallCount > 0 ) {
return ;
}
popped = true ;
resume ( ) ;
} ;
}
// Exports test.push() to the user API
// Alias of pushResult.
} , {
key : "push" ,
value : function push ( result , actual , expected , message , negative ) {
2017-03-20 06:07:18 +01:00
Logger . warn ( "assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/)." ) ;
2017-02-05 13:39:16 +01:00
var currentAssert = this instanceof Assert ? this : config . current . assert ;
return currentAssert . pushResult ( {
result : result ,
actual : actual ,
expected : expected ,
message : message ,
negative : negative
} ) ;
}
} , {
key : "pushResult" ,
value : function pushResult ( resultInfo ) {
// Destructure of resultInfo = { result, actual, expected, message, negative }
var assert = this ,
currentTest = assert instanceof Assert && assert . test || config . current ;
// Backwards compatibility fix.
// Allows the direct use of global exported assertions and QUnit.assert.*
// Although, it's use is not recommended as it can leak assertions
// to other tests from async tests, because we only get a reference to the current test,
// not exactly the test where assertion were intended to be called.
if ( ! currentTest ) {
throw new Error ( "assertion outside test context, in " + sourceFromStacktrace ( 2 ) ) ;
}
if ( currentTest . usedAsync === true && currentTest . semaphore === 0 ) {
currentTest . pushFailure ( "Assertion after the final `assert.async` was resolved" , sourceFromStacktrace ( 2 ) ) ;
// Allow this assertion to continue running anyway...
}
if ( ! ( assert instanceof Assert ) ) {
assert = currentTest . assert ;
}
return assert . test . pushResult ( resultInfo ) ;
}
} , {
key : "ok" ,
value : function ok ( result , message ) {
if ( ! message ) {
message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump . parse ( result ) ;
}
this . pushResult ( {
result : ! ! result ,
actual : result ,
expected : true ,
message : message
} ) ;
}
} , {
key : "notOk" ,
value : function notOk ( result , message ) {
if ( ! message ) {
message = ! result ? "okay" : "failed, expected argument to be falsy, was: " + dump . parse ( result ) ;
}
this . pushResult ( {
result : ! result ,
actual : result ,
expected : false ,
message : message
} ) ;
}
} , {
key : "equal" ,
value : function equal ( actual , expected , message ) {
// eslint-disable-next-line eqeqeq
var result = expected == actual ;
this . pushResult ( {
result : result ,
actual : actual ,
expected : expected ,
message : message
} ) ;
}
} , {
key : "notEqual" ,
value : function notEqual ( actual , expected , message ) {
// eslint-disable-next-line eqeqeq
var result = expected != actual ;
this . pushResult ( {
result : result ,
actual : actual ,
expected : expected ,
message : message ,
negative : true
} ) ;
}
} , {
key : "propEqual" ,
value : function propEqual ( actual , expected , message ) {
actual = objectValues ( actual ) ;
expected = objectValues ( expected ) ;
this . pushResult ( {
result : equiv ( actual , expected ) ,
actual : actual ,
expected : expected ,
message : message
} ) ;
}
} , {
key : "notPropEqual" ,
value : function notPropEqual ( actual , expected , message ) {
actual = objectValues ( actual ) ;
expected = objectValues ( expected ) ;
this . pushResult ( {
result : ! equiv ( actual , expected ) ,
actual : actual ,
expected : expected ,
message : message ,
negative : true
} ) ;
}
} , {
key : "deepEqual" ,
value : function deepEqual ( actual , expected , message ) {
this . pushResult ( {
result : equiv ( actual , expected ) ,
actual : actual ,
expected : expected ,
message : message
} ) ;
}
} , {
key : "notDeepEqual" ,
value : function notDeepEqual ( actual , expected , message ) {
this . pushResult ( {
result : ! equiv ( actual , expected ) ,
actual : actual ,
expected : expected ,
message : message ,
negative : true
} ) ;
}
} , {
key : "strictEqual" ,
value : function strictEqual ( actual , expected , message ) {
this . pushResult ( {
result : expected === actual ,
actual : actual ,
expected : expected ,
message : message
} ) ;
}
} , {
key : "notStrictEqual" ,
value : function notStrictEqual ( actual , expected , message ) {
this . pushResult ( {
result : expected !== actual ,
actual : actual ,
expected : expected ,
message : message ,
negative : true
} ) ;
}
} , {
key : "throws" ,
value : function throws ( block , expected , message ) {
var actual = void 0 ,
result = false ,
currentTest = this instanceof Assert && this . test || config . current ;
// 'expected' is optional unless doing string comparison
if ( objectType ( expected ) === "string" ) {
if ( message == null ) {
message = expected ;
expected = null ;
} else {
throw new Error ( "throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary." ) ;
}
}
currentTest . ignoreGlobalErrors = true ;
try {
block . call ( currentTest . testEnvironment ) ;
} catch ( e ) {
actual = e ;
}
currentTest . ignoreGlobalErrors = false ;
if ( actual ) {
var expectedType = objectType ( expected ) ;
// We don't want to validate thrown error
if ( ! expected ) {
result = true ;
expected = null ;
// Expected is a regexp
} else if ( expectedType === "regexp" ) {
result = expected . test ( errorString ( actual ) ) ;
// Expected is a constructor, maybe an Error constructor
} else if ( expectedType === "function" && actual instanceof expected ) {
result = true ;
// Expected is an Error object
} else if ( expectedType === "object" ) {
result = actual instanceof expected . constructor && actual . name === expected . name && actual . message === expected . message ;
// Expected is a validation function which returns true if validation passed
} else if ( expectedType === "function" && expected . call ( { } , actual ) === true ) {
expected = null ;
result = true ;
}
}
currentTest . assert . pushResult ( {
result : result ,
actual : actual ,
expected : expected ,
message : message
} ) ;
}
} ] ) ;
return Assert ;
} ( ) ;
// Provide an alternative to assert.throws(), for environments that consider throws a reserved word
// Known to us are: Closure Compiler, Narwhal
// eslint-disable-next-line dot-notation
Assert . prototype . raises = Assert . prototype [ "throws" ] ;
/ * *
* Converts an error into a simple string for comparisons .
*
* @ param { Error } error
* @ return { String }
* /
function errorString ( error ) {
var resultErrorString = error . toString ( ) ;
if ( resultErrorString . substring ( 0 , 7 ) === "[object" ) {
var name = error . name ? error . name . toString ( ) : "Error" ;
var message = error . message ? error . message . toString ( ) : "" ;
if ( name && message ) {
return name + ": " + message ;
} else if ( name ) {
return name ;
} else if ( message ) {
return message ;
} else {
return "Error" ;
}
} else {
return resultErrorString ;
}
}
/* global module, exports, define */
function exportQUnit ( QUnit ) {
if ( defined . document ) {
// QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
if ( window . QUnit && window . QUnit . version ) {
throw new Error ( "QUnit has already been defined." ) ;
}
window . QUnit = QUnit ;
}
// For nodejs
if ( typeof module !== "undefined" && module && module . exports ) {
module . exports = QUnit ;
// For consistency with CommonJS environments' exports
module . exports . QUnit = QUnit ;
}
// For CommonJS with exports, but without module.exports, like Rhino
if ( typeof exports !== "undefined" && exports ) {
exports . QUnit = QUnit ;
}
if ( typeof define === "function" && define . amd ) {
define ( function ( ) {
return QUnit ;
} ) ;
QUnit . config . autostart = false ;
}
}
2017-03-20 06:07:18 +01:00
var SuiteReport = function ( ) {
function SuiteReport ( name , parentSuite ) {
classCallCheck ( this , SuiteReport ) ;
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
this . name = name ;
this . fullName = parentSuite ? parentSuite . fullName . concat ( name ) : [ ] ;
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
this . tests = [ ] ;
this . childSuites = [ ] ;
if ( parentSuite ) {
parentSuite . pushChildSuite ( this ) ;
2017-02-05 13:39:16 +01:00
}
2017-03-20 06:07:18 +01:00
}
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
createClass ( SuiteReport , [ {
key : "start" ,
value : function start ( recordTime ) {
if ( recordTime ) {
this . _startTime = Date . now ( ) ;
}
return {
name : this . name ,
fullName : this . fullName . slice ( ) ,
tests : this . tests . map ( function ( test ) {
return test . start ( ) ;
} ) ,
childSuites : this . childSuites . map ( function ( suite ) {
return suite . start ( ) ;
} ) ,
testCounts : {
total : this . getTestCounts ( ) . total
2017-02-05 13:39:16 +01:00
}
2017-03-20 06:07:18 +01:00
} ;
}
} , {
key : "end" ,
value : function end ( recordTime ) {
if ( recordTime ) {
this . _endTime = Date . now ( ) ;
}
return {
name : this . name ,
fullName : this . fullName . slice ( ) ,
tests : this . tests . map ( function ( test ) {
return test . end ( ) ;
} ) ,
childSuites : this . childSuites . map ( function ( suite ) {
return suite . end ( ) ;
} ) ,
testCounts : this . getTestCounts ( ) ,
runtime : this . getRuntime ( ) ,
status : this . getStatus ( )
} ;
}
} , {
key : "pushChildSuite" ,
value : function pushChildSuite ( suite ) {
this . childSuites . push ( suite ) ;
}
} , {
key : "pushTest" ,
value : function pushTest ( test ) {
this . tests . push ( test ) ;
}
} , {
key : "getRuntime" ,
value : function getRuntime ( ) {
return this . _endTime - this . _startTime ;
}
} , {
key : "getTestCounts" ,
value : function getTestCounts ( ) {
var counts = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : { passed : 0 , failed : 0 , skipped : 0 , todo : 0 , total : 0 } ;
counts = this . tests . reduce ( function ( counts , test ) {
counts [ test . getStatus ( ) ] ++ ;
counts . total ++ ;
return counts ;
} , counts ) ;
return this . childSuites . reduce ( function ( counts , suite ) {
return suite . getTestCounts ( counts ) ;
} , counts ) ;
}
} , {
key : "getStatus" ,
value : function getStatus ( ) {
var _getTestCounts = this . getTestCounts ( ) ,
total = _getTestCounts . total ,
failed = _getTestCounts . failed ,
skipped = _getTestCounts . skipped ,
todo = _getTestCounts . todo ;
if ( failed ) {
return "failed" ;
2017-02-05 13:39:16 +01:00
} else {
2017-03-20 06:07:18 +01:00
if ( skipped === total ) {
return "skipped" ;
} else if ( todo === total ) {
return "todo" ;
} else {
return "passed" ;
}
2017-02-05 13:39:16 +01:00
}
}
2017-03-20 06:07:18 +01:00
} ] ) ;
return SuiteReport ;
} ( ) ;
2017-02-05 13:39:16 +01:00
2017-03-20 06:07:18 +01:00
// Handle an unhandled exception. By convention, returns true if further
// error handling should be suppressed and false otherwise.
// In this case, we will only suppress further error handling if the
// "ignoreGlobalErrors" configuration option is enabled.
function onError ( error ) {
for ( var _len = arguments . length , args = Array ( _len > 1 ? _len - 1 : 0 ) , _key = 1 ; _key < _len ; _key ++ ) {
args [ _key - 1 ] = arguments [ _key ] ;
}
if ( config . current ) {
if ( config . current . ignoreGlobalErrors ) {
return true ;
}
pushFailure . apply ( undefined , [ error . message , error . fileName + ":" + error . lineNumber ] . concat ( args ) ) ;
} else {
test ( "global failure" , extend ( function ( ) {
pushFailure . apply ( undefined , [ error . message , error . fileName + ":" + error . lineNumber ] . concat ( args ) ) ;
} , { validTest : true } ) ) ;
}
return false ;
}
2017-02-05 13:39:16 +01:00
var QUnit = { } ;
2017-03-20 06:07:18 +01:00
var globalSuite = new SuiteReport ( ) ;
// The initial "currentModule" represents the global (or top-level) module that
// is not explicitly defined by the user, therefore we add the "globalSuite" to
// it since each module has a suiteReport associated with it.
config . currentModule . suiteReport = globalSuite ;
2017-02-05 13:39:16 +01:00
var globalStartCalled = false ;
var runStarted = false ;
var internalState = {
autorun : false
} ;
// Figure out if we're running the tests from a server or not
QUnit . isLocal = ! ( defined . document && window . location . protocol !== "file:" ) ;
// Expose the current QUnit version
2017-03-20 06:07:18 +01:00
QUnit . version = "2.2.0" ;
2017-02-05 13:39:16 +01:00
extend ( QUnit , {
2017-03-20 06:07:18 +01:00
on : on ,
2017-02-05 13:39:16 +01:00
// Call on start of module test to prepend name to all tests
module : function module ( name , testEnvironment , executeNow ) {
var module , moduleFns ;
var currentModule = config . currentModule ;
if ( arguments . length === 2 ) {
if ( objectType ( testEnvironment ) === "function" ) {
executeNow = testEnvironment ;
testEnvironment = undefined ;
}
}
module = createModule ( ) ;
moduleFns = {
before : setHook ( module , "before" ) ,
beforeEach : setHook ( module , "beforeEach" ) ,
afterEach : setHook ( module , "afterEach" ) ,
after : setHook ( module , "after" )
} ;
if ( objectType ( executeNow ) === "function" ) {
config . moduleStack . push ( module ) ;
setCurrentModule ( module ) ;
executeNow . call ( module . testEnvironment , moduleFns ) ;
config . moduleStack . pop ( ) ;
module = module . parentModule || currentModule ;
}
setCurrentModule ( module ) ;
function createModule ( ) {
var parentModule = config . moduleStack . length ? config . moduleStack . slice ( - 1 ) [ 0 ] : null ;
var moduleName = parentModule !== null ? [ parentModule . name , name ] . join ( " > " ) : name ;
2017-03-20 06:07:18 +01:00
var parentSuite = parentModule ? parentModule . suiteReport : globalSuite ;
2017-02-05 13:39:16 +01:00
var module = {
name : moduleName ,
parentModule : parentModule ,
tests : [ ] ,
moduleId : generateHash ( moduleName ) ,
testsRun : 0 ,
2017-03-20 06:07:18 +01:00
childModules : [ ] ,
suiteReport : new SuiteReport ( name , parentSuite )
2017-02-05 13:39:16 +01:00
} ;
var env = { } ;
if ( parentModule ) {
parentModule . childModules . push ( module ) ;
extend ( env , parentModule . testEnvironment ) ;
delete env . beforeEach ;
delete env . afterEach ;
}
extend ( env , testEnvironment ) ;
module . testEnvironment = env ;
config . modules . push ( module ) ;
return module ;
}
function setCurrentModule ( module ) {
config . currentModule = module ;
}
} ,
test : test ,
2017-03-20 06:07:18 +01:00
todo : todo ,
2017-02-05 13:39:16 +01:00
skip : skip ,
only : only ,
start : function start ( count ) {
var globalStartAlreadyCalled = globalStartCalled ;
if ( ! config . current ) {
globalStartCalled = true ;
if ( runStarted ) {
throw new Error ( "Called start() while test already started running" ) ;
} else if ( globalStartAlreadyCalled || count > 1 ) {
throw new Error ( "Called start() outside of a test context too many times" ) ;
} else if ( config . autostart ) {
throw new Error ( "Called start() outside of a test context when " + "QUnit.config.autostart was true" ) ;
} else if ( ! config . pageLoaded ) {
2017-03-20 06:07:18 +01:00
// The page isn't completely loaded yet, so we set autostart and then
// load if we're in Node or wait for the browser's load event.
2017-02-05 13:39:16 +01:00
config . autostart = true ;
2017-03-20 06:07:18 +01:00
// Starts from Node even if .load was not previously called. We still return
// early otherwise we'll wind up "beginning" twice.
if ( ! defined . document ) {
QUnit . load ( ) ;
}
2017-02-05 13:39:16 +01:00
return ;
}
} else {
throw new Error ( "QUnit.start cannot be called inside a test context." ) ;
}
scheduleBegin ( ) ;
} ,
config : config ,
is : is ,
objectType : objectType ,
extend : extend ,
load : function load ( ) {
config . pageLoaded = true ;
// Initialize the configuration options
extend ( config , {
stats : { all : 0 , bad : 0 } ,
started : 0 ,
updateRate : 1000 ,
autostart : true ,
filter : ""
} , true ) ;
if ( ! runStarted ) {
config . blocking = false ;
if ( config . autostart ) {
scheduleBegin ( ) ;
}
}
} ,
stack : function stack ( offset ) {
offset = ( offset || 0 ) + 2 ;
return sourceFromStacktrace ( offset ) ;
2017-03-20 06:07:18 +01:00
} ,
onError : onError
2017-02-05 13:39:16 +01:00
} ) ;
QUnit . pushFailure = pushFailure ;
QUnit . assert = Assert . prototype ;
QUnit . equiv = equiv ;
QUnit . dump = dump ;
registerLoggingCallbacks ( QUnit ) ;
function scheduleBegin ( ) {
runStarted = true ;
// Add a slight delay to allow definition of more modules and tests.
if ( defined . setTimeout ) {
setTimeout ( function ( ) {
begin ( ) ;
} , 13 ) ;
} else {
begin ( ) ;
}
}
function begin ( ) {
var i ,
l ,
modulesLog = [ ] ;
// If the test run hasn't officially begun yet
if ( ! config . started ) {
// Record the time of the test run's beginning
config . started = now ( ) ;
// Delete the loose unnamed module if unused.
if ( config . modules [ 0 ] . name === "" && config . modules [ 0 ] . tests . length === 0 ) {
config . modules . shift ( ) ;
}
// Avoid unnecessary information by not logging modules' test environments
for ( i = 0 , l = config . modules . length ; i < l ; i ++ ) {
modulesLog . push ( {
name : config . modules [ i ] . name ,
tests : config . modules [ i ] . tests
} ) ;
}
// The test run is officially beginning now
2017-03-20 06:07:18 +01:00
emit ( "runStart" , globalSuite . start ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "begin" , {
totalTests : Test . count ,
modules : modulesLog
} ) ;
}
config . blocking = false ;
process ( true ) ;
}
function process ( last ) {
function next ( ) {
process ( last ) ;
}
var start = now ( ) ;
config . depth = ( config . depth || 0 ) + 1 ;
while ( config . queue . length && ! config . blocking ) {
if ( ! defined . setTimeout || config . updateRate <= 0 || now ( ) - start < config . updateRate ) {
if ( config . current ) {
// Reset async tracking for each phase of the Test lifecycle
config . current . usedAsync = false ;
}
config . queue . shift ( ) ( ) ;
} else {
setTimeout ( next , 13 ) ;
break ;
}
}
config . depth -- ;
if ( last && ! config . blocking && ! config . queue . length && config . depth === 0 ) {
done ( ) ;
}
}
function done ( ) {
var runtime ,
passed ,
i ,
key ,
storage = config . storage ;
internalState . autorun = true ;
runtime = now ( ) - config . started ;
passed = config . stats . all - config . stats . bad ;
2017-03-20 06:07:18 +01:00
emit ( "runEnd" , globalSuite . end ( true ) ) ;
2017-02-05 13:39:16 +01:00
runLoggingCallbacks ( "done" , {
failed : config . stats . bad ,
passed : passed ,
total : config . stats . all ,
runtime : runtime
} ) ;
// Clear own storage items if all tests passed
if ( storage && config . stats . bad === 0 ) {
for ( i = storage . length - 1 ; i >= 0 ; i -- ) {
key = storage . key ( i ) ;
if ( key . indexOf ( "qunit-test-" ) === 0 ) {
storage . removeItem ( key ) ;
}
}
}
}
function setHook ( module , hookName ) {
if ( module . testEnvironment === undefined ) {
module . testEnvironment = { } ;
}
return function ( callback ) {
module . testEnvironment [ hookName ] = callback ;
} ;
}
exportQUnit ( QUnit ) ;
( function ( ) {
if ( typeof window === "undefined" || typeof document === "undefined" ) {
return ;
}
var config = QUnit . config ,
hasOwn = Object . prototype . hasOwnProperty ;
// Stores fixture HTML for resetting later
function storeFixture ( ) {
// Avoid overwriting user-defined values
if ( hasOwn . call ( config , "fixture" ) ) {
return ;
}
var fixture = document . getElementById ( "qunit-fixture" ) ;
if ( fixture ) {
config . fixture = fixture . innerHTML ;
}
}
QUnit . begin ( storeFixture ) ;
// Resets the fixture DOM element if available.
function resetFixture ( ) {
if ( config . fixture == null ) {
return ;
}
var fixture = document . getElementById ( "qunit-fixture" ) ;
if ( fixture ) {
fixture . innerHTML = config . fixture ;
}
}
QUnit . testStart ( resetFixture ) ;
} ) ( ) ;
( function ( ) {
// Only interact with URLs via window.location
var location = typeof window !== "undefined" && window . location ;
if ( ! location ) {
return ;
}
var urlParams = getUrlParams ( ) ;
QUnit . urlParams = urlParams ;
// Match module/test by inclusion in an array
QUnit . config . moduleId = [ ] . concat ( urlParams . moduleId || [ ] ) ;
QUnit . config . testId = [ ] . concat ( urlParams . testId || [ ] ) ;
// Exact case-insensitive match of the module name
QUnit . config . module = urlParams . module ;
// Regular expression or case-insenstive substring match against "moduleName: testName"
QUnit . config . filter = urlParams . filter ;
// Test order randomization
if ( urlParams . seed === true ) {
// Generate a random seed if the option is specified without a value
QUnit . config . seed = Math . random ( ) . toString ( 36 ) . slice ( 2 ) ;
} else if ( urlParams . seed ) {
QUnit . config . seed = urlParams . seed ;
}
// Add URL-parameter-mapped config values with UI form rendering data
QUnit . config . urlConfig . push ( {
id : "hidepassed" ,
label : "Hide passed tests" ,
tooltip : "Only show tests and assertions that fail. Stored as query-strings."
} , {
id : "noglobals" ,
label : "Check for Globals" ,
tooltip : "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
} , {
id : "notrycatch" ,
label : "No try-catch" ,
tooltip : "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
} ) ;
QUnit . begin ( function ( ) {
var i ,
option ,
urlConfig = QUnit . config . urlConfig ;
for ( i = 0 ; i < urlConfig . length ; i ++ ) {
// Options can be either strings or objects with nonempty "id" properties
option = QUnit . config . urlConfig [ i ] ;
if ( typeof option !== "string" ) {
option = option . id ;
}
if ( QUnit . config [ option ] === undefined ) {
QUnit . config [ option ] = urlParams [ option ] ;
}
}
} ) ;
function getUrlParams ( ) {
var i , param , name , value ;
var urlParams = Object . create ( null ) ;
var params = location . search . slice ( 1 ) . split ( "&" ) ;
var length = params . length ;
for ( i = 0 ; i < length ; i ++ ) {
if ( params [ i ] ) {
param = params [ i ] . split ( "=" ) ;
name = decodeQueryParam ( param [ 0 ] ) ;
// Allow just a key to turn on a flag, e.g., test.html?noglobals
value = param . length === 1 || decodeQueryParam ( param . slice ( 1 ) . join ( "=" ) ) ;
if ( name in urlParams ) {
urlParams [ name ] = [ ] . concat ( urlParams [ name ] , value ) ;
} else {
urlParams [ name ] = value ;
}
}
}
return urlParams ;
}
function decodeQueryParam ( param ) {
return decodeURIComponent ( param . replace ( /\+/g , "%20" ) ) ;
}
} ) ( ) ;
2017-03-20 06:07:18 +01:00
var stats = {
passedTests : 0 ,
failedTests : 0 ,
skippedTests : 0 ,
todoTests : 0
} ;
2017-02-05 13:39:16 +01:00
// Escape text for attribute or text content.
function escapeText ( s ) {
if ( ! s ) {
return "" ;
}
s = s + "" ;
// Both single quotes and double quotes (for attributes)
return s . replace ( /['"<>&]/g , function ( s ) {
switch ( s ) {
case "'" :
return "'" ;
case "\"" :
return """ ;
case "<" :
return "<" ;
case ">" :
return ">" ;
case "&" :
return "&" ;
}
} ) ;
}
( function ( ) {
// Don't load the HTML Reporter on non-browser environments
if ( typeof window === "undefined" || ! window . document ) {
return ;
}
var config = QUnit . config ,
document $$1 = window . document ,
collapseNext = false ,
hasOwn = Object . prototype . hasOwnProperty ,
unfilteredUrl = setUrl ( { filter : undefined , module : undefined ,
moduleId : undefined , testId : undefined } ) ,
modulesList = [ ] ;
function addEvent ( elem , type , fn ) {
elem . addEventListener ( type , fn , false ) ;
}
function removeEvent ( elem , type , fn ) {
elem . removeEventListener ( type , fn , false ) ;
}
function addEvents ( elems , type , fn ) {
var i = elems . length ;
while ( i -- ) {
addEvent ( elems [ i ] , type , fn ) ;
}
}
function hasClass ( elem , name ) {
return ( " " + elem . className + " " ) . indexOf ( " " + name + " " ) >= 0 ;
}
function addClass ( elem , name ) {
if ( ! hasClass ( elem , name ) ) {
elem . className += ( elem . className ? " " : "" ) + name ;
}
}
function toggleClass ( elem , name , force ) {
if ( force || typeof force === "undefined" && ! hasClass ( elem , name ) ) {
addClass ( elem , name ) ;
} else {
removeClass ( elem , name ) ;
}
}
function removeClass ( elem , name ) {
var set = " " + elem . className + " " ;
// Class name may appear multiple times
while ( set . indexOf ( " " + name + " " ) >= 0 ) {
set = set . replace ( " " + name + " " , " " ) ;
}
// Trim for prettiness
elem . className = typeof set . trim === "function" ? set . trim ( ) : set . replace ( /^\s+|\s+$/g , "" ) ;
}
function id ( name ) {
return document $$1 . getElementById && document $$1 . getElementById ( name ) ;
}
function abortTests ( ) {
var abortButton = id ( "qunit-abort-tests-button" ) ;
if ( abortButton ) {
abortButton . disabled = true ;
abortButton . innerHTML = "Aborting..." ;
}
QUnit . config . queue . length = 0 ;
return false ;
}
function interceptNavigation ( ev ) {
applyUrlParams ( ) ;
if ( ev && ev . preventDefault ) {
ev . preventDefault ( ) ;
}
return false ;
}
function getUrlConfigHtml ( ) {
var i ,
j ,
val ,
escaped ,
escapedTooltip ,
selection = false ,
urlConfig = config . urlConfig ,
urlConfigHtml = "" ;
for ( i = 0 ; i < urlConfig . length ; i ++ ) {
// Options can be either strings or objects with nonempty "id" properties
val = config . urlConfig [ i ] ;
if ( typeof val === "string" ) {
val = {
id : val ,
label : val
} ;
}
escaped = escapeText ( val . id ) ;
escapedTooltip = escapeText ( val . tooltip ) ;
if ( ! val . value || typeof val . value === "string" ) {
urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'><input id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' type='checkbox'" + ( val . value ? " value='" + escapeText ( val . value ) + "'" : "" ) + ( config [ val . id ] ? " checked='checked'" : "" ) + " title='" + escapedTooltip + "' />" + escapeText ( val . label ) + "</label>" ;
} else {
urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val . label + ": </label><select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>" ;
if ( QUnit . is ( "array" , val . value ) ) {
for ( j = 0 ; j < val . value . length ; j ++ ) {
escaped = escapeText ( val . value [ j ] ) ;
urlConfigHtml += "<option value='" + escaped + "'" + ( config [ val . id ] === val . value [ j ] ? ( selection = true ) && " selected='selected'" : "" ) + ">" + escaped + "</option>" ;
}
} else {
for ( j in val . value ) {
if ( hasOwn . call ( val . value , j ) ) {
urlConfigHtml += "<option value='" + escapeText ( j ) + "'" + ( config [ val . id ] === j ? ( selection = true ) && " selected='selected'" : "" ) + ">" + escapeText ( val . value [ j ] ) + "</option>" ;
}
}
}
if ( config [ val . id ] && ! selection ) {
escaped = escapeText ( config [ val . id ] ) ;
urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + "</option>" ;
}
urlConfigHtml += "</select>" ;
}
}
return urlConfigHtml ;
}
// Handle "click" events on toolbar checkboxes and "change" for select menus.
// Updates the URL with the new state of `config.urlConfig` values.
function toolbarChanged ( ) {
var updatedUrl ,
value ,
tests ,
field = this ,
params = { } ;
// Detect if field is a select menu or a checkbox
if ( "selectedIndex" in field ) {
value = field . options [ field . selectedIndex ] . value || undefined ;
} else {
value = field . checked ? field . defaultValue || true : undefined ;
}
params [ field . name ] = value ;
updatedUrl = setUrl ( params ) ;
// Check if we can apply the change without a page refresh
if ( "hidepassed" === field . name && "replaceState" in window . history ) {
QUnit . urlParams [ field . name ] = value ;
config [ field . name ] = value || false ;
tests = id ( "qunit-tests" ) ;
if ( tests ) {
toggleClass ( tests , "hidepass" , value || false ) ;
}
window . history . replaceState ( null , "" , updatedUrl ) ;
} else {
window . location = updatedUrl ;
}
}
function setUrl ( params ) {
var key ,
arrValue ,
i ,
querystring = "?" ,
location = window . location ;
params = QUnit . extend ( QUnit . extend ( { } , QUnit . urlParams ) , params ) ;
for ( key in params ) {
// Skip inherited or undefined properties
if ( hasOwn . call ( params , key ) && params [ key ] !== undefined ) {
// Output a parameter for each value of this key (but usually just one)
arrValue = [ ] . concat ( params [ key ] ) ;
for ( i = 0 ; i < arrValue . length ; i ++ ) {
querystring += encodeURIComponent ( key ) ;
if ( arrValue [ i ] !== true ) {
querystring += "=" + encodeURIComponent ( arrValue [ i ] ) ;
}
querystring += "&" ;
}
}
}
return location . protocol + "//" + location . host + location . pathname + querystring . slice ( 0 , - 1 ) ;
}
function applyUrlParams ( ) {
var i ,
selectedModules = [ ] ,
modulesList = id ( "qunit-modulefilter-dropdown-list" ) . getElementsByTagName ( "input" ) ,
filter = id ( "qunit-filter-input" ) . value ;
for ( i = 0 ; i < modulesList . length ; i ++ ) {
if ( modulesList [ i ] . checked ) {
selectedModules . push ( modulesList [ i ] . value ) ;
}
}
window . location = setUrl ( {
filter : filter === "" ? undefined : filter ,
moduleId : selectedModules . length === 0 ? undefined : selectedModules ,
// Remove module and testId filter
module : undefined ,
testId : undefined
} ) ;
}
function toolbarUrlConfigContainer ( ) {
var urlConfigContainer = document $$1 . createElement ( "span" ) ;
urlConfigContainer . innerHTML = getUrlConfigHtml ( ) ;
addClass ( urlConfigContainer , "qunit-url-config" ) ;
addEvents ( urlConfigContainer . getElementsByTagName ( "input" ) , "change" , toolbarChanged ) ;
addEvents ( urlConfigContainer . getElementsByTagName ( "select" ) , "change" , toolbarChanged ) ;
return urlConfigContainer ;
}
function abortTestsButton ( ) {
var button = document $$1 . createElement ( "button" ) ;
button . id = "qunit-abort-tests-button" ;
button . innerHTML = "Abort" ;
addEvent ( button , "click" , abortTests ) ;
return button ;
}
function toolbarLooseFilter ( ) {
var filter = document $$1 . createElement ( "form" ) ,
label = document $$1 . createElement ( "label" ) ,
input = document $$1 . createElement ( "input" ) ,
button = document $$1 . createElement ( "button" ) ;
addClass ( filter , "qunit-filter" ) ;
label . innerHTML = "Filter: " ;
input . type = "text" ;
input . value = config . filter || "" ;
input . name = "filter" ;
input . id = "qunit-filter-input" ;
button . innerHTML = "Go" ;
label . appendChild ( input ) ;
filter . appendChild ( label ) ;
filter . appendChild ( document $$1 . createTextNode ( " " ) ) ;
filter . appendChild ( button ) ;
addEvent ( filter , "submit" , interceptNavigation ) ;
return filter ;
}
function moduleListHtml ( ) {
var i ,
checked ,
html = "" ;
for ( i = 0 ; i < config . modules . length ; i ++ ) {
if ( config . modules [ i ] . name !== "" ) {
checked = config . moduleId . indexOf ( config . modules [ i ] . moduleId ) > - 1 ;
html += "<li><label class='clickable" + ( checked ? " checked" : "" ) + "'><input type='checkbox' " + "value='" + config . modules [ i ] . moduleId + "'" + ( checked ? " checked='checked'" : "" ) + " />" + escapeText ( config . modules [ i ] . name ) + "</label></li>" ;
}
}
return html ;
}
function toolbarModuleFilter ( ) {
var allCheckbox ,
commit ,
reset ,
moduleFilter = document $$1 . createElement ( "form" ) ,
label = document $$1 . createElement ( "label" ) ,
moduleSearch = document $$1 . createElement ( "input" ) ,
dropDown = document $$1 . createElement ( "div" ) ,
actions = document $$1 . createElement ( "span" ) ,
dropDownList = document $$1 . createElement ( "ul" ) ,
dirty = false ;
moduleSearch . id = "qunit-modulefilter-search" ;
addEvent ( moduleSearch , "input" , searchInput ) ;
addEvent ( moduleSearch , "input" , searchFocus ) ;
addEvent ( moduleSearch , "focus" , searchFocus ) ;
addEvent ( moduleSearch , "click" , searchFocus ) ;
label . id = "qunit-modulefilter-search-container" ;
label . innerHTML = "Module: " ;
label . appendChild ( moduleSearch ) ;
actions . id = "qunit-modulefilter-actions" ;
actions . innerHTML = "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + ( config . moduleId . length ? "" : " checked" ) + "'><input type='checkbox'" + ( config . moduleId . length ? "" : " checked='checked'" ) + ">All modules</label>" ;
allCheckbox = actions . lastChild . firstChild ;
commit = actions . firstChild ;
reset = commit . nextSibling ;
addEvent ( commit , "click" , applyUrlParams ) ;
dropDownList . id = "qunit-modulefilter-dropdown-list" ;
dropDownList . innerHTML = moduleListHtml ( ) ;
dropDown . id = "qunit-modulefilter-dropdown" ;
dropDown . style . display = "none" ;
dropDown . appendChild ( actions ) ;
dropDown . appendChild ( dropDownList ) ;
addEvent ( dropDown , "change" , selectionChange ) ;
selectionChange ( ) ;
moduleFilter . id = "qunit-modulefilter" ;
moduleFilter . appendChild ( label ) ;
moduleFilter . appendChild ( dropDown ) ;
addEvent ( moduleFilter , "submit" , interceptNavigation ) ;
addEvent ( moduleFilter , "reset" , function ( ) {
// Let the reset happen, then update styles
window . setTimeout ( selectionChange ) ;
} ) ;
// Enables show/hide for the dropdown
function searchFocus ( ) {
if ( dropDown . style . display !== "none" ) {
return ;
}
dropDown . style . display = "block" ;
addEvent ( document $$1 , "click" , hideHandler ) ;
addEvent ( document $$1 , "keydown" , hideHandler ) ;
// Hide on Escape keydown or outside-container click
function hideHandler ( e ) {
var inContainer = moduleFilter . contains ( e . target ) ;
if ( e . keyCode === 27 || ! inContainer ) {
if ( e . keyCode === 27 && inContainer ) {
moduleSearch . focus ( ) ;
}
dropDown . style . display = "none" ;
removeEvent ( document $$1 , "click" , hideHandler ) ;
removeEvent ( document $$1 , "keydown" , hideHandler ) ;
moduleSearch . value = "" ;
searchInput ( ) ;
}
}
}
// Processes module search box input
function searchInput ( ) {
var i ,
item ,
searchText = moduleSearch . value . toLowerCase ( ) ,
listItems = dropDownList . children ;
for ( i = 0 ; i < listItems . length ; i ++ ) {
item = listItems [ i ] ;
if ( ! searchText || item . textContent . toLowerCase ( ) . indexOf ( searchText ) > - 1 ) {
item . style . display = "" ;
} else {
item . style . display = "none" ;
}
}
}
// Processes selection changes
function selectionChange ( evt ) {
var i ,
item ,
checkbox = evt && evt . target || allCheckbox ,
modulesList = dropDownList . getElementsByTagName ( "input" ) ,
selectedNames = [ ] ;
toggleClass ( checkbox . parentNode , "checked" , checkbox . checked ) ;
dirty = false ;
if ( checkbox . checked && checkbox !== allCheckbox ) {
allCheckbox . checked = false ;
removeClass ( allCheckbox . parentNode , "checked" ) ;
}
for ( i = 0 ; i < modulesList . length ; i ++ ) {
item = modulesList [ i ] ;
if ( ! evt ) {
toggleClass ( item . parentNode , "checked" , item . checked ) ;
} else if ( checkbox === allCheckbox && checkbox . checked ) {
item . checked = false ;
removeClass ( item . parentNode , "checked" ) ;
}
dirty = dirty || item . checked !== item . defaultChecked ;
if ( item . checked ) {
selectedNames . push ( item . parentNode . textContent ) ;
}
}
commit . style . display = reset . style . display = dirty ? "" : "none" ;
moduleSearch . placeholder = selectedNames . join ( ", " ) || allCheckbox . parentNode . textContent ;
moduleSearch . title = "Type to filter list. Current selection:\n" + ( selectedNames . join ( "\n" ) || allCheckbox . parentNode . textContent ) ;
}
return moduleFilter ;
}
function appendToolbar ( ) {
var toolbar = id ( "qunit-testrunner-toolbar" ) ;
if ( toolbar ) {
toolbar . appendChild ( toolbarUrlConfigContainer ( ) ) ;
toolbar . appendChild ( toolbarModuleFilter ( ) ) ;
toolbar . appendChild ( toolbarLooseFilter ( ) ) ;
toolbar . appendChild ( document $$1 . createElement ( "div" ) ) . className = "clearfix" ;
}
}
function appendHeader ( ) {
var header = id ( "qunit-header" ) ;
if ( header ) {
header . innerHTML = "<a href='" + escapeText ( unfilteredUrl ) + "'>" + header . innerHTML + "</a> " ;
}
}
function appendBanner ( ) {
var banner = id ( "qunit-banner" ) ;
if ( banner ) {
banner . className = "" ;
}
}
function appendTestResults ( ) {
var tests = id ( "qunit-tests" ) ,
result = id ( "qunit-testresult" ) ,
controls ;
if ( result ) {
result . parentNode . removeChild ( result ) ;
}
if ( tests ) {
tests . innerHTML = "" ;
result = document $$1 . createElement ( "p" ) ;
result . id = "qunit-testresult" ;
result . className = "result" ;
tests . parentNode . insertBefore ( result , tests ) ;
result . innerHTML = "<div id=\"qunit-testresult-display\">Running...<br /> </div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>" ;
controls = id ( "qunit-testresult-controls" ) ;
}
if ( controls ) {
controls . appendChild ( abortTestsButton ( ) ) ;
}
}
function appendFilteredTest ( ) {
var testId = QUnit . config . testId ;
if ( ! testId || testId . length <= 0 ) {
return "" ;
}
return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText ( testId . join ( ", " ) ) + " <a id='qunit-clearFilter' href='" + escapeText ( unfilteredUrl ) + "'>Run all tests</a></div>" ;
}
function appendUserAgent ( ) {
var userAgent = id ( "qunit-userAgent" ) ;
if ( userAgent ) {
userAgent . innerHTML = "" ;
userAgent . appendChild ( document $$1 . createTextNode ( "QUnit " + QUnit . version + "; " + navigator . userAgent ) ) ;
}
}
function appendInterface ( ) {
var qunit = id ( "qunit" ) ;
if ( qunit ) {
qunit . innerHTML = "<h1 id='qunit-header'>" + escapeText ( document $$1 . title ) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest ( ) + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>" ;
}
appendHeader ( ) ;
appendBanner ( ) ;
appendTestResults ( ) ;
appendUserAgent ( ) ;
appendToolbar ( ) ;
}
function appendTestsList ( modules ) {
var i , l , x , z , test , moduleObj ;
for ( i = 0 , l = modules . length ; i < l ; i ++ ) {
moduleObj = modules [ i ] ;
for ( x = 0 , z = moduleObj . tests . length ; x < z ; x ++ ) {
test = moduleObj . tests [ x ] ;
appendTest ( test . name , test . testId , moduleObj . name ) ;
}
}
}
function appendTest ( name , testId , moduleName ) {
var title ,
rerunTrigger ,
testBlock ,
assertList ,
tests = id ( "qunit-tests" ) ;
if ( ! tests ) {
return ;
}
title = document $$1 . createElement ( "strong" ) ;
title . innerHTML = getNameHtml ( name , moduleName ) ;
rerunTrigger = document $$1 . createElement ( "a" ) ;
rerunTrigger . innerHTML = "Rerun" ;
rerunTrigger . href = setUrl ( { testId : testId } ) ;
testBlock = document $$1 . createElement ( "li" ) ;
testBlock . appendChild ( title ) ;
testBlock . appendChild ( rerunTrigger ) ;
testBlock . id = "qunit-test-output-" + testId ;
assertList = document $$1 . createElement ( "ol" ) ;
assertList . className = "qunit-assert-list" ;
testBlock . appendChild ( assertList ) ;
tests . appendChild ( testBlock ) ;
}
// HTML Reporter initialization and load
QUnit . begin ( function ( details ) {
var i , moduleObj , tests ;
// Sort modules by name for the picker
for ( i = 0 ; i < details . modules . length ; i ++ ) {
moduleObj = details . modules [ i ] ;
if ( moduleObj . name ) {
modulesList . push ( moduleObj . name ) ;
}
}
modulesList . sort ( function ( a , b ) {
return a . localeCompare ( b ) ;
} ) ;
// Initialize QUnit elements
appendInterface ( ) ;
appendTestsList ( details . modules ) ;
tests = id ( "qunit-tests" ) ;
if ( tests && config . hidepassed ) {
addClass ( tests , "hidepass" ) ;
}
} ) ;
QUnit . done ( function ( details ) {
var banner = id ( "qunit-banner" ) ,
tests = id ( "qunit-tests" ) ,
abortButton = id ( "qunit-abort-tests-button" ) ,
2017-03-20 06:07:18 +01:00
totalTests = stats . passedTests + stats . skippedTests + stats . todoTests + stats . failedTests ,
html = [ totalTests , " tests completed in " , details . runtime , " milliseconds, with " , stats . failedTests , " failed, " , stats . skippedTests , " skipped, and " , stats . todoTests , " todo.<br />" , "<span class='passed'>" , details . passed , "</span> assertions of <span class='total'>" , details . total , "</span> passed, <span class='failed'>" , details . failed , "</span> failed." ] . join ( "" ) ,
2017-02-05 13:39:16 +01:00
test ,
assertLi ,
assertList ;
// Update remaing tests to aborted
if ( abortButton && abortButton . disabled ) {
html = "Tests aborted after " + details . runtime + " milliseconds." ;
for ( var i = 0 ; i < tests . children . length ; i ++ ) {
test = tests . children [ i ] ;
if ( test . className === "" || test . className === "running" ) {
test . className = "aborted" ;
assertList = test . getElementsByTagName ( "ol" ) [ 0 ] ;
assertLi = document $$1 . createElement ( "li" ) ;
assertLi . className = "fail" ;
assertLi . innerHTML = "Test aborted." ;
assertList . appendChild ( assertLi ) ;
}
}
}
if ( banner && ( ! abortButton || abortButton . disabled === false ) ) {
2017-03-20 06:07:18 +01:00
banner . className = stats . failedTests ? "qunit-fail" : "qunit-pass" ;
2017-02-05 13:39:16 +01:00
}
if ( abortButton ) {
abortButton . parentNode . removeChild ( abortButton ) ;
}
if ( tests ) {
id ( "qunit-testresult-display" ) . innerHTML = html ;
}
if ( config . altertitle && document $$1 . title ) {
// Show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
2017-03-20 06:07:18 +01:00
document $$1 . title = [ stats . failedTests ? "\u2716" : "\u2714" , document $$1 . title . replace ( /^[\u2714\u2716] /i , "" ) ] . join ( " " ) ;
2017-02-05 13:39:16 +01:00
}
// Scroll back to top to show results
if ( config . scrolltop && window . scrollTo ) {
window . scrollTo ( 0 , 0 ) ;
}
} ) ;
function getNameHtml ( name , module ) {
var nameHtml = "" ;
if ( module ) {
nameHtml = "<span class='module-name'>" + escapeText ( module ) + "</span>: " ;
}
nameHtml += "<span class='test-name'>" + escapeText ( name ) + "</span>" ;
return nameHtml ;
}
QUnit . testStart ( function ( details ) {
var running , testBlock , bad ;
testBlock = id ( "qunit-test-output-" + details . testId ) ;
if ( testBlock ) {
testBlock . className = "running" ;
} else {
// Report later registered tests
appendTest ( details . name , details . testId , details . module ) ;
}
running = id ( "qunit-testresult-display" ) ;
if ( running ) {
bad = QUnit . config . reorder && details . previousFailure ;
running . innerHTML = ( bad ? "Rerunning previously failed test: <br />" : "Running: <br />" ) + getNameHtml ( details . name , details . module ) ;
}
} ) ;
function stripHtml ( string ) {
// Strip tags, html entity and whitespaces
return string . replace ( /<\/?[^>]+(>|$)/g , "" ) . replace ( /\"/g , "" ) . replace ( /\s+/g , "" ) ;
}
QUnit . log ( function ( details ) {
var assertList ,
assertLi ,
message ,
expected ,
actual ,
diff ,
showDiff = false ,
testItem = id ( "qunit-test-output-" + details . testId ) ;
if ( ! testItem ) {
return ;
}
message = escapeText ( details . message ) || ( details . result ? "okay" : "failed" ) ;
message = "<span class='test-message'>" + message + "</span>" ;
message += "<span class='runtime'>@ " + details . runtime + " ms</span>" ;
// The pushFailure doesn't provide details.expected
// when it calls, it's implicit to also not show expected and diff stuff
// Also, we need to check details.expected existence, as it can exist and be undefined
if ( ! details . result && hasOwn . call ( details , "expected" ) ) {
if ( details . negative ) {
expected = "NOT " + QUnit . dump . parse ( details . expected ) ;
} else {
expected = QUnit . dump . parse ( details . expected ) ;
}
actual = QUnit . dump . parse ( details . actual ) ;
message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText ( expected ) + "</pre></td></tr>" ;
if ( actual !== expected ) {
message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText ( actual ) + "</pre></td></tr>" ;
// Don't show diff if actual or expected are booleans
if ( ! /^(true|false)$/ . test ( actual ) && ! /^(true|false)$/ . test ( expected ) ) {
diff = QUnit . diff ( expected , actual ) ;
showDiff = stripHtml ( diff ) . length !== stripHtml ( expected ) . length + stripHtml ( actual ) . length ;
}
// Don't show diff if expected and actual are totally different
if ( showDiff ) {
message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>" ;
}
} else if ( expected . indexOf ( "[object Array]" ) !== - 1 || expected . indexOf ( "[object Object]" ) !== - 1 ) {
message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the depth of object is more than current max depth (" + QUnit . config . maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " + " run with a higher max depth or <a href='" + escapeText ( setUrl ( { maxDepth : - 1 } ) ) + "'>" + "Rerun</a> without max depth.</p></td></tr>" ;
} else {
message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>" ;
}
if ( details . source ) {
message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText ( details . source ) + "</pre></td></tr>" ;
}
message += "</table>" ;
// This occurs when pushFailure is set and we have an extracted stack trace
} else if ( ! details . result && details . source ) {
message += "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText ( details . source ) + "</pre></td></tr>" + "</table>" ;
}
assertList = testItem . getElementsByTagName ( "ol" ) [ 0 ] ;
assertLi = document $$1 . createElement ( "li" ) ;
assertLi . className = details . result ? "pass" : "fail" ;
assertLi . innerHTML = message ;
assertList . appendChild ( assertLi ) ;
} ) ;
QUnit . testDone ( function ( details ) {
var testTitle ,
time ,
testItem ,
assertList ,
good ,
bad ,
testCounts ,
skipped ,
sourceName ,
tests = id ( "qunit-tests" ) ;
if ( ! tests ) {
return ;
}
testItem = id ( "qunit-test-output-" + details . testId ) ;
assertList = testItem . getElementsByTagName ( "ol" ) [ 0 ] ;
good = details . passed ;
bad = details . failed ;
2017-03-20 06:07:18 +01:00
// This test passed if it has no unexpected failed assertions
var testPassed = details . failed > 0 ? details . todo : ! details . todo ;
if ( testPassed ) {
2017-02-05 13:39:16 +01:00
// Collapse the passing tests
addClass ( assertList , "qunit-collapsed" ) ;
} else if ( config . collapse ) {
if ( ! collapseNext ) {
// Skip collapsing the first failing test
collapseNext = true ;
} else {
// Collapse remaining tests
addClass ( assertList , "qunit-collapsed" ) ;
}
}
// The testItem.firstChild is the test name
testTitle = testItem . firstChild ;
testCounts = bad ? "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : "" ;
testTitle . innerHTML += " <b class='counts'>(" + testCounts + details . assertions . length + ")</b>" ;
if ( details . skipped ) {
2017-03-20 06:07:18 +01:00
stats . skippedTests ++ ;
2017-02-05 13:39:16 +01:00
testItem . className = "skipped" ;
skipped = document $$1 . createElement ( "em" ) ;
skipped . className = "qunit-skipped-label" ;
skipped . innerHTML = "skipped" ;
testItem . insertBefore ( skipped , testTitle ) ;
} else {
addEvent ( testTitle , "click" , function ( ) {
toggleClass ( assertList , "qunit-collapsed" ) ;
} ) ;
2017-03-20 06:07:18 +01:00
testItem . className = testPassed ? "pass" : "fail" ;
if ( details . todo ) {
var todoLabel = document $$1 . createElement ( "em" ) ;
todoLabel . className = "qunit-todo-label" ;
todoLabel . innerHTML = "todo" ;
testItem . insertBefore ( todoLabel , testTitle ) ;
}
2017-02-05 13:39:16 +01:00
time = document $$1 . createElement ( "span" ) ;
time . className = "runtime" ;
time . innerHTML = details . runtime + " ms" ;
testItem . insertBefore ( time , assertList ) ;
2017-03-20 06:07:18 +01:00
if ( ! testPassed ) {
stats . failedTests ++ ;
} else if ( details . todo ) {
stats . todoTests ++ ;
} else {
stats . passedTests ++ ;
}
2017-02-05 13:39:16 +01:00
}
// Show the source of the test when showing assertions
if ( details . source ) {
sourceName = document $$1 . createElement ( "p" ) ;
sourceName . innerHTML = "<strong>Source: </strong>" + details . source ;
addClass ( sourceName , "qunit-source" ) ;
2017-03-20 06:07:18 +01:00
if ( testPassed ) {
2017-02-05 13:39:16 +01:00
addClass ( sourceName , "qunit-collapsed" ) ;
}
addEvent ( testTitle , "click" , function ( ) {
toggleClass ( sourceName , "qunit-collapsed" ) ;
} ) ;
testItem . appendChild ( sourceName ) ;
}
} ) ;
// Avoid readyState issue with phantomjs
// Ref: #818
var notPhantom = function ( p ) {
return ! ( p && p . version && p . version . major > 0 ) ;
} ( window . phantom ) ;
if ( notPhantom && document $$1 . readyState === "complete" ) {
QUnit . load ( ) ;
} else {
addEvent ( window , "load" , QUnit . load ) ;
}
2017-03-20 06:07:18 +01:00
// Wrap window.onerror. We will call the original window.onerror to see if
// the existing handler fully handles the error; if not, we will call the
// QUnit.onError function.
var originalWindowOnError = window . onerror ;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window . onerror = function ( message , fileName , lineNumber ) {
var ret = false ;
if ( originalWindowOnError ) {
for ( var _len = arguments . length , args = Array ( _len > 3 ? _len - 3 : 0 ) , _key = 3 ; _key < _len ; _key ++ ) {
args [ _key - 3 ] = arguments [ _key ] ;
}
ret = originalWindowOnError . call . apply ( originalWindowOnError , [ this , message , fileName , lineNumber ] . concat ( args ) ) ;
}
// Treat return value as window.onerror itself does,
// Only do our handling if not suppressed.
if ( ret !== true ) {
var error = {
message : message ,
fileName : fileName ,
lineNumber : lineNumber
} ;
ret = QUnit . onError ( error ) ;
}
return ret ;
} ;
2017-02-05 13:39:16 +01:00
} ) ( ) ;
/ *
* This file is a modified version of google - diff - match - patch ' s JavaScript implementation
* ( https : //code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
* modifications are licensed as more fully set forth in LICENSE . txt .
*
* The original source of google - diff - match - patch is attributable and licensed as follows :
*
* Copyright 2006 Google Inc .
* https : //code.google.com/p/google-diff-match-patch/
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* https : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*
* More Info :
* https : //code.google.com/p/google-diff-match-patch/
*
* Usage : QUnit . diff ( expected , actual )
*
* /
QUnit . diff = function ( ) {
function DiffMatchPatch ( ) { }
// DIFF FUNCTIONS
/ * *
* The data structure representing a diff is an array of tuples :
* [ [ DIFF _DELETE , 'Hello' ] , [ DIFF _INSERT , 'Goodbye' ] , [ DIFF _EQUAL , ' world.' ] ]
* which means : delete 'Hello' , add 'Goodbye' and keep ' world.'
* /
var DIFF _DELETE = - 1 ,
DIFF _INSERT = 1 ,
DIFF _EQUAL = 0 ;
/ * *
* Find the differences between two texts . Simplifies the problem by stripping
* any common prefix or suffix off the texts before diffing .
* @ param { string } text1 Old string to be diffed .
* @ param { string } text2 New string to be diffed .
* @ param { boolean = } optChecklines Optional speedup flag . If present and false ,
* then don ' t run a line - level diff first to identify the changed areas .
* Defaults to true , which does a faster , slightly less optimal diff .
* @ return { ! Array . < ! DiffMatchPatch . Diff > } Array of diff tuples .
* /
DiffMatchPatch . prototype . DiffMain = function ( text1 , text2 , optChecklines ) {
var deadline , checklines , commonlength , commonprefix , commonsuffix , diffs ;
// The diff must be complete in up to 1 second.
deadline = new Date ( ) . getTime ( ) + 1000 ;
// Check for null inputs.
if ( text1 === null || text2 === null ) {
throw new Error ( "Null input. (DiffMain)" ) ;
}
// Check for equality (speedup).
if ( text1 === text2 ) {
if ( text1 ) {
return [ [ DIFF _EQUAL , text1 ] ] ;
}
return [ ] ;
}
if ( typeof optChecklines === "undefined" ) {
optChecklines = true ;
}
checklines = optChecklines ;
// Trim off common prefix (speedup).
commonlength = this . diffCommonPrefix ( text1 , text2 ) ;
commonprefix = text1 . substring ( 0 , commonlength ) ;
text1 = text1 . substring ( commonlength ) ;
text2 = text2 . substring ( commonlength ) ;
// Trim off common suffix (speedup).
commonlength = this . diffCommonSuffix ( text1 , text2 ) ;
commonsuffix = text1 . substring ( text1 . length - commonlength ) ;
text1 = text1 . substring ( 0 , text1 . length - commonlength ) ;
text2 = text2 . substring ( 0 , text2 . length - commonlength ) ;
// Compute the diff on the middle block.
diffs = this . diffCompute ( text1 , text2 , checklines , deadline ) ;
// Restore the prefix and suffix.
if ( commonprefix ) {
diffs . unshift ( [ DIFF _EQUAL , commonprefix ] ) ;
}
if ( commonsuffix ) {
diffs . push ( [ DIFF _EQUAL , commonsuffix ] ) ;
}
this . diffCleanupMerge ( diffs ) ;
return diffs ;
} ;
/ * *
* Reduce the number of edits by eliminating operationally trivial equalities .
* @ param { ! Array . < ! DiffMatchPatch . Diff > } diffs Array of diff tuples .
* /
DiffMatchPatch . prototype . diffCleanupEfficiency = function ( diffs ) {
var changes , equalities , equalitiesLength , lastequality , pointer , preIns , preDel , postIns , postDel ;
changes = false ;
equalities = [ ] ; // Stack of indices where equalities are found.
equalitiesLength = 0 ; // Keeping our own length var is faster in JS.
/** @type {?string} */
lastequality = null ;
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
pointer = 0 ; // Index of current position.
// Is there an insertion operation before the last equality.
preIns = false ;
// Is there a deletion operation before the last equality.
preDel = false ;
// Is there an insertion operation after the last equality.
postIns = false ;
// Is there a deletion operation after the last equality.
postDel = false ;
while ( pointer < diffs . length ) {
// Equality found.
if ( diffs [ pointer ] [ 0 ] === DIFF _EQUAL ) {
if ( diffs [ pointer ] [ 1 ] . length < 4 && ( postIns || postDel ) ) {
// Candidate found.
equalities [ equalitiesLength ++ ] = pointer ;
preIns = postIns ;
preDel = postDel ;
lastequality = diffs [ pointer ] [ 1 ] ;
} else {
// Not a candidate, and can never become one.
equalitiesLength = 0 ;
lastequality = null ;
}
postIns = postDel = false ;
// An insertion or deletion.
} else {
if ( diffs [ pointer ] [ 0 ] === DIFF _DELETE ) {
postDel = true ;
} else {
postIns = true ;
}
/ *
* Five types to be split :
* < ins > A < / i n s > < d e l > B < / d e l > X Y < i n s > C < / i n s > < d e l > D < / d e l >
* < ins > A < / i n s > X < i n s > C < / i n s > < d e l > D < / d e l >
* < ins > A < / i n s > < d e l > B < / d e l > X < i n s > C < / i n s >
* < ins > A < / d e l > X < i n s > C < / i n s > < d e l > D < / d e l >
* < ins > A < / i n s > < d e l > B < / d e l > X < d e l > C < / d e l >
* /
if ( lastequality && ( preIns && preDel && postIns && postDel || lastequality . length < 2 && preIns + preDel + postIns + postDel === 3 ) ) {
// Duplicate record.
diffs . splice ( equalities [ equalitiesLength - 1 ] , 0 , [ DIFF _DELETE , lastequality ] ) ;
// Change second copy to insert.
diffs [ equalities [ equalitiesLength - 1 ] + 1 ] [ 0 ] = DIFF _INSERT ;
equalitiesLength -- ; // Throw away the equality we just deleted;
lastequality = null ;
if ( preIns && preDel ) {
// No changes made which could affect previous entry, keep going.
postIns = postDel = true ;
equalitiesLength = 0 ;
} else {
equalitiesLength -- ; // Throw away the previous equality.
pointer = equalitiesLength > 0 ? equalities [ equalitiesLength - 1 ] : - 1 ;
postIns = postDel = false ;
}
changes = true ;
}
}
pointer ++ ;
}
if ( changes ) {
this . diffCleanupMerge ( diffs ) ;
}
} ;
/ * *
* Convert a diff array into a pretty HTML report .
* @ param { ! Array . < ! DiffMatchPatch . Diff > } diffs Array of diff tuples .
* @ param { integer } string to be beautified .
* @ return { string } HTML representation .
* /
DiffMatchPatch . prototype . diffPrettyHtml = function ( diffs ) {
var op ,
data ,
x ,
html = [ ] ;
for ( x = 0 ; x < diffs . length ; x ++ ) {
op = diffs [ x ] [ 0 ] ; // Operation (insert, delete, equal)
data = diffs [ x ] [ 1 ] ; // Text of change.
switch ( op ) {
case DIFF _INSERT :
html [ x ] = "<ins>" + escapeText ( data ) + "</ins>" ;
break ;
case DIFF _DELETE :
html [ x ] = "<del>" + escapeText ( data ) + "</del>" ;
break ;
case DIFF _EQUAL :
html [ x ] = "<span>" + escapeText ( data ) + "</span>" ;
break ;
}
}
return html . join ( "" ) ;
} ;
/ * *
* Determine the common prefix of two strings .
* @ param { string } text1 First string .
* @ param { string } text2 Second string .
* @ return { number } The number of characters common to the start of each
* string .
* /
DiffMatchPatch . prototype . diffCommonPrefix = function ( text1 , text2 ) {
var pointermid , pointermax , pointermin , pointerstart ;
// Quick check for common null cases.
if ( ! text1 || ! text2 || text1 . charAt ( 0 ) !== text2 . charAt ( 0 ) ) {
return 0 ;
}
// Binary search.
// Performance analysis: https://neil.fraser.name/news/2007/10/09/
pointermin = 0 ;
pointermax = Math . min ( text1 . length , text2 . length ) ;
pointermid = pointermax ;
pointerstart = 0 ;
while ( pointermin < pointermid ) {
if ( text1 . substring ( pointerstart , pointermid ) === text2 . substring ( pointerstart , pointermid ) ) {
pointermin = pointermid ;
pointerstart = pointermin ;
} else {
pointermax = pointermid ;
}
pointermid = Math . floor ( ( pointermax - pointermin ) / 2 + pointermin ) ;
}
return pointermid ;
} ;
/ * *
* Determine the common suffix of two strings .
* @ param { string } text1 First string .
* @ param { string } text2 Second string .
* @ return { number } The number of characters common to the end of each string .
* /
DiffMatchPatch . prototype . diffCommonSuffix = function ( text1 , text2 ) {
var pointermid , pointermax , pointermin , pointerend ;
// Quick check for common null cases.
if ( ! text1 || ! text2 || text1 . charAt ( text1 . length - 1 ) !== text2 . charAt ( text2 . length - 1 ) ) {
return 0 ;
}
// Binary search.
// Performance analysis: https://neil.fraser.name/news/2007/10/09/
pointermin = 0 ;
pointermax = Math . min ( text1 . length , text2 . length ) ;
pointermid = pointermax ;
pointerend = 0 ;
while ( pointermin < pointermid ) {
if ( text1 . substring ( text1 . length - pointermid , text1 . length - pointerend ) === text2 . substring ( text2 . length - pointermid , text2 . length - pointerend ) ) {
pointermin = pointermid ;
pointerend = pointermin ;
} else {
pointermax = pointermid ;
}
pointermid = Math . floor ( ( pointermax - pointermin ) / 2 + pointermin ) ;
}
return pointermid ;
} ;
/ * *
* Find the differences between two texts . Assumes that the texts do not
* have any common prefix or suffix .
* @ param { string } text1 Old string to be diffed .
* @ param { string } text2 New string to be diffed .
* @ param { boolean } checklines Speedup flag . If false , then don ' t run a
* line - level diff first to identify the changed areas .
* If true , then run a faster , slightly less optimal diff .
* @ param { number } deadline Time when the diff should be complete by .
* @ return { ! Array . < ! DiffMatchPatch . Diff > } Array of diff tuples .
* @ private
* /
DiffMatchPatch . prototype . diffCompute = function ( text1 , text2 , checklines , deadline ) {
var diffs , longtext , shorttext , i , hm , text1A , text2A , text1B , text2B , midCommon , diffsA , diffsB ;
if ( ! text1 ) {
// Just add some text (speedup).
return [ [ DIFF _INSERT , text2 ] ] ;
}
if ( ! text2 ) {
// Just delete some text (speedup).
return [ [ DIFF _DELETE , text1 ] ] ;
}
longtext = text1 . length > text2 . length ? text1 : text2 ;
shorttext = text1 . length > text2 . length ? text2 : text1 ;
i = longtext . indexOf ( shorttext ) ;
if ( i !== - 1 ) {
// Shorter text is inside the longer text (speedup).
diffs = [ [ DIFF _INSERT , longtext . substring ( 0 , i ) ] , [ DIFF _EQUAL , shorttext ] , [ DIFF _INSERT , longtext . substring ( i + shorttext . length ) ] ] ;
// Swap insertions for deletions if diff is reversed.
if ( text1 . length > text2 . length ) {
diffs [ 0 ] [ 0 ] = diffs [ 2 ] [ 0 ] = DIFF _DELETE ;
}
return diffs ;
}
if ( shorttext . length === 1 ) {
// Single character string.
// After the previous speedup, the character can't be an equality.
return [ [ DIFF _DELETE , text1 ] , [ DIFF _INSERT , text2 ] ] ;
}
// Check to see if the problem can be split in two.
hm = this . diffHalfMatch ( text1 , text2 ) ;
if ( hm ) {
// A half-match was found, sort out the return data.
text1A = hm [ 0 ] ;
text1B = hm [ 1 ] ;
text2A = hm [ 2 ] ;
text2B = hm [ 3 ] ;
midCommon = hm [ 4 ] ;
// Send both pairs off for separate processing.
diffsA = this . DiffMain ( text1A , text2A , checklines , deadline ) ;
diffsB = this . DiffMain ( text1B , text2B , checklines , deadline ) ;
// Merge the results.
return diffsA . concat ( [ [ DIFF _EQUAL , midCommon ] ] , diffsB ) ;
}
if ( checklines && text1 . length > 100 && text2 . length > 100 ) {
return this . diffLineMode ( text1 , text2 , deadline ) ;
}
return this . diffBisect ( text1 , text2 , deadline ) ;
} ;
/ * *
* Do the two texts share a substring which is at least half the length of the
* longer text ?
* This speedup can produce non - minimal diffs .
* @ param { string } text1 First string .
* @ param { string } text2 Second string .
* @ return { Array . < string > } Five element Array , containing the prefix of
* text1 , the suffix of text1 , the prefix of text2 , the suffix of
* text2 and the common middle . Or null if there was no match .
* @ private
* /
DiffMatchPatch . prototype . diffHalfMatch = function ( text1 , text2 ) {
var longtext , shorttext , dmp , text1A , text2B , text2A , text1B , midCommon , hm1 , hm2 , hm ;
longtext = text1 . length > text2 . length ? text1 : text2 ;
shorttext = text1 . length > text2 . length ? text2 : text1 ;
if ( longtext . length < 4 || shorttext . length * 2 < longtext . length ) {
return null ; // Pointless.
}
dmp = this ; // 'this' becomes 'window' in a closure.
/ * *
* Does a substring of shorttext exist within longtext such that the substring
* is at least half the length of longtext ?
* Closure , but does not reference any external variables .
* @ param { string } longtext Longer string .
* @ param { string } shorttext Shorter string .
* @ param { number } i Start index of quarter length substring within longtext .
* @ return { Array . < string > } Five element Array , containing the prefix of
* longtext , the suffix of longtext , the prefix of shorttext , the suffix
* of shorttext and the common middle . Or null if there was no match .
* @ private
* /
function diffHalfMatchI ( longtext , shorttext , i ) {
var seed , j , bestCommon , prefixLength , suffixLength , bestLongtextA , bestLongtextB , bestShorttextA , bestShorttextB ;
// Start with a 1/4 length substring at position i as a seed.
seed = longtext . substring ( i , i + Math . floor ( longtext . length / 4 ) ) ;
j = - 1 ;
bestCommon = "" ;
while ( ( j = shorttext . indexOf ( seed , j + 1 ) ) !== - 1 ) {
prefixLength = dmp . diffCommonPrefix ( longtext . substring ( i ) , shorttext . substring ( j ) ) ;
suffixLength = dmp . diffCommonSuffix ( longtext . substring ( 0 , i ) , shorttext . substring ( 0 , j ) ) ;
if ( bestCommon . length < suffixLength + prefixLength ) {
bestCommon = shorttext . substring ( j - suffixLength , j ) + shorttext . substring ( j , j + prefixLength ) ;
bestLongtextA = longtext . substring ( 0 , i - suffixLength ) ;
bestLongtextB = longtext . substring ( i + prefixLength ) ;
bestShorttextA = shorttext . substring ( 0 , j - suffixLength ) ;
bestShorttextB = shorttext . substring ( j + prefixLength ) ;
}
}
if ( bestCommon . length * 2 >= longtext . length ) {
return [ bestLongtextA , bestLongtextB , bestShorttextA , bestShorttextB , bestCommon ] ;
} else {
return null ;
}
}
// First check if the second quarter is the seed for a half-match.
hm1 = diffHalfMatchI ( longtext , shorttext , Math . ceil ( longtext . length / 4 ) ) ;
// Check again based on the third quarter.
hm2 = diffHalfMatchI ( longtext , shorttext , Math . ceil ( longtext . length / 2 ) ) ;
if ( ! hm1 && ! hm2 ) {
return null ;
} else if ( ! hm2 ) {
hm = hm1 ;
} else if ( ! hm1 ) {
hm = hm2 ;
} else {
// Both matched. Select the longest.
hm = hm1 [ 4 ] . length > hm2 [ 4 ] . length ? hm1 : hm2 ;
}
// A half-match was found, sort out the return data.
if ( text1 . length > text2 . length ) {
text1A = hm [ 0 ] ;
text1B = hm [ 1 ] ;
text2A = hm [ 2 ] ;
text2B = hm [ 3 ] ;
} else {
text2A = hm [ 0 ] ;
text2B = hm [ 1 ] ;
text1A = hm [ 2 ] ;
text1B = hm [ 3 ] ;
}
midCommon = hm [ 4 ] ;
return [ text1A , text1B , text2A , text2B , midCommon ] ;
} ;
/ * *
* Do a quick line - level diff on both strings , then rediff the parts for
* greater accuracy .
* This speedup can produce non - minimal diffs .
* @ param { string } text1 Old string to be diffed .
* @ param { string } text2 New string to be diffed .
* @ param { number } deadline Time when the diff should be complete by .
* @ return { ! Array . < ! DiffMatchPatch . Diff > } Array of diff tuples .
* @ private
* /
DiffMatchPatch . prototype . diffLineMode = function ( text1 , text2 , deadline ) {
var a , diffs , linearray , pointer , countInsert , countDelete , textInsert , textDelete , j ;
// Scan the text on a line-by-line basis first.
a = this . diffLinesToChars ( text1 , text2 ) ;
text1 = a . chars1 ;
text2 = a . chars2 ;
linearray = a . lineArray ;
diffs = this . DiffMain ( text1 , text2 , false , deadline ) ;
// Convert the diff back to original text.
this . diffCharsToLines ( diffs , linearray ) ;
// Eliminate freak matches (e.g. blank lines)
this . diffCleanupSemantic ( diffs ) ;
// Rediff any replacement blocks, this time character-by-character.
// Add a dummy entry at the end.
diffs . push ( [ DIFF _EQUAL , "" ] ) ;
pointer = 0 ;
countDelete = 0 ;
countInsert = 0 ;
textDelete = "" ;
textInsert = "" ;
while ( pointer < diffs . length ) {
switch ( diffs [ pointer ] [ 0 ] ) {
case DIFF _INSERT :
countInsert ++ ;
textInsert += diffs [ pointer ] [ 1 ] ;
break ;
case DIFF _DELETE :
countDelete ++ ;
textDelete += diffs [ pointer ] [ 1 ] ;
break ;
case DIFF _EQUAL :
// Upon reaching an equality, check for prior redundancies.
if ( countDelete >= 1 && countInsert >= 1 ) {
// Delete the offending records and add the merged ones.
diffs . splice ( pointer - countDelete - countInsert , countDelete + countInsert ) ;
pointer = pointer - countDelete - countInsert ;
a = this . DiffMain ( textDelete , textInsert , false , deadline ) ;
for ( j = a . length - 1 ; j >= 0 ; j -- ) {
diffs . splice ( pointer , 0 , a [ j ] ) ;
}
pointer = pointer + a . length ;
}
countInsert = 0 ;
countDelete = 0 ;
textDelete = "" ;
textInsert = "" ;
break ;
}
pointer ++ ;
}
diffs . pop ( ) ; // Remove the dummy entry at the end.
return diffs ;
} ;
/ * *
* Find the 'middle snake' of a diff , split the problem in two
* and return the recursively constructed diff .
* See Myers 1986 paper : An O ( ND ) Difference Algorithm and Its Variations .
* @ param { string } text1 Old string to be diffed .
* @ param { string } text2 New string to be diffed .
* @ param { number } deadline Time at which to bail if not yet complete .
* @ return { ! Array . < ! DiffMatchPatch . Diff > } Array of diff tuples .
* @ private
* /
DiffMatchPatch . prototype . diffBisect = function ( text1 , text2 , deadline ) {
var text1Length , text2Length , maxD , vOffset , vLength , v1 , v2 , x , delta , front , k1start , k1end , k2start , k2end , k2Offset , k1Offset , x1 , x2 , y1 , y2 , d , k1 , k2 ;
// Cache the text lengths to prevent multiple calls.
text1Length = text1 . length ;
text2Length = text2 . length ;
maxD = Math . ceil ( ( text1Length + text2Length ) / 2 ) ;
vOffset = maxD ;
vLength = 2 * maxD ;
v1 = new Array ( vLength ) ;
v2 = new Array ( vLength ) ;
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
// integers and undefined.
for ( x = 0 ; x < vLength ; x ++ ) {
v1 [ x ] = - 1 ;
v2 [ x ] = - 1 ;
}
v1 [ vOffset + 1 ] = 0 ;
v2 [ vOffset + 1 ] = 0 ;
delta = text1Length - text2Length ;
// If the total number of characters is odd, then the front path will collide
// with the reverse path.
front = delta % 2 !== 0 ;
// Offsets for start and end of k loop.
// Prevents mapping of space beyond the grid.
k1start = 0 ;
k1end = 0 ;
k2start = 0 ;
k2end = 0 ;
for ( d = 0 ; d < maxD ; d ++ ) {
// Bail out if deadline is reached.
if ( new Date ( ) . getTime ( ) > deadline ) {
break ;
}
// Walk the front path one step.
for ( k1 = - d + k1start ; k1 <= d - k1end ; k1 += 2 ) {
k1Offset = vOffset + k1 ;
if ( k1 === - d || k1 !== d && v1 [ k1Offset - 1 ] < v1 [ k1Offset + 1 ] ) {
x1 = v1 [ k1Offset + 1 ] ;
} else {
x1 = v1 [ k1Offset - 1 ] + 1 ;
}
y1 = x1 - k1 ;
while ( x1 < text1Length && y1 < text2Length && text1 . charAt ( x1 ) === text2 . charAt ( y1 ) ) {
x1 ++ ;
y1 ++ ;
}
v1 [ k1Offset ] = x1 ;
if ( x1 > text1Length ) {
// Ran off the right of the graph.
k1end += 2 ;
} else if ( y1 > text2Length ) {
// Ran off the bottom of the graph.
k1start += 2 ;
} else if ( front ) {
k2Offset = vOffset + delta - k1 ;
if ( k2Offset >= 0 && k2Offset < vLength && v2 [ k2Offset ] !== - 1 ) {
// Mirror x2 onto top-left coordinate system.
x2 = text1Length - v2 [ k2Offset ] ;
if ( x1 >= x2 ) {
// Overlap detected.
return this . diffBisectSplit ( text1 , text2 , x1 , y1 , deadline ) ;
}
}
}
}
// Walk the reverse path one step.
for ( k2 = - d + k2start ; k2 <= d - k2end ; k2 += 2 ) {
k2Offset = vOffset + k2 ;
if ( k2 === - d || k2 !== d && v2 [ k2Offset - 1 ] < v2 [ k2Offset + 1 ] ) {
x2 = v2 [ k2Offset + 1 ] ;
} else {
x2 = v2 [ k2Offset - 1 ] + 1 ;
}
y2 = x2 - k2 ;
while ( x2 < text1Length && y2 < text2Length && text1 . charAt ( text1Length - x2 - 1 ) === text2 . charAt ( text2Length - y2 - 1 ) ) {
x2 ++ ;
y2 ++ ;
}
v2 [ k2Offset ] = x2 ;
if ( x2 > text1Length ) {
// Ran off the left of the graph.
k2end += 2 ;
} else if ( y2 > text2Length ) {
// Ran off the top of the graph.
k2start += 2 ;
} else if ( ! front ) {
k1Offset = vOffset + delta - k2 ;
if ( k1Offset >= 0 && k1Offset < vLength && v1 [ k1Offset ] !== - 1 ) {
x1 = v1 [ k1Offset ] ;
y1 = vOffset + x1 - k1Offset ;
// Mirror x2 onto top-left coordinate system.
x2 = text1Length - x2 ;
if ( x1 >= x2 ) {
// Overlap detected.
return this . diffBisectSplit ( text1 , text2 , x1 , y1 , deadline ) ;
}
}
}
}
}
// Diff took too long and hit the deadline or
// number of diffs equals number of characters, no commonality at all.
return [ [ DIFF _DELETE , text1 ] , [ DIFF _INSERT , text2 ] ] ;
} ;
/ * *
* Given the location of the 'middle snake' , split the diff in two parts
* and recurse .
* @ param { string } text1 Old string to be diffed .
* @ param { string } text2 New string to be diffed .
* @ param { number } x Index of split point in text1 .
* @ param { number } y Index of split point in text2 .
* @ param { number } deadline Time at which to bail if not yet complete .
* @ return { ! Array . < ! DiffMatchPatch . Diff > } Array of diff tuples .
* @ private
* /
DiffMatchPatch . prototype . diffBisectSplit = function ( text1 , text2 , x , y , deadline ) {
var text1a , text1b , text2a , text2b , diffs , diffsb ;
text1a = text1 . substring ( 0 , x ) ;
text2a = text2 . substring ( 0 , y ) ;
text1b = text1 . substring ( x ) ;
text2b = text2 . substring ( y ) ;
// Compute both diffs serially.
diffs = this . DiffMain ( text1a , text2a , false , deadline ) ;
diffsb = this . DiffMain ( text1b , text2b , false , deadline ) ;
return diffs . concat ( diffsb ) ;
} ;
/ * *
* Reduce the number of edits by eliminating semantically trivial equalities .
* @ param { ! Array . < ! DiffMatchPatch . Diff > } diffs Array of diff tuples .
* /
DiffMatchPatch . prototype . diffCleanupSemantic = function ( diffs ) {
var changes , equalities , equalitiesLength , lastequality , pointer , lengthInsertions2 , lengthDeletions2 , lengthInsertions1 , lengthDeletions1 , deletion , insertion , overlapLength1 , overlapLength2 ;
changes = false ;
equalities = [ ] ; // Stack of indices where equalities are found.
equalitiesLength = 0 ; // Keeping our own length var is faster in JS.
/** @type {?string} */
lastequality = null ;
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
pointer = 0 ; // Index of current position.
// Number of characters that changed prior to the equality.
lengthInsertions1 = 0 ;
lengthDeletions1 = 0 ;
// Number of characters that changed after the equality.
lengthInsertions2 = 0 ;
lengthDeletions2 = 0 ;
while ( pointer < diffs . length ) {
if ( diffs [ pointer ] [ 0 ] === DIFF _EQUAL ) {
// Equality found.
equalities [ equalitiesLength ++ ] = pointer ;
lengthInsertions1 = lengthInsertions2 ;
lengthDeletions1 = lengthDeletions2 ;
lengthInsertions2 = 0 ;
lengthDeletions2 = 0 ;
lastequality = diffs [ pointer ] [ 1 ] ;
} else {
// An insertion or deletion.
if ( diffs [ pointer ] [ 0 ] === DIFF _INSERT ) {
lengthInsertions2 += diffs [ pointer ] [ 1 ] . length ;
} else {
lengthDeletions2 += diffs [ pointer ] [ 1 ] . length ;
}
// Eliminate an equality that is smaller or equal to the edits on both
// sides of it.
if ( lastequality && lastequality . length <= Math . max ( lengthInsertions1 , lengthDeletions1 ) && lastequality . length <= Math . max ( lengthInsertions2 , lengthDeletions2 ) ) {
// Duplicate record.
diffs . splice ( equalities [ equalitiesLength - 1 ] , 0 , [ DIFF _DELETE , lastequality ] ) ;
// Change second copy to insert.
diffs [ equalities [ equalitiesLength - 1 ] + 1 ] [ 0 ] = DIFF _INSERT ;
// Throw away the equality we just deleted.
equalitiesLength -- ;
// Throw away the previous equality (it needs to be reevaluated).
equalitiesLength -- ;
pointer = equalitiesLength > 0 ? equalities [ equalitiesLength - 1 ] : - 1 ;
// Reset the counters.
lengthInsertions1 = 0 ;
lengthDeletions1 = 0 ;
lengthInsertions2 = 0 ;
lengthDeletions2 = 0 ;
lastequality = null ;
changes = true ;
}
}
pointer ++ ;
}
// Normalize the diff.
if ( changes ) {
this . diffCleanupMerge ( diffs ) ;
}
// Find any overlaps between deletions and insertions.
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
// -> <del>abc</del>xxx<ins>def</ins>
// e.g: <del>xxxabc</del><ins>defxxx</ins>
// -> <ins>def</ins>xxx<del>abc</del>
// Only extract an overlap if it is as big as the edit ahead or behind it.
pointer = 1 ;
while ( pointer < diffs . length ) {
if ( diffs [ pointer - 1 ] [ 0 ] === DIFF _DELETE && diffs [ pointer ] [ 0 ] === DIFF _INSERT ) {
deletion = diffs [ pointer - 1 ] [ 1 ] ;
insertion = diffs [ pointer ] [ 1 ] ;
overlapLength1 = this . diffCommonOverlap ( deletion , insertion ) ;
overlapLength2 = this . diffCommonOverlap ( insertion , deletion ) ;
if ( overlapLength1 >= overlapLength2 ) {
if ( overlapLength1 >= deletion . length / 2 || overlapLength1 >= insertion . length / 2 ) {
// Overlap found. Insert an equality and trim the surrounding edits.
diffs . splice ( pointer , 0 , [ DIFF _EQUAL , insertion . substring ( 0 , overlapLength1 ) ] ) ;
diffs [ pointer - 1 ] [ 1 ] = deletion . substring ( 0 , deletion . length - overlapLength1 ) ;
diffs [ pointer + 1 ] [ 1 ] = insertion . substring ( overlapLength1 ) ;
pointer ++ ;
}
} else {
if ( overlapLength2 >= deletion . length / 2 || overlapLength2 >= insertion . length / 2 ) {
// Reverse overlap found.
// Insert an equality and swap and trim the surrounding edits.
diffs . splice ( pointer , 0 , [ DIFF _EQUAL , deletion . substring ( 0 , overlapLength2 ) ] ) ;
diffs [ pointer - 1 ] [ 0 ] = DIFF _INSERT ;
diffs [ pointer - 1 ] [ 1 ] = insertion . substring ( 0 , insertion . length - overlapLength2 ) ;
diffs [ pointer + 1 ] [ 0 ] = DIFF _DELETE ;
diffs [ pointer + 1 ] [ 1 ] = deletion . substring ( overlapLength2 ) ;
pointer ++ ;
}
}
pointer ++ ;
}
pointer ++ ;
}
} ;
/ * *
* Determine if the suffix of one string is the prefix of another .
* @ param { string } text1 First string .
* @ param { string } text2 Second string .
* @ return { number } The number of characters common to the end of the first
* string and the start of the second string .
* @ private
* /
DiffMatchPatch . prototype . diffCommonOverlap = function ( text1 , text2 ) {
var text1Length , text2Length , textLength , best , length , pattern , found ;
// Cache the text lengths to prevent multiple calls.
text1Length = text1 . length ;
text2Length = text2 . length ;
// Eliminate the null case.
if ( text1Length === 0 || text2Length === 0 ) {
return 0 ;
}
// Truncate the longer string.
if ( text1Length > text2Length ) {
text1 = text1 . substring ( text1Length - text2Length ) ;
} else if ( text1Length < text2Length ) {
text2 = text2 . substring ( 0 , text1Length ) ;
}
textLength = Math . min ( text1Length , text2Length ) ;
// Quick check for the worst case.
if ( text1 === text2 ) {
return textLength ;
}
// Start by looking for a single character match
// and increase length until no match is found.
// Performance analysis: https://neil.fraser.name/news/2010/11/04/
best = 0 ;
length = 1 ;
while ( true ) {
pattern = text1 . substring ( textLength - length ) ;
found = text2 . indexOf ( pattern ) ;
if ( found === - 1 ) {
return best ;
}
length += found ;
if ( found === 0 || text1 . substring ( textLength - length ) === text2 . substring ( 0 , length ) ) {
best = length ;
length ++ ;
}
}
} ;
/ * *
* Split two texts into an array of strings . Reduce the texts to a string of
* hashes where each Unicode character represents one line .
* @ param { string } text1 First string .
* @ param { string } text2 Second string .
* @ return { { chars1 : string , chars2 : string , lineArray : ! Array . < string > } }
* An object containing the encoded text1 , the encoded text2 and
* the array of unique strings .
* The zeroth element of the array of unique strings is intentionally blank .
* @ private
* /
DiffMatchPatch . prototype . diffLinesToChars = function ( text1 , text2 ) {
var lineArray , lineHash , chars1 , chars2 ;
lineArray = [ ] ; // E.g. lineArray[4] === 'Hello\n'
lineHash = { } ; // E.g. lineHash['Hello\n'] === 4
// '\x00' is a valid character, but various debuggers don't like it.
// So we'll insert a junk entry to avoid generating a null character.
lineArray [ 0 ] = "" ;
/ * *
* Split a text into an array of strings . Reduce the texts to a string of
* hashes where each Unicode character represents one line .
* Modifies linearray and linehash through being a closure .
* @ param { string } text String to encode .
* @ return { string } Encoded string .
* @ private
* /
function diffLinesToCharsMunge ( text ) {
var chars , lineStart , lineEnd , lineArrayLength , line ;
chars = "" ;
// Walk the text, pulling out a substring for each line.
// text.split('\n') would would temporarily double our memory footprint.
// Modifying text would create many large strings to garbage collect.
lineStart = 0 ;
lineEnd = - 1 ;
// Keeping our own length variable is faster than looking it up.
lineArrayLength = lineArray . length ;
while ( lineEnd < text . length - 1 ) {
lineEnd = text . indexOf ( "\n" , lineStart ) ;
if ( lineEnd === - 1 ) {
lineEnd = text . length - 1 ;
}
line = text . substring ( lineStart , lineEnd + 1 ) ;
lineStart = lineEnd + 1 ;
if ( lineHash . hasOwnProperty ? lineHash . hasOwnProperty ( line ) : lineHash [ line ] !== undefined ) {
chars += String . fromCharCode ( lineHash [ line ] ) ;
} else {
chars += String . fromCharCode ( lineArrayLength ) ;
lineHash [ line ] = lineArrayLength ;
lineArray [ lineArrayLength ++ ] = line ;
}
}
return chars ;
}
chars1 = diffLinesToCharsMunge ( text1 ) ;
chars2 = diffLinesToCharsMunge ( text2 ) ;
return {
chars1 : chars1 ,
chars2 : chars2 ,
lineArray : lineArray
} ;
} ;
/ * *
* Rehydrate the text in a diff from a string of line hashes to real lines of
* text .
* @ param { ! Array . < ! DiffMatchPatch . Diff > } diffs Array of diff tuples .
* @ param { ! Array . < string > } lineArray Array of unique strings .
* @ private
* /
DiffMatchPatch . prototype . diffCharsToLines = function ( diffs , lineArray ) {
var x , chars , text , y ;
for ( x = 0 ; x < diffs . length ; x ++ ) {
chars = diffs [ x ] [ 1 ] ;
text = [ ] ;
for ( y = 0 ; y < chars . length ; y ++ ) {
text [ y ] = lineArray [ chars . charCodeAt ( y ) ] ;
}
diffs [ x ] [ 1 ] = text . join ( "" ) ;
}
} ;
/ * *
* Reorder and merge like edit sections . Merge equalities .
* Any edit section can move as long as it doesn ' t cross an equality .
* @ param { ! Array . < ! DiffMatchPatch . Diff > } diffs Array of diff tuples .
* /
DiffMatchPatch . prototype . diffCleanupMerge = function ( diffs ) {
var pointer , countDelete , countInsert , textInsert , textDelete , commonlength , changes , diffPointer , position ;
diffs . push ( [ DIFF _EQUAL , "" ] ) ; // Add a dummy entry at the end.
pointer = 0 ;
countDelete = 0 ;
countInsert = 0 ;
textDelete = "" ;
textInsert = "" ;
while ( pointer < diffs . length ) {
switch ( diffs [ pointer ] [ 0 ] ) {
case DIFF _INSERT :
countInsert ++ ;
textInsert += diffs [ pointer ] [ 1 ] ;
pointer ++ ;
break ;
case DIFF _DELETE :
countDelete ++ ;
textDelete += diffs [ pointer ] [ 1 ] ;
pointer ++ ;
break ;
case DIFF _EQUAL :
// Upon reaching an equality, check for prior redundancies.
if ( countDelete + countInsert > 1 ) {
if ( countDelete !== 0 && countInsert !== 0 ) {
// Factor out any common prefixes.
commonlength = this . diffCommonPrefix ( textInsert , textDelete ) ;
if ( commonlength !== 0 ) {
if ( pointer - countDelete - countInsert > 0 && diffs [ pointer - countDelete - countInsert - 1 ] [ 0 ] === DIFF _EQUAL ) {
diffs [ pointer - countDelete - countInsert - 1 ] [ 1 ] += textInsert . substring ( 0 , commonlength ) ;
} else {
diffs . splice ( 0 , 0 , [ DIFF _EQUAL , textInsert . substring ( 0 , commonlength ) ] ) ;
pointer ++ ;
}
textInsert = textInsert . substring ( commonlength ) ;
textDelete = textDelete . substring ( commonlength ) ;
}
// Factor out any common suffixies.
commonlength = this . diffCommonSuffix ( textInsert , textDelete ) ;
if ( commonlength !== 0 ) {
diffs [ pointer ] [ 1 ] = textInsert . substring ( textInsert . length - commonlength ) + diffs [ pointer ] [ 1 ] ;
textInsert = textInsert . substring ( 0 , textInsert . length - commonlength ) ;
textDelete = textDelete . substring ( 0 , textDelete . length - commonlength ) ;
}
}
// Delete the offending records and add the merged ones.
if ( countDelete === 0 ) {
diffs . splice ( pointer - countInsert , countDelete + countInsert , [ DIFF _INSERT , textInsert ] ) ;
} else if ( countInsert === 0 ) {
diffs . splice ( pointer - countDelete , countDelete + countInsert , [ DIFF _DELETE , textDelete ] ) ;
} else {
diffs . splice ( pointer - countDelete - countInsert , countDelete + countInsert , [ DIFF _DELETE , textDelete ] , [ DIFF _INSERT , textInsert ] ) ;
}
pointer = pointer - countDelete - countInsert + ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1 ;
} else if ( pointer !== 0 && diffs [ pointer - 1 ] [ 0 ] === DIFF _EQUAL ) {
// Merge this equality with the previous one.
diffs [ pointer - 1 ] [ 1 ] += diffs [ pointer ] [ 1 ] ;
diffs . splice ( pointer , 1 ) ;
} else {
pointer ++ ;
}
countInsert = 0 ;
countDelete = 0 ;
textDelete = "" ;
textInsert = "" ;
break ;
}
}
if ( diffs [ diffs . length - 1 ] [ 1 ] === "" ) {
diffs . pop ( ) ; // Remove the dummy entry at the end.
}
// Second pass: look for single edits surrounded on both sides by equalities
// which can be shifted sideways to eliminate an equality.
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
changes = false ;
pointer = 1 ;
// Intentionally ignore the first and last element (don't need checking).
while ( pointer < diffs . length - 1 ) {
if ( diffs [ pointer - 1 ] [ 0 ] === DIFF _EQUAL && diffs [ pointer + 1 ] [ 0 ] === DIFF _EQUAL ) {
diffPointer = diffs [ pointer ] [ 1 ] ;
position = diffPointer . substring ( diffPointer . length - diffs [ pointer - 1 ] [ 1 ] . length ) ;
// This is a single edit surrounded by equalities.
if ( position === diffs [ pointer - 1 ] [ 1 ] ) {
// Shift the edit over the previous equality.
diffs [ pointer ] [ 1 ] = diffs [ pointer - 1 ] [ 1 ] + diffs [ pointer ] [ 1 ] . substring ( 0 , diffs [ pointer ] [ 1 ] . length - diffs [ pointer - 1 ] [ 1 ] . length ) ;
diffs [ pointer + 1 ] [ 1 ] = diffs [ pointer - 1 ] [ 1 ] + diffs [ pointer + 1 ] [ 1 ] ;
diffs . splice ( pointer - 1 , 1 ) ;
changes = true ;
} else if ( diffPointer . substring ( 0 , diffs [ pointer + 1 ] [ 1 ] . length ) === diffs [ pointer + 1 ] [ 1 ] ) {
// Shift the edit over the next equality.
diffs [ pointer - 1 ] [ 1 ] += diffs [ pointer + 1 ] [ 1 ] ;
diffs [ pointer ] [ 1 ] = diffs [ pointer ] [ 1 ] . substring ( diffs [ pointer + 1 ] [ 1 ] . length ) + diffs [ pointer + 1 ] [ 1 ] ;
diffs . splice ( pointer + 1 , 1 ) ;
changes = true ;
}
}
pointer ++ ;
}
// If shifts were made, the diff needs reordering and another shift sweep.
if ( changes ) {
this . diffCleanupMerge ( diffs ) ;
}
} ;
return function ( o , n ) {
var diff , output , text ;
diff = new DiffMatchPatch ( ) ;
output = diff . DiffMain ( o , n ) ;
diff . diffCleanupEfficiency ( output ) ;
text = diff . diffPrettyHtml ( output ) ;
return text ;
} ;
} ( ) ;
} ( ( function ( ) { return this ; } ( ) ) ) ) ;