0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-02-19 16:54:24 +01:00

tests: bump QUnit to v1.20.0.

[skip validator]
This commit is contained in:
XhmikosR 2015-10-26 23:54:22 +02:00
parent 713e53f105
commit e55fcbcdfe
2 changed files with 2199 additions and 1859 deletions

View File

@ -1,27 +1,27 @@
/*!
* QUnit 1.18.0
* QUnit 1.20.0
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-03T10:23Z
* Date: 2015-10-27T17:53Z
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
@ -68,6 +68,12 @@
overflow: hidden;
}
#qunit-filteredTest {
padding: 0.5em 1em 0.5em 1em;
background-color: #F4FF77;
color: #366097;
}
#qunit-userAgent {
padding: 0.5em 1em 0.5em 1em;
background-color: #2B81AF;
@ -118,8 +124,8 @@
#qunit-tests.hidepass li.pass {
visibility: hidden;
position: absolute;
width: 0px;
height: 0px;
width: 0;
height: 0;
padding: 0;
border: 0;
margin: 0;
@ -162,6 +168,10 @@
border-radius: 5px;
}
.qunit-source {
margin: 0.6em 0 0.3em;
}
.qunit-collapsed {
display: none;
}

4034
js/tests/vendor/qunit.js vendored
View File

@ -1,168 +1,168 @@
/*!
* QUnit 1.18.0
* QUnit 1.20.0
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-03T10:23Z
* Date: 2015-10-27T17:53Z
*/
(function( window ) {
(function( global ) {
var QUnit,
config,
onErrorFnPrev,
loggingCallbacks = {},
fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
now = Date.now || function() {
return new Date().getTime();
},
globalStartCalled = false,
runStarted = false,
setTimeout = window.setTimeout,
clearTimeout = window.clearTimeout,
defined = {
document: window.document !== undefined,
setTimeout: window.setTimeout !== undefined,
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem( x, x );
sessionStorage.removeItem( x );
return true;
} catch ( e ) {
return false;
}
}())
},
/**
* Provides a normalized error string, correcting an issue
* with IE 7 (and prior) where Error.prototype.toString is
* not properly implemented
*
* Based on http://es5.github.com/#x15.11.4.4
*
* @param {String|Error} error
* @return {String} error message
*/
errorString = function( error ) {
var name, message,
errorString = error.toString();
if ( errorString.substring( 0, 7 ) === "[object" ) {
name = error.name ? error.name.toString() : "Error";
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 errorString;
}
},
/**
* 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).
*/
objectValues = function( obj ) {
var key, val,
vals = QUnit.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;
};
var QUnit = {};
QUnit = {};
/**
* Config object: Maintain internal state
* Later exposed as QUnit.config
* `config` initialized at top of scope
*/
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,
// by default, scroll to top of the page when suite is done
scrolltop: true,
// when enabled, all tests must call expect()
requireExpects: false,
// depth up-to which object will be dumped
maxDepth: 5,
// add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
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 " +
"`window` object. 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."
}
],
// Set of all modules.
modules: [],
// The first unnamed module
currentModule: {
name: "",
tests: []
},
callbacks: {}
var Date = global.Date;
var now = Date.now || function() {
return new Date().getTime();
};
// Push a loose unnamed module to the modules collection
config.modules.push( config.currentModule );
var setTimeout = global.setTimeout;
var clearTimeout = global.clearTimeout;
// Initialize more QUnit.config and QUnit.urlParams
(function() {
var i, current,
location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {};
// Store a local window from the global to allow direct references.
var window = global.window;
var defined = {
document: window && window.document !== undefined,
setTimeout: setTimeout !== undefined,
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem( x, x );
sessionStorage.removeItem( x );
return true;
} catch ( e ) {
return false;
}
}() )
};
var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
var globalStartCalled = false;
var runStarted = false;
var toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
// 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;
}
// from jquery.js
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
}
/**
* 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 = QUnit.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 ) ) {
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
// This block runs on every environment, so `global` is being used instead of `window`
// to avoid errors on node.
if ( prop !== "constructor" || a !== global ) {
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 === "object" ) {
return "object";
}
}
// Safe object type checking
function is( type, obj ) {
return QUnit.objectType( obj ) === type;
}
var getUrlParams = function() {
var i, current;
var urlParams = {};
var location = window.location;
var params = location.search.slice( 1 ).split( "&" );
var length = params.length;
if ( params[ 0 ] ) {
for ( i = 0; i < length; i++ ) {
@ -179,323 +179,9 @@ config.modules.push( config.currentModule );
}
}
if ( urlParams.filter === true ) {
delete urlParams.filter;
}
QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
if ( urlParams.maxDepth ) {
config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
Number.POSITIVE_INFINITY :
urlParams.maxDepth;
}
config.testId = [];
if ( urlParams.testId ) {
// Ensure that urlParams.testId is an array
urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
for ( i = 0; i < urlParams.testId.length; i++ ) {
config.testId.push( urlParams.testId[ i ] );
}
}
// Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === "file:";
// Expose the current QUnit version
QUnit.version = "1.18.0";
}());
// Root QUnit object.
// `QUnit` initialized at top of scope
extend( QUnit, {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
var currentModule = {
name: name,
testEnvironment: testEnvironment,
tests: []
};
// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
if ( testEnvironment && testEnvironment.setup ) {
testEnvironment.beforeEach = testEnvironment.setup;
delete testEnvironment.setup;
}
if ( testEnvironment && testEnvironment.teardown ) {
testEnvironment.afterEach = testEnvironment.teardown;
delete testEnvironment.teardown;
}
config.modules.push( currentModule );
config.currentModule = currentModule;
},
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test( testName, expected, callback, true );
},
test: function( testName, expected, callback, async ) {
var test;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
test = new Test({
testName: testName,
expected: expected,
async: async,
callback: callback
});
test.queue();
},
skip: function( testName ) {
var test = new Test({
testName: testName,
skip: true
});
test.queue();
},
// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
start: function( count ) {
var globalStartAlreadyCalled = globalStartCalled;
if ( !config.current ) {
globalStartCalled = true;
if ( runStarted ) {
throw new Error( "Called start() outside of a test context while already started" );
} 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 ) {
// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
config.autostart = true;
return;
}
} else {
// If a test is running, adjust its semaphore
config.current.semaphore -= count || 1;
// Don't start until equal number of stop-calls
if ( config.current.semaphore > 0 ) {
return;
}
// throw an Error if start is called more often than stop
if ( config.current.semaphore < 0 ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() while already started (test's semaphore was 0 already)",
sourceFromStacktrace( 2 )
);
return;
}
}
resumeProcessing();
},
// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
stop: function( count ) {
// If there isn't a test running, don't allow QUnit.stop() to be called
if ( !config.current ) {
throw new Error( "Called stop() outside of a test context" );
}
// If a test is running, adjust its semaphore
config.current.semaphore += count || 1;
pauseProcessing();
},
config: config,
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) === type;
},
objectType: function( 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 "Date":
case "RegExp":
case "Function":
return type.toLowerCase();
}
if ( typeof obj === "object" ) {
return "object";
}
return undefined;
},
extend: extend,
load: function() {
config.pageLoaded = true;
// Initialize the configuration options
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: 0,
updateRate: 1000,
autostart: true,
filter: ""
}, true );
config.blocking = false;
if ( config.autostart ) {
resumeProcessing();
}
}
});
// Register logging callbacks
(function() {
var i, l, key,
callbacks = [ "begin", "done", "log", "testStart", "testDone",
"moduleStart", "moduleDone" ];
function registerLoggingCallback( key ) {
var loggingCallback = function( callback ) {
if ( QUnit.objectType( callback ) !== "function" ) {
throw new Error(
"QUnit logging methods require a callback function as their first parameters."
);
}
config.callbacks[ key ].push( callback );
};
// DEPRECATED: This will be removed on QUnit 2.0.0+
// Stores the registered functions allowing restoring
// at verifyLoggingCallbacks() if modified
loggingCallbacks[ key ] = loggingCallback;
return loggingCallback;
}
for ( i = 0, l = callbacks.length; i < l; i++ ) {
key = callbacks[ i ];
// Initialize key collection of logging callback
if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
config.callbacks[ key ] = [];
}
QUnit[ key ] = registerLoggingCallback( key );
}
})();
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
onErrorFnPrev = window.onerror;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window.onerror = function( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
}
// Treat return value as window.onerror itself does,
// Only do our handling if not suppressed.
if ( ret !== true ) {
if ( QUnit.config.current ) {
if ( QUnit.config.current.ignoreGlobalErrors ) {
return true;
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", extend(function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: true } ) );
}
return false;
}
return ret;
return urlParams;
};
function done() {
var runtime, passed;
config.autorun = true;
// Log the last module results
if ( config.previousModule ) {
runLoggingCallbacks( "moduleDone", {
name: config.previousModule.name,
tests: config.previousModule.tests,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all,
runtime: now() - config.moduleStats.started
});
}
delete config.previousModule;
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
runLoggingCallbacks( "done", {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
});
}
// Doesn't support IE6 to IE9, it will return undefined on these browsers
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
@ -551,46 +237,405 @@ function sourceFromStacktrace( offset ) {
return extractStacktrace( error, offset );
}
function synchronize( callback, last ) {
if ( QUnit.objectType( callback ) === "array" ) {
while ( callback.length ) {
synchronize( callback.shift() );
/**
* 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,
// add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
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."
}
],
// Set of all modules.
modules: [],
// Stack of nested modules
moduleStack: [],
// The first unnamed module
currentModule: {
name: "",
tests: []
},
callbacks: {}
};
var urlParams = defined.document ? getUrlParams() : {};
// Push a loose unnamed module to the modules collection
config.modules.push( config.currentModule );
if ( urlParams.filter === true ) {
delete urlParams.filter;
}
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
config.testId = [];
if ( urlParams.testId ) {
// Ensure that urlParams.testId is an array
urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
for (var i = 0; i < urlParams.testId.length; i++ ) {
config.testId.push( urlParams.testId[ i ] );
}
}
var loggingCallbacks = {};
// Register logging callbacks
function registerLoggingCallbacks( obj ) {
var i, l, key,
callbackNames = [ "begin", "done", "log", "testStart", "testDone",
"moduleStart", "moduleDone" ];
function registerLoggingCallback( key ) {
var loggingCallback = function( callback ) {
if ( objectType( callback ) !== "function" ) {
throw new Error(
"QUnit logging methods require a callback function as their first parameters."
);
}
config.callbacks[ key ].push( callback );
};
// DEPRECATED: This will be removed on QUnit 2.0.0+
// Stores the registered functions allowing restoring
// at verifyLoggingCallbacks() if modified
loggingCallbacks[ key ] = loggingCallback;
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 );
}
}
// DEPRECATED: This will be removed on 2.0.0+
// This function verifies if the loggingCallbacks were modified by the user
// If so, it will restore it, assign the given callback and print a console warning
function verifyLoggingCallbacks() {
var loggingCallback, userCallback;
for ( loggingCallback in loggingCallbacks ) {
if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
userCallback = QUnit[ loggingCallback ];
// Restore the callback function
QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
// Assign the deprecated given callback
QUnit[ loggingCallback ]( userCallback );
if ( global.console && global.console.warn ) {
global.console.warn(
"QUnit." + loggingCallback + " was replaced with a new value.\n" +
"Please, check out the documentation on how to apply logging callbacks.\n" +
"Reference: http://api.qunitjs.com/category/callbacks/"
);
}
}
}
}
( function() {
if ( !defined.document ) {
return;
}
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
process( last );
}
}
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
var onErrorFnPrev = window.onerror;
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;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window.onerror = function( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
}
// Treat return value as window.onerror itself does,
// Only do our handling if not suppressed.
if ( ret !== true ) {
if ( QUnit.config.current ) {
if ( QUnit.config.current.ignoreGlobalErrors ) {
return true;
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", extend(function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: true } ) );
}
return false;
}
return ret;
};
} )();
QUnit.urlParams = urlParams;
// 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
QUnit.version = "1.20.0";
extend( QUnit, {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment, executeNow ) {
var module, moduleFns;
var currentModule = config.currentModule;
if ( arguments.length === 2 ) {
if ( testEnvironment instanceof Function ) {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}
// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
if ( testEnvironment && testEnvironment.setup ) {
testEnvironment.beforeEach = testEnvironment.setup;
delete testEnvironment.setup;
}
if ( testEnvironment && testEnvironment.teardown ) {
testEnvironment.afterEach = testEnvironment.teardown;
delete testEnvironment.teardown;
}
module = createModule();
moduleFns = {
beforeEach: setHook( module, "beforeEach" ),
afterEach: setHook( module, "afterEach" )
};
if ( executeNow instanceof 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;
var module = {
name: moduleName,
parentModule: parentModule,
tests: []
};
var env = {};
if ( parentModule ) {
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;
}
},
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
asyncTest: asyncTest,
test: test,
skip: skip,
only: only,
// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
start: function( count ) {
var globalStartAlreadyCalled = globalStartCalled;
if ( !config.current ) {
globalStartCalled = true;
if ( runStarted ) {
throw new Error( "Called start() outside of a test context while already started" );
} 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 ) {
// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
config.autostart = true;
return;
}
} else {
// If a test is running, adjust its semaphore
config.current.semaphore -= count || 1;
// If semaphore is non-numeric, throw error
if ( isNaN( config.current.semaphore ) ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() with a non-numeric decrement.",
sourceFromStacktrace( 2 )
);
return;
}
// Don't start until equal number of stop-calls
if ( config.current.semaphore > 0 ) {
return;
}
// throw an Error if start is called more often than stop
if ( config.current.semaphore < 0 ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() while already started (test's semaphore was 0 already)",
sourceFromStacktrace( 2 )
);
return;
}
}
resumeProcessing();
},
// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
stop: function( count ) {
// If there isn't a test running, don't allow QUnit.stop() to be called
if ( !config.current ) {
throw new Error( "Called stop() outside of a test context" );
}
// If a test is running, adjust its semaphore
config.current.semaphore += count || 1;
pauseProcessing();
},
config: config,
is: is,
objectType: objectType,
extend: extend,
load: function() {
config.pageLoaded = true;
// Initialize the configuration options
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: 0,
updateRate: 1000,
autostart: true,
filter: ""
}, true );
config.blocking = false;
if ( config.autostart ) {
resumeProcessing();
}
},
stack: function( offset ) {
offset = ( offset || 0 ) + 2;
return sourceFromStacktrace( offset );
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}
});
registerLoggingCallbacks( QUnit );
function begin() {
var i, l,
@ -628,23 +673,30 @@ function begin() {
process( true );
}
function resumeProcessing() {
runStarted = true;
function process( last ) {
function next() {
process( last );
}
var start = now();
config.depth = ( config.depth || 0 ) + 1;
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
if ( defined.setTimeout ) {
setTimeout(function() {
if ( config.current && config.current.semaphore > 0 ) {
return;
}
if ( config.timeout ) {
clearTimeout( config.timeout );
}
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 ||
( ( now() - start ) < config.updateRate ) ) {
if ( config.current ) {
begin();
}, 13 );
} else {
begin();
// 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();
}
}
@ -665,127 +717,67 @@ function pauseProcessing() {
}
}
function saveGlobal() {
config.pollution = [];
function resumeProcessing() {
runStarted = true;
if ( config.noglobals ) {
for ( var key in window ) {
if ( hasOwn.call( window, key ) ) {
// in Opera sometimes DOM element ids show up here, ignore them
if ( /^qunit-test-output/.test( key ) ) {
continue;
}
config.pollution.push( key );
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
if ( defined.setTimeout ) {
setTimeout(function() {
if ( config.current && config.current.semaphore > 0 ) {
return;
}
}
}
}
function checkPollution() {
var newGlobals,
deletedGlobals,
old = config.pollution;
saveGlobal();
newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
}
}
// 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;
if ( config.timeout ) {
clearTimeout( config.timeout );
}
}
}
return result;
}
function extend( a, b, undefOnly ) {
for ( var prop in b ) {
if ( hasOwn.call( b, prop ) ) {
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
if ( !( prop === "constructor" && a === window ) ) {
if ( b[ prop ] === undefined ) {
delete a[ prop ];
} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
a[ prop ] = b[ prop ];
}
}
}
}
return a;
}
function runLoggingCallbacks( key, args ) {
var i, l, callbacks;
callbacks = config.callbacks[ key ];
for ( i = 0, l = callbacks.length; i < l; i++ ) {
callbacks[ i ]( args );
begin();
}, 13 );
} else {
begin();
}
}
// DEPRECATED: This will be removed on 2.0.0+
// This function verifies if the loggingCallbacks were modified by the user
// If so, it will restore it, assign the given callback and print a console warning
function verifyLoggingCallbacks() {
var loggingCallback, userCallback;
function done() {
var runtime, passed;
for ( loggingCallback in loggingCallbacks ) {
if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
config.autorun = true;
userCallback = QUnit[ loggingCallback ];
// Restore the callback function
QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
// Assign the deprecated given callback
QUnit[ loggingCallback ]( userCallback );
if ( window.console && window.console.warn ) {
window.console.warn(
"QUnit." + loggingCallback + " was replaced with a new value.\n" +
"Please, check out the documentation on how to apply logging callbacks.\n" +
"Reference: http://api.qunitjs.com/category/callbacks/"
);
}
}
// Log the last module results
if ( config.previousModule ) {
runLoggingCallbacks( "moduleDone", {
name: config.previousModule.name,
tests: config.previousModule.tests,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all,
runtime: now() - config.moduleStats.started
});
}
delete config.previousModule;
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
runLoggingCallbacks( "done", {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
});
}
// from jquery.js
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
function setHook( module, hookName ) {
if ( module.testEnvironment === undefined ) {
module.testEnvironment = {};
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
return function( callback ) {
module.testEnvironment[ hookName ] = callback;
};
}
var focused = false;
function Test( settings ) {
var i, l;
@ -858,9 +850,11 @@ Test.prototype = {
config.current = this;
if ( this.module.testEnvironment ) {
delete this.module.testEnvironment.beforeEach;
delete this.module.testEnvironment.afterEach;
}
this.testEnvironment = extend( {}, this.module.testEnvironment );
delete this.testEnvironment.beforeEach;
delete this.testEnvironment.afterEach;
this.started = now();
runLoggingCallbacks( "testStart", {
@ -886,14 +880,12 @@ Test.prototype = {
this.callbackStarted = now();
if ( config.notrycatch ) {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
runTest( this );
return;
}
try {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
runTest( this );
} catch ( e ) {
this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
@ -906,6 +898,11 @@ Test.prototype = {
QUnit.start();
}
}
function runTest( test ) {
promise = test.callback.call( test.testEnvironment, test.assert );
test.resolvePromise( promise );
}
},
after: function() {
@ -918,16 +915,19 @@ Test.prototype = {
return function runHook() {
config.current = test;
if ( config.notrycatch ) {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
callHook();
return;
}
try {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
callHook();
} catch ( error ) {
test.pushFailure( hookName + " failed on " + test.testName + ": " +
( error.message || error ), extractStacktrace( error, 0 ) );
( error.message || error ), extractStacktrace( error, 0 ) );
}
function callHook() {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
}
};
},
@ -936,16 +936,20 @@ Test.prototype = {
hooks: function( handler ) {
var hooks = [];
function processHooks( test, module ) {
if ( module.parentModule ) {
processHooks( test, module.parentModule );
}
if ( module.testEnvironment &&
QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
}
}
// Hooks are ignored on skipped tests
if ( this.skip ) {
return hooks;
if ( !this.skip ) {
processHooks( this, this.module );
}
if ( this.module.testEnvironment &&
QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
}
return hooks;
},
@ -990,6 +994,9 @@ Test.prototype = {
assertions: this.assertions,
testId: this.testId,
// Source of Test
source: this.stack,
// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
duration: this.runtime
});
@ -1003,7 +1010,7 @@ Test.prototype = {
},
queue: function() {
var bad,
var priority,
test = this;
if ( !this.valid() ) {
@ -1019,7 +1026,6 @@ Test.prototype = {
},
test.hooks( "beforeEach" ),
function() {
test.run();
},
@ -1035,19 +1041,14 @@ Test.prototype = {
]);
}
// `bad` initialized at top of scope
// defer when previous test run passed, if storage is available
bad = QUnit.config.reorder && defined.sessionStorage &&
// Prioritize previously failed tests, detected from sessionStorage
priority = QUnit.config.reorder && defined.sessionStorage &&
+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
if ( bad ) {
run();
} else {
synchronize( run, true );
}
return synchronize( run, priority );
},
push: function( result, actual, expected, message ) {
push: function( result, actual, expected, message, negative ) {
var source,
details = {
module: this.module.name,
@ -1057,6 +1058,7 @@ Test.prototype = {
actual: actual,
expected: expected,
testId: this.testId,
negative: negative || false,
runtime: now() - this.started
};
@ -1077,7 +1079,7 @@ Test.prototype = {
},
pushFailure: function( message, source, actual ) {
if ( !this instanceof Test ) {
if ( !( this instanceof Test ) ) {
throw new Error( "pushFailure() assertion outside test context, was " +
sourceFromStacktrace( 2 ) );
}
@ -1113,7 +1115,7 @@ Test.prototype = {
QUnit.stop();
then.call(
promise,
QUnit.start,
function() { QUnit.start(); },
function( error ) {
message = "Promise rejected " +
( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
@ -1137,6 +1139,17 @@ Test.prototype = {
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
function testInModuleChain( testModule ) {
var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
if ( testModuleName === module ) {
return true;
} else if ( testModule.parentModule ) {
return testInModuleChain( testModule.parentModule );
} else {
return false;
}
}
// Internally-generated tests are always valid
if ( this.callback && this.callback.validTest ) {
return true;
@ -1146,7 +1159,7 @@ Test.prototype = {
return false;
}
if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
if ( module && !testInModuleChain( this.module ) ) {
return false;
}
@ -1167,7 +1180,6 @@ Test.prototype = {
// Otherwise, do the opposite
return !include;
}
};
// Resets the test setup. Useful for tests that modify the DOM.
@ -1180,7 +1192,7 @@ QUnit.reset = function() {
// Return on non-browser environments
// This is necessary to not break on node tests
if ( typeof window === "undefined" ) {
if ( !defined.document ) {
return;
}
@ -1228,6 +1240,145 @@ function generateHash( module, testName ) {
return hex.slice( -8 );
}
function synchronize( callback, priority ) {
var last = !priority;
if ( QUnit.objectType( callback ) === "array" ) {
while ( callback.length ) {
synchronize( callback.shift() );
}
return;
}
if ( priority ) {
priorityFill( callback );
} else {
config.queue.push( callback );
}
if ( config.autorun && !config.blocking ) {
process( last );
}
}
// Place previously failed tests on a queue priority line, respecting the order they get assigned.
function priorityFill( callback ) {
var queue, prioritizedQueue;
queue = config.queue.slice( priorityFill.pos );
prioritizedQueue = config.queue.slice( 0, -config.queue.length + priorityFill.pos );
queue.unshift( callback );
queue.unshift.apply( queue, prioritizedQueue );
config.queue = queue;
priorityFill.pos += 1;
}
priorityFill.pos = 0;
function saveGlobal() {
config.pollution = [];
if ( config.noglobals ) {
for ( var key in global ) {
if ( hasOwn.call( global, 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 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
}
}
// Will be exposed as QUnit.asyncTest
function asyncTest( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test( testName, expected, callback, true );
}
// Will be exposed as QUnit.test
function test( testName, expected, callback, async ) {
if ( focused ) { return; }
var newTest;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
newTest = new Test({
testName: testName,
expected: expected,
async: async,
callback: callback
});
newTest.queue();
}
// 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, expected, callback, async ) {
var newTest;
if ( focused ) { return; }
QUnit.config.queue.length = 0;
focused = true;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
newTest = new Test({
testName: testName,
expected: expected,
async: async,
callback: callback
});
newTest.queue();
}
function Assert( testContext ) {
this.test = testContext;
}
@ -1245,30 +1396,41 @@ QUnit.assert = Assert.prototype = {
}
},
// Increment this Test's semaphore counter, then return a single-use function that
// Increment this Test's semaphore counter, then return a function that
// decrements that counter a maximum of once.
async: function() {
async: function( count ) {
var test = this.test,
popped = false;
popped = false,
acceptCallCount = count;
if ( typeof acceptCallCount === "undefined" ) {
acceptCallCount = 1;
}
test.semaphore += 1;
test.usedAsync = true;
pauseProcessing();
return function done() {
if ( !popped ) {
test.semaphore -= 1;
popped = true;
resumeProcessing();
} else {
test.pushFailure( "Called the callback returned from `assert.async` more than once",
if ( popped ) {
test.pushFailure( "Too many calls to the `assert.async` callback",
sourceFromStacktrace( 2 ) );
return;
}
acceptCallCount -= 1;
if ( acceptCallCount > 0 ) {
return;
}
test.semaphore -= 1;
popped = true;
resumeProcessing();
};
},
// Exports test.push() to the user API
push: function( /* result, actual, expected, message */ ) {
push: function( /* result, actual, expected, message, negative */ ) {
var assert = this,
currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
@ -1303,7 +1465,7 @@ QUnit.assert = Assert.prototype = {
notOk: function( result, message ) {
message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
QUnit.dump.parse( result ) );
this.push( !result, result, false, message );
this.push( !result, result, false, message, true );
},
equal: function( actual, expected, message ) {
@ -1313,7 +1475,7 @@ QUnit.assert = Assert.prototype = {
notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */
this.push( expected != actual, actual, expected, message );
this.push( expected != actual, actual, expected, message, true );
},
propEqual: function( actual, expected, message ) {
@ -1325,7 +1487,7 @@ QUnit.assert = Assert.prototype = {
notPropEqual: function( actual, expected, message ) {
actual = objectValues( actual );
expected = objectValues( expected );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
},
deepEqual: function( actual, expected, message ) {
@ -1333,7 +1495,7 @@ QUnit.assert = Assert.prototype = {
},
notDeepEqual: function( actual, expected, message ) {
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
},
strictEqual: function( actual, expected, message ) {
@ -1341,7 +1503,7 @@ QUnit.assert = Assert.prototype = {
},
notStrictEqual: function( actual, expected, message ) {
this.push( expected !== actual, actual, expected, message );
this.push( expected !== actual, actual, expected, message, true );
},
"throws": function( block, expected, message ) {
@ -1401,229 +1563,305 @@ QUnit.assert = Assert.prototype = {
}
};
// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
// Provide an alternative to assert.throws(), for environments that consider throws a reserved word
// Known to us are: Closure Compiler, Narwhal
(function() {
/*jshint sub:true */
Assert.prototype.raises = Assert.prototype[ "throws" ];
}());
function errorString( error ) {
var name, message,
resultErrorString = error.toString();
if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
name = error.name ? error.name.toString() : "Error";
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;
}
}
// Test for equality any JavaScript type.
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = (function() {
// Call the o related callback with the given arguments.
function bindCallbacks( o, callbacks, args ) {
var prop = QUnit.objectType( o );
if ( prop ) {
if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
return callbacks[ prop ].apply( callbacks, args );
} else {
return callbacks[ prop ]; // or undefined
}
// Stack to decide between skip/abort functions
var callers = [];
// Stack to avoiding loops from circular referencing
var parents = [];
var parentsB = [];
function useStrictEquality( b, a ) {
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
// To catch short annotation VS 'new' annotation of a declaration. e.g.:
// `var i = 1;`
// `var j = new Number(1);`
return a == b;
} else {
return a === b;
}
}
// the real equiv function
var innerEquiv,
function compareConstructors( a, b ) {
var getProto = Object.getPrototypeOf || function( obj ) {
// stack to decide between skip/abort functions
callers = [],
// stack to avoiding loops from circular referencing
parents = [],
parentsB = [],
getProto = Object.getPrototypeOf || function( obj ) {
/* jshint camelcase: false, proto: true */
/*jshint proto: true */
return obj.__proto__;
};
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;
}
var callbacks = {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"symbol": useStrictEquality,
"nan": function( b ) {
return isNaN( b );
},
callbacks = (function() {
// for string, boolean, number and null
function useStrictEquality( b, a ) {
"date": function( b, a ) {
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
},
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
// to catch short annotation VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
// The regex itself
a.source === b.source &&
// And its modifiers
a.global === b.global &&
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.sticky === b.sticky;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function() {
var caller = callers[ callers.length - 1 ];
return caller !== Object && typeof caller !== "undefined";
},
"array": function( b, a ) {
var i, j, len, loop, aCircular, bCircular;
// b could be an object literal here
if ( QUnit.objectType( b ) !== "array" ) {
return false;
}
return {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
len = a.length;
if ( len !== b.length ) {
// safe and faster
return false;
}
"nan": function( b ) {
return isNaN( b );
},
"date": function( b, a ) {
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
},
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
// the regex itself
a.source === b.source &&
// and its modifiers
a.global === b.global &&
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.sticky === b.sticky;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function() {
var caller = callers[ callers.length - 1 ];
return caller !== Object && typeof caller !== "undefined";
},
"array": function( b, a ) {
var i, j, len, loop, aCircular, bCircular;
// b could be an object literal here
if ( QUnit.objectType( b ) !== "array" ) {
return false;
}
len = a.length;
if ( len !== b.length ) {
// safe and faster
return false;
}
// track reference to avoid circular references
parents.push( a );
parentsB.push( b );
for ( i = 0; i < len; i++ ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[ j ] === a[ i ];
bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
parents.pop();
parentsB.pop();
return false;
}
}
}
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
// Track reference to avoid circular references
parents.push( a );
parentsB.push( b );
for ( i = 0; i < len; i++ ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[ j ] === a[ i ];
bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
parents.pop();
parentsB.pop();
return false;
}
}
}
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
parents.pop();
parentsB.pop();
return true;
},
return false;
}
}
parents.pop();
parentsB.pop();
return true;
},
"object": function( b, a ) {
"set": function( b, a ) {
var aArray, bArray;
/*jshint forin:false */
var i, j, loop, aCircular, bCircular,
// Default to true
eq = true,
aProperties = [],
bProperties = [];
// `b` could be any object here
if ( QUnit.objectType( b ) !== "set" ) {
return false;
}
// comparing constructors is more strict than using
// instanceof
if ( a.constructor !== b.constructor ) {
aArray = [];
a.forEach( function( v ) {
aArray.push( v );
});
bArray = [];
b.forEach( function( v ) {
bArray.push( v );
});
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
return false;
}
}
return innerEquiv( bArray, aArray );
},
// stack constructor before traversing properties
callers.push( a.constructor );
"map": function( b, a ) {
var aArray, bArray;
// track reference to avoid circular references
parents.push( a );
parentsB.push( b );
// `b` could be any object here
if ( QUnit.objectType( b ) !== "map" ) {
return false;
}
// be strict: don't ensure hasOwnProperty and go deep
for ( i in a ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[ j ] === a[ i ];
bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
eq = false;
break;
}
}
}
aProperties.push( i );
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
aArray = [];
a.forEach( function( v, k ) {
aArray.push( [ k, v ] );
});
bArray = [];
b.forEach( function( v, k ) {
bArray.push( [ k, v ] );
});
return innerEquiv( bArray, aArray );
},
"object": function( b, a ) {
var i, j, loop, aCircular, bCircular;
// Default to true
var eq = true;
var aProperties = [];
var bProperties = [];
if ( compareConstructors( a, b ) === false ) {
return false;
}
// Stack constructor before traversing properties
callers.push( a.constructor );
// Track reference to avoid circular references
parents.push( a );
parentsB.push( b );
// Be strict: don't ensure hasOwnProperty and go deep
for ( i in a ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[ j ] === a[ i ];
bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
eq = false;
break;
}
}
parents.pop();
parentsB.pop();
callers.pop(); // unstack, we are done
for ( i in b ) {
bProperties.push( i ); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
}
};
}());
aProperties.push( i );
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
eq = false;
break;
}
}
innerEquiv = function() { // can take multiple arguments
parents.pop();
parentsB.pop();
// Unstack, we are done
callers.pop();
for ( i in b ) {
// Collect b's properties
bProperties.push( i );
}
// Ensures identical properties name
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
}
};
function typeEquiv( a, b ) {
var prop = QUnit.objectType( a );
return callbacks[ prop ]( b, a );
}
// The real equiv function
function innerEquiv() {
var args = [].slice.apply( arguments );
if ( args.length < 2 ) {
return true; // end transition
// End transition
return true;
}
return ( (function( a, b ) {
if ( a === b ) {
return true; // catch the most you can
// Catch the most you can
return true;
} else if ( a === null || b === null || typeof a === "undefined" ||
typeof b === "undefined" ||
QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
// don't lose time with error prone cases
// Don't lose time with error prone cases
return false;
} else {
return bindCallbacks( a, callbacks, [ b, a ] );
return typeEquiv( a, b );
}
// apply transition with (1..n) arguments
// Apply transition with (1..n) arguments
}( args[ 0 ], args[ 1 ] ) ) &&
innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
};
}
return innerEquiv;
}());
@ -1632,7 +1870,7 @@ QUnit.equiv = (function() {
// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
QUnit.dump = (function() {
function quote( str ) {
return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
}
function literal( o ) {
return o + "";
@ -1893,7 +2131,7 @@ QUnit.dump = (function() {
QUnit.jsDump = QUnit.dump;
// For browser, export only select globals
if ( typeof window !== "undefined" ) {
if ( defined.document ) {
// Deprecated
// Extend assert methods to QUnit and Global scope through Backwards compatibility
@ -1932,7 +2170,8 @@ if ( typeof window !== "undefined" ) {
"notDeepEqual",
"strictEqual",
"notStrictEqual",
"throws"
"throws",
"raises"
];
for ( i = 0, l = keys.length; i < l; i++ ) {
@ -1963,13 +2202,6 @@ if ( typeof define === "function" && define.amd ) {
QUnit.config.autostart = false;
}
// Get a reference to the global object, like window in browsers
}( (function() {
return this;
})() ));
/*istanbul ignore next */
// jscs:disable maximumLineLength
/*
* 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),
@ -1997,1067 +2229,1103 @@ if ( typeof define === "function" && define.amd ) {
*
* Usage: QUnit.diff(expected, actual)
*
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
*/
QUnit.diff = (function() {
QUnit.diff = ( function() {
function DiffMatchPatch() {
}
function DiffMatchPatch() {
// DIFF FUNCTIONS
// Defaults.
// Redefine these in your program to override the defaults.
// Number of seconds to map a diff before giving up (0 for infinity).
this.DiffTimeout = 1.0;
// Cost of an empty edit operation in terms of edit characters.
this.DiffEditCost = 4;
}
// 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,
/**
* 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.
* @param {number} optDeadline Optional time when the diff should be complete
* by. Used internally for recursive calls. Users should set DiffTimeout
* instead.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
*/
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
var deadline, checklines, commonlength,
/**
* 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;
// Set a deadline by which time the diff must be complete.
if ( typeof optDeadline === "undefined" ) {
if ( this.DiffTimeout <= 0 ) {
optDeadline = Number.MAX_VALUE;
} else {
optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
}
}
deadline = optDeadline;
// Check for null inputs.
if ( text1 === null || text2 === null ) {
throw new Error( "Null input. (DiffMain)" );
}
// The diff must be complete in up to 1 second.
deadline = ( new Date() ).getTime() + 1000;
// Check for equality (speedup).
if ( text1 === text2 ) {
if ( text1 ) {
return [
[ DIFF_EQUAL, text1 ]
];
}
return [];
}
// Check for null inputs.
if ( text1 === null || text2 === null ) {
throw new Error( "Null input. (DiffMain)" );
}
if ( typeof optChecklines === "undefined" ) {
optChecklines = true;
}
// Check for equality (speedup).
if ( text1 === text2 ) {
if ( text1 ) {
return [
[ DIFF_EQUAL, text1 ]
];
}
return [];
}
checklines = optChecklines;
if ( typeof optChecklines === "undefined" ) {
optChecklines = true;
}
// Trim off common prefix (speedup).
commonlength = this.diffCommonPrefix( text1, text2 );
commonprefix = text1.substring( 0, commonlength );
text1 = text1.substring( commonlength );
text2 = text2.substring( commonlength );
checklines = optChecklines;
// 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 );
// Trim off common prefix (speedup).
commonlength = this.diffCommonPrefix( text1, text2 );
commonprefix = text1.substring( 0, commonlength );
text1 = text1.substring( commonlength );
text2 = text2.substring( commonlength );
// Compute the diff on the middle block.
diffs = this.diffCompute( text1, text2, checklines, deadline );
// 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 );
// Restore the prefix and suffix.
if ( commonprefix ) {
diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
}
if ( commonsuffix ) {
diffs.push( [ DIFF_EQUAL, commonsuffix ] );
}
this.diffCleanupMerge( diffs );
return diffs;
};
// Compute the diff on the middle block.
diffs = this.diffCompute( text1, text2, checklines, deadline );
/**
* 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,
// 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 ) {
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( 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;
} else { // An insertion or deletion.
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
postDel = true;
} else {
postIns = true;
}
/*
* Five types to be split:
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
* <ins>A</ins>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<ins>C</ins>
* <ins>A</del>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<del>C</del>
*/
if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
( ( lastequality.length < this.DiffEditCost / 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++;
}
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 ) {
if ( changes ) {
this.diffCleanupMerge( diffs );
}
};
// Equality found.
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
/**
* 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>" + data + "</ins>";
break;
case DIFF_DELETE:
html[x] = "<del>" + data + "</del>";
break;
case DIFF_EQUAL:
html[x] = "<span>" + data + "</span>";
break;
}
}
return html.join("");
};
// Candidate found.
equalities[ equalitiesLength++ ] = pointer;
preIns = postIns;
preDel = postDel;
lastequality = diffs[ pointer ][ 1 ];
} else {
/**
* 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: http://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;
};
// Not a candidate, and can never become one.
equalitiesLength = 0;
lastequality = null;
}
postIns = postDel = false;
/**
* 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: http://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;
};
// An insertion or deletion.
} else {
/**
* 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,
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
postDel = true;
} else {
postIns = true;
}
/*
* Five types to be split:
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
* <ins>A</ins>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<ins>C</ins>
* <ins>A</del>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<del>C</del>
*/
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>" + data + "</ins>";
break;
case DIFF_DELETE:
html[ x ] = "<del>" + data + "</del>";
break;
case DIFF_EQUAL:
html[ x ] = "<span>" + 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: http://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: http://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 ( !text1 ) {
// Just add some text (speedup).
return [
[ DIFF_INSERT, text2 ]
];
}
if (!text2) {
// Just delete some text (speedup).
return [
[ DIFF_DELETE, text1 ]
];
}
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;
}
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 ]
];
}
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);
}
// 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);
}
if ( checklines && text1.length > 100 && text2.length > 100 ) {
return this.diffLineMode( text1, text2, deadline );
}
return this.diffBisect(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,
/**
* 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;
if (this.DiffTimeout <= 0) {
// Don't risk returning a non-optimal diff if we have unlimited time.
return null;
}
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,
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;
}
}
// 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;
}
// 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.
text1A, text1B, text2A, text2B;
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 ];
};
// A half-match was found, sort out the return data.
text1A, text1B, text2A, text2B;
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,
/**
* 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;
// 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);
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);
// 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.
// 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;
};
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,
/**
* 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;
}
// 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 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 ]
];
};
// 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);
/**
* 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);
// Compute both diffs serially.
diffs = this.DiffMain( text1a, text2a, false, deadline );
diffsb = this.DiffMain( text1b, text2b, false, deadline );
return diffs.concat(diffsb);
};
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,
/**
* 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;
lengthInsertions1 = 0; // Reset the counters.
lengthDeletions1 = 0;
lengthInsertions2 = 0;
lengthDeletions2 = 0;
lastequality = null;
changes = true;
}
}
pointer++;
}
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 ) ) ) {
// Normalize the diff.
if (changes) {
this.diffCleanupMerge(diffs);
}
// Duplicate record.
diffs.splice(
equalities[ equalitiesLength - 1 ],
0,
[ DIFF_DELETE, lastequality ]
);
// 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++;
}
};
// Change second copy to insert.
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
/**
* 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,
// 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;
}
// 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: http://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++;
}
}
};
// Start by looking for a single character match
// and increase length until no match is found.
// Performance analysis: http://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
/**
* 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] = "";
// '\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;
/**
* 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;
}
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
};
};
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("");
}
};
/**
* 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;
diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
pointer = 0;
countDelete = 0;
countInsert = 0;
textDelete = "";
textInsert = "";
commonlength;
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 prefixies.
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.
}
/**
* 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 = "";
commonlength;
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 prefixies.
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 ) {
// 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) {
// This is a single edit surrounded by equalities.
if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
diffs[ pointer - 1 ][ 1 ].length ) === 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 ( diffs[ pointer ][ 1 ].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);
}
};
// 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.
}
return function(o, n) {
// 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);
//console.log(output);
diff.diffCleanupEfficiency(output);
text = diff.diffPrettyHtml(output);
diff = new DiffMatchPatch();
output = diff.DiffMain( o, n );
diff.diffCleanupEfficiency( output );
text = diff.diffPrettyHtml( output );
return text;
};
}());
// jscs:enable
return text;
};
}() );
// Get a reference to the global object, like window in browsers
}( (function() {
return this;
})() ));
(function() {
// Don't load the HTML Reporter on non-Browser environments
if ( typeof window === "undefined" || !window.document ) {
return;
}
// Deprecated QUnit.init - Ref #530
// Re-initialize the configuration options
QUnit.init = function() {
@ -3115,12 +3383,8 @@ QUnit.init = function() {
}
};
// Don't load the HTML Reporter on non-Browser environments
if ( typeof window === "undefined" ) {
return;
}
var config = QUnit.config,
collapseNext = false,
hasOwn = Object.prototype.hasOwnProperty,
defined = {
document: window.document !== undefined,
@ -3517,6 +3781,17 @@ function storeFixture() {
}
}
function appendFilteredTest() {
var testId = QUnit.config.testId;
if ( !testId || testId.length <= 0 ) {
return "";
}
return "<div id='qunit-filteredTest'>Rerunning selected tests: " + testId.join(", ") +
" <a id='qunit-clearFilter' href='" +
setUrl({ filter: undefined, module: undefined, testId: undefined }) +
"'>" + "Run all tests" + "</a></div>";
}
function appendUserAgent() {
var userAgent = id( "qunit-userAgent" );
@ -3524,7 +3799,7 @@ function appendUserAgent() {
userAgent.innerHTML = "";
userAgent.appendChild(
document.createTextNode(
"QUnit " + QUnit.version + "; " + navigator.userAgent
"QUnit " + QUnit.version + "; " + navigator.userAgent
)
);
}
@ -3588,6 +3863,7 @@ QUnit.begin(function( details ) {
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
appendFilteredTest() +
"<h2 id='qunit-userAgent'></h2>" +
"<ol id='qunit-tests'></ol>";
}
@ -3693,9 +3969,15 @@ QUnit.testStart(function( details ) {
});
function stripHtml( string ) {
// strip tags, html entity and whitespaces
return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
}
QUnit.log(function( details ) {
var assertList, assertLi,
message, expected, actual,
message, expected, actual, diff,
showDiff = false,
testItem = id( "qunit-test-output-" + details.testId );
if ( !testItem ) {
@ -3710,26 +3992,43 @@ QUnit.log(function( details ) {
// 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" ) ) {
expected = escapeText( QUnit.dump.parse( details.expected ) );
if ( details.negative ) {
expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
} else {
expected = escapeText( QUnit.dump.parse( details.expected ) );
}
actual = escapeText( QUnit.dump.parse( details.actual ) );
message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
expected +
"</pre></td></tr>";
if ( actual !== expected ) {
message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
actual + "</pre></td></tr>" +
"<tr class='test-diff'><th>Diff: </th><td><pre>" +
QUnit.diff( expected, actual ) + "</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='" + setUrl({ maxDepth: -1 }) + "'>" +
"Rerun</a> without max depth.</p></td></tr>";
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='" + setUrl({ maxDepth: -1 }) + "'>" +
"Rerun</a> without max depth.</p></td></tr>";
}
if ( details.source ) {
@ -3757,7 +4056,7 @@ QUnit.log(function( details ) {
QUnit.testDone(function( details ) {
var testTitle, time, testItem, assertList,
good, bad, testCounts, skipped,
good, bad, testCounts, skipped, sourceName,
tests = id( "qunit-tests" );
if ( !tests ) {
@ -3781,6 +4080,16 @@ QUnit.testDone(function( details ) {
}
if ( bad === 0 ) {
// Collapse the passing tests
addClass( assertList, "qunit-collapsed" );
} else if ( bad && config.collapse && !collapseNext ) {
// Skip collapsing the first failing test
collapseNext = true;
} else {
// Collapse remaining tests
addClass( assertList, "qunit-collapsed" );
}
@ -3812,10 +4121,31 @@ QUnit.testDone(function( details ) {
time.innerHTML = details.runtime + " ms";
testItem.insertBefore( time, assertList );
}
// Show the source of the test when showing assertions
if ( details.source ) {
sourceName = document.createElement( "p" );
sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
addClass( sourceName, "qunit-source" );
if ( bad === 0 ) {
addClass( sourceName, "qunit-collapsed" );
}
addEvent( testTitle, "click", function() {
toggleClass( sourceName, "qunit-collapsed" );
});
testItem.appendChild( sourceName );
}
});
if ( defined.document ) {
if ( document.readyState === "complete" ) {
// Avoid readyState issue with phantomjs
// Ref: #818
var notPhantom = ( function( p ) {
return !( p && p.version && p.version.major > 0 );
} )( window.phantom );
if ( notPhantom && document.readyState === "complete" ) {
QUnit.load();
} else {
addEvent( window, "load", QUnit.load );