WordPress.org

Make WordPress Core

Changeset 32343


Ignore:
Timestamp:
05/04/2015 07:43:38 PM (5 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.