Make WordPress Core

Ticket #37117: 37117.diff

File 37117.diff, 408.9 KB (added by SergeyBiryukov, 4 years ago)
  • package.json

     
    6363                "jquery-migrate": "1.4.1",
    6464                "matchdep": "~2.0.0",
    6565                "node-sass": "~4.13.1",
     66                "qunit": "~2.9.0",
     67                "sinon": "~9.0.0",
     68                "sinon-test": "~3.0.0",
    6669                "source-map-loader": "^0.2.4",
    6770                "uglify-js": "^3.6.0",
    6871                "uglifyjs-webpack-plugin": "2.2.0",
  • tests/qunit/index.html

     
    5656                <script src="../../build/wp-includes/js/mce-view.js"></script>
    5757
    5858                <!-- QUnit -->
    59                 <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
    60                 <script src="vendor/qunit.js"></script>
    61                 <script src="vendor/sinon.js"></script>
    62                 <script src="vendor/sinon-qunit.js"></script>
    63                 <script>QUnit.config.hidepassed = false;</script>
     59                <link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" type="text/css" media="screen" />
     60                <script src="../../node_modules/qunit/qunit/qunit.js"></script>
     61                <script src="../../node_modules/sinon/pkg/sinon.js"></script>
     62                <script src="../../node_modules/sinon-test/dist/sinon-test.js"></script>
     63                <script>
     64                        var qTest = QUnit.test, sTest = sinonTest( sinon );
     65
     66                        QUnit.config.hidepassed = false;
     67
     68                        QUnit.test = function( testName, callback ) {
     69                                return qTest( testName, sTest(callback) );
     70                        };
     71                </script>
    6472        </head>
    6573        <body>
    6674                <div id="qunit"></div>
  • tests/qunit/vendor/qunit.css

     
    1 /*!
    2  * QUnit 1.18.0
    3  * http://qunitjs.com/
    4  *
    5  * Copyright jQuery Foundation and other contributors
    6  * Released under the MIT license
    7  * http://jquery.org/license
    8  *
    9  * Date: 2015-04-03T10:23Z
    10  */
    11 
    12 /** Font Family and Sizes */
    13 
    14 #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
    15         font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
    16 }
    17 
    18 #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
    19 #qunit-tests { font-size: smaller; }
    20 
    21 
    22 /** Resets */
    23 
    24 #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
    25         margin: 0;
    26         padding: 0;
    27 }
    28 
    29 
    30 /** Header */
    31 
    32 #qunit-header {
    33         padding: 0.5em 0 0.5em 1em;
    34 
    35         color: #8699A4;
    36         background-color: #0D3349;
    37 
    38         font-size: 1.5em;
    39         line-height: 1em;
    40         font-weight: 400;
    41 
    42         border-radius: 5px 5px 0 0;
    43 }
    44 
    45 #qunit-header a {
    46         text-decoration: none;
    47         color: #C2CCD1;
    48 }
    49 
    50 #qunit-header a:hover,
    51 #qunit-header a:focus {
    52         color: #FFF;
    53 }
    54 
    55 #qunit-testrunner-toolbar label {
    56         display: inline-block;
    57         padding: 0 0.5em 0 0.1em;
    58 }
    59 
    60 #qunit-banner {
    61         height: 5px;
    62 }
    63 
    64 #qunit-testrunner-toolbar {
    65         padding: 0.5em 1em 0.5em 1em;
    66         color: #5E740B;
    67         background-color: #EEE;
    68         overflow: hidden;
    69 }
    70 
    71 #qunit-userAgent {
    72         padding: 0.5em 1em 0.5em 1em;
    73         background-color: #2B81AF;
    74         color: #FFF;
    75         text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
    76 }
    77 
    78 #qunit-modulefilter-container {
    79         float: right;
    80         padding: 0.2em;
    81 }
    82 
    83 .qunit-url-config {
    84         display: inline-block;
    85         padding: 0.1em;
    86 }
    87 
    88 .qunit-filter {
    89         display: block;
    90         float: right;
    91         margin-left: 1em;
    92 }
    93 
    94 /** Tests: Pass/Fail */
    95 
    96 #qunit-tests {
    97         list-style-position: inside;
    98 }
    99 
    100 #qunit-tests li {
    101         padding: 0.4em 1em 0.4em 1em;
    102         border-bottom: 1px solid #FFF;
    103         list-style-position: inside;
    104 }
    105 
    106 #qunit-tests > li {
    107         display: none;
    108 }
    109 
    110 #qunit-tests li.running,
    111 #qunit-tests li.pass,
    112 #qunit-tests li.fail,
    113 #qunit-tests li.skipped {
    114         display: list-item;
    115 }
    116 
    117 #qunit-tests.hidepass li.running,
    118 #qunit-tests.hidepass li.pass {
    119         visibility: hidden;
    120         position: absolute;
    121         width:   0px;
    122         height:  0px;
    123         padding: 0;
    124         border:  0;
    125         margin:  0;
    126 }
    127 
    128 #qunit-tests li strong {
    129         cursor: pointer;
    130 }
    131 
    132 #qunit-tests li.skipped strong {
    133         cursor: default;
    134 }
    135 
    136 #qunit-tests li a {
    137         padding: 0.5em;
    138         color: #C2CCD1;
    139         text-decoration: none;
    140 }
    141 
    142 #qunit-tests li p a {
    143         padding: 0.25em;
    144         color: #6B6464;
    145 }
    146 #qunit-tests li a:hover,
    147 #qunit-tests li a:focus {
    148         color: #000;
    149 }
    150 
    151 #qunit-tests li .runtime {
    152         float: right;
    153         font-size: smaller;
    154 }
    155 
    156 .qunit-assert-list {
    157         margin-top: 0.5em;
    158         padding: 0.5em;
    159 
    160         background-color: #FFF;
    161 
    162         border-radius: 5px;
    163 }
    164 
    165 .qunit-collapsed {
    166         display: none;
    167 }
    168 
    169 #qunit-tests table {
    170         border-collapse: collapse;
    171         margin-top: 0.2em;
    172 }
    173 
    174 #qunit-tests th {
    175         text-align: right;
    176         vertical-align: top;
    177         padding: 0 0.5em 0 0;
    178 }
    179 
    180 #qunit-tests td {
    181         vertical-align: top;
    182 }
    183 
    184 #qunit-tests pre {
    185         margin: 0;
    186         white-space: pre-wrap;
    187         word-wrap: break-word;
    188 }
    189 
    190 #qunit-tests del {
    191         background-color: #E0F2BE;
    192         color: #374E0C;
    193         text-decoration: none;
    194 }
    195 
    196 #qunit-tests ins {
    197         background-color: #FFCACA;
    198         color: #500;
    199         text-decoration: none;
    200 }
    201 
    202 /*** Test Counts */
    203 
    204 #qunit-tests b.counts                       { color: #000; }
    205 #qunit-tests b.passed                       { color: #5E740B; }
    206 #qunit-tests b.failed                       { color: #710909; }
    207 
    208 #qunit-tests li li {
    209         padding: 5px;
    210         background-color: #FFF;
    211         border-bottom: none;
    212         list-style-position: inside;
    213 }
    214 
    215 /*** Passing Styles */
    216 
    217 #qunit-tests li li.pass {
    218         color: #3C510C;
    219         background-color: #FFF;
    220         border-left: 10px solid #C6E746;
    221 }
    222 
    223 #qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
    224 #qunit-tests .pass .test-name               { color: #366097; }
    225 
    226 #qunit-tests .pass .test-actual,
    227 #qunit-tests .pass .test-expected           { color: #999; }
    228 
    229 #qunit-banner.qunit-pass                    { background-color: #C6E746; }
    230 
    231 /*** Failing Styles */
    232 
    233 #qunit-tests li li.fail {
    234         color: #710909;
    235         background-color: #FFF;
    236         border-left: 10px solid #EE5757;
    237         white-space: pre;
    238 }
    239 
    240 #qunit-tests > li:last-child {
    241         border-radius: 0 0 5px 5px;
    242 }
    243 
    244 #qunit-tests .fail                          { color: #000; background-color: #EE5757; }
    245 #qunit-tests .fail .test-name,
    246 #qunit-tests .fail .module-name             { color: #000; }
    247 
    248 #qunit-tests .fail .test-actual             { color: #EE5757; }
    249 #qunit-tests .fail .test-expected           { color: #008000; }
    250 
    251 #qunit-banner.qunit-fail                    { background-color: #EE5757; }
    252 
    253 /*** Skipped tests */
    254 
    255 #qunit-tests .skipped {
    256         background-color: #EBECE9;
    257 }
    258 
    259 #qunit-tests .qunit-skipped-label {
    260         background-color: #F4FF77;
    261         display: inline-block;
    262         font-style: normal;
    263         color: #366097;
    264         line-height: 1.8em;
    265         padding: 0 0.5em;
    266         margin: -0.4em 0.4em -0.4em 0;
    267 }
    268 
    269 /** Result */
    270 
    271 #qunit-testresult {
    272         padding: 0.5em 1em 0.5em 1em;
    273 
    274         color: #2B81AF;
    275         background-color: #D2E0E6;
    276 
    277         border-bottom: 1px solid #FFF;
    278 }
    279 #qunit-testresult .module-name {
    280         font-weight: 700;
    281 }
    282 
    283 /** Fixture */
    284 
    285 #qunit-fixture {
    286         position: absolute;
    287         top: -10000px;
    288         left: -10000px;
    289         width: 1000px;
    290         height: 1000px;
    291 }
  • tests/qunit/vendor/qunit.js

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

    Property changes on: tests/qunit/vendor/qunit.js
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    1 /**
    2  * sinon-qunit 1.0.0, 2010/12/09
    3  *
    4  * @author Christian Johansen (christian@cjohansen.no)
    5  *
    6  * (The BSD License)
    7  *
    8  * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
    9  * All rights reserved.
    10  *
    11  * Redistribution and use in source and binary forms, with or without modification,
    12  * are permitted provided that the following conditions are met:
    13  *
    14  *     * Redistributions of source code must retain the above copyright notice,
    15  *       this list of conditions and the following disclaimer.
    16  *     * Redistributions in binary form must reproduce the above copyright notice,
    17  *       this list of conditions and the following disclaimer in the documentation
    18  *       and/or other materials provided with the distribution.
    19  *     * Neither the name of Christian Johansen nor the names of his contributors
    20  *       may be used to endorse or promote products derived from this software
    21  *       without specific prior written permission.
    22  *
    23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    25  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    26  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    32  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    33  */
    34 /*global sinon, QUnit, test*/
    35 sinon.assert.fail = function (msg) {
    36     QUnit.ok(false, msg);
    37 };
    38 
    39 sinon.assert.pass = function (assertion) {
    40     QUnit.ok(true, assertion);
    41 };
    42 
    43 sinon.config = {
    44     injectIntoThis: true,
    45     injectInto: null,
    46     properties: ["spy", "stub", "mock", "clock", "sandbox"],
    47     useFakeTimers: true,
    48     useFakeServer: false
    49 };
    50 
    51 (function (global) {
    52     var qTest = QUnit.test;
    53    
    54     QUnit.test = global.test = function (testName, expected, callback, async) {
    55         if (arguments.length === 2) {
    56             callback = expected;
    57             expected = null;
    58         }
    59 
    60 
    61         return qTest(testName, expected, sinon.test(callback), async);
    62     };
    63 }(this));
  • tests/qunit/vendor/sinon.js

    Property changes on: tests/qunit/vendor/sinon-qunit.js
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    1 /**
    2  * Sinon.JS 1.8.2, 2014/02/11
    3  *
    4  * @author Christian Johansen (christian@cjohansen.no)
    5  * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
    6  *
    7  * (The BSD License)
    8  *
    9  * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
    10  * All rights reserved.
    11  *
    12  * Redistribution and use in source and binary forms, with or without modification,
    13  * are permitted provided that the following conditions are met:
    14  *
    15  *     * Redistributions of source code must retain the above copyright notice,
    16  *       this list of conditions and the following disclaimer.
    17  *     * Redistributions in binary form must reproduce the above copyright notice,
    18  *       this list of conditions and the following disclaimer in the documentation
    19  *       and/or other materials provided with the distribution.
    20  *     * Neither the name of Christian Johansen nor the names of his contributors
    21  *       may be used to endorse or promote products derived from this software
    22  *       without specific prior written permission.
    23  *
    24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    27  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    30  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    31  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    32  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    33  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    34  */
    35 
    36 this.sinon = (function () {
    37 var samsam, formatio;
    38 function define(mod, deps, fn) { if (mod == "samsam") { samsam = deps(); } else { formatio = fn(samsam); } }
    39 define.amd = true;
    40 ((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||
    41  (typeof module === "object" &&
    42       function (m) { module.exports = m(); }) || // Node
    43  function (m) { this.samsam = m(); } // Browser globals
    44 )(function () {
    45     var o = Object.prototype;
    46     var div = typeof document !== "undefined" && document.createElement("div");
    47 
    48     function isNaN(value) {
    49         // Unlike global isNaN, this avoids type coercion
    50         // typeof check avoids IE host object issues, hat tip to
    51         // lodash
    52         var val = value; // JsLint thinks value !== value is "weird"
    53         return typeof value === "number" && value !== val;
    54     }
    55 
    56     function getClass(value) {
    57         // Returns the internal [[Class]] by calling Object.prototype.toString
    58         // with the provided value as this. Return value is a string, naming the
    59         // internal class, e.g. "Array"
    60         return o.toString.call(value).split(/[ \]]/)[1];
    61     }
    62 
    63     /**
    64      * @name samsam.isArguments
    65      * @param Object object
    66      *
    67      * Returns ``true`` if ``object`` is an ``arguments`` object,
    68      * ``false`` otherwise.
    69      */
    70     function isArguments(object) {
    71         if (typeof object !== "object" || typeof object.length !== "number" ||
    72                 getClass(object) === "Array") {
    73             return false;
    74         }
    75         if (typeof object.callee == "function") { return true; }
    76         try {
    77             object[object.length] = 6;
    78             delete object[object.length];
    79         } catch (e) {
    80             return true;
    81         }
    82         return false;
    83     }
    84 
    85     /**
    86      * @name samsam.isElement
    87      * @param Object object
    88      *
    89      * Returns ``true`` if ``object`` is a DOM element node. Unlike
    90      * Underscore.js/lodash, this function will return ``false`` if ``object``
    91      * is an *element-like* object, i.e. a regular object with a ``nodeType``
    92      * property that holds the value ``1``.
    93      */
    94     function isElement(object) {
    95         if (!object || object.nodeType !== 1 || !div) { return false; }
    96         try {
    97             object.appendChild(div);
    98             object.removeChild(div);
    99         } catch (e) {
    100             return false;
    101         }
    102         return true;
    103     }
    104 
    105     /**
    106      * @name samsam.keys
    107      * @param Object object
    108      *
    109      * Return an array of own property names.
    110      */
    111     function keys(object) {
    112         var ks = [], prop;
    113         for (prop in object) {
    114             if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
    115         }
    116         return ks;
    117     }
    118 
    119     /**
    120      * @name samsam.isDate
    121      * @param Object value
    122      *
    123      * Returns true if the object is a ``Date``, or *date-like*. Duck typing
    124      * of date objects work by checking that the object has a ``getTime``
    125      * function whose return value equals the return value from the object's
    126      * ``valueOf``.
    127      */
    128     function isDate(value) {
    129         return typeof value.getTime == "function" &&
    130             value.getTime() == value.valueOf();
    131     }
    132 
    133     /**
    134      * @name samsam.isNegZero
    135      * @param Object value
    136      *
    137      * Returns ``true`` if ``value`` is ``-0``.
    138      */
    139     function isNegZero(value) {
    140         return value === 0 && 1 / value === -Infinity;
    141     }
    142 
    143     /**
    144      * @name samsam.equal
    145      * @param Object obj1
    146      * @param Object obj2
    147      *
    148      * Returns ``true`` if two objects are strictly equal. Compared to
    149      * ``===`` there are two exceptions:
    150      *
    151      *   - NaN is considered equal to NaN
    152      *   - -0 and +0 are not considered equal
    153      */
    154     function identical(obj1, obj2) {
    155         if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
    156             return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
    157         }
    158     }
    159 
    160 
    161     /**
    162      * @name samsam.deepEqual
    163      * @param Object obj1
    164      * @param Object obj2
    165      *
    166      * Deep equal comparison. Two values are "deep equal" if:
    167      *
    168      *   - They are equal, according to samsam.identical
    169      *   - They are both date objects representing the same time
    170      *   - They are both arrays containing elements that are all deepEqual
    171      *   - They are objects with the same set of properties, and each property
    172      *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
    173      *
    174      * Supports cyclic objects.
    175      */
    176     function deepEqualCyclic(obj1, obj2) {
    177 
    178         // used for cyclic comparison
    179         // contain already visited objects
    180         var objects1 = [],
    181             objects2 = [],
    182         // contain pathes (position in the object structure)
    183         // of the already visited objects
    184         // indexes same as in objects arrays
    185             paths1 = [],
    186             paths2 = [],
    187         // contains combinations of already compared objects
    188         // in the manner: { "$1['ref']$2['ref']": true }
    189             compared = {};
    190 
    191         /**
    192          * used to check, if the value of a property is an object
    193          * (cyclic logic is only needed for objects)
    194          * only needed for cyclic logic
    195          */
    196         function isObject(value) {
    197 
    198             if (typeof value === 'object' && value !== null &&
    199                     !(value instanceof Boolean) &&
    200                     !(value instanceof Date)    &&
    201                     !(value instanceof Number)  &&
    202                     !(value instanceof RegExp)  &&
    203                     !(value instanceof String)) {
    204 
    205                 return true;
    206             }
    207 
    208             return false;
    209         }
    210 
    211         /**
    212          * returns the index of the given object in the
    213          * given objects array, -1 if not contained
    214          * only needed for cyclic logic
    215          */
    216         function getIndex(objects, obj) {
    217 
    218             var i;
    219             for (i = 0; i < objects.length; i++) {
    220                 if (objects[i] === obj) {
    221                     return i;
    222                 }
    223             }
    224 
    225             return -1;
    226         }
    227 
    228         // does the recursion for the deep equal check
    229         return (function deepEqual(obj1, obj2, path1, path2) {
    230             var type1 = typeof obj1;
    231             var type2 = typeof obj2;
    232 
    233             // == null also matches undefined
    234             if (obj1 === obj2 ||
    235                     isNaN(obj1) || isNaN(obj2) ||
    236                     obj1 == null || obj2 == null ||
    237                     type1 !== "object" || type2 !== "object") {
    238 
    239                 return identical(obj1, obj2);
    240             }
    241 
    242             // Elements are only equal if identical(expected, actual)
    243             if (isElement(obj1) || isElement(obj2)) { return false; }
    244 
    245             var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
    246             if (isDate1 || isDate2) {
    247                 if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
    248                     return false;
    249                 }
    250             }
    251 
    252             if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
    253                 if (obj1.toString() !== obj2.toString()) { return false; }
    254             }
    255 
    256             var class1 = getClass(obj1);
    257             var class2 = getClass(obj2);
    258             var keys1 = keys(obj1);
    259             var keys2 = keys(obj2);
    260 
    261             if (isArguments(obj1) || isArguments(obj2)) {
    262                 if (obj1.length !== obj2.length) { return false; }
    263             } else {
    264                 if (type1 !== type2 || class1 !== class2 ||
    265                         keys1.length !== keys2.length) {
    266                     return false;
    267                 }
    268             }
    269 
    270             var key, i, l,
    271                 // following vars are used for the cyclic logic
    272                 value1, value2,
    273                 isObject1, isObject2,
    274                 index1, index2,
    275                 newPath1, newPath2;
    276 
    277             for (i = 0, l = keys1.length; i < l; i++) {
    278                 key = keys1[i];
    279                 if (!o.hasOwnProperty.call(obj2, key)) {
    280                     return false;
    281                 }
    282 
    283                 // Start of the cyclic logic
    284 
    285                 value1 = obj1[key];
    286                 value2 = obj2[key];
    287 
    288                 isObject1 = isObject(value1);
    289                 isObject2 = isObject(value2);
    290 
    291                 // determine, if the objects were already visited
    292                 // (it's faster to check for isObject first, than to
    293                 // get -1 from getIndex for non objects)
    294                 index1 = isObject1 ? getIndex(objects1, value1) : -1;
    295                 index2 = isObject2 ? getIndex(objects2, value2) : -1;
    296 
    297                 // determine the new pathes of the objects
    298                 // - for non cyclic objects the current path will be extended
    299                 //   by current property name
    300                 // - for cyclic objects the stored path is taken
    301                 newPath1 = index1 !== -1
    302                     ? paths1[index1]
    303                     : path1 + '[' + JSON.stringify(key) + ']';
    304                 newPath2 = index2 !== -1
    305                     ? paths2[index2]
    306                     : path2 + '[' + JSON.stringify(key) + ']';
    307 
    308                 // stop recursion if current objects are already compared
    309                 if (compared[newPath1 + newPath2]) {
    310                     return true;
    311                 }
    312 
    313                 // remember the current objects and their pathes
    314                 if (index1 === -1 && isObject1) {
    315                     objects1.push(value1);
    316                     paths1.push(newPath1);
    317                 }
    318                 if (index2 === -1 && isObject2) {
    319                     objects2.push(value2);
    320                     paths2.push(newPath2);
    321                 }
    322 
    323                 // remember that the current objects are already compared
    324                 if (isObject1 && isObject2) {
    325                     compared[newPath1 + newPath2] = true;
    326                 }
    327 
    328                 // End of cyclic logic
    329 
    330                 // neither value1 nor value2 is a cycle
    331                 // continue with next level
    332                 if (!deepEqual(value1, value2, newPath1, newPath2)) {
    333                     return false;
    334                 }
    335             }
    336 
    337             return true;
    338 
    339         }(obj1, obj2, '$1', '$2'));
    340     }
    341 
    342     var match;
    343 
    344     function arrayContains(array, subset) {
    345         if (subset.length === 0) { return true; }
    346         var i, l, j, k;
    347         for (i = 0, l = array.length; i < l; ++i) {
    348             if (match(array[i], subset[0])) {
    349                 for (j = 0, k = subset.length; j < k; ++j) {
    350                     if (!match(array[i + j], subset[j])) { return false; }
    351                 }
    352                 return true;
    353             }
    354         }
    355         return false;
    356     }
    357 
    358     /**
    359      * @name samsam.match
    360      * @param Object object
    361      * @param Object matcher
    362      *
    363      * Compare arbitrary value ``object`` with matcher.
    364      */
    365     match = function match(object, matcher) {
    366         if (matcher && typeof matcher.test === "function") {
    367             return matcher.test(object);
    368         }
    369 
    370         if (typeof matcher === "function") {
    371             return matcher(object) === true;
    372         }
    373 
    374         if (typeof matcher === "string") {
    375             matcher = matcher.toLowerCase();
    376             var notNull = typeof object === "string" || !!object;
    377             return notNull &&
    378                 (String(object)).toLowerCase().indexOf(matcher) >= 0;
    379         }
    380 
    381         if (typeof matcher === "number") {
    382             return matcher === object;
    383         }
    384 
    385         if (typeof matcher === "boolean") {
    386             return matcher === object;
    387         }
    388 
    389         if (getClass(object) === "Array" && getClass(matcher) === "Array") {
    390             return arrayContains(object, matcher);
    391         }
    392 
    393         if (matcher && typeof matcher === "object") {
    394             var prop;
    395             for (prop in matcher) {
    396                 if (!match(object[prop], matcher[prop])) {
    397                     return false;
    398                 }
    399             }
    400             return true;
    401         }
    402 
    403         throw new Error("Matcher was not a string, a number, a " +
    404                         "function, a boolean or an object");
    405     };
    406 
    407     return {
    408         isArguments: isArguments,
    409         isElement: isElement,
    410         isDate: isDate,
    411         isNegZero: isNegZero,
    412         identical: identical,
    413         deepEqual: deepEqualCyclic,
    414         match: match,
    415         keys: keys
    416     };
    417 });
    418 ((typeof define === "function" && define.amd && function (m) {
    419     define("formatio", ["samsam"], m);
    420 }) || (typeof module === "object" && function (m) {
    421     module.exports = m(require("samsam"));
    422 }) || function (m) { this.formatio = m(this.samsam); }
    423 )(function (samsam) {
    424    
    425     var formatio = {
    426         excludeConstructors: ["Object", /^.$/],
    427         quoteStrings: true
    428     };
    429 
    430     var hasOwn = Object.prototype.hasOwnProperty;
    431 
    432     var specialObjects = [];
    433     if (typeof global !== "undefined") {
    434         specialObjects.push({ object: global, value: "[object global]" });
    435     }
    436     if (typeof document !== "undefined") {
    437         specialObjects.push({
    438             object: document,
    439             value: "[object HTMLDocument]"
    440         });
    441     }
    442     if (typeof window !== "undefined") {
    443         specialObjects.push({ object: window, value: "[object Window]" });
    444     }
    445 
    446     function functionName(func) {
    447         if (!func) { return ""; }
    448         if (func.displayName) { return func.displayName; }
    449         if (func.name) { return func.name; }
    450         var matches = func.toString().match(/function\s+([^\(]+)/m);
    451         return (matches && matches[1]) || "";
    452     }
    453 
    454     function constructorName(f, object) {
    455         var name = functionName(object && object.constructor);
    456         var excludes = f.excludeConstructors ||
    457                 formatio.excludeConstructors || [];
    458 
    459         var i, l;
    460         for (i = 0, l = excludes.length; i < l; ++i) {
    461             if (typeof excludes[i] === "string" && excludes[i] === name) {
    462                 return "";
    463             } else if (excludes[i].test && excludes[i].test(name)) {
    464                 return "";
    465             }
    466         }
    467 
    468         return name;
    469     }
    470 
    471     function isCircular(object, objects) {
    472         if (typeof object !== "object") { return false; }
    473         var i, l;
    474         for (i = 0, l = objects.length; i < l; ++i) {
    475             if (objects[i] === object) { return true; }
    476         }
    477         return false;
    478     }
    479 
    480     function ascii(f, object, processed, indent) {
    481         if (typeof object === "string") {
    482             var qs = f.quoteStrings;
    483             var quote = typeof qs !== "boolean" || qs;
    484             return processed || quote ? '"' + object + '"' : object;
    485         }
    486 
    487         if (typeof object === "function" && !(object instanceof RegExp)) {
    488             return ascii.func(object);
    489         }
    490 
    491         processed = processed || [];
    492 
    493         if (isCircular(object, processed)) { return "[Circular]"; }
    494 
    495         if (Object.prototype.toString.call(object) === "[object Array]") {
    496             return ascii.array.call(f, object, processed);
    497         }
    498 
    499         if (!object) { return String((1/object) === -Infinity ? "-0" : object); }
    500         if (samsam.isElement(object)) { return ascii.element(object); }
    501 
    502         if (typeof object.toString === "function" &&
    503                 object.toString !== Object.prototype.toString) {
    504             return object.toString();
    505         }
    506 
    507         var i, l;
    508         for (i = 0, l = specialObjects.length; i < l; i++) {
    509             if (object === specialObjects[i].object) {
    510                 return specialObjects[i].value;
    511             }
    512         }
    513 
    514         return ascii.object.call(f, object, processed, indent);
    515     }
    516 
    517     ascii.func = function (func) {
    518         return "function " + functionName(func) + "() {}";
    519     };
    520 
    521     ascii.array = function (array, processed) {
    522         processed = processed || [];
    523         processed.push(array);
    524         var i, l, pieces = [];
    525         for (i = 0, l = array.length; i < l; ++i) {
    526             pieces.push(ascii(this, array[i], processed));
    527         }
    528         return "[" + pieces.join(", ") + "]";
    529     };
    530 
    531     ascii.object = function (object, processed, indent) {
    532         processed = processed || [];
    533         processed.push(object);
    534         indent = indent || 0;
    535         var pieces = [], properties = samsam.keys(object).sort();
    536         var length = 3;
    537         var prop, str, obj, i, l;
    538 
    539         for (i = 0, l = properties.length; i < l; ++i) {
    540             prop = properties[i];
    541             obj = object[prop];
    542 
    543             if (isCircular(obj, processed)) {
    544                 str = "[Circular]";
    545             } else {
    546                 str = ascii(this, obj, processed, indent + 2);
    547             }
    548 
    549             str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
    550             length += str.length;
    551             pieces.push(str);
    552         }
    553 
    554         var cons = constructorName(this, object);
    555         var prefix = cons ? "[" + cons + "] " : "";
    556         var is = "";
    557         for (i = 0, l = indent; i < l; ++i) { is += " "; }
    558 
    559         if (length + indent > 80) {
    560             return prefix + "{\n  " + is + pieces.join(",\n  " + is) + "\n" +
    561                 is + "}";
    562         }
    563         return prefix + "{ " + pieces.join(", ") + " }";
    564     };
    565 
    566     ascii.element = function (element) {
    567         var tagName = element.tagName.toLowerCase();
    568         var attrs = element.attributes, attr, pairs = [], attrName, i, l, val;
    569 
    570         for (i = 0, l = attrs.length; i < l; ++i) {
    571             attr = attrs.item(i);
    572             attrName = attr.nodeName.toLowerCase().replace("html:", "");
    573             val = attr.nodeValue;
    574             if (attrName !== "contenteditable" || val !== "inherit") {
    575                 if (!!val) { pairs.push(attrName + "=\"" + val + "\""); }
    576             }
    577         }
    578 
    579         var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
    580         var content = element.innerHTML;
    581 
    582         if (content.length > 20) {
    583             content = content.substr(0, 20) + "[...]";
    584         }
    585 
    586         var res = formatted + pairs.join(" ") + ">" + content +
    587                 "</" + tagName + ">";
    588 
    589         return res.replace(/ contentEditable="inherit"/, "");
    590     };
    591 
    592     function Formatio(options) {
    593         for (var opt in options) {
    594             this[opt] = options[opt];
    595         }
    596     }
    597 
    598     Formatio.prototype = {
    599         functionName: functionName,
    600 
    601         configure: function (options) {
    602             return new Formatio(options);
    603         },
    604 
    605         constructorName: function (object) {
    606             return constructorName(this, object);
    607         },
    608 
    609         ascii: function (object, processed, indent) {
    610             return ascii(this, object, processed, indent);
    611         }
    612     };
    613 
    614     return Formatio.prototype;
    615 });
    616 /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
    617 /*global module, require, __dirname, document*/
    618 /**
    619  * Sinon core utilities. For internal use only.
    620  *
    621  * @author Christian Johansen (christian@cjohansen.no)
    622  * @license BSD
    623  *
    624  * Copyright (c) 2010-2013 Christian Johansen
    625  */
    626 
    627 var sinon = (function (formatio) {
    628     var div = typeof document != "undefined" && document.createElement("div");
    629     var hasOwn = Object.prototype.hasOwnProperty;
    630 
    631     function isDOMNode(obj) {
    632         var success = false;
    633 
    634         try {
    635             obj.appendChild(div);
    636             success = div.parentNode == obj;
    637         } catch (e) {
    638             return false;
    639         } finally {
    640             try {
    641                 obj.removeChild(div);
    642             } catch (e) {
    643                 // Remove failed, not much we can do about that
    644             }
    645         }
    646 
    647         return success;
    648     }
    649 
    650     function isElement(obj) {
    651         return div && obj && obj.nodeType === 1 && isDOMNode(obj);
    652     }
    653 
    654     function isFunction(obj) {
    655         return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);
    656     }
    657 
    658     function mirrorProperties(target, source) {
    659         for (var prop in source) {
    660             if (!hasOwn.call(target, prop)) {
    661                 target[prop] = source[prop];
    662             }
    663         }
    664     }
    665 
    666     function isRestorable (obj) {
    667         return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon;
    668     }
    669 
    670     var sinon = {
    671         wrapMethod: function wrapMethod(object, property, method) {
    672             if (!object) {
    673                 throw new TypeError("Should wrap property of object");
    674             }
    675 
    676             if (typeof method != "function") {
    677                 throw new TypeError("Method wrapper should be function");
    678             }
    679 
    680             var wrappedMethod = object[property],
    681                 error;
    682 
    683             if (!isFunction(wrappedMethod)) {
    684                 error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
    685                                     property + " as function");
    686             }
    687 
    688             if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
    689                 error = new TypeError("Attempted to wrap " + property + " which is already wrapped");
    690             }
    691 
    692             if (wrappedMethod.calledBefore) {
    693                 var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
    694                 error = new TypeError("Attempted to wrap " + property + " which is already " + verb);
    695             }
    696 
    697             if (error) {
    698                 if (wrappedMethod._stack) {
    699                     error.stack += '\n--------------\n' + wrappedMethod._stack;
    700                 }
    701                 throw error;
    702             }
    703 
    704             // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem
    705             // when using hasOwn.call on objects from other frames.
    706             var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property);
    707             object[property] = method;
    708             method.displayName = property;
    709             // Set up a stack trace which can be used later to find what line of
    710             // code the original method was created on.
    711             method._stack = (new Error('Stack Trace for original')).stack;
    712 
    713             method.restore = function () {
    714                 // For prototype properties try to reset by delete first.
    715                 // If this fails (ex: localStorage on mobile safari) then force a reset
    716                 // via direct assignment.
    717                 if (!owned) {
    718                     delete object[property];
    719                 }
    720                 if (object[property] === method) {
    721                     object[property] = wrappedMethod;
    722                 }
    723             };
    724 
    725             method.restore.sinon = true;
    726             mirrorProperties(method, wrappedMethod);
    727 
    728             return method;
    729         },
    730 
    731         extend: function extend(target) {
    732             for (var i = 1, l = arguments.length; i < l; i += 1) {
    733                 for (var prop in arguments[i]) {
    734                     if (arguments[i].hasOwnProperty(prop)) {
    735                         target[prop] = arguments[i][prop];
    736                     }
    737 
    738                     // DONT ENUM bug, only care about toString
    739                     if (arguments[i].hasOwnProperty("toString") &&
    740                         arguments[i].toString != target.toString) {
    741                         target.toString = arguments[i].toString;
    742                     }
    743                 }
    744             }
    745 
    746             return target;
    747         },
    748 
    749         create: function create(proto) {
    750             var F = function () {};
    751             F.prototype = proto;
    752             return new F();
    753         },
    754 
    755         deepEqual: function deepEqual(a, b) {
    756             if (sinon.match && sinon.match.isMatcher(a)) {
    757                 return a.test(b);
    758             }
    759             if (typeof a != "object" || typeof b != "object") {
    760                 return a === b;
    761             }
    762 
    763             if (isElement(a) || isElement(b)) {
    764                 return a === b;
    765             }
    766 
    767             if (a === b) {
    768                 return true;
    769             }
    770 
    771             if ((a === null && b !== null) || (a !== null && b === null)) {
    772                 return false;
    773             }
    774 
    775             var aString = Object.prototype.toString.call(a);
    776             if (aString != Object.prototype.toString.call(b)) {
    777                 return false;
    778             }
    779 
    780             if (aString == "[object Date]") {
    781                 return a.valueOf() === b.valueOf();
    782             }
    783 
    784             var prop, aLength = 0, bLength = 0;
    785 
    786             if (aString == "[object Array]" && a.length !== b.length) {
    787                 return false;
    788             }
    789 
    790             for (prop in a) {
    791                 aLength += 1;
    792 
    793                 if (!deepEqual(a[prop], b[prop])) {
    794                     return false;
    795                 }
    796             }
    797 
    798             for (prop in b) {
    799                 bLength += 1;
    800             }
    801 
    802             return aLength == bLength;
    803         },
    804 
    805         functionName: function functionName(func) {
    806             var name = func.displayName || func.name;
    807 
    808             // Use function decomposition as a last resort to get function
    809             // name. Does not rely on function decomposition to work - if it
    810             // doesn't debugging will be slightly less informative
    811             // (i.e. toString will say 'spy' rather than 'myFunc').
    812             if (!name) {
    813                 var matches = func.toString().match(/function ([^\s\(]+)/);
    814                 name = matches && matches[1];
    815             }
    816 
    817             return name;
    818         },
    819 
    820         functionToString: function toString() {
    821             if (this.getCall && this.callCount) {
    822                 var thisValue, prop, i = this.callCount;
    823 
    824                 while (i--) {
    825                     thisValue = this.getCall(i).thisValue;
    826 
    827                     for (prop in thisValue) {
    828                         if (thisValue[prop] === this) {
    829                             return prop;
    830                         }
    831                     }
    832                 }
    833             }
    834 
    835             return this.displayName || "sinon fake";
    836         },
    837 
    838         getConfig: function (custom) {
    839             var config = {};
    840             custom = custom || {};
    841             var defaults = sinon.defaultConfig;
    842 
    843             for (var prop in defaults) {
    844                 if (defaults.hasOwnProperty(prop)) {
    845                     config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
    846                 }
    847             }
    848 
    849             return config;
    850         },
    851 
    852         format: function (val) {
    853             return "" + val;
    854         },
    855 
    856         defaultConfig: {
    857             injectIntoThis: true,
    858             injectInto: null,
    859             properties: ["spy", "stub", "mock", "clock", "server", "requests"],
    860             useFakeTimers: true,
    861             useFakeServer: true
    862         },
    863 
    864         timesInWords: function timesInWords(count) {
    865             return count == 1 && "once" ||
    866                 count == 2 && "twice" ||
    867                 count == 3 && "thrice" ||
    868                 (count || 0) + " times";
    869         },
    870 
    871         calledInOrder: function (spies) {
    872             for (var i = 1, l = spies.length; i < l; i++) {
    873                 if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) {
    874                     return false;
    875                 }
    876             }
    877 
    878             return true;
    879         },
    880 
    881         orderByFirstCall: function (spies) {
    882             return spies.sort(function (a, b) {
    883                 // uuid, won't ever be equal
    884                 var aCall = a.getCall(0);
    885                 var bCall = b.getCall(0);
    886                 var aId = aCall && aCall.callId || -1;
    887                 var bId = bCall && bCall.callId || -1;
    888 
    889                 return aId < bId ? -1 : 1;
    890             });
    891         },
    892 
    893         log: function () {},
    894 
    895         logError: function (label, err) {
    896             var msg = label + " threw exception: ";
    897             sinon.log(msg + "[" + err.name + "] " + err.message);
    898             if (err.stack) { sinon.log(err.stack); }
    899 
    900             setTimeout(function () {
    901                 err.message = msg + err.message;
    902                 throw err;
    903             }, 0);
    904         },
    905 
    906         typeOf: function (value) {
    907             if (value === null) {
    908                 return "null";
    909             }
    910             else if (value === undefined) {
    911                 return "undefined";
    912             }
    913             var string = Object.prototype.toString.call(value);
    914             return string.substring(8, string.length - 1).toLowerCase();
    915         },
    916 
    917         createStubInstance: function (constructor) {
    918             if (typeof constructor !== "function") {
    919                 throw new TypeError("The constructor should be a function.");
    920             }
    921             return sinon.stub(sinon.create(constructor.prototype));
    922         },
    923 
    924         restore: function (object) {
    925             if (object !== null && typeof object === "object") {
    926                 for (var prop in object) {
    927                     if (isRestorable(object[prop])) {
    928                         object[prop].restore();
    929                     }
    930                 }
    931             }
    932             else if (isRestorable(object)) {
    933                 object.restore();
    934             }
    935         }
    936     };
    937 
    938     var isNode = typeof module !== "undefined" && module.exports;
    939     var isAMD = typeof define === 'function' && typeof define.amd === 'object' && define.amd;
    940 
    941     if (isAMD) {
    942         define(function(){
    943             return sinon;
    944         });
    945     } else if (isNode) {
    946         try {
    947             formatio = require("formatio");
    948         } catch (e) {}
    949         module.exports = sinon;
    950         module.exports.spy = require("./sinon/spy");
    951         module.exports.spyCall = require("./sinon/call");
    952         module.exports.behavior = require("./sinon/behavior");
    953         module.exports.stub = require("./sinon/stub");
    954         module.exports.mock = require("./sinon/mock");
    955         module.exports.collection = require("./sinon/collection");
    956         module.exports.assert = require("./sinon/assert");
    957         module.exports.sandbox = require("./sinon/sandbox");
    958         module.exports.test = require("./sinon/test");
    959         module.exports.testCase = require("./sinon/test_case");
    960         module.exports.assert = require("./sinon/assert");
    961         module.exports.match = require("./sinon/match");
    962     }
    963 
    964     if (formatio) {
    965         var formatter = formatio.configure({ quoteStrings: false });
    966         sinon.format = function () {
    967             return formatter.ascii.apply(formatter, arguments);
    968         };
    969     } else if (isNode) {
    970         try {
    971             var util = require("util");
    972             sinon.format = function (value) {
    973                 return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;
    974             };
    975         } catch (e) {
    976             /* Node, but no util module - would be very old, but better safe than
    977              sorry */
    978         }
    979     }
    980 
    981     return sinon;
    982 }(typeof formatio == "object" && formatio));
    983 
    984 /* @depend ../sinon.js */
    985 /*jslint eqeqeq: false, onevar: false, plusplus: false*/
    986 /*global module, require, sinon*/
    987 /**
    988  * Match functions
    989  *
    990  * @author Maximilian Antoni (mail@maxantoni.de)
    991  * @license BSD
    992  *
    993  * Copyright (c) 2012 Maximilian Antoni
    994  */
    995 
    996 (function (sinon) {
    997     var commonJSModule = typeof module !== 'undefined' && module.exports;
    998 
    999     if (!sinon && commonJSModule) {
    1000         sinon = require("../sinon");
    1001     }
    1002 
    1003     if (!sinon) {
    1004         return;
    1005     }
    1006 
    1007     function assertType(value, type, name) {
    1008         var actual = sinon.typeOf(value);
    1009         if (actual !== type) {
    1010             throw new TypeError("Expected type of " + name + " to be " +
    1011                 type + ", but was " + actual);
    1012         }
    1013     }
    1014 
    1015     var matcher = {
    1016         toString: function () {
    1017             return this.message;
    1018         }
    1019     };
    1020 
    1021     function isMatcher(object) {
    1022         return matcher.isPrototypeOf(object);
    1023     }
    1024 
    1025     function matchObject(expectation, actual) {
    1026         if (actual === null || actual === undefined) {
    1027             return false;
    1028         }
    1029         for (var key in expectation) {
    1030             if (expectation.hasOwnProperty(key)) {
    1031                 var exp = expectation[key];
    1032                 var act = actual[key];
    1033                 if (match.isMatcher(exp)) {
    1034                     if (!exp.test(act)) {
    1035                         return false;
    1036                     }
    1037                 } else if (sinon.typeOf(exp) === "object") {
    1038                     if (!matchObject(exp, act)) {
    1039                         return false;
    1040                     }
    1041                 } else if (!sinon.deepEqual(exp, act)) {
    1042                     return false;
    1043                 }
    1044             }
    1045         }
    1046         return true;
    1047     }
    1048 
    1049     matcher.or = function (m2) {
    1050         if (!isMatcher(m2)) {
    1051             throw new TypeError("Matcher expected");
    1052         }
    1053         var m1 = this;
    1054         var or = sinon.create(matcher);
    1055         or.test = function (actual) {
    1056             return m1.test(actual) || m2.test(actual);
    1057         };
    1058         or.message = m1.message + ".or(" + m2.message + ")";
    1059         return or;
    1060     };
    1061 
    1062     matcher.and = function (m2) {
    1063         if (!isMatcher(m2)) {
    1064             throw new TypeError("Matcher expected");
    1065         }
    1066         var m1 = this;
    1067         var and = sinon.create(matcher);
    1068         and.test = function (actual) {
    1069             return m1.test(actual) && m2.test(actual);
    1070         };
    1071         and.message = m1.message + ".and(" + m2.message + ")";
    1072         return and;
    1073     };
    1074 
    1075     var match = function (expectation, message) {
    1076         var m = sinon.create(matcher);
    1077         var type = sinon.typeOf(expectation);
    1078         switch (type) {
    1079         case "object":
    1080             if (typeof expectation.test === "function") {
    1081                 m.test = function (actual) {
    1082                     return expectation.test(actual) === true;
    1083                 };
    1084                 m.message = "match(" + sinon.functionName(expectation.test) + ")";
    1085                 return m;
    1086             }
    1087             var str = [];
    1088             for (var key in expectation) {
    1089                 if (expectation.hasOwnProperty(key)) {
    1090                     str.push(key + ": " + expectation[key]);
    1091                 }
    1092             }
    1093             m.test = function (actual) {
    1094                 return matchObject(expectation, actual);
    1095             };
    1096             m.message = "match(" + str.join(", ") + ")";
    1097             break;
    1098         case "number":
    1099             m.test = function (actual) {
    1100                 return expectation == actual;
    1101             };
    1102             break;
    1103         case "string":
    1104             m.test = function (actual) {
    1105                 if (typeof actual !== "string") {
    1106                     return false;
    1107                 }
    1108                 return actual.indexOf(expectation) !== -1;
    1109             };
    1110             m.message = "match(\"" + expectation + "\")";
    1111             break;
    1112         case "regexp":
    1113             m.test = funct