Make WordPress Core

Changeset 32343


Ignore:
Timestamp:
05/04/2015 07:43:38 PM (11 years ago)
Author:
jorbin
Message:

Update QUnit to v1.18.0

See ​https://github.com/jquery/qunit/blob/1.18.0/History.md for changes from 1.12.0 to now. Mostly bug fixes and internal changes in preparation for QUnit v2.

See #30824

Location:
trunk/tests/qunit/vendor
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/qunit/vendor/qunit.css

    r25166 r32343  
    1 /**
    2  * QUnit v1.12.0 - A JavaScript Unit Testing Framework
     1/*!
     2 * QUnit 1.18.0
     3 * http://qunitjs.com/
    34 *
    4  * http://qunitjs.com
     5 * Copyright jQuery Foundation and other contributors
     6 * Released under the MIT license
     7 * http://jquery.org/license
    58 *
    6  * Copyright 2012 jQuery Foundation and other contributors
    7  * Released under the MIT license.
    8  * http://jquery.org/license
     9 * Date: 2015-04-03T10:23Z
    910 */
    1011
    … …  
    3233        padding: 0.5em 0 0.5em 1em;
    3334
    34         color: #8699a4;
    35         background-color: #0d3349;
     35        color: #8699A4;
     36        background-color: #0D3349;
    3637
    3738        font-size: 1.5em;
    3839        line-height: 1em;
    39         font-weight: normal;
     40        font-weight: 400;
    4041
    4142        border-radius: 5px 5px 0 0;
    42         -moz-border-radius: 5px 5px 0 0;
    43         -webkit-border-top-right-radius: 5px;
    44         -webkit-border-top-left-radius: 5px;
    4543}
    4644
    4745#qunit-header a {
    4846        text-decoration: none;
    49         color: #c2ccd1;
     47        color: #C2CCD1;
    5048}
    5149
    5250#qunit-header a:hover,
    5351#qunit-header a:focus {
    54         color: #fff;
     52        color: #FFF;
    5553}
    5654
    5755#qunit-testrunner-toolbar label {
    5856        display: inline-block;
    59         padding: 0 .5em 0 .1em;
     57        padding: 0 0.5em 0 0.1em;
    6058}
    6159
    … …  
    6563
    6664#qunit-testrunner-toolbar {
    67         padding: 0.5em 0 0.5em 2em;
     65        padding: 0.5em 1em 0.5em 1em;
    6866        color: #5E740B;
    69         background-color: #eee;
     67        background-color: #EEE;
    7068        overflow: hidden;
    7169}
    7270
    7371#qunit-userAgent {
    74         padding: 0.5em 0 0.5em 2.5em;
    75         background-color: #2b81af;
    76         color: #fff;
     72        padding: 0.5em 1em 0.5em 1em;
     73        background-color: #2B81AF;
     74        color: #FFF;
    7775        text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
    7876}
    … …  
    8078#qunit-modulefilter-container {
    8179        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;
    8292}
    8393
    … …  
    8999
    90100#qunit-tests li {
    91         padding: 0.4em 0.5em 0.4em 2.5em;
    92         border-bottom: 1px solid #fff;
     101        padding: 0.4em 1em 0.4em 1em;
     102        border-bottom: 1px solid #FFF;
    93103        list-style-position: inside;
    94104}
    95105
    96 #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
     106#qunit-tests > li {
    97107        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;
    98126}
    99127
    … …  
    102130}
    103131
     132#qunit-tests li.skipped strong {
     133        cursor: default;
     134}
     135
    104136#qunit-tests li a {
    105137        padding: 0.5em;
    106         color: #c2ccd1;
    107         text-decoration: none;
     138        color: #C2CCD1;
     139        text-decoration: none;
     140}
     141
     142#qunit-tests li p a {
     143        padding: 0.25em;
     144        color: #6B6464;
    108145}
    109146#qunit-tests li a:hover,
    … …  
    121158        padding: 0.5em;
    122159
    123         background-color: #fff;
     160        background-color: #FFF;
    124161
    125162        border-radius: 5px;
    126         -moz-border-radius: 5px;
    127         -webkit-border-radius: 5px;
    128163}
    129164
    … …  
    134169#qunit-tests table {
    135170        border-collapse: collapse;
    136         margin-top: .2em;
     171        margin-top: 0.2em;
    137172}
    138173
    … …  
    140175        text-align: right;
    141176        vertical-align: top;
    142         padding: 0 .5em 0 0;
     177        padding: 0 0.5em 0 0;
    143178}
    144179
    … …  
    154189
    155190#qunit-tests del {
    156         background-color: #e0f2be;
    157         color: #374e0c;
     191        background-color: #E0F2BE;
     192        color: #374E0C;
    158193        text-decoration: none;
    159194}
    160195
    161196#qunit-tests ins {
    162         background-color: #ffcaca;
     197        background-color: #FFCACA;
    163198        color: #500;
    164199        text-decoration: none;
    … …  
    167202/*** Test Counts */
    168203
    169 #qunit-tests b.counts                       { color: black; }
     204#qunit-tests b.counts                       { color: #000; }
    170205#qunit-tests b.passed                       { color: #5E740B; }
    171206#qunit-tests b.failed                       { color: #710909; }
    … …  
    173208#qunit-tests li li {
    174209        padding: 5px;
    175         background-color: #fff;
     210        background-color: #FFF;
    176211        border-bottom: none;
    177212        list-style-position: inside;
    … …  
    181216
    182217#qunit-tests li li.pass {
    183         color: #3c510c;
    184         background-color: #fff;
     218        color: #3C510C;
     219        background-color: #FFF;
    185220        border-left: 10px solid #C6E746;
    186221}
    … …  
    190225
    191226#qunit-tests .pass .test-actual,
    192 #qunit-tests .pass .test-expected           { color: #999999; }
     227#qunit-tests .pass .test-expected           { color: #999; }
    193228
    194229#qunit-banner.qunit-pass                    { background-color: #C6E746; }
    … …  
    198233#qunit-tests li li.fail {
    199234        color: #710909;
    200         background-color: #fff;
     235        background-color: #FFF;
    201236        border-left: 10px solid #EE5757;
    202237        white-space: pre;
    … …  
    205240#qunit-tests > li:last-child {
    206241        border-radius: 0 0 5px 5px;
    207         -moz-border-radius: 0 0 5px 5px;
    208         -webkit-border-bottom-right-radius: 5px;
    209         -webkit-border-bottom-left-radius: 5px;
    210 }
    211 
    212 #qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
     242}
     243
     244#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
    213245#qunit-tests .fail .test-name,
    214 #qunit-tests .fail .module-name             { color: #000000; }
     246#qunit-tests .fail .module-name             { color: #000; }
    215247
    216248#qunit-tests .fail .test-actual             { color: #EE5757; }
    217 #qunit-tests .fail .test-expected           { color: green;  }
     249#qunit-tests .fail .test-expected           { color: #008000; }
    218250
    219251#qunit-banner.qunit-fail                    { background-color: #EE5757; }
    220252
     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}
    221268
    222269/** Result */
    223270
    224271#qunit-testresult {
    225         padding: 0.5em 0.5em 0.5em 2.5em;
    226 
    227         color: #2b81af;
     272        padding: 0.5em 1em 0.5em 1em;
     273
     274        color: #2B81AF;
    228275        background-color: #D2E0E6;
    229276
    230         border-bottom: 1px solid white;
     277        border-bottom: 1px solid #FFF;
    231278}
    232279#qunit-testresult .module-name {
    233         font-weight: bold;
     280        font-weight: 700;
    234281}
    235282
  • trunk/tests/qunit/vendor/qunit.js

    r25166 r32343  
    1 /**
    2  * QUnit v1.12.0 - A JavaScript Unit Testing Framework
     1/*!
     2 * QUnit 1.18.0
     3 * http://qunitjs.com/
    34 *
    4  * http://qunitjs.com
     5 * Copyright jQuery Foundation and other contributors
     6 * Released under the MIT license
     7 * http://jquery.org/license
    58 *
    6  * Copyright 2013 jQuery Foundation and other contributors
    7  * Released under the MIT license.
    8  * https://jquery.org/license/
     9 * Date: 2015-04-03T10:23Z
    910 */
    1011
    … …  
    1213
    1314var QUnit,
    14         assert,
    1515        config,
    1616        onErrorFnPrev,
    17         testId = 0,
    18         fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
     17        loggingCallbacks = {},
     18        fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
    1919        toString = Object.prototype.toString,
    2020        hasOwn = Object.prototype.hasOwnProperty,
    2121        // Keep a local reference to Date (GH-283)
    2222        Date = window.Date,
     23        now = Date.now || function() {
     24                return new Date().getTime();
     25        },
     26        globalStartCalled = false,
     27        runStarted = false,
    2328        setTimeout = window.setTimeout,
     29        clearTimeout = window.clearTimeout,
    2430        defined = {
    25                 setTimeout: typeof window.setTimeout !== "undefined",
     31                document: window.document !== undefined,
     32                setTimeout: window.setTimeout !== undefined,
    2633                sessionStorage: (function() {
    2734                        var x = "qunit-test-string";
    … …  
    3037                                sessionStorage.removeItem( x );
    3138                                return true;
    32                         } catch( e ) {
     39                        } catch ( e ) {
    3340                                return false;
    3441                        }
    … …  
    7279         */
    7380        objectValues = function( obj ) {
    74                 // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
    75                 /*jshint newcap: false */
    7681                var key, val,
    7782                        vals = QUnit.is( "array", obj ) ? [] : {};
    7883                for ( key in obj ) {
    7984                        if ( hasOwn.call( obj, key ) ) {
    80                                 val = obj[key];
    81                                 vals[key] = val === Object(val) ? objectValues(val) : val;
     85                                val = obj[ key ];
     86                                vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
    8287                        }
    8388                }
    … …  
    8590        };
    8691
    87 function Test( settings ) {
    88         extend( this, settings );
    89         this.assertions = [];
    90         this.testNumber = ++Test.count;
    91 }
    92 
    93 Test.count = 0;
    94 
    95 Test.prototype = {
    96         init: function() {
    97                 var a, b, li,
    98                         tests = id( "qunit-tests" );
    99 
    100                 if ( tests ) {
    101                         b = document.createElement( "strong" );
    102                         b.innerHTML = this.nameHtml;
    103 
    104                         // `a` initialized at top of scope
    105                         a = document.createElement( "a" );
    106                         a.innerHTML = "Rerun";
    107                         a.href = QUnit.url({ testNumber: this.testNumber });
    108 
    109                         li = document.createElement( "li" );
    110                         li.appendChild( b );
    111                         li.appendChild( a );
    112                         li.className = "running";
    113                         li.id = this.id = "qunit-test-output" + testId++;
    114 
    115                         tests.appendChild( li );
    116                 }
    117         },
    118         setup: function() {
    119                 if (
    120                         // Emit moduleStart when we're switching from one module to another
    121                         this.module !== config.previousModule ||
    122                                 // They could be equal (both undefined) but if the previousModule property doesn't
    123                                 // yet exist it means this is the first test in a suite that isn't wrapped in a
    124                                 // module, in which case we'll just emit a moduleStart event for 'undefined'.
    125                                 // Without this, reporters can get testStart before moduleStart  which is a problem.
    126                                 !hasOwn.call( config, "previousModule" )
    127                 ) {
    128                         if ( hasOwn.call( config, "previousModule" ) ) {
    129                                 runLoggingCallbacks( "moduleDone", QUnit, {
    130                                         name: config.previousModule,
    131                                         failed: config.moduleStats.bad,
    132                                         passed: config.moduleStats.all - config.moduleStats.bad,
    133                                         total: config.moduleStats.all
    134                                 });
    135                         }
    136                         config.previousModule = this.module;
    137                         config.moduleStats = { all: 0, bad: 0 };
    138                         runLoggingCallbacks( "moduleStart", QUnit, {
    139                                 name: this.module
    140                         });
    141                 }
    142 
    143                 config.current = this;
    144 
    145                 this.testEnvironment = extend({
    146                         setup: function() {},
    147                         teardown: function() {}
    148                 }, this.moduleTestEnvironment );
    149 
    150                 this.started = +new Date();
    151                 runLoggingCallbacks( "testStart", QUnit, {
    152                         name: this.testName,
    153                         module: this.module
    154                 });
    155 
    156                 /*jshint camelcase:false */
    157 
    158 
    159                 /**
    160                  * Expose the current test environment.
    161                  *
    162                  * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
    163                  */
    164                 QUnit.current_testEnvironment = this.testEnvironment;
    165 
    166                 /*jshint camelcase:true */
    167 
    168                 if ( !config.pollution ) {
    169                         saveGlobal();
    170                 }
    171                 if ( config.notrycatch ) {
    172                         this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
    173                         return;
    174                 }
    175                 try {
    176                         this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
    177                 } catch( e ) {
    178                         QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
    179                 }
    180         },
    181         run: function() {
    182                 config.current = this;
    183 
    184                 var running = id( "qunit-testresult" );
    185 
    186                 if ( running ) {
    187                         running.innerHTML = "Running: <br/>" + this.nameHtml;
    188                 }
    189 
    190                 if ( this.async ) {
    191                         QUnit.stop();
    192                 }
    193 
    194                 this.callbackStarted = +new Date();
    195 
    196                 if ( config.notrycatch ) {
    197                         this.callback.call( this.testEnvironment, QUnit.assert );
    198                         this.callbackRuntime = +new Date() - this.callbackStarted;
    199                         return;
    200                 }
    201 
    202                 try {
    203                         this.callback.call( this.testEnvironment, QUnit.assert );
    204                         this.callbackRuntime = +new Date() - this.callbackStarted;
    205                 } catch( e ) {
    206                         this.callbackRuntime = +new Date() - this.callbackStarted;
    207 
    208                         QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
    209                         // else next test will carry the responsibility
    210                         saveGlobal();
    211 
    212                         // Restart the tests if they're blocking
    213                         if ( config.blocking ) {
    214                                 QUnit.start();
    215                         }
    216                 }
    217         },
    218         teardown: function() {
    219                 config.current = this;
    220                 if ( config.notrycatch ) {
    221                         if ( typeof this.callbackRuntime === "undefined" ) {
    222                                 this.callbackRuntime = +new Date() - this.callbackStarted;
    223                         }
    224                         this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
    225                         return;
    226                 } else {
    227                         try {
    228                                 this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
    229                         } catch( e ) {
    230                                 QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
    231                         }
    232                 }
    233                 checkPollution();
    234         },
    235         finish: function() {
    236                 config.current = this;
    237                 if ( config.requireExpects && this.expected === null ) {
    238                         QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
    239                 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
    240                         QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
    241                 } else if ( this.expected === null && !this.assertions.length ) {
    242                         QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
    243                 }
    244 
    245                 var i, assertion, a, b, time, li, ol,
    246                         test = this,
    247                         good = 0,
    248                         bad = 0,
    249                         tests = id( "qunit-tests" );
    250 
    251                 this.runtime = +new Date() - this.started;
    252                 config.stats.all += this.assertions.length;
    253                 config.moduleStats.all += this.assertions.length;
    254 
    255                 if ( tests ) {
    256                         ol = document.createElement( "ol" );
    257                         ol.className = "qunit-assert-list";
    258 
    259                         for ( i = 0; i < this.assertions.length; i++ ) {
    260                                 assertion = this.assertions[i];
    261 
    262                                 li = document.createElement( "li" );
    263                                 li.className = assertion.result ? "pass" : "fail";
    264                                 li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
    265                                 ol.appendChild( li );
    266 
    267                                 if ( assertion.result ) {
    268                                         good++;
    269                                 } else {
    270                                         bad++;
    271                                         config.stats.bad++;
    272                                         config.moduleStats.bad++;
    273                                 }
    274                         }
    275 
    276                         // store result when possible
    277                         if ( QUnit.config.reorder && defined.sessionStorage ) {
    278                                 if ( bad ) {
    279                                         sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
    280                                 } else {
    281                                         sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
    282                                 }
    283                         }
    284 
    285                         if ( bad === 0 ) {
    286                                 addClass( ol, "qunit-collapsed" );
    287                         }
    288 
    289                         // `b` initialized at top of scope
    290                         b = document.createElement( "strong" );
    291                         b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
    292 
    293                         addEvent(b, "click", function() {
    294                                 var next = b.parentNode.lastChild,
    295                                         collapsed = hasClass( next, "qunit-collapsed" );
    296                                 ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
    297                         });
    298 
    299                         addEvent(b, "dblclick", function( e ) {
    300                                 var target = e && e.target ? e.target : window.event.srcElement;
    301                                 if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
    302                                         target = target.parentNode;
    303                                 }
    304                                 if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
    305                                         window.location = QUnit.url({ testNumber: test.testNumber });
    306                                 }
    307                         });
    308 
    309                         // `time` initialized at top of scope
    310                         time = document.createElement( "span" );
    311                         time.className = "runtime";
    312                         time.innerHTML = this.runtime + " ms";
    313 
    314                         // `li` initialized at top of scope
    315                         li = id( this.id );
    316                         li.className = bad ? "fail" : "pass";
    317                         li.removeChild( li.firstChild );
    318                         a = li.firstChild;
    319                         li.appendChild( b );
    320                         li.appendChild( a );
    321                         li.appendChild( time );
    322                         li.appendChild( ol );
    323 
    324                 } else {
    325                         for ( i = 0; i < this.assertions.length; i++ ) {
    326                                 if ( !this.assertions[i].result ) {
    327                                         bad++;
    328                                         config.stats.bad++;
    329                                         config.moduleStats.bad++;
    330                                 }
    331                         }
    332                 }
    333 
    334                 runLoggingCallbacks( "testDone", QUnit, {
    335                         name: this.testName,
    336                         module: this.module,
    337                         failed: bad,
    338                         passed: this.assertions.length - bad,
    339                         total: this.assertions.length,
    340                         duration: this.runtime
    341                 });
    342 
    343                 QUnit.reset();
    344 
    345                 config.current = undefined;
    346         },
    347 
    348         queue: function() {
    349                 var bad,
    350                         test = this;
    351 
    352                 synchronize(function() {
    353                         test.init();
    354                 });
    355                 function run() {
    356                         // each of these can by async
    357                         synchronize(function() {
    358                                 test.setup();
    359                         });
    360                         synchronize(function() {
    361                                 test.run();
    362                         });
    363                         synchronize(function() {
    364                                 test.teardown();
    365                         });
    366                         synchronize(function() {
    367                                 test.finish();
    368                         });
    369                 }
    370 
    371                 // `bad` initialized at top of scope
    372                 // defer when previous test run passed, if storage is available
    373                 bad = QUnit.config.reorder && defined.sessionStorage &&
    374                                                 +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
    375 
    376                 if ( bad ) {
    377                         run();
    378                 } else {
    379                         synchronize( run, true );
    380                 }
    381         }
    382 };
    383 
    384 // Root QUnit object.
    385 // `QUnit` initialized at top of scope
    386 QUnit = {
    387 
    388         // call on start of module test to prepend name to all tests
    389         module: function( name, testEnvironment ) {
    390                 config.currentModule = name;
    391                 config.currentModuleTestEnvironment = testEnvironment;
    392                 config.modules[name] = true;
    393         },
    394 
    395         asyncTest: function( testName, expected, callback ) {
    396                 if ( arguments.length === 2 ) {
    397                         callback = expected;
    398                         expected = null;
    399                 }
    400 
    401                 QUnit.test( testName, expected, callback, true );
    402         },
    403 
    404         test: function( testName, expected, callback, async ) {
    405                 var test,
    406                         nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
    407 
    408                 if ( arguments.length === 2 ) {
    409                         callback = expected;
    410                         expected = null;
    411                 }
    412 
    413                 if ( config.currentModule ) {
    414                         nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
    415                 }
    416 
    417                 test = new Test({
    418                         nameHtml: nameHtml,
    419                         testName: testName,
    420                         expected: expected,
    421                         async: async,
    422                         callback: callback,
    423                         module: config.currentModule,
    424                         moduleTestEnvironment: config.currentModuleTestEnvironment,
    425                         stack: sourceFromStacktrace( 2 )
    426                 });
    427 
    428                 if ( !validTest( test ) ) {
    429                         return;
    430                 }
    431 
    432                 test.queue();
    433         },
    434 
    435         // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
    436         expect: function( asserts ) {
    437                 if (arguments.length === 1) {
    438                         config.current.expected = asserts;
    439                 } else {
    440                         return config.current.expected;
    441                 }
    442         },
    443 
    444         start: function( count ) {
    445                 // QUnit hasn't been initialized yet.
    446                 // Note: RequireJS (et al) may delay onLoad
    447                 if ( config.semaphore === undefined ) {
    448                         QUnit.begin(function() {
    449                                 // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
    450                                 setTimeout(function() {
    451                                         QUnit.start( count );
    452                                 });
    453                         });
    454                         return;
    455                 }
    456 
    457                 config.semaphore -= count || 1;
    458                 // don't start until equal number of stop-calls
    459                 if ( config.semaphore > 0 ) {
    460                         return;
    461                 }
    462                 // ignore if start is called more often then stop
    463                 if ( config.semaphore < 0 ) {
    464                         config.semaphore = 0;
    465                         QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
    466                         return;
    467                 }
    468                 // A slight delay, to avoid any current callbacks
    469                 if ( defined.setTimeout ) {
    470                         setTimeout(function() {
    471                                 if ( config.semaphore > 0 ) {
    472                                         return;
    473                                 }
    474                                 if ( config.timeout ) {
    475                                         clearTimeout( config.timeout );
    476                                 }
    477 
    478                                 config.blocking = false;
    479                                 process( true );
    480                         }, 13);
    481                 } else {
    482                         config.blocking = false;
    483                         process( true );
    484                 }
    485         },
    486 
    487         stop: function( count ) {
    488                 config.semaphore += count || 1;
    489                 config.blocking = true;
    490 
    491                 if ( config.testTimeout && defined.setTimeout ) {
    492                         clearTimeout( config.timeout );
    493                         config.timeout = setTimeout(function() {
    494                                 QUnit.ok( false, "Test timed out" );
    495                                 config.semaphore = 1;
    496                                 QUnit.start();
    497                         }, config.testTimeout );
    498                 }
    499         }
    500 };
    501 
    502 // `assert` initialized at top of scope
    503 // Assert helpers
    504 // All of these must either call QUnit.push() or manually do:
    505 // - runLoggingCallbacks( "log", .. );
    506 // - config.current.assertions.push({ .. });
    507 // We attach it to the QUnit object *after* we expose the public API,
    508 // otherwise `assert` will become a global variable in browsers (#341).
    509 assert = {
    510         /**
    511          * Asserts rough true-ish result.
    512          * @name ok
    513          * @function
    514          * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
    515          */
    516         ok: function( result, msg ) {
    517                 if ( !config.current ) {
    518                         throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
    519                 }
    520                 result = !!result;
    521                 msg = msg || (result ? "okay" : "failed" );
    522 
    523                 var source,
    524                         details = {
    525                                 module: config.current.module,
    526                                 name: config.current.testName,
    527                                 result: result,
    528                                 message: msg
    529                         };
    530 
    531                 msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
    532 
    533                 if ( !result ) {
    534                         source = sourceFromStacktrace( 2 );
    535                         if ( source ) {
    536                                 details.source = source;
    537                                 msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
    538                         }
    539                 }
    540                 runLoggingCallbacks( "log", QUnit, details );
    541                 config.current.assertions.push({
    542                         result: result,
    543                         message: msg
    544                 });
    545         },
    546 
    547         /**
    548          * Assert that the first two arguments are equal, with an optional message.
    549          * Prints out both actual and expected values.
    550          * @name equal
    551          * @function
    552          * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
    553          */
    554         equal: function( actual, expected, message ) {
    555                 /*jshint eqeqeq:false */
    556                 QUnit.push( expected == actual, actual, expected, message );
    557         },
    558 
    559         /**
    560          * @name notEqual
    561          * @function
    562          */
    563         notEqual: function( actual, expected, message ) {
    564                 /*jshint eqeqeq:false */
    565                 QUnit.push( expected != actual, actual, expected, message );
    566         },
    567 
    568         /**
    569          * @name propEqual
    570          * @function
    571          */
    572         propEqual: function( actual, expected, message ) {
    573                 actual = objectValues(actual);
    574                 expected = objectValues(expected);
    575                 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
    576         },
    577 
    578         /**
    579          * @name notPropEqual
    580          * @function
    581          */
    582         notPropEqual: function( actual, expected, message ) {
    583                 actual = objectValues(actual);
    584                 expected = objectValues(expected);
    585                 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
    586         },
    587 
    588         /**
    589          * @name deepEqual
    590          * @function
    591          */
    592         deepEqual: function( actual, expected, message ) {
    593                 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
    594         },
    595 
    596         /**
    597          * @name notDeepEqual
    598          * @function
    599          */
    600         notDeepEqual: function( actual, expected, message ) {
    601                 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
    602         },
    603 
    604         /**
    605          * @name strictEqual
    606          * @function
    607          */
    608         strictEqual: function( actual, expected, message ) {
    609                 QUnit.push( expected === actual, actual, expected, message );
    610         },
    611 
    612         /**
    613          * @name notStrictEqual
    614          * @function
    615          */
    616         notStrictEqual: function( actual, expected, message ) {
    617                 QUnit.push( expected !== actual, actual, expected, message );
    618         },
    619 
    620         "throws": function( block, expected, message ) {
    621                 var actual,
    622                         expectedOutput = expected,
    623                         ok = false;
    624 
    625                 // 'expected' is optional
    626                 if ( typeof expected === "string" ) {
    627                         message = expected;
    628                         expected = null;
    629                 }
    630 
    631                 config.current.ignoreGlobalErrors = true;
    632                 try {
    633                         block.call( config.current.testEnvironment );
    634                 } catch (e) {
    635                         actual = e;
    636                 }
    637                 config.current.ignoreGlobalErrors = false;
    638 
    639                 if ( actual ) {
    640                         // we don't want to validate thrown error
    641                         if ( !expected ) {
    642                                 ok = true;
    643                                 expectedOutput = null;
    644                         // expected is a regexp
    645                         } else if ( QUnit.objectType( expected ) === "regexp" ) {
    646                                 ok = expected.test( errorString( actual ) );
    647                         // expected is a constructor
    648                         } else if ( actual instanceof expected ) {
    649                                 ok = true;
    650                         // expected is a validation function which returns true is validation passed
    651                         } else if ( expected.call( {}, actual ) === true ) {
    652                                 expectedOutput = null;
    653                                 ok = true;
    654                         }
    655 
    656                         QUnit.push( ok, actual, expectedOutput, message );
    657                 } else {
    658                         QUnit.pushFailure( message, null, "No exception was thrown." );
    659                 }
    660         }
    661 };
    662 
    663 /**
    664  * @deprecated since 1.8.0
    665  * Kept assertion helpers in root for backwards compatibility.
    666  */
    667 extend( QUnit, assert );
    668 
    669 /**
    670  * @deprecated since 1.9.0
    671  * Kept root "raises()" for backwards compatibility.
    672  * (Note that we don't introduce assert.raises).
    673  */
    674 QUnit.raises = assert[ "throws" ];
    675 
    676 /**
    677  * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
    678  * Kept to avoid TypeErrors for undefined methods.
    679  */
    680 QUnit.equals = function() {
    681         QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
    682 };
    683 QUnit.same = function() {
    684         QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
    685 };
    686 
    687 // We want access to the constructor's prototype
    688 (function() {
    689         function F() {}
    690         F.prototype = QUnit;
    691         QUnit = new F();
    692         // Make F QUnit's constructor so that we can add to the prototype later
    693         QUnit.constructor = F;
    694 }());
     92QUnit = {};
    69593
    69694/**
    … …  
    706104        blocking: true,
    707105
    708         // when enabled, show only failing tests
    709         // gets persisted through sessionStorage and can be changed in UI via checkbox
    710         hidepassed: false,
    711 
    712106        // by default, run previously failed tests first
    713107        // very useful in combination with "Hide passed tests" checked
    … …  
    717111        altertitle: true,
    718112
     113        // by default, scroll to top of the page when suite is done
     114        scrolltop: true,
     115
    719116        // when enabled, all tests must call expect()
    720117        requireExpects: false,
     118
     119        // depth up-to which object will be dumped
     120        maxDepth: 5,
    721121
    722122        // add checkboxes that are persisted in the query-string
    … …  
    724124        urlConfig: [
    725125                {
     126                        id: "hidepassed",
     127                        label: "Hide passed tests",
     128                        tooltip: "Only show tests and assertions that fail. Stored as query-strings."
     129                },
     130                {
    726131                        id: "noglobals",
    727132                        label: "Check for Globals",
    728                         tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
     133                        tooltip: "Enabling this will test if any test introduces new properties on the " +
     134                                "`window` object. Stored as query-strings."
    729135                },
    730136                {
    731137                        id: "notrycatch",
    732138                        label: "No try-catch",
    733                         tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
     139                        tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
     140                                "exceptions in IE reasonable. Stored as query-strings."
    734141                }
    735142        ],
    736143
    737144        // Set of all modules.
    738         modules: {},
    739 
    740         // logging callback queues
    741         begin: [],
    742         done: [],
    743         log: [],
    744         testStart: [],
    745         testDone: [],
    746         moduleStart: [],
    747         moduleDone: []
     145        modules: [],
     146
     147        // The first unnamed module
     148        currentModule: {
     149                name: "",
     150                tests: []
     151        },
     152
     153        callbacks: {}
    748154};
    749155
    750 // Export global variables, unless an 'exports' object exists,
    751 // in that case we assume we're in CommonJS (dealt with on the bottom of the script)
    752 if ( typeof exports === "undefined" ) {
    753         extend( window, QUnit.constructor.prototype );
    754 
    755         // Expose QUnit object
    756         window.QUnit = QUnit;
    757 }
     156// Push a loose unnamed module to the modules collection
     157config.modules.push( config.currentModule );
    758158
    759159// Initialize more QUnit.config and QUnit.urlParams
    760160(function() {
    761         var i,
     161        var i, current,
    762162                location = window.location || { search: "", protocol: "file:" },
    763163                params = location.search.slice( 1 ).split( "&" ),
    764164                length = params.length,
    765                 urlParams = {},
    766                 current;
     165                urlParams = {};
    767166
    768167        if ( params[ 0 ] ) {
    … …  
    770169                        current = params[ i ].split( "=" );
    771170                        current[ 0 ] = decodeURIComponent( current[ 0 ] );
     171
    772172                        // allow just a key to turn on a flag, e.g., test.html?noglobals
    773173                        current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
    774                         urlParams[ current[ 0 ] ] = current[ 1 ];
    775                 }
     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;
    776184        }
    777185
    … …  
    781189        config.filter = urlParams.filter;
    782190
    783         // Exact match of the module name
    784         config.module = urlParams.module;
    785 
    786         config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
     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        }
    787206
    788207        // Figure out if we're running the tests from a server or not
    789208        QUnit.isLocal = location.protocol === "file:";
     209
     210        // Expose the current QUnit version
     211        QUnit.version = "1.18.0";
    790212}());
    791213
    792 // Extend QUnit object,
    793 // these after set here because they should not be exposed as global functions
     214// Root QUnit object.
     215// `QUnit` initialized at top of scope
    794216extend( QUnit, {
    795         assert: assert,
     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        },
    796337
    797338        config: config,
    798 
    799         // Initialize the configuration options
    800         init: function() {
    801                 extend( config, {
    802                         stats: { all: 0, bad: 0 },
    803                         moduleStats: { all: 0, bad: 0 },
    804                         started: +new Date(),
    805                         updateRate: 1000,
    806                         blocking: false,
    807                         autostart: true,
    808                         autorun: false,
    809                         filter: "",
    810                         queue: [],
    811                         semaphore: 1
    812                 });
    813 
    814                 var tests, banner, result,
    815                         qunit = id( "qunit" );
    816 
    817                 if ( qunit ) {
    818                         qunit.innerHTML =
    819                                 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
    820                                 "<h2 id='qunit-banner'></h2>" +
    821                                 "<div id='qunit-testrunner-toolbar'></div>" +
    822                                 "<h2 id='qunit-userAgent'></h2>" +
    823                                 "<ol id='qunit-tests'></ol>";
    824                 }
    825 
    826                 tests = id( "qunit-tests" );
    827                 banner = id( "qunit-banner" );
    828                 result = id( "qunit-testresult" );
    829 
    830                 if ( tests ) {
    831                         tests.innerHTML = "";
    832                 }
    833 
    834                 if ( banner ) {
    835                         banner.className = "";
    836                 }
    837 
    838                 if ( result ) {
    839                         result.parentNode.removeChild( result );
    840                 }
    841 
    842                 if ( tests ) {
    843                         result = document.createElement( "p" );
    844                         result.id = "qunit-testresult";
    845                         result.className = "result";
    846                         tests.parentNode.insertBefore( result, tests );
    847                         result.innerHTML = "Running...<br/>&nbsp;";
    848                 }
    849         },
    850 
    851         // Resets the test setup. Useful for tests that modify the DOM.
    852         /*
    853         DEPRECATED: Use multiple tests instead of resetting inside a test.
    854         Use testStart or testDone for custom cleanup.
    855         This method will throw an error in 2.0, and will be removed in 2.1
    856         */
    857         reset: function() {
    858                 var fixture = id( "qunit-fixture" );
    859                 if ( fixture ) {
    860                         fixture.innerHTML = config.fixture;
    861                 }
    862         },
    863 
    864         // Trigger an event on an element.
    865         // @example triggerEvent( document.body, "click" );
    866         triggerEvent: function( elem, type, event ) {
    867                 if ( document.createEvent ) {
    868                         event = document.createEvent( "MouseEvents" );
    869                         event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
    870                                 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    871 
    872                         elem.dispatchEvent( event );
    873                 } else if ( elem.fireEvent ) {
    874                         elem.fireEvent( "on" + type );
    875                 }
    876         },
    877339
    878340        // Safe object type checking
    … …  
    883345        objectType: function( obj ) {
    884346                if ( typeof obj === "undefined" ) {
    885                                 return "undefined";
    886                 // consider: typeof null === object
    887                 }
     347                        return "undefined";
     348                }
     349
     350                // Consider: typeof null === object
    888351                if ( obj === null ) {
    889                                 return "null";
    890                 }
    891 
    892                 var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
    893                         type = match && match[1] || "";
     352                        return "null";
     353                }
     354
     355                var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
     356                        type = match && match[ 1 ] || "";
    894357
    895358                switch ( type ) {
    896359                        case "Number":
    897                                 if ( isNaN(obj) ) {
     360                                if ( isNaN( obj ) ) {
    898361                                        return "nan";
    899362                                }
    … …  
    913376        },
    914377
    915         push: function( result, actual, expected, message ) {
    916                 if ( !config.current ) {
    917                         throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
    918                 }
    919 
    920                 var output, source,
    921                         details = {
    922                                 module: config.current.module,
    923                                 name: config.current.testName,
    924                                 result: result,
    925                                 message: message,
    926                                 actual: actual,
    927                                 expected: expected
    928                         };
    929 
    930                 message = escapeText( message ) || ( result ? "okay" : "failed" );
    931                 message = "<span class='test-message'>" + message + "</span>";
    932                 output = message;
    933 
    934                 if ( !result ) {
    935                         expected = escapeText( QUnit.jsDump.parse(expected) );
    936                         actual = escapeText( QUnit.jsDump.parse(actual) );
    937                         output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
    938 
    939                         if ( actual !== expected ) {
    940                                 output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
    941                                 output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
     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                                );
    942413                        }
    943414
    944                         source = sourceFromStacktrace();
    945 
    946                         if ( source ) {
    947                                 details.source = source;
    948                                 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
    949                         }
    950 
    951                         output += "</table>";
    952                 }
    953 
    954                 runLoggingCallbacks( "log", QUnit, details );
    955 
    956                 config.current.assertions.push({
    957                         result: !!result,
    958                         message: output
    959                 });
    960         },
    961 
    962         pushFailure: function( message, source, actual ) {
    963                 if ( !config.current ) {
    964                         throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
    965                 }
    966 
    967                 var output,
    968                         details = {
    969                                 module: config.current.module,
    970                                 name: config.current.testName,
    971                                 result: false,
    972                                 message: message
    973                         };
    974 
    975                 message = escapeText( message ) || "error";
    976                 message = "<span class='test-message'>" + message + "</span>";
    977                 output = message;
    978 
    979                 output += "<table>";
    980 
    981                 if ( actual ) {
    982                         output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
    983                 }
    984 
    985                 if ( source ) {
    986                         details.source = source;
    987                         output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
    988                 }
    989 
    990                 output += "</table>";
    991 
    992                 runLoggingCallbacks( "log", QUnit, details );
    993 
    994                 config.current.assertions.push({
    995                         result: false,
    996                         message: output
    997                 });
    998         },
    999 
    1000         url: function( params ) {
    1001                 params = extend( extend( {}, QUnit.urlParams ), params );
    1002                 var key,
    1003                         querystring = "?";
    1004 
    1005                 for ( key in params ) {
    1006                         if ( hasOwn.call( params, key ) ) {
    1007                                 querystring += encodeURIComponent( key ) + "=" +
    1008                                         encodeURIComponent( params[ key ] ) + "&";
    1009                         }
    1010                 }
    1011                 return window.location.protocol + "//" + window.location.host +
    1012                         window.location.pathname + querystring.slice( 0, -1 );
    1013         },
    1014 
    1015         extend: extend,
    1016         id: id,
    1017         addEvent: addEvent,
    1018         addClass: addClass,
    1019         hasClass: hasClass,
    1020         removeClass: removeClass
    1021         // load, equiv, jsDump, diff: Attached later
    1022 });
    1023 
    1024 /**
    1025  * @deprecated: Created for backwards compatibility with test runner that set the hook function
    1026  * into QUnit.{hook}, instead of invoking it and passing the hook function.
    1027  * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
    1028  * Doing this allows us to tell if the following methods have been overwritten on the actual
    1029  * QUnit object.
    1030  */
    1031 extend( QUnit.constructor.prototype, {
    1032 
    1033         // Logging callbacks; all receive a single argument with the listed properties
    1034         // run test/logs.html for any related changes
    1035         begin: registerLoggingCallback( "begin" ),
    1036 
    1037         // done: { failed, passed, total, runtime }
    1038         done: registerLoggingCallback( "done" ),
    1039 
    1040         // log: { result, actual, expected, message }
    1041         log: registerLoggingCallback( "log" ),
    1042 
    1043         // testStart: { name }
    1044         testStart: registerLoggingCallback( "testStart" ),
    1045 
    1046         // testDone: { name, failed, passed, total, duration }
    1047         testDone: registerLoggingCallback( "testDone" ),
    1048 
    1049         // moduleStart: { name }
    1050         moduleStart: registerLoggingCallback( "moduleStart" ),
    1051 
    1052         // moduleDone: { name, failed, passed, total }
    1053         moduleDone: registerLoggingCallback( "moduleDone" )
    1054 });
    1055 
    1056 if ( typeof document === "undefined" || document.readyState === "complete" ) {
    1057         config.autorun = true;
    1058 }
    1059 
    1060 QUnit.load = function() {
    1061         runLoggingCallbacks( "begin", QUnit, {} );
    1062 
    1063         // Initialize the config, saving the execution queue
    1064         var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
    1065                 urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
    1066                 numModules = 0,
    1067                 moduleNames = [],
    1068                 moduleFilterHtml = "",
    1069                 urlConfigHtml = "",
    1070                 oldconfig = extend( {}, config );
    1071 
    1072         QUnit.init();
    1073         extend(config, oldconfig);
    1074 
    1075         config.blocking = false;
    1076 
    1077         len = config.urlConfig.length;
    1078 
    1079         for ( i = 0; i < len; i++ ) {
    1080                 val = config.urlConfig[i];
    1081                 if ( typeof val === "string" ) {
    1082                         val = {
    1083                                 id: val,
    1084                                 label: val,
    1085                                 tooltip: "[no tooltip available]"
    1086                         };
    1087                 }
    1088                 config[ val.id ] = QUnit.urlParams[ val.id ];
    1089                 urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
    1090                         "' name='" + escapeText( val.id ) +
    1091                         "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
    1092                         " title='" + escapeText( val.tooltip ) +
    1093                         "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
    1094                         "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
    1095         }
    1096         for ( i in config.modules ) {
    1097                 if ( config.modules.hasOwnProperty( i ) ) {
    1098                         moduleNames.push(i);
    1099                 }
    1100         }
    1101         numModules = moduleNames.length;
    1102         moduleNames.sort( function( a, b ) {
    1103                 return a.localeCompare( b );
    1104         });
    1105         moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
    1106                 ( config.module === undefined  ? "selected='selected'" : "" ) +
    1107                 ">< All Modules ></option>";
    1108 
    1109 
    1110         for ( i = 0; i < numModules; i++) {
    1111                         moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
    1112                                 ( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
    1113                                 ">" + escapeText(moduleNames[i]) + "</option>";
    1114         }
    1115         moduleFilterHtml += "</select>";
    1116 
    1117         // `userAgent` initialized at top of scope
    1118         userAgent = id( "qunit-userAgent" );
    1119         if ( userAgent ) {
    1120                 userAgent.innerHTML = navigator.userAgent;
    1121         }
    1122 
    1123         // `banner` initialized at top of scope
    1124         banner = id( "qunit-header" );
    1125         if ( banner ) {
    1126                 banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
    1127         }
    1128 
    1129         // `toolbar` initialized at top of scope
    1130         toolbar = id( "qunit-testrunner-toolbar" );
    1131         if ( toolbar ) {
    1132                 // `filter` initialized at top of scope
    1133                 filter = document.createElement( "input" );
    1134                 filter.type = "checkbox";
    1135                 filter.id = "qunit-filter-pass";
    1136 
    1137                 addEvent( filter, "click", function() {
    1138                         var tmp,
    1139                                 ol = document.getElementById( "qunit-tests" );
    1140 
    1141                         if ( filter.checked ) {
    1142                                 ol.className = ol.className + " hidepass";
    1143                         } else {
    1144                                 tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
    1145                                 ol.className = tmp.replace( / hidepass /, " " );
    1146                         }
    1147                         if ( defined.sessionStorage ) {
    1148                                 if (filter.checked) {
    1149                                         sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
    1150                                 } else {
    1151                                         sessionStorage.removeItem( "qunit-filter-passed-tests" );
    1152                                 }
    1153                         }
    1154                 });
    1155 
    1156                 if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
    1157                         filter.checked = true;
    1158                         // `ol` initialized at top of scope
    1159                         ol = document.getElementById( "qunit-tests" );
    1160                         ol.className = ol.className + " hidepass";
    1161                 }
    1162                 toolbar.appendChild( filter );
    1163 
    1164                 // `label` initialized at top of scope
    1165                 label = document.createElement( "label" );
    1166                 label.setAttribute( "for", "qunit-filter-pass" );
    1167                 label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
    1168                 label.innerHTML = "Hide passed tests";
    1169                 toolbar.appendChild( label );
    1170 
    1171                 urlConfigCheckboxesContainer = document.createElement("span");
    1172                 urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
    1173                 urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
    1174                 // For oldIE support:
    1175                 // * Add handlers to the individual elements instead of the container
    1176                 // * Use "click" instead of "change"
    1177                 // * Fallback from event.target to event.srcElement
    1178                 addEvents( urlConfigCheckboxes, "click", function( event ) {
    1179                         var params = {},
    1180                                 target = event.target || event.srcElement;
    1181                         params[ target.name ] = target.checked ? true : undefined;
    1182                         window.location = QUnit.url( params );
    1183                 });
    1184                 toolbar.appendChild( urlConfigCheckboxesContainer );
    1185 
    1186                 if (numModules > 1) {
    1187                         moduleFilter = document.createElement( "span" );
    1188                         moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
    1189                         moduleFilter.innerHTML = moduleFilterHtml;
    1190                         addEvent( moduleFilter.lastChild, "change", function() {
    1191                                 var selectBox = moduleFilter.getElementsByTagName("select")[0],
    1192                                         selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
    1193 
    1194                                 window.location = QUnit.url({
    1195                                         module: ( selectedModule === "" ) ? undefined : selectedModule,
    1196                                         // Remove any existing filters
    1197                                         filter: undefined,
    1198                                         testNumber: undefined
    1199                                 });
    1200                         });
    1201                         toolbar.appendChild(moduleFilter);
    1202                 }
    1203         }
    1204 
    1205         // `main` initialized at top of scope
    1206         main = id( "qunit-fixture" );
    1207         if ( main ) {
    1208                 config.fixture = main.innerHTML;
    1209         }
    1210 
    1211         if ( config.autostart ) {
    1212                 QUnit.start();
    1213         }
    1214 };
    1215 
    1216 addEvent( window, "load", QUnit.load );
     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})();
    1217437
    1218438// `onErrorFnPrev` initialized at top of scope
    … …  
    1223443// Returning true will suppress the default browser handler,
    1224444// returning false will let it run.
    1225 window.onerror = function ( error, filePath, linerNr ) {
     445window.onerror = function( error, filePath, linerNr ) {
    1226446        var ret = false;
    1227447        if ( onErrorFnPrev ) {
    … …  
    1238458                        QUnit.pushFailure( error, filePath + ":" + linerNr );
    1239459                } else {
    1240                         QUnit.test( "global failure", extend( function() {
     460                        QUnit.test( "global failure", extend(function() {
    1241461                                QUnit.pushFailure( error, filePath + ":" + linerNr );
    1242                         }, { validTest: validTest } ) );
     462                        }, { validTest: true } ) );
    1243463                }
    1244464                return false;
    … …  
    1249469
    1250470function done() {
     471        var runtime, passed;
     472
    1251473        config.autorun = true;
    1252474
    1253475        // Log the last module results
    1254         if ( config.currentModule ) {
    1255                 runLoggingCallbacks( "moduleDone", QUnit, {
    1256                         name: config.currentModule,
     476        if ( config.previousModule ) {
     477                runLoggingCallbacks( "moduleDone", {
     478                        name: config.previousModule.name,
     479                        tests: config.previousModule.tests,
    1257480                        failed: config.moduleStats.bad,
    1258481                        passed: config.moduleStats.all - config.moduleStats.bad,
    1259                         total: config.moduleStats.all
     482                        total: config.moduleStats.all,
     483                        runtime: now() - config.moduleStats.started
    1260484                });
    1261485        }
    1262486        delete config.previousModule;
    1263487
    1264         var i, key,
    1265                 banner = id( "qunit-banner" ),
    1266                 tests = id( "qunit-tests" ),
    1267                 runtime = +new Date() - config.started,
    1268                 passed = config.stats.all - config.stats.bad,
    1269                 html = [
    1270                         "Tests completed in ",
    1271                         runtime,
    1272                         " milliseconds.<br/>",
    1273                         "<span class='passed'>",
    1274                         passed,
    1275                         "</span> assertions of <span class='total'>",
    1276                         config.stats.all,
    1277                         "</span> passed, <span class='failed'>",
    1278                         config.stats.bad,
    1279                         "</span> failed."
    1280                 ].join( "" );
    1281 
    1282         if ( banner ) {
    1283                 banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
    1284         }
    1285 
    1286         if ( tests ) {
    1287                 id( "qunit-testresult" ).innerHTML = html;
    1288         }
    1289 
    1290         if ( config.altertitle && typeof document !== "undefined" && document.title ) {
    1291                 // show ✖ for good, ✔ for bad suite result in title
    1292                 // use escape sequences in case file gets loaded with non-utf-8-charset
    1293                 document.title = [
    1294                         ( config.stats.bad ? "\u2716" : "\u2714" ),
    1295                         document.title.replace( /^[\u2714\u2716] /i, "" )
    1296                 ].join( " " );
    1297         }
    1298 
    1299         // clear own sessionStorage items if all tests passed
    1300         if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
    1301                 // `key` & `i` initialized at top of scope
    1302                 for ( i = 0; i < sessionStorage.length; i++ ) {
    1303                         key = sessionStorage.key( i++ );
    1304                         if ( key.indexOf( "qunit-test-" ) === 0 ) {
    1305                                 sessionStorage.removeItem( key );
    1306                         }
    1307                 }
    1308         }
    1309 
    1310         // scroll back to top to show results
    1311         if ( window.scrollTo ) {
    1312                 window.scrollTo(0, 0);
    1313         }
    1314 
    1315         runLoggingCallbacks( "done", QUnit, {
     488        runtime = now() - config.started;
     489        passed = config.stats.all - config.stats.bad;
     490
     491        runLoggingCallbacks( "done", {
    1316492                failed: config.stats.bad,
    1317493                passed: passed,
    … …  
    1321497}
    1322498
    1323 /** @return Boolean: true if this test should be ran */
    1324 function validTest( test ) {
    1325         var include,
    1326                 filter = config.filter && config.filter.toLowerCase(),
    1327                 module = config.module && config.module.toLowerCase(),
    1328                 fullName = (test.module + ": " + test.testName).toLowerCase();
    1329 
    1330         // Internally-generated tests are always valid
    1331         if ( test.callback && test.callback.validTest === validTest ) {
    1332                 delete test.callback.validTest;
    1333                 return true;
    1334         }
    1335 
    1336         if ( config.testNumber ) {
    1337                 return test.testNumber === config.testNumber;
    1338         }
    1339 
    1340         if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
    1341                 return false;
    1342         }
    1343 
    1344         if ( !filter ) {
    1345                 return true;
    1346         }
    1347 
    1348         include = filter.charAt( 0 ) !== "!";
    1349         if ( !include ) {
    1350                 filter = filter.slice( 1 );
    1351         }
    1352 
    1353         // If the filter matches, we need to honour include
    1354         if ( fullName.indexOf( filter ) !== -1 ) {
    1355                 return include;
    1356         }
    1357 
    1358         // Otherwise, do the opposite
    1359         return !include;
    1360 }
    1361 
    1362 // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
    1363 // Later Safari and IE10 are supposed to support error.stack as well
     499// Doesn't support IE6 to IE9, it will return undefined on these browsers
    1364500// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
    1365501function extractStacktrace( e, offset ) {
    1366         offset = offset === undefined ? 3 : offset;
     502        offset = offset === undefined ? 4 : offset;
    1367503
    1368504        var stack, include, i;
    1369505
    1370         if ( e.stacktrace ) {
    1371                 // Opera
    1372                 return e.stacktrace.split( "\n" )[ offset + 3 ];
    1373         } else if ( e.stack ) {
    1374                 // Firefox, Chrome
     506        if ( e.stack ) {
    1375507                stack = e.stack.split( "\n" );
    1376                 if (/^error$/i.test( stack[0] ) ) {
     508                if ( /^error$/i.test( stack[ 0 ] ) ) {
    1377509                        stack.shift();
    1378510                }
    … …  
    1390522                }
    1391523                return stack[ offset ];
     524
     525        // Support: Safari <=6 only
    1392526        } else if ( e.sourceURL ) {
    1393                 // Safari, PhantomJS
    1394                 // hopefully one day Safari provides actual stacktraces
     527
    1395528                // exclude useless self-reference for generated Error objects
    1396529                if ( /qunit.js$/.test( e.sourceURL ) ) {
    1397530                        return;
    1398531                }
     532
    1399533                // for actual exceptions, this is useful
    1400534                return e.sourceURL + ":" + e.line;
    1401535        }
    1402536}
     537
    1403538function sourceFromStacktrace( offset ) {
    1404         try {
    1405                 throw new Error();
    1406         } catch ( e ) {
    1407                 return extractStacktrace( e, offset );
    1408         }
    1409 }
    1410 
    1411 /**
    1412  * Escape text for attribute or text content.
    1413  */
    1414 function escapeText( s ) {
    1415         if ( !s ) {
    1416                 return "";
    1417         }
    1418         s = s + "";
    1419         // Both single quotes and double quotes (for attributes)
    1420         return s.replace( /['"<>&]/g, function( s ) {
    1421                 switch( s ) {
    1422                         case "'":
    1423                                 return "&#039;";
    1424                         case "\"":
    1425                                 return "&quot;";
    1426                         case "<":
    1427                                 return "&lt;";
    1428                         case ">":
    1429                                 return "&gt;";
    1430                         case "&":
    1431                                 return "&amp;";
    1432                 }
    1433         });
     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 );
    1434552}
    1435553
    1436554function synchronize( callback, last ) {
     555        if ( QUnit.objectType( callback ) === "array" ) {
     556                while ( callback.length ) {
     557                        synchronize( callback.shift() );
     558                }
     559                return;
     560        }
    1437561        config.queue.push( callback );
    1438562
    … …  
    1446570                process( last );
    1447571        }
    1448         var start = new Date().getTime();
    1449         config.depth = config.depth ? config.depth + 1 : 1;
     572        var start = now();
     573        config.depth = ( config.depth || 0 ) + 1;
    1450574
    1451575        while ( config.queue.length && !config.blocking ) {
    1452                 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
     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                        }
    1453583                        config.queue.shift()();
    1454584                } else {
    … …  
    1460590        if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
    1461591                done();
     592        }
     593}
     594
     595function 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
     631function 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
     651function 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 );
    1462665        }
    1463666}
    … …  
    1488691        newGlobals = diff( config.pollution, old );
    1489692        if ( newGlobals.length > 0 ) {
    1490                 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
     693                QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
    1491694        }
    1492695
    1493696        deletedGlobals = diff( old, config.pollution );
    1494697        if ( deletedGlobals.length > 0 ) {
    1495                 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
     698                QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
    1496699        }
    1497700}
    … …  
    1504707        for ( i = 0; i < result.length; i++ ) {
    1505708                for ( j = 0; j < b.length; j++ ) {
    1506                         if ( result[i] === b[j] ) {
     709                        if ( result[ i ] === b[ j ] ) {
    1507710                                result.splice( i, 1 );
    1508711                                i--;
    … …  
    1514717}
    1515718
    1516 function extend( a, b ) {
     719function extend( a, b, undefOnly ) {
    1517720        for ( var prop in b ) {
    1518721                if ( hasOwn.call( b, prop ) ) {
     722
    1519723                        // Avoid "Member not found" error in IE8 caused by messing with window.constructor
    1520724                        if ( !( prop === "constructor" && a === window ) ) {
    1521725                                if ( b[ prop ] === undefined ) {
    1522726                                        delete a[ prop ];
    1523                                 } else {
     727                                } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
    1524728                                        a[ prop ] = b[ prop ];
    1525729                                }
    … …  
    1531735}
    1532736
    1533 /**
    1534  * @param {HTMLElement} elem
    1535  * @param {string} type
    1536  * @param {Function} fn
    1537  */
    1538 function addEvent( elem, type, fn ) {
    1539         // Standards-based browsers
    1540         if ( elem.addEventListener ) {
    1541                 elem.addEventListener( type, fn, false );
    1542         // IE
     737function 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
     749function 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
     775function 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
     789function 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;
    1543821        } else {
    1544                 elem.attachEvent( "on" + type, fn );
    1545         }
    1546 }
    1547 
    1548 /**
    1549  * @param {Array|NodeList} elems
    1550  * @param {string} type
    1551  * @param {Function} fn
    1552  */
    1553 function addEvents( elems, type, fn ) {
    1554         var i = elems.length;
    1555         while ( i-- ) {
    1556                 addEvent( elems[i], type, fn );
    1557         }
    1558 }
    1559 
    1560 function hasClass( elem, name ) {
    1561         return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
    1562 }
    1563 
    1564 function addClass( elem, name ) {
    1565         if ( !hasClass( elem, name ) ) {
    1566                 elem.className += (elem.className ? " " : "") + name;
    1567         }
    1568 }
    1569 
    1570 function removeClass( elem, name ) {
    1571         var set = " " + elem.className + " ";
    1572         // Class name may appear multiple times
    1573         while ( set.indexOf(" " + name + " ") > -1 ) {
    1574                 set = set.replace(" " + name + " " , " ");
    1575         }
    1576         // If possible, trim it for prettiness, but not necessarily
    1577         elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
    1578 }
    1579 
    1580 function id( name ) {
    1581         return !!( typeof document !== "undefined" && document && document.getElementById ) &&
    1582                 document.getElementById( name );
    1583 }
    1584 
    1585 function registerLoggingCallback( key ) {
    1586         return function( callback ) {
    1587                 config[key].push( callback );
    1588         };
    1589 }
    1590 
    1591 // Supports deprecated method of completely overwriting logging callbacks
    1592 function runLoggingCallbacks( key, scope, args ) {
    1593         var i, callbacks;
    1594         if ( QUnit.hasOwnProperty( key ) ) {
    1595                 QUnit[ key ].call(scope, args );
    1596         } else {
    1597                 callbacks = config[ key ];
    1598                 for ( i = 0; i < callbacks.length; i++ ) {
    1599                         callbacks[ i ].call( scope, args );
    1600                 }
    1601         }
    1602 }
     822                this.assert = new Assert( this );
     823        }
     824}
     825
     826Test.count = 0;
     827
     828Test.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/*
     1175DEPRECATED: Use multiple tests instead of resetting inside a test.
     1176Use testStart or testDone for custom cleanup.
     1177This method will throw an error in 2.0, and will be removed in 2.1
     1178*/
     1179QUnit.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
     1195QUnit.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
     1209function 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
     1231function Assert( testContext ) {
     1232        this.test = testContext;
     1233}
     1234
     1235// Assert helpers
     1236QUnit.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}());
    16031410
    16041411// Test for equality any JavaScript type.
    … …  
    16201427        // the real equiv function
    16211428        var innerEquiv,
     1429
    16221430                // stack to decide between skip/abort functions
    16231431                callers = [],
     1432
    16241433                // stack to avoiding loops from circular referencing
    16251434                parents = [],
    16261435                parentsB = [],
    16271436
    1628                 getProto = Object.getPrototypeOf || function ( obj ) {
    1629                         /*jshint camelcase:false */
     1437                getProto = Object.getPrototypeOf || function( obj ) {
     1438                        /* jshint camelcase: false, proto: true */
    16301439                        return obj.__proto__;
    16311440                },
    1632                 callbacks = (function () {
     1441                callbacks = (function() {
    16331442
    16341443                        // for string, boolean, number and null
    16351444                        function useStrictEquality( b, a ) {
     1445
    16361446                                /*jshint eqeqeq:false */
    16371447                                if ( b instanceof a.constructor || a instanceof b.constructor ) {
     1448
    16381449                                        // to catch short annotation VS 'new' annotation of a
    16391450                                        // declaration
    … …  
    16631474                                "regexp": function( b, a ) {
    16641475                                        return QUnit.objectType( b ) === "regexp" &&
     1476
    16651477                                                // the regex itself
    16661478                                                a.source === b.source &&
     1479
    16671480                                                // and its modifiers
    16681481                                                a.global === b.global &&
     1482
    16691483                                                // (gmi) ...
    16701484                                                a.ignoreCase === b.ignoreCase &&
    … …  
    16771491                                // initial === would have catch identical references anyway
    16781492                                "function": function() {
    1679                                         var caller = callers[callers.length - 1];
     1493                                        var caller = callers[ callers.length - 1 ];
    16801494                                        return caller !== Object && typeof caller !== "undefined";
    16811495                                },
    … …  
    17011515                                                loop = false;
    17021516                                                for ( j = 0; j < parents.length; j++ ) {
    1703                                                         aCircular = parents[j] === a[i];
    1704                                                         bCircular = parentsB[j] === b[i];
     1517                                                        aCircular = parents[ j ] === a[ i ];
     1518                                                        bCircular = parentsB[ j ] === b[ i ];
    17051519                                                        if ( aCircular || bCircular ) {
    1706                                                                 if ( a[i] === b[i] || aCircular && bCircular ) {
     1520                                                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
    17071521                                                                        loop = true;
    17081522                                                                } else {
    … …  
    17131527                                                        }
    17141528                                                }
    1715                                                 if ( !loop && !innerEquiv(a[i], b[i]) ) {
     1529                                                if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
    17161530                                                        parents.pop();
    17171531                                                        parentsB.pop();
    … …  
    17251539
    17261540                                "object": function( b, a ) {
     1541
    17271542                                        /*jshint forin:false */
    17281543                                        var i, j, loop, aCircular, bCircular,
    … …  
    17351550                                        // instanceof
    17361551                                        if ( a.constructor !== b.constructor ) {
     1552
    17371553                                                // Allow objects with no prototype to be equivalent to
    17381554                                                // objects with Object as their constructor.
    1739                                                 if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
    1740                                                         ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
    1741                                                                 return false;
     1555                                                if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
     1556                                                        ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
     1557                                                        return false;
    17421558                                                }
    17431559                                        }
    … …  
    17541570                                                loop = false;
    17551571                                                for ( j = 0; j < parents.length; j++ ) {
    1756                                                         aCircular = parents[j] === a[i];
    1757                                                         bCircular = parentsB[j] === b[i];
     1572                                                        aCircular = parents[ j ] === a[ i ];
     1573                                                        bCircular = parentsB[ j ] === b[ i ];
    17581574                                                        if ( aCircular || bCircular ) {
    1759                                                                 if ( a[i] === b[i] || aCircular && bCircular ) {
     1575                                                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
    17601576                                                                        loop = true;
    17611577                                                                } else {
    … …  
    17651581                                                        }
    17661582                                                }
    1767                                                 aProperties.push(i);
    1768                                                 if ( !loop && !innerEquiv(a[i], b[i]) ) {
     1583                                                aProperties.push( i );
     1584                                                if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
    17691585                                                        eq = false;
    17701586                                                        break;
    … …  
    17921608                }
    17931609
    1794                 return (function( a, b ) {
     1610                return ( (function( a, b ) {
    17951611                        if ( a === b ) {
    17961612                                return true; // catch the most you can
    17971613                        } else if ( a === null || b === null || typeof a === "undefined" ||
    17981614                                        typeof b === "undefined" ||
    1799                                         QUnit.objectType(a) !== QUnit.objectType(b) ) {
    1800                                 return false; // don't lose time with error prone cases
     1615                                        QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
     1616
     1617                                // don't lose time with error prone cases
     1618                                return false;
    18011619                        } else {
    1802                                 return bindCallbacks(a, callbacks, [ b, a ]);
     1620                                return bindCallbacks( a, callbacks, [ b, a ] );
    18031621                        }
    18041622
    18051623                        // apply transition with (1..n) arguments
    1806                 }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) );
     1624                }( args[ 0 ], args[ 1 ] ) ) &&
     1625                        innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
    18071626        };
    18081627
    … …  
    18101629}());
    18111630
    1812 /**
    1813  * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
    1814  * http://flesler.blogspot.com Licensed under BSD
    1815  * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
    1816  *
    1817  * @projectDescription Advanced and extensible data dumping for Javascript.
    1818  * @version 1.0.0
    1819  * @author Ariel Flesler
    1820  * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
    1821  */
    1822 QUnit.jsDump = (function() {
     1631// Based on jsDump by Ariel Flesler
     1632// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
     1633QUnit.dump = (function() {
    18231634        function quote( str ) {
    18241635                return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
    … …  
    18281639        }
    18291640        function join( pre, arr, post ) {
    1830                 var s = jsDump.separator(),
    1831                         base = jsDump.indent(),
    1832                         inner = jsDump.indent(1);
     1641                var s = dump.separator(),
     1642                        base = dump.indent(),
     1643                        inner = dump.indent( 1 );
    18331644                if ( arr.join ) {
    18341645                        arr = arr.join( "," + s + inner );
    … …  
    18371648                        return pre + post;
    18381649                }
    1839                 return [ pre, inner + arr, base + post ].join(s);
     1650                return [ pre, inner + arr, base + post ].join( s );
    18401651        }
    18411652        function array( arr, stack ) {
    1842                 var i = arr.length, ret = new Array(i);
     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
    18431660                this.up();
    18441661                while ( i-- ) {
    1845                         ret[i] = this.parse( arr[i] , undefined , stack);
     1662                        ret[ i ] = this.parse( arr[ i ], undefined, stack );
    18461663                }
    18471664                this.down();
    … …  
    18501667
    18511668        var reName = /^function (\w+)/,
    1852                 jsDump = {
    1853                         // type is used mostly internally, you can fix a (custom)type in advance
    1854                         parse: function( obj, type, stack ) {
    1855                                 stack = stack || [ ];
    1856                                 var inStack, res,
    1857                                         parser = this.parsers[ type || this.typeOf(obj) ];
    1858 
    1859                                 type = typeof parser;
    1860                                 inStack = inArray( obj, stack );
     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 );
    18611676
    18621677                                if ( inStack !== -1 ) {
    1863                                         return "recursion(" + (inStack - stack.length) + ")";
     1678                                        return "recursion(" + ( inStack - stack.length ) + ")";
    18641679                                }
    1865                                 if ( type === "function" )  {
     1680
     1681                                objType = objType || this.typeOf( obj  );
     1682                                parser = this.parsers[ objType ];
     1683                                parserType = typeof parser;
     1684
     1685                                if ( parserType === "function" ) {
    18661686                                        stack.push( obj );
    18671687                                        res = parser.call( this, obj, stack );
    … …  
    18691689                                        return res;
    18701690                                }
    1871                                 return ( type === "string" ) ? parser : this.parsers.error;
     1691                                return ( parserType === "string" ) ? parser : this.parsers.error;
    18721692                        },
    18731693                        typeOf: function( obj ) {
    … …  
    18771697                                } else if ( typeof obj === "undefined" ) {
    18781698                                        type = "undefined";
    1879                                 } else if ( QUnit.is( "regexp", obj) ) {
     1699                                } else if ( QUnit.is( "regexp", obj ) ) {
    18801700                                        type = "regexp";
    1881                                 } else if ( QUnit.is( "date", obj) ) {
     1701                                } else if ( QUnit.is( "date", obj ) ) {
    18821702                                        type = "date";
    1883                                 } else if ( QUnit.is( "function", obj) ) {
     1703                                } else if ( QUnit.is( "function", obj ) ) {
    18841704                                        type = "function";
    1885                                 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
     1705                                } else if ( obj.setInterval !== undefined &&
     1706                                                obj.document !== undefined &&
     1707                                                obj.nodeType === undefined ) {
    18861708                                        type = "window";
    18871709                                } else if ( obj.nodeType === 9 ) {
    … …  
    18901712                                        type = "node";
    18911713                                } else if (
     1714
    18921715                                        // native arrays
    18931716                                        toString.call( obj ) === "[object Array]" ||
     1717
    18941718                                        // NodeList objects
    1895                                         ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
     1719                                        ( typeof obj.length === "number" && obj.item !== undefined &&
     1720                                        ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
     1721                                        obj[ 0 ] === undefined ) ) )
    18961722                                ) {
    18971723                                        type = "array";
    … …  
    19041730                        },
    19051731                        separator: function() {
    1906                                 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
     1732                                return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
    19071733                        },
    19081734                        // extra can be a number, shortcut for increasing-calling-decreasing
    … …  
    19131739                                var chr = this.indentChar;
    19141740                                if ( this.HTML ) {
    1915                                         chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
     1741                                        chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
    19161742                                }
    1917                                 return new Array( this.depth + ( extra || 0 ) ).join(chr);
     1743                                return new Array( this.depth + ( extra || 0 ) ).join( chr );
    19181744                        },
    19191745                        up: function( a ) {
    … …  
    19241750                        },
    19251751                        setParser: function( name, parser ) {
    1926                                 this.parsers[name] = parser;
     1752                                this.parsers[ name ] = parser;
    19271753                        },
    19281754                        // The next 3 are exposed so you can use them
    … …  
    19321758                        //
    19331759                        depth: 1,
    1934                         // This is the list of parsers, to modify them, use jsDump.setParser
     1760                        maxDepth: QUnit.config.maxDepth,
     1761
     1762                        // This is the list of parsers, to modify them, use dump.setParser
    19351763                        parsers: {
    19361764                                window: "[Window]",
    19371765                                document: "[Document]",
    1938                                 error: function(error) {
     1766                                error: function( error ) {
    19391767                                        return "Error(\"" + error.message + "\")";
    19401768                                },
    … …  
    19441772                                "function": function( fn ) {
    19451773                                        var ret = "function",
     1774
    19461775                                                // functions never have name in IE
    1947                                                 name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
     1776                                                name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
    19481777
    19491778                                        if ( name ) {
    … …  
    19521781                                        ret += "( ";
    19531782
    1954                                         ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
    1955                                         return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
     1783                                        ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
     1784                                        return join( ret, dump.parse( fn, "functionCode" ), "}" );
    19561785                                },
    19571786                                array: array,
    … …  
    19591788                                "arguments": array,
    19601789                                object: function( map, stack ) {
    1961                                         /*jshint forin:false */
    1962                                         var ret = [ ], keys, key, val, i;
    1963                                         QUnit.jsDump.up();
     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();
    19641798                                        keys = [];
    19651799                                        for ( key in map ) {
    19661800                                                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                                                }
    19671810                                        }
    19681811                                        keys.sort();
    … …  
    19701813                                                key = keys[ i ];
    19711814                                                val = map[ key ];
    1972                                                 ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
     1815                                                ret.push( dump.parse( key, "key" ) + ": " +
     1816                                                        dump.parse( val, undefined, stack ) );
    19731817                                        }
    1974                                         QUnit.jsDump.down();
     1818                                        dump.down();
    19751819                                        return join( "{", ret, "}" );
    19761820                                },
    19771821                                node: function( node ) {
    19781822                                        var len, i, val,
    1979                                                 open = QUnit.jsDump.HTML ? "&lt;" : "<",
    1980                                                 close = QUnit.jsDump.HTML ? "&gt;" : ">",
     1823                                                open = dump.HTML ? "&lt;" : "<",
     1824                                                close = dump.HTML ? "&gt;" : ">",
    19811825                                                tag = node.nodeName.toLowerCase(),
    19821826                                                ret = open + tag,
    … …  
    19851829                                        if ( attrs ) {
    19861830                                                for ( i = 0, len = attrs.length; i < len; i++ ) {
    1987                                                         val = attrs[i].nodeValue;
    1988                                                         // IE6 includes all attributes in .attributes, even ones not explicitly set.
    1989                                                         // Those have values like undefined, null, 0, false, "" or "inherit".
     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".
    19901836                                                        if ( val && val !== "inherit" ) {
    1991                                                                 ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
     1837                                                                ret += " " + attrs[ i ].nodeName + "=" +
     1838                                                                        dump.parse( val, "attribute" );
    19921839                                                        }
    19931840                                                }
    … …  
    20021849                                        return ret + open + "/" + tag + close;
    20031850                                },
     1851
    20041852                                // function calls it internally, it's the arguments part of the function
    20051853                                functionArgs: function( fn ) {
    … …  
    20111859                                        }
    20121860
    2013                                         args = new Array(l);
     1861                                        args = new Array( l );
    20141862                                        while ( l-- ) {
     1863
    20151864                                                // 97 is 'a'
    2016                                                 args[l] = String.fromCharCode(97+l);
     1865                                                args[ l ] = String.fromCharCode( 97 + l );
    20171866                                        }
    20181867                                        return " " + args.join( ", " ) + " ";
    … …  
    20381887                };
    20391888
    2040         return jsDump;
     1889        return dump;
    20411890}());
    20421891
    2043 // from jquery.js
    2044 function inArray( elem, array ) {
    2045         if ( array.indexOf ) {
    2046                 return array.indexOf( elem );
    2047         }
    2048 
    2049         for ( var i = 0, length = array.length; i < length; i++ ) {
    2050                 if ( array[ i ] === elem ) {
    2051                         return i;
    2052                 }
    2053         }
    2054 
    2055         return -1;
    2056 }
    2057 
     1892// back compat
     1893QUnit.jsDump = QUnit.dump;
     1894
     1895// For browser, export only select globals
     1896if ( 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
     1947if ( 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
     1955if ( typeof exports !== "undefined" && exports ) {
     1956        exports.QUnit = QUnit;
     1957}
     1958
     1959if ( 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
    20581973/*
    2059  * Javascript Diff Algorithm
    2060  *  By John Resig (http://ejohn.org/)
    2061  *  Modified by Chu Alan "sprite"
     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.
    20621977 *
    2063  * Released under the MIT license.
     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.
    20641994 *
    20651995 * More Info:
    2066  *  http://ejohn.org/projects/javascript-diff-algorithm/
     1996 *  https://code.google.com/p/google-diff-match-patch/
    20671997 *
    20681998 * Usage: QUnit.diff(expected, actual)
    20691999 *
    2070  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
     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"
    20712001 */
    20722002QUnit.diff = (function() {
    2073         /*jshint eqeqeq:false, eqnull:true */
    2074         function diff( o, n ) {
    2075                 var i,
    2076                         ns = {},
    2077                         os = {};
    2078 
    2079                 for ( i = 0; i < n.length; i++ ) {
    2080                         if ( !hasOwn.call( ns, n[i] ) ) {
    2081                                 ns[ n[i] ] = {
    2082                                         rows: [],
    2083                                         o: null
    2084                                 };
     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
     3063QUnit.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
     3119if ( typeof window === "undefined" ) {
     3120        return;
     3121}
     3122
     3123var 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;
    20853135                        }
    2086                         ns[ n[i] ].rows.push( i );
    2087                 }
    2088 
    2089                 for ( i = 0; i < o.length; i++ ) {
    2090                         if ( !hasOwn.call( os, o[i] ) ) {
    2091                                 os[ o[i] ] = {
    2092                                         rows: [],
    2093                                         n: null
    2094                                 };
     3136                }())
     3137        },
     3138        modulesList = [];
     3139
     3140/**
     3141* Escape text for attribute or text content.
     3142*/
     3143function 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 */
     3171function 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;
    20953183                        }
    2096                         os[ o[i] ].rows.push( i );
    2097                 }
    2098 
    2099                 for ( i in ns ) {
    2100                         if ( hasOwn.call( ns, i ) ) {
    2101                                 if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
    2102                                         n[ ns[i].rows[0] ] = {
    2103                                                 text: n[ ns[i].rows[0] ],
    2104                                                 row: os[i].rows[0]
    2105                                         };
    2106                                         o[ os[i].rows[0] ] = {
    2107                                                 text: o[ os[i].rows[0] ],
    2108                                                 row: ns[i].rows[0]
    2109                                         };
     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 */
     3195function addEvents( elems, type, fn ) {
     3196        var i = elems.length;
     3197        while ( i-- ) {
     3198                addEvent( elems[ i ], type, fn );
     3199        }
     3200}
     3201
     3202function hasClass( elem, name ) {
     3203        return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
     3204}
     3205
     3206function addClass( elem, name ) {
     3207        if ( !hasClass( elem, name ) ) {
     3208                elem.className += ( elem.className ? " " : "" ) + name;
     3209        }
     3210}
     3211
     3212function toggleClass( elem, name ) {
     3213        if ( hasClass( elem, name ) ) {
     3214                removeClass( elem, name );
     3215        } else {
     3216                addClass( elem, name );
     3217        }
     3218}
     3219
     3220function 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
     3232function id( name ) {
     3233        return defined.document && document.getElementById && document.getElementById( name );
     3234}
     3235
     3236function 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                                        }
    21103288                                }
    21113289                        }
    2112                 }
    2113 
    2114                 for ( i = 0; i < n.length - 1; i++ ) {
    2115                         if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
    2116                                                 n[ i + 1 ] == o[ n[i].row + 1 ] ) {
    2117 
    2118                                 n[ i + 1 ] = {
    2119                                         text: n[ i + 1 ],
    2120                                         row: n[i].row + 1
    2121                                 };
    2122                                 o[ n[i].row + 1 ] = {
    2123                                         text: o[ n[i].row + 1 ],
    2124                                         row: i + 1
    2125                                 };
     3290                        if ( config[ val.id ] && !selection ) {
     3291                                escaped = escapeText( config[ val.id ] );
     3292                                urlConfigHtml += "<option value='" + escaped +
     3293                                        "' selected='selected' disabled='disabled'>" + escaped + "</option>";
    21263294                        }
    2127                 }
    2128 
    2129                 for ( i = n.length - 1; i > 0; i-- ) {
    2130                         if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
    2131                                                 n[ i - 1 ] == o[ n[i].row - 1 ]) {
    2132 
    2133                                 n[ i - 1 ] = {
    2134                                         text: n[ i - 1 ],
    2135                                         row: n[i].row - 1
    2136                                 };
    2137                                 o[ n[i].row - 1 ] = {
    2138                                         text: o[ n[i].row - 1 ],
    2139                                         row: i - 1
    2140                                 };
     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.
     3304function 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
     3334function 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;
    21413344                        }
    2142                 }
    2143 
    2144                 return {
    2145                         o: o,
    2146                         n: n
    2147                 };
    2148         }
    2149 
    2150         return function( o, n ) {
    2151                 o = o.replace( /\s+$/, "" );
    2152                 n = n.replace( /\s+$/, "" );
    2153 
    2154                 var i, pre,
    2155                         str = "",
    2156                         out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
    2157                         oSpace = o.match(/\s+/g),
    2158                         nSpace = n.match(/\s+/g);
    2159 
    2160                 if ( oSpace == null ) {
    2161                         oSpace = [ " " ];
    2162                 }
    2163                 else {
    2164                         oSpace.push( " " );
    2165                 }
    2166 
    2167                 if ( nSpace == null ) {
    2168                         nSpace = [ " " ];
    2169                 }
    2170                 else {
    2171                         nSpace.push( " " );
    2172                 }
    2173 
    2174                 if ( out.n.length === 0 ) {
    2175                         for ( i = 0; i < out.o.length; i++ ) {
    2176                                 str += "<del>" + out.o[i] + oSpace[i] + "</del>";
     3345                        querystring += encodeURIComponent( key );
     3346                        if ( params[ key ] !== true ) {
     3347                                querystring += "=" + encodeURIComponent( params[ key ] );
    21773348                        }
    2178                 }
    2179                 else {
    2180                         if ( out.n[0].text == null ) {
    2181                                 for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
    2182                                         str += "<del>" + out.o[n] + oSpace[n] + "</del>";
    2183                                 }
     3349                        querystring += "&";
     3350                }
     3351        }
     3352        return location.protocol + "//" + location.host +
     3353                location.pathname + querystring.slice( 0, -1 );
     3354}
     3355
     3356function 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
     3374function 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
     3389function 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
     3423function 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
     3451function 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
     3468function appendToolbar() {
     3469        var toolbar = id( "qunit-testrunner-toolbar" );
     3470
     3471        if ( toolbar ) {
     3472                toolbar.appendChild( toolbarUrlConfigContainer() );
     3473                toolbar.appendChild( toolbarLooseFilter() );
     3474        }
     3475}
     3476
     3477function 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
     3487function appendBanner() {
     3488        var banner = id( "qunit-banner" );
     3489
     3490        if ( banner ) {
     3491                banner.className = "";
     3492        }
     3493}
     3494
     3495function 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
     3513function storeFixture() {
     3514        var fixture = id( "qunit-fixture" );
     3515        if ( fixture ) {
     3516                config.fixture = fixture.innerHTML;
     3517        }
     3518}
     3519
     3520function 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
     3533function 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
     3551function 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
     3580QUnit.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
     3608QUnit.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 );
    21843649                        }
    2185 
    2186                         for ( i = 0; i < out.n.length; i++ ) {
    2187                                 if (out.n[i].text == null) {
    2188                                         str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
    2189                                 }
    2190                                 else {
    2191                                         // `pre` initialized at top of scope
    2192                                         pre = "";
    2193 
    2194                                         for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
    2195                                                 pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
    2196                                         }
    2197                                         str += " " + out.n[i].text + nSpace[i] + pre;
    2198                                 }
     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
     3659function 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
     3671QUnit.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
     3696QUnit.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>";
    21993732                        }
    22003733                }
    22013734
    2202                 return str;
    2203         };
    2204 }());
    2205 
    2206 // for CommonJS environments, export everything
    2207 if ( typeof exports !== "undefined" ) {
    2208         extend( exports, QUnit.constructor.prototype );
    2209 }
    2210 
    2211 // get at whatever the global object is, like window in browsers
    2212 }( (function() {return this;}.call()) ));
     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
     3758QUnit.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
     3817if ( 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})();
Note: See TracChangeset for help on using the changeset viewer.