Merge "Add AvailableRightsTest for User::getAllRights completeness"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 10 Feb 2015 16:21:40 +0000 (16:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 10 Feb 2015 16:21:40 +0000 (16:21 +0000)
40 files changed:
.jscsrc
autoload.php
composer.json
docs/extension.schema.json
img_auth.php
includes/Import.php
includes/Message.php
includes/Status.php
includes/filerepo/FileRepo.php
includes/filerepo/FileRepoStatus.php
includes/jobqueue/JobQueueRedis.php
includes/libs/MessageSpecifier.php [new file with mode: 0644]
includes/libs/StatusValue.php [new file with mode: 0644]
includes/registration/ExtensionProcessor.php
maintenance/Maintenance.php
maintenance/convertExtensionToRegistration.php
package.json
profileinfo.php
resources/Resources.php
resources/lib/jquery/jquery.qunit.css [deleted file]
resources/lib/jquery/jquery.qunit.js [deleted file]
resources/lib/qunitjs/qunit.css [new file with mode: 0644]
resources/lib/qunitjs/qunit.js [new file with mode: 0644]
resources/src/jquery/jquery.mwExtension.js
resources/src/jquery/jquery.placeholder.js
resources/src/jquery/jquery.textSelection.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
resources/src/mediawiki.special/mediawiki.special.pageLanguage.js
resources/src/mediawiki/mediawiki.cookie.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.startUp.js [new file with mode: 0644]
resources/src/mediawiki/mediawiki.user.js
resources/src/mediawiki/mediawiki.util.js
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/registration/ExtensionProcessorTest.php
tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js
tests/qunit/suites/resources/jquery/jquery.placeholder.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
thumb.php

diff --git a/.jscsrc b/.jscsrc
index 2ebd40e..34b2435 100644 (file)
--- a/.jscsrc
+++ b/.jscsrc
@@ -3,6 +3,10 @@
 
        "disallowKeywordsOnNewLine": null,
        "disallowQuotedKeysInObjects": null,
+       "disallowImplicitTypeConversion": null,
+       "requireLineBreakAfterVariableAssignment": null,
+       "requireSpaceAfterLineComment": null,
+       "requireSpacesInsideParentheses": null,
        "requireSpacesInsideArrayBrackets": null,
        "validateIndentation": null
 }
index 5c515a0..d78b0ff 100644 (file)
@@ -535,6 +535,7 @@ $wgAutoloadLocalClasses = array(
        'ImageQueryPage' => __DIR__ . '/includes/specialpage/ImageQueryPage.php',
        'ImportReporter' => __DIR__ . '/includes/specials/SpecialImport.php',
        'ImportSiteScripts' => __DIR__ . '/maintenance/importSiteScripts.php',
+       'ImportSource' => __DIR__ . '/includes/Import.php',
        'ImportStreamSource' => __DIR__ . '/includes/Import.php',
        'ImportStringSource' => __DIR__ . '/includes/Import.php',
        'ImportTitleFactory' => __DIR__ . '/includes/title/ImportTitleFactory.php',
@@ -745,6 +746,7 @@ $wgAutoloadLocalClasses = array(
        'MessageBlobStore' => __DIR__ . '/includes/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
+       'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeMagic' => __DIR__ . '/includes/MimeMagic.php',
        'MinifyScript' => __DIR__ . '/maintenance/minify.php',
@@ -1151,6 +1153,7 @@ $wgAutoloadLocalClasses = array(
        'StatCounter' => __DIR__ . '/includes/StatCounter.php',
        'StatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'Status' => __DIR__ . '/includes/Status.php',
+       'StatusValue' => __DIR__ . '/includes/libs/StatusValue.php',
        'StorageTypeStats' => __DIR__ . '/maintenance/storage/storageTypeStats.php',
        'StoreFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'StreamFile' => __DIR__ . '/includes/StreamFile.php',
index bae3b55..94bec94 100644 (file)
@@ -27,7 +27,7 @@
        },
        "require-dev": {
                "justinrainbow/json-schema": "~1.3",
-               "phpunit/phpunit": "*"
+               "phpunit/phpunit": "~4.5"
        },
        "suggest": {
                "ext-fileinfo": "*",
index 4583559..33029bd 100644 (file)
                                "Unlicense"
                        ]
                },
+               "ResourceFileModulePaths": {
+                       "type": "object",
+                       "description": "Default paths to use for all ResourceLoader file modules",
+                       "additionalProperties": false,
+                       "properties": {
+                               "localBasePath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all local paths, relative to current directory"
+                               },
+                               "remoteExtPath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all remote paths, relative to $wgExtensionAssetsPath"
+                               },
+                               "remoteSkinPath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all remote paths, relative to $wgStylePath"
+                               }
+                       }
+               },
                "ResourceLoaderModules": {
                        "type": "object",
                        "description": "ResourceLoader modules to register",
index 51470b6..f44cac0 100644 (file)
@@ -201,7 +201,12 @@ function wfForbidden( $msg1, $msg2 ) {
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );
        echo <<<ENDS
+<!DOCTYPE html>
 <html>
+<head>
+<meta charset="UTF-8" />
+<title>$msgHdr</title>
+</head>
 <body>
 <h1>$msgHdr</h1>
 <p>$detailMsg</p>
index 9d1bbc0..36028ea 100644 (file)
@@ -45,10 +45,10 @@ class WikiImporter {
 
        /**
         * Creates an ImportXMLReader drawing from the source provided
-        * @param ImportStreamSource $source
+        * @param ImportSource $source
         * @param Config $config
         */
-       function __construct( ImportStreamSource $source, Config $config = null ) {
+       function __construct( ImportSource $source, Config $config = null ) {
                $this->reader = new XMLReader();
                if ( !$config ) {
                        wfDeprecated( __METHOD__ . ' without a Config instance', '1.25' );
@@ -967,10 +967,10 @@ class UploadSourceAdapter {
        private $mPosition;
 
        /**
-        * @param ImportStreamSource $source
+        * @param ImportSource $source
         * @return string
         */
-       static function registerSource( ImportStreamSource $source ) {
+       static function registerSource( ImportSource $source ) {
                $id = wfRandomString();
 
                self::$sourceRegistrations[$id] = $source;
@@ -1708,6 +1708,30 @@ class WikiRevision {
 
 }
 
+/**
+ * Source interface for XML import.
+ */
+interface ImportSource {
+
+       /**
+        * Indicates whether the end of the input has been reached.
+        * Will return true after a finite number of calls to readChunk.
+        *
+        * @return bool true if there is no more input, false otherwise.
+        */
+       function atEnd();
+
+       /**
+        * Return a chunk of the input, as a (possibly empty) string.
+        * When the end of input is reached, readChunk() returns false.
+        * If atEnd() returns false, readChunk() will return a string.
+        * If atEnd() returns true, readChunk() will return false.
+        *
+        * @return bool|string
+        */
+       function readChunk();
+}
+
 /**
  * Used for importing XML dumps where the content of the dump is in a string.
  * This class is ineffecient, and should only be used for small dumps.
@@ -1715,7 +1739,7 @@ class WikiRevision {
  *
  * @ingroup SpecialPage
  */
-class ImportStringSource {
+class ImportStringSource implements ImportSource {
        function __construct( $string ) {
                $this->mString = $string;
                $this->mRead = false;
@@ -1744,7 +1768,7 @@ class ImportStringSource {
  * Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
  * @ingroup SpecialPage
  */
-class ImportStreamSource {
+class ImportStreamSource implements ImportSource {
        function __construct( $handle ) {
                $this->mHandle = $handle;
        }
index 93a37cb..49437f4 100644 (file)
  *
  * @since 1.17
  */
-class Message {
+class Message implements MessageSpecifier {
 
        /**
         * In which language to get this message. True, which is the default,
@@ -276,7 +276,7 @@ class Message {
         * Returns the message key.
         *
         * If a list of multiple possible keys was supplied to the constructor, this method may
-        * return any of these keys. After the message ahs been fetched, this method will return
+        * return any of these keys. After the message has been fetched, this method will return
         * the key that was actually used to fetch the message.
         *
         * @since 1.21
index fb267bd..61a0047 100644 (file)
  * so that a lack of error-handling will be explicit.
  */
 class Status {
-       /** @var bool */
-       public $ok = true;
+       /** @var StatusValue */
+       protected $sv;
 
        /** @var mixed */
        public $value;
-
-       /** Counters for batch operations */
-       /** @var int */
+       /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+       public $success = array();
+       /** @var int Counter for batch operations */
        public $successCount = 0;
-
-       /** @var int */
+       /** @var int Counter for batch operations */
        public $failCount = 0;
 
-       /** Array to indicate which items of the batch operations were successful */
-       /** @var array */
-       public $success = array();
-
-       /** @var array */
-       public $errors = array();
-
        /** @var callable */
        public $cleanCallback = false;
 
+       /**
+        * @param StatusValue $sv [optional]
+        */
+       public function __construct( StatusValue $sv = null ) {
+               $this->sv = ( $sv === null ) ? new StatusValue() : $sv;
+               // B/C field aliases
+               $this->value =& $this->sv->value;
+               $this->successCount =& $this->sv->successCount;
+               $this->failCount =& $this->sv->failCount;
+               $this->success =& $this->sv->success;
+       }
+
+       /**
+        * Succinct helper method to wrap a StatusValue
+        *
+        * This is is useful when formatting StatusValue objects:
+        * <code>
+        *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
+        * </code>
+        *
+        * @param StatusValue|Status $sv
+        * @return Status
+        */
+       public static function wrap( $sv ) {
+               return $sv instanceof Status ? $sv : new self( $sv );
+       }
+
        /**
         * Factory function for fatal errors
         *
         * @param string|Message $message Message name or object
         * @return Status
         */
-       static function newFatal( $message /*, parameters...*/ ) {
-               $params = func_get_args();
-               $result = new self;
-               call_user_func_array( array( &$result, 'error' ), $params );
-               $result->ok = false;
-               return $result;
+       public static function newFatal( $message /*, parameters...*/ ) {
+               return new self( call_user_func_array(
+                       array( 'StatusValue', 'newFatal' ), func_get_args()
+               ) );
        }
 
        /**
@@ -81,10 +98,11 @@ class Status {
         * @param mixed $value
         * @return Status
         */
-       static function newGood( $value = null ) {
-               $result = new self;
-               $result->value = $value;
-               return $result;
+       public static function newGood( $value = null ) {
+               $sv = new StatusValue();
+               $sv->value = $value;
+
+               return new self( $sv );
        }
 
        /**
@@ -94,8 +112,7 @@ class Status {
         * @param mixed $value
         */
        public function setResult( $ok, $value = null ) {
-               $this->ok = $ok;
-               $this->value = $value;
+               $this->sv->setResult( $ok, $value );
        }
 
        /**
@@ -105,7 +122,7 @@ class Status {
         * @return bool
         */
        public function isGood() {
-               return $this->ok && !$this->errors;
+               return $this->sv->isGood();
        }
 
        /**
@@ -114,7 +131,7 @@ class Status {
         * @return bool
         */
        public function isOK() {
-               return $this->ok;
+               return $this->sv->isOK();
        }
 
        /**
@@ -123,11 +140,7 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function warning( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'warning',
-                       'message' => $message,
-                       'params' => $params );
+               call_user_func_array( array( $this->sv, 'warning' ), func_get_args() );
        }
 
        /**
@@ -137,11 +150,7 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function error( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
+               call_user_func_array( array( $this->sv, 'error' ), func_get_args() );
        }
 
        /**
@@ -151,35 +160,14 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function fatal( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
-               $this->ok = false;
-       }
-
-       /**
-        * Don't save the callback when serializing, because Closures can't be
-        * serialized and we're going to clear it in __wakeup anyway.
-        */
-       public function __sleep() {
-               $keys = array_keys( get_object_vars( $this ) );
-               return array_diff( $keys, array( 'cleanCallback' ) );
-       }
-
-       /**
-        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
-        */
-       public function __wakeup() {
-               $this->cleanCallback = false;
+               call_user_func_array( array( $this->sv, 'fatal' ), func_get_args() );
        }
 
        /**
         * @param array $params
         * @return array
         */
-       protected function cleanParams( $params ) {
+       protected function cleanParams( array $params ) {
                if ( !$this->cleanCallback ) {
                        return $params;
                }
@@ -199,24 +187,26 @@ class Status {
         * @return string
         */
        public function getWikiText( $shortContext = false, $longContext = false ) {
-               if ( count( $this->errors ) == 0 ) {
-                       if ( $this->ok ) {
-                               $this->fatal( 'internalerror_info',
+               $rawErrors = $this->sv->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->sv->isOK() ) {
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->fatal( 'internalerror_info',
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
+                       $rawErrors = $this->sv->getErrors(); // just added a fatal
                }
-               if ( count( $this->errors ) == 1 ) {
-                       $s = $this->getErrorMessage( $this->errors[0] )->plain();
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0] )->plain();
                        if ( $shortContext ) {
                                $s = wfMessage( $shortContext, $s )->plain();
                        } elseif ( $longContext ) {
                                $s = wfMessage( $longContext, "* $s\n" )->plain();
                        }
                } else {
-                       $errors = $this->getErrorMessageArray( $this->errors );
+                       $errors = $this->getErrorMessageArray( $rawErrors );
                        foreach ( $errors as &$error ) {
                                $error = $error->plain();
                        }
@@ -241,17 +231,19 @@ class Status {
         * @return Message
         */
        public function getMessage( $shortContext = false, $longContext = false ) {
-               if ( count( $this->errors ) == 0 ) {
-                       if ( $this->ok ) {
-                               $this->fatal( 'internalerror_info',
+               $rawErrors = $this->sv->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->sv->isOK() ) {
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->fatal( 'internalerror_info',
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
+                       $rawErrors = $this->sv->getErrors(); // just added a fatal
                }
-               if ( count( $this->errors ) == 1 ) {
-                       $s = $this->getErrorMessage( $this->errors[0] );
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0] );
                        if ( $shortContext ) {
                                $s = wfMessage( $shortContext, $s );
                        } elseif ( $longContext ) {
@@ -260,7 +252,7 @@ class Status {
                                $s = wfMessage( $longContext, $wrapper );
                        }
                } else {
-                       $msgs = $this->getErrorMessageArray( $this->errors );
+                       $msgs = $this->getErrorMessageArray( $rawErrors );
                        $msgCount = count( $msgs );
 
                        if ( $shortContext ) {
@@ -339,13 +331,7 @@ class Status {
         * @param bool $overwriteValue Whether to override the "value" member
         */
        public function merge( $other, $overwriteValue = false ) {
-               $this->errors = array_merge( $this->errors, $other->errors );
-               $this->ok = $this->ok && $other->ok;
-               if ( $overwriteValue ) {
-                       $this->value = $other->value;
-               }
-               $this->successCount += $other->successCount;
-               $this->failCount += $other->failCount;
+               $this->sv->merge( $other->sv, $overwriteValue );
        }
 
        /**
@@ -353,9 +339,10 @@ class Status {
         *
         * @return array A list in which each entry is an array with a message key as its first element.
         *         The remaining array elements are the message parameters.
+        * @deprecated 1.25
         */
        public function getErrorsArray() {
-               return $this->getStatusArray( "error" );
+               return $this->getStatusArray( 'error' );
        }
 
        /**
@@ -363,21 +350,26 @@ class Status {
         *
         * @return array A list in which each entry is an array with a message key as its first element.
         *         The remaining array elements are the message parameters.
+        * @deprecated 1.25
         */
        public function getWarningsArray() {
-               return $this->getStatusArray( "warning" );
+               return $this->getStatusArray( 'warning' );
        }
 
        /**
         * Returns a list of status messages of the given type (or all if false)
+        *
+        * @note: this handles RawMessage poorly
+        *
         * @param string $type
         * @return array
         */
        protected function getStatusArray( $type = false ) {
                $result = array();
-               foreach ( $this->errors as $error ) {
+
+               foreach ( $this->sv->getErrors() as $error ) {
                        if ( $type === false || $error['type'] === $type ) {
-                               if ( $error['message'] instanceof Message ) {
+                               if ( $error['message'] instanceof MessageSpecifier ) {
                                        $result[] = array_merge(
                                                array( $error['message']->getKey() ),
                                                $error['message']->getParams()
@@ -402,13 +394,7 @@ class Status {
         * @return array
         */
        public function getErrorsByType( $type ) {
-               $result = array();
-               foreach ( $this->errors as $error ) {
-                       if ( $error['type'] === $type ) {
-                               $result[] = $error;
-                       }
-               }
-               return $result;
+               return $this->sv->getErrorsByType( $type );
        }
 
        /**
@@ -419,19 +405,7 @@ class Status {
         * @return bool
         */
        public function hasMessage( $message ) {
-               if ( $message instanceof Message ) {
-                       $message = $message->getKey();
-               }
-               foreach ( $this->errors as $error ) {
-                       if ( $error['message'] instanceof Message
-                               && $error['message']->getKey() === $message
-                       ) {
-                               return true;
-                       } elseif ( $error['message'] === $message ) {
-                               return true;
-                       }
-               }
-               return false;
+               return $this->sv->hasMessage( $message );
        }
 
        /**
@@ -446,61 +420,67 @@ class Status {
         * @return bool Return true if the replacement was done, false otherwise.
         */
        public function replaceMessage( $source, $dest ) {
-               $replaced = false;
-               foreach ( $this->errors as $index => $error ) {
-                       if ( $error['message'] === $source ) {
-                               $this->errors[$index]['message'] = $dest;
-                               $replaced = true;
-                       }
-               }
-               return $replaced;
+               return $this->sv->replaceMessage( $source, $dest );
        }
 
        /**
         * @return mixed
         */
        public function getValue() {
-               return $this->value;
+               return $this->sv->getValue();
        }
 
        /**
-        * @return string
+        * Backwards compatibility logic
+        *
+        * @param string $name
         */
-       public function __toString() {
-               $status = $this->isOK() ? "OK" : "Error";
-               if ( count( $this->errors ) ) {
-                       $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
-               } else {
-                       $errorcount = "no errors detected";
+       function __get( $name ) {
+               if ( $name === 'ok' ) {
+                       return $this->sv->getOK();
+               } elseif ( $name === 'errors' ) {
+                       return $this->sv->getErrors();
                }
-               if ( isset( $this->value ) ) {
-                       $valstr = gettype( $this->value ) . " value set";
-                       if ( is_object( $this->value ) ) {
-                               $valstr .= "\"" . get_class( $this->value ) . "\" instance";
-                       }
+               throw new Exception( "Cannot get '$name' property." );
+       }
+
+       /**
+        * Backwards compatibility logic
+        *
+        * @param string $name
+        * @param mixed $value
+        */
+       function __set( $name, $value ) {
+               if ( $name === 'ok' ) {
+                       $this->sv->setOK( $value );
+               } elseif ( !property_exists( $this, $name ) ) {
+                       // Caller is using undeclared ad-hoc properties
+                       $this->$name = $value;
                } else {
-                       $valstr = "no value set";
+                       throw new Exception( "Cannot set '$name' property." );
                }
-               $out = sprintf( "<%s, %s, %s>",
-                       $status,
-                       $errorcount,
-                       $valstr
-               );
-               if ( count( $this->errors ) > 0 ) {
-                       $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
-                       $i = 1;
-                       $out .= "\n";
-                       $out .= $hdr;
-                       foreach ( $this->getStatusArray() as $stat ) {
-                               $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
-                                       $i,
-                                       $stat[0],
-                                       implode( " ", array_slice( $stat, 1 ) )
-                               );
-                               $i += 1;
-                       }
-                       $out .= $hdr;
-               };
-               return $out;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return $this->sv->__toString();
+       }
+
+       /**
+        * Don't save the callback when serializing, because Closures can't be
+        * serialized and we're going to clear it in __wakeup anyway.
+        */
+       function __sleep() {
+               $keys = array_keys( get_object_vars( $this ) );
+               return array_diff( $keys, array( 'cleanCallback' ) );
+       }
+
+       /**
+        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
+        */
+       function __wakeup() {
+               $this->cleanCallback = false;
        }
 }
index 58245a5..0393416 100644 (file)
@@ -1681,23 +1681,26 @@ class FileRepo {
         * Create a new fatal error
         *
         * @param string $message
-        * @return FileRepoStatus
+        * @return Status
         */
        public function newFatal( $message /*, parameters...*/ ) {
-               $params = func_get_args();
-               array_unshift( $params, $this );
+               $status = call_user_func_array( array( 'Status', 'newFatal' ), func_get_args() );
+               $status->cleanCallback = $this->getErrorCleanupFunction();
 
-               return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+               return $status;
        }
 
        /**
         * Create a new good result
         *
         * @param null|string $value
-        * @return FileRepoStatus
+        * @return Status
         */
        public function newGood( $value = null ) {
-               return FileRepoStatus::newGood( $this, $value );
+               $status = Status::newGood( $this, $value );
+               $status->cleanCallback = $this->getErrorCleanupFunction();
+
+               return $status;
        }
 
        /**
index 56848df..daf26ba 100644 (file)
 /**
  * Generic operation result class for FileRepo-related operations
  * @ingroup FileRepo
+ * @deprecated 1.25
  */
-class FileRepoStatus extends Status {
-       /**
-        * Factory function for fatal errors
-        *
-        * @param FileRepo $repo
-        * @return FileRepoStatus
-        */
-       static function newFatal( $repo /*, parameters...*/ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $result = new self( $repo );
-               call_user_func_array( array( &$result, 'error' ), $params );
-               $result->ok = false;
-
-               return $result;
-       }
-
-       /**
-        * @param FileRepo|bool $repo Default: false
-        * @param mixed $value
-        * @return FileRepoStatus
-        */
-       static function newGood( $repo = false, $value = null ) {
-               $result = new self( $repo );
-               $result->value = $value;
-
-               return $result;
-       }
-
-       /**
-        * @param bool|FileRepo $repo
-        */
-       function __construct( $repo = false ) {
-               if ( $repo ) {
-                       $this->cleanCallback = $repo->getErrorCleanupFunction();
-               }
-       }
-}
+class FileRepoStatus extends Status {}
index abfdc8c..9368fbf 100644 (file)
@@ -24,7 +24,7 @@
 /**
  * Class to handle job queues stored in Redis
  *
- * This is faster, less resource intensive, queue that JobQueueDB.
+ * This is a faster and less resource-intensive job queue than JobQueueDB.
  * All data for a queue using this class is placed into one redis server.
  *
  * There are eight main redis keys used to track jobs:
@@ -49,7 +49,7 @@
  *
  * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
  * Additionally, it should be noted that redis has different persistence modes, such
- * as rdb snapshots, journaling, and no persistent. Appropriate configuration should be
+ * as rdb snapshots, journaling, and no persistence. Appropriate configuration should be
  * made on the servers based on what queues are using it and what tolerance they have.
  *
  * @ingroup JobQueue
diff --git a/includes/libs/MessageSpecifier.php b/includes/libs/MessageSpecifier.php
new file mode 100644 (file)
index 0000000..b417f29
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+interface MessageSpecifier {
+       /**
+        * Returns the message key
+        *
+        * If a list of multiple possible keys was supplied to the constructor, this method may
+        * return any of these keys. After the message has been fetched, this method will return
+        * the key that was actually used to fetch the message.
+        *
+        * @return string
+        */
+       public function getKey();
+
+       /**
+        * Returns the message parameters
+        *
+        * @return array
+        */
+       public function getParams();
+}
diff --git a/includes/libs/StatusValue.php b/includes/libs/StatusValue.php
new file mode 100644 (file)
index 0000000..3c2dd40
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Generic operation result class
+ * Has warning/error list, boolean status and arbitrary value
+ *
+ * "Good" means the operation was completed with no warnings or errors.
+ *
+ * "OK" means the operation was partially or wholly completed.
+ *
+ * An operation which is not OK should have errors so that the user can be
+ * informed as to what went wrong. Calling the fatal() function sets an error
+ * message and simultaneously switches off the OK flag.
+ *
+ * The recommended pattern for Status objects is to return a StatusValue
+ * unconditionally, i.e. both on success and on failure -- so that the
+ * developer of the calling code is reminded that the function can fail, and
+ * so that a lack of error-handling will be explicit.
+ *
+ * The use of Message objects should be avoided when serializability is needed.
+ *
+ * @since 1.25
+ */
+class StatusValue {
+       /** @var bool */
+       protected $ok = true;
+       /** @var array */
+       protected $errors = array();
+
+       /** @var mixed */
+       public $value;
+       /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+       public $success = array();
+       /** @var int Counter for batch operations */
+       public $successCount = 0;
+       /** @var int Counter for batch operations */
+       public $failCount = 0;
+
+       /**
+        * Factory function for fatal errors
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        * @return Status
+        */
+       public static function newFatal( $message /*, parameters...*/ ) {
+               $params = func_get_args();
+               $result = new static();
+               call_user_func_array( array( &$result, 'fatal' ), $params );
+               return $result;
+       }
+
+       /**
+        * Factory function for good results
+        *
+        * @param mixed $value
+        * @return Status
+        */
+       public static function newGood( $value = null ) {
+               $result = new static();
+               $result->value = $value;
+               return $result;
+       }
+
+       /**
+        * Returns whether the operation completed and didn't have any error or
+        * warnings
+        *
+        * @return bool
+        */
+       public function isGood() {
+               return $this->ok && !$this->errors;
+       }
+
+       /**
+        * Returns whether the operation completed
+        *
+        * @return bool
+        */
+       public function isOK() {
+               return $this->ok;
+       }
+
+       /**
+        * @return mixed
+        */
+       public function getValue() {
+               return $this->value;
+       }
+
+       /**
+        * Get the list of errors
+        *
+        * Each error is a (message:string or MessageSpecifier,params:array) map
+        *
+        * @return array
+        */
+       public function getErrors() {
+               return $this->errors;
+       }
+
+       /**
+        * Change operation status
+        *
+        * @param bool $ok
+        */
+       public function setOK( $ok ) {
+               $this->ok = $ok;
+       }
+
+       /**
+        * Change operation resuklt
+        *
+        * @param bool $ok Whether the operation completed
+        * @param mixed $value
+        */
+       public function setResult( $ok, $value = null ) {
+               $this->ok = $ok;
+               $this->value = $value;
+       }
+
+       /**
+        * Add a new warning
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function warning( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'warning',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+       }
+
+       /**
+        * Add an error, do not set fatal flag
+        * This can be used for non-fatal errors
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function error( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'error',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+       }
+
+       /**
+        * Add an error and set OK to false, indicating that the operation
+        * as a whole was fatal
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function fatal( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'error',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+               $this->ok = false;
+       }
+
+       /**
+        * Merge another status object into this one
+        *
+        * @param Status $other Other Status object
+        * @param bool $overwriteValue Whether to override the "value" member
+        */
+       public function merge( $other, $overwriteValue = false ) {
+               $this->errors = array_merge( $this->errors, $other->errors );
+               $this->ok = $this->ok && $other->ok;
+               if ( $overwriteValue ) {
+                       $this->value = $other->value;
+               }
+               $this->successCount += $other->successCount;
+               $this->failCount += $other->failCount;
+       }
+
+       /**
+        * Returns a list of status messages of the given type
+        *
+        * Each entry is a map of (message:string or MessageSpecifier,params:array))
+        *
+        * @param string $type
+        * @return array
+        */
+       public function getErrorsByType( $type ) {
+               $result = array();
+               foreach ( $this->errors as $error ) {
+                       if ( $error['type'] === $type ) {
+                               $result[] = $error;
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * Returns true if the specified message is present as a warning or error
+        *
+        * @param string|MessageSpecifier $message Message key or object to search for
+        *
+        * @return bool
+        */
+       public function hasMessage( $message ) {
+               if ( $message instanceof MessageSpecifier ) {
+                       $message = $message->getKey();
+               }
+               foreach ( $this->errors as $error ) {
+                       if ( $error['message'] instanceof MessageSpecifier
+                               && $error['message']->getKey() === $message
+                       ) {
+                               return true;
+                       } elseif ( $error['message'] === $message ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * If the specified source message exists, replace it with the specified
+        * destination message, but keep the same parameters as in the original error.
+        *
+        * Note, due to the lack of tools for comparing IStatusMessage objects, this
+        * function will not work when using such an object as the search parameter.
+        *
+        * @param IStatusMessage|string $source Message key or object to search for
+        * @param IStatusMessage|string $dest Replacement message key or object
+        * @return bool Return true if the replacement was done, false otherwise.
+        */
+       public function replaceMessage( $source, $dest ) {
+               $replaced = false;
+
+               foreach ( $this->errors as $index => $error ) {
+                       if ( $error['message'] === $source ) {
+                               $this->errors[$index]['message'] = $dest;
+                               $replaced = true;
+                       }
+               }
+
+               return $replaced;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               $status = $this->isOK() ? "OK" : "Error";
+               if ( count( $this->errors ) ) {
+                       $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
+               } else {
+                       $errorcount = "no errors detected";
+               }
+               if ( isset( $this->value ) ) {
+                       $valstr = gettype( $this->value ) . " value set";
+                       if ( is_object( $this->value ) ) {
+                               $valstr .= "\"" . get_class( $this->value ) . "\" instance";
+                       }
+               } else {
+                       $valstr = "no value set";
+               }
+               $out = sprintf( "<%s, %s, %s>",
+                       $status,
+                       $errorcount,
+                       $valstr
+               );
+               if ( count( $this->errors ) > 0 ) {
+                       $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
+                       $i = 1;
+                       $out .= "\n";
+                       $out .= $hdr;
+                       foreach ( $this->errors as $error ) {
+                               if ( $error['message'] instanceof MessageSpecifier ) {
+                                       $key = $error['message']->getKey();
+                                       $params = $error['message']->getParams();
+                               } elseif ( $error['params'] ) {
+                                       $key = $error['message'];
+                                       $params = $error['params'];
+                               } else {
+                                       $key = $error['message'];
+                                       $params = array();
+                               }
+
+                               $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
+                                       $i,
+                                       $key,
+                                       implode( " ", $params )
+                               );
+                               $i += 1;
+                       }
+                       $out .= $hdr;
+               }
+
+               return $out;
+       }
+}
index 1462840..bf42aba 100644 (file)
@@ -185,11 +185,21 @@ class ExtensionProcessor implements Processor {
        }
 
        protected function extractResourceLoaderModules( $dir, array $info ) {
+               $defaultPaths = isset( $info['ResourceFileModulePaths'] )
+                       ? $info['ResourceFileModulePaths']
+                       : false;
+               if ( isset( $defaultPaths['localBasePath'] ) ) {
+                       $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
+               }
+
                if ( isset( $info['ResourceModules'] ) ) {
                        foreach ( $info['ResourceModules'] as $name => $data ) {
                                if ( isset( $data['localBasePath'] ) ) {
                                        $data['localBasePath'] = "$dir/{$data['localBasePath']}";
                                }
+                               if ( $defaultPaths && !isset( $data['class'] ) ) {
+                                       $data += $defaultPaths;
+                               }
                                $this->globals['wgResourceModules'][$name] = $data;
                        }
                }
index 9b98b20..af14bb3 100644 (file)
@@ -1207,7 +1207,13 @@ abstract class Maintenance {
                }
 
                if ( $isatty && function_exists( 'readline' ) ) {
-                       return readline( $prompt );
+                       $resp = readline( $prompt );
+                       if ( $resp === null ) {
+                               // Workaround for https://github.com/facebook/hhvm/issues/4776
+                               return false;
+                       } else {
+                               return $resp;
+                       }
                } else {
                        if ( $isatty ) {
                                $st = self::readlineEmulation( $prompt );
index a0dee3c..76bc982 100644 (file)
@@ -155,12 +155,36 @@ class ConvertExtensionToRegistration extends Maintenance {
        }
 
        protected function handleResourceModules( $realName, $value ) {
+               $defaults = array();
+               $remote = $this->hasOption( 'skin' ) ? 'remoteSkinPath' : 'remoteExtPath';
                foreach ( $value as $name => $data ) {
                        if ( isset( $data['localBasePath'] ) ) {
                                $data['localBasePath'] = $this->stripPath( $data['localBasePath'], $this->dir );
+                               if ( !$defaults ) {
+                                       $defaults['localBasePath'] = $data['localBasePath'];
+                                       unset( $data['localBasePath'] );
+                                       if ( isset( $data[$remote] ) ) {
+                                               $defaults[$remote] = $data[$remote];
+                                               unset( $data[$remote] );
+                                       }
+                               } else {
+                                       if ( $data['localBasePath'] === $defaults['localBasePath'] ) {
+                                               unset( $data['localBasePath'] );
+                                       }
+                                       if ( isset( $data[$remote] ) && isset( $defaults[$remote] )
+                                               && $data[$remote] === $defaults[$remote]
+                                       ) {
+                                               unset( $data[$remote] );
+                                       }
+                               }
                        }
+
+
                        $this->json[$realName][$name] = $data;
                }
+               if ( $defaults ) {
+                       $this->json['ResourceFileModulePaths'] = $defaults;
+               }
        }
 }
 
index 6bbcf39..4ef12ba 100644 (file)
@@ -5,17 +5,17 @@
     "test": "grunt test"
   },
   "devDependencies": {
-    "grunt": "0.4.2",
+    "grunt": "0.4.5",
     "grunt-banana-checker": "0.2.0",
-    "grunt-contrib-jshint": "0.10.0",
+    "grunt-contrib-jshint": "0.11.0",
     "grunt-contrib-watch": "0.6.1",
-    "grunt-jscs": "0.8.1",
+    "grunt-jscs": "1.5.0",
     "grunt-jsonlint": "1.0.4",
-    "grunt-karma": "0.9.0",
+    "grunt-karma": "0.10.1",
     "karma": "0.12.31",
     "karma-chrome-launcher": "0.1.7",
-    "karma-firefox-launcher": "0.1.3",
+    "karma-firefox-launcher": "0.1.4",
     "karma-qunit": "0.1.4",
-    "qunitjs": "1.15.0"
+    "qunitjs": "1.17.1"
   }
 }
index 4e3fb5a..f172cfb 100644 (file)
@@ -36,7 +36,7 @@ header( 'Content-Type: text/html; charset=utf-8' );
 <!DOCTYPE html>
 <html>
 <head>
-       <meta charset="UTF-8">
+       <meta charset="UTF-8" />
        <title>Profiling data</title>
        <style>
                /* noc.wikimedia.org/base.css */
@@ -422,7 +422,7 @@ if ( isset( $_REQUEST['filter'] ) ) {
        ?>
        </tbody>
 </table>
-<hr>
+<hr />
 <p>Total time: <code><?php printf( '%5.02f', profile_point::$totaltime ); ?></code></p>
 
 <p>Total memory: <code><?php printf( '%5.02f', profile_point::$totalmemory / 1024 ); ?></code></p>
index 835e33f..233485c 100644 (file)
@@ -266,8 +266,8 @@ return array(
                'targets' => array( 'desktop', 'mobile' ),
        ),
        'jquery.qunit' => array(
-               'scripts' => 'resources/lib/jquery/jquery.qunit.js',
-               'styles' => 'resources/lib/jquery/jquery.qunit.css',
+               'scripts' => 'resources/lib/qunitjs/qunit.js',
+               'styles' => 'resources/lib/qunitjs/qunit.css',
                'position' => 'top',
                'targets' => array( 'desktop', 'mobile' ),
        ),
@@ -765,7 +765,10 @@ return array(
        /* MediaWiki */
 
        'mediawiki' => array(
-               'scripts' => 'resources/src/mediawiki/mediawiki.js',
+               'scripts' => array(
+                       'resources/src/mediawiki/mediawiki.js',
+                       'resources/src/mediawiki/mediawiki.startUp.js',
+               ),
                'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
                'raw' => true,
                'targets' => array( 'desktop', 'mobile' ),
diff --git a/resources/lib/jquery/jquery.qunit.css b/resources/lib/jquery/jquery.qunit.css
deleted file mode 100644 (file)
index 385a1ce..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-/*!
- * QUnit 1.16.0
- * http://qunitjs.com/
- *
- * Copyright 2006, 2014 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: 2014-12-03T16:32Z
- */
-
-/** Font Family and Sizes */
-
-#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
-       font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
-}
-
-#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
-#qunit-tests { font-size: smaller; }
-
-
-/** Resets */
-
-#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
-       margin: 0;
-       padding: 0;
-}
-
-
-/** Header */
-
-#qunit-header {
-       padding: 0.5em 0 0.5em 1em;
-
-       color: #8699A4;
-       background-color: #0D3349;
-
-       font-size: 1.5em;
-       line-height: 1em;
-       font-weight: 400;
-
-       border-radius: 5px 5px 0 0;
-}
-
-#qunit-header a {
-       text-decoration: none;
-       color: #C2CCD1;
-}
-
-#qunit-header a:hover,
-#qunit-header a:focus {
-       color: #FFF;
-}
-
-#qunit-testrunner-toolbar label {
-       display: inline-block;
-       padding: 0 0.5em 0 0.1em;
-}
-
-#qunit-banner {
-       height: 5px;
-}
-
-#qunit-testrunner-toolbar {
-       padding: 0.5em 1em 0.5em 1em;
-       color: #5E740B;
-       background-color: #EEE;
-       overflow: hidden;
-}
-
-#qunit-userAgent {
-       padding: 0.5em 1em 0.5em 1em;
-       background-color: #2B81AF;
-       color: #FFF;
-       text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
-}
-
-#qunit-modulefilter-container {
-       float: right;
-}
-
-/** Tests: Pass/Fail */
-
-#qunit-tests {
-       list-style-position: inside;
-}
-
-#qunit-tests li {
-       padding: 0.4em 1em 0.4em 1em;
-       border-bottom: 1px solid #FFF;
-       list-style-position: inside;
-}
-
-#qunit-tests > li {
-       display: none;
-}
-
-#qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail {
-       display: list-item;
-}
-
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
-       display: none;
-}
-
-#qunit-tests li strong {
-       cursor: pointer;
-}
-
-#qunit-tests li.skipped strong {
-       cursor: default;
-}
-
-#qunit-tests li a {
-       padding: 0.5em;
-       color: #C2CCD1;
-       text-decoration: none;
-}
-#qunit-tests li a:hover,
-#qunit-tests li a:focus {
-       color: #000;
-}
-
-#qunit-tests li .runtime {
-       float: right;
-       font-size: smaller;
-}
-
-.qunit-assert-list {
-       margin-top: 0.5em;
-       padding: 0.5em;
-
-       background-color: #FFF;
-
-       border-radius: 5px;
-}
-
-.qunit-collapsed {
-       display: none;
-}
-
-#qunit-tests table {
-       border-collapse: collapse;
-       margin-top: 0.2em;
-}
-
-#qunit-tests th {
-       text-align: right;
-       vertical-align: top;
-       padding: 0 0.5em 0 0;
-}
-
-#qunit-tests td {
-       vertical-align: top;
-}
-
-#qunit-tests pre {
-       margin: 0;
-       white-space: pre-wrap;
-       word-wrap: break-word;
-}
-
-#qunit-tests del {
-       background-color: #E0F2BE;
-       color: #374E0C;
-       text-decoration: none;
-}
-
-#qunit-tests ins {
-       background-color: #FFCACA;
-       color: #500;
-       text-decoration: none;
-}
-
-/*** Test Counts */
-
-#qunit-tests b.counts                       { color: #000; }
-#qunit-tests b.passed                       { color: #5E740B; }
-#qunit-tests b.failed                       { color: #710909; }
-
-#qunit-tests li li {
-       padding: 5px;
-       background-color: #FFF;
-       border-bottom: none;
-       list-style-position: inside;
-}
-
-/*** Passing Styles */
-
-#qunit-tests li li.pass {
-       color: #3C510C;
-       background-color: #FFF;
-       border-left: 10px solid #C6E746;
-}
-
-#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
-#qunit-tests .pass .test-name               { color: #366097; }
-
-#qunit-tests .pass .test-actual,
-#qunit-tests .pass .test-expected           { color: #999; }
-
-#qunit-banner.qunit-pass                    { background-color: #C6E746; }
-
-/*** Failing Styles */
-
-#qunit-tests li li.fail {
-       color: #710909;
-       background-color: #FFF;
-       border-left: 10px solid #EE5757;
-       white-space: pre;
-}
-
-#qunit-tests > li:last-child {
-       border-radius: 0 0 5px 5px;
-}
-
-#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
-#qunit-tests .fail .test-name,
-#qunit-tests .fail .module-name             { color: #000; }
-
-#qunit-tests .fail .test-actual             { color: #EE5757; }
-#qunit-tests .fail .test-expected           { color: #008000; }
-
-#qunit-banner.qunit-fail                    { background-color: #EE5757; }
-
-/*** Skipped tests */
-
-#qunit-tests .skipped {
-       background-color: #EBECE9;
-}
-
-#qunit-tests .qunit-skipped-label {
-       background-color: #F4FF77;
-       display: inline-block;
-       font-style: normal;
-       color: #366097;
-       line-height: 1.8em;
-       padding: 0 0.5em;
-       margin: -0.4em 0.4em -0.4em 0;
-}
-
-/** Result */
-
-#qunit-testresult {
-       padding: 0.5em 1em 0.5em 1em;
-
-       color: #2B81AF;
-       background-color: #D2E0E6;
-
-       border-bottom: 1px solid #FFF;
-}
-#qunit-testresult .module-name {
-       font-weight: 700;
-}
-
-/** Fixture */
-
-#qunit-fixture {
-       position: absolute;
-       top: -10000px;
-       left: -10000px;
-       width: 1000px;
-       height: 1000px;
-}
diff --git a/resources/lib/jquery/jquery.qunit.js b/resources/lib/jquery/jquery.qunit.js
deleted file mode 100644 (file)
index 82020d4..0000000
+++ /dev/null
@@ -1,2819 +0,0 @@
-/*!
- * QUnit 1.16.0
- * http://qunitjs.com/
- *
- * Copyright 2006, 2014 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: 2014-12-03T16:32Z
- */
-
-(function( window ) {
-
-var QUnit,
-       config,
-       onErrorFnPrev,
-       loggingCallbacks = {},
-       fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
-       toString = Object.prototype.toString,
-       hasOwn = Object.prototype.hasOwnProperty,
-       // Keep a local reference to Date (GH-283)
-       Date = window.Date,
-       now = Date.now || function() {
-               return new Date().getTime();
-       },
-       globalStartCalled = false,
-       runStarted = false,
-       setTimeout = window.setTimeout,
-       clearTimeout = window.clearTimeout,
-       defined = {
-               document: window.document !== undefined,
-               setTimeout: window.setTimeout !== undefined,
-               sessionStorage: (function() {
-                       var x = "qunit-test-string";
-                       try {
-                               sessionStorage.setItem( x, x );
-                               sessionStorage.removeItem( x );
-                               return true;
-                       } catch ( e ) {
-                               return false;
-                       }
-               }())
-       },
-       /**
-        * Provides a normalized error string, correcting an issue
-        * with IE 7 (and prior) where Error.prototype.toString is
-        * not properly implemented
-        *
-        * Based on http://es5.github.com/#x15.11.4.4
-        *
-        * @param {String|Error} error
-        * @return {String} error message
-        */
-       errorString = function( error ) {
-               var name, message,
-                       errorString = error.toString();
-               if ( errorString.substring( 0, 7 ) === "[object" ) {
-                       name = error.name ? error.name.toString() : "Error";
-                       message = error.message ? error.message.toString() : "";
-                       if ( name && message ) {
-                               return name + ": " + message;
-                       } else if ( name ) {
-                               return name;
-                       } else if ( message ) {
-                               return message;
-                       } else {
-                               return "Error";
-                       }
-               } else {
-                       return errorString;
-               }
-       },
-       /**
-        * Makes a clone of an object using only Array or Object as base,
-        * and copies over the own enumerable properties.
-        *
-        * @param {Object} obj
-        * @return {Object} New object with only the own properties (recursively).
-        */
-       objectValues = function( obj ) {
-               var key, val,
-                       vals = QUnit.is( "array", obj ) ? [] : {};
-               for ( key in obj ) {
-                       if ( hasOwn.call( obj, key ) ) {
-                               val = obj[ key ];
-                               vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
-                       }
-               }
-               return vals;
-       };
-
-QUnit = {};
-
-/**
- * Config object: Maintain internal state
- * Later exposed as QUnit.config
- * `config` initialized at top of scope
- */
-config = {
-       // The queue of tests to run
-       queue: [],
-
-       // block until document ready
-       blocking: true,
-
-       // when enabled, show only failing tests
-       // gets persisted through sessionStorage and can be changed in UI via checkbox
-       hidepassed: false,
-
-       // by default, run previously failed tests first
-       // very useful in combination with "Hide passed tests" checked
-       reorder: true,
-
-       // by default, modify document.title when suite is done
-       altertitle: true,
-
-       // by default, scroll to top of the page when suite is done
-       scrolltop: true,
-
-       // when enabled, all tests must call expect()
-       requireExpects: false,
-
-       // add checkboxes that are persisted in the query-string
-       // when enabled, the id is set to `true` as a `QUnit.config` property
-       urlConfig: [
-               {
-                       id: "hidepassed",
-                       label: "Hide passed tests",
-                       tooltip: "Only show tests and assertions that fail. Stored as query-strings."
-               },
-               {
-                       id: "noglobals",
-                       label: "Check for Globals",
-                       tooltip: "Enabling this will test if any test introduces new properties on the " +
-                               "`window` object. Stored as query-strings."
-               },
-               {
-                       id: "notrycatch",
-                       label: "No try-catch",
-                       tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
-                               "exceptions in IE reasonable. Stored as query-strings."
-               }
-       ],
-
-       // Set of all modules.
-       modules: [],
-
-       // The first unnamed module
-       currentModule: {
-               name: "",
-               tests: []
-       },
-
-       callbacks: {}
-};
-
-// Push a loose unnamed module to the modules collection
-config.modules.push( config.currentModule );
-
-// Initialize more QUnit.config and QUnit.urlParams
-(function() {
-       var i, current,
-               location = window.location || { search: "", protocol: "file:" },
-               params = location.search.slice( 1 ).split( "&" ),
-               length = params.length,
-               urlParams = {};
-
-       if ( params[ 0 ] ) {
-               for ( i = 0; i < length; i++ ) {
-                       current = params[ i ].split( "=" );
-                       current[ 0 ] = decodeURIComponent( current[ 0 ] );
-
-                       // allow just a key to turn on a flag, e.g., test.html?noglobals
-                       current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
-                       if ( urlParams[ current[ 0 ] ] ) {
-                               urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
-                       } else {
-                               urlParams[ current[ 0 ] ] = current[ 1 ];
-                       }
-               }
-       }
-
-       QUnit.urlParams = urlParams;
-
-       // String search anywhere in moduleName+testName
-       config.filter = urlParams.filter;
-
-       config.testId = [];
-       if ( urlParams.testId ) {
-
-               // Ensure that urlParams.testId is an array
-               urlParams.testId = [].concat( urlParams.testId );
-               for ( i = 0; i < urlParams.testId.length; i++ ) {
-                       config.testId.push( urlParams.testId[ i ] );
-               }
-       }
-
-       // Figure out if we're running the tests from a server or not
-       QUnit.isLocal = location.protocol === "file:";
-}());
-
-// Root QUnit object.
-// `QUnit` initialized at top of scope
-extend( QUnit, {
-
-       // call on start of module test to prepend name to all tests
-       module: function( name, testEnvironment ) {
-               var currentModule = {
-                       name: name,
-                       testEnvironment: testEnvironment,
-                       tests: []
-               };
-
-               // DEPRECATED: handles setup/teardown functions,
-               // beforeEach and afterEach should be used instead
-               if ( testEnvironment && testEnvironment.setup ) {
-                       testEnvironment.beforeEach = testEnvironment.setup;
-                       delete testEnvironment.setup;
-               }
-               if ( testEnvironment && testEnvironment.teardown ) {
-                       testEnvironment.afterEach = testEnvironment.teardown;
-                       delete testEnvironment.teardown;
-               }
-
-               config.modules.push( currentModule );
-               config.currentModule = currentModule;
-       },
-
-       // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
-       asyncTest: function( testName, expected, callback ) {
-               if ( arguments.length === 2 ) {
-                       callback = expected;
-                       expected = null;
-               }
-
-               QUnit.test( testName, expected, callback, true );
-       },
-
-       test: function( testName, expected, callback, async ) {
-               var test;
-
-               if ( arguments.length === 2 ) {
-                       callback = expected;
-                       expected = null;
-               }
-
-               test = new Test({
-                       testName: testName,
-                       expected: expected,
-                       async: async,
-                       callback: callback
-               });
-
-               test.queue();
-       },
-
-       skip: function( testName ) {
-               var test = new Test({
-                       testName: testName,
-                       skip: true
-               });
-
-               test.queue();
-       },
-
-       // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
-       // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
-       start: function( count ) {
-               var globalStartAlreadyCalled = globalStartCalled;
-
-               if ( !config.current ) {
-                       globalStartCalled = true;
-
-                       if ( runStarted ) {
-                               throw new Error( "Called start() outside of a test context while already started" );
-                       } else if ( globalStartAlreadyCalled || count > 1 ) {
-                               throw new Error( "Called start() outside of a test context too many times" );
-                       } else if ( config.autostart ) {
-                               throw new Error( "Called start() outside of a test context when " +
-                                       "QUnit.config.autostart was true" );
-                       } else if ( !config.pageLoaded ) {
-
-                               // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
-                               config.autostart = true;
-                               return;
-                       }
-               } else {
-
-                       // If a test is running, adjust its semaphore
-                       config.current.semaphore -= count || 1;
-
-                       // Don't start until equal number of stop-calls
-                       if ( config.current.semaphore > 0 ) {
-                               return;
-                       }
-
-                       // throw an Error if start is called more often than stop
-                       if ( config.current.semaphore < 0 ) {
-                               config.current.semaphore = 0;
-
-                               QUnit.pushFailure(
-                                       "Called start() while already started (test's semaphore was 0 already)",
-                                       sourceFromStacktrace( 2 )
-                               );
-                               return;
-                       }
-               }
-
-               resumeProcessing();
-       },
-
-       // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
-       stop: function( count ) {
-
-               // If there isn't a test running, don't allow QUnit.stop() to be called
-               if ( !config.current ) {
-                       throw new Error( "Called stop() outside of a test context" );
-               }
-
-               // If a test is running, adjust its semaphore
-               config.current.semaphore += count || 1;
-
-               pauseProcessing();
-       },
-
-       config: config,
-
-       // Safe object type checking
-       is: function( type, obj ) {
-               return QUnit.objectType( obj ) === type;
-       },
-
-       objectType: function( obj ) {
-               if ( typeof obj === "undefined" ) {
-                       return "undefined";
-               }
-
-               // Consider: typeof null === object
-               if ( obj === null ) {
-                       return "null";
-               }
-
-               var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
-                       type = match && match[ 1 ] || "";
-
-               switch ( type ) {
-                       case "Number":
-                               if ( isNaN( obj ) ) {
-                                       return "nan";
-                               }
-                               return "number";
-                       case "String":
-                       case "Boolean":
-                       case "Array":
-                       case "Date":
-                       case "RegExp":
-                       case "Function":
-                               return type.toLowerCase();
-               }
-               if ( typeof obj === "object" ) {
-                       return "object";
-               }
-               return undefined;
-       },
-
-       url: function( params ) {
-               params = extend( extend( {}, QUnit.urlParams ), params );
-               var key,
-                       querystring = "?";
-
-               for ( key in params ) {
-                       if ( hasOwn.call( params, key ) ) {
-                               querystring += encodeURIComponent( key );
-                               if ( params[ key ] !== true ) {
-                                       querystring += "=" + encodeURIComponent( params[ key ] );
-                               }
-                               querystring += "&";
-                       }
-               }
-               return location.protocol + "//" + location.host +
-                       location.pathname + querystring.slice( 0, -1 );
-       },
-
-       extend: extend,
-
-       load: function() {
-               config.pageLoaded = true;
-
-               // Initialize the configuration options
-               extend( config, {
-                       stats: { all: 0, bad: 0 },
-                       moduleStats: { all: 0, bad: 0 },
-                       started: 0,
-                       updateRate: 1000,
-                       autostart: true,
-                       filter: ""
-               }, true );
-
-               config.blocking = false;
-
-               if ( config.autostart ) {
-                       resumeProcessing();
-               }
-       }
-});
-
-// Register logging callbacks
-(function() {
-       var i, l, key,
-               callbacks = [ "begin", "done", "log", "testStart", "testDone",
-                       "moduleStart", "moduleDone" ];
-
-       function registerLoggingCallback( key ) {
-               var loggingCallback = function( callback ) {
-                       if ( QUnit.objectType( callback ) !== "function" ) {
-                               throw new Error(
-                                       "QUnit logging methods require a callback function as their first parameters."
-                               );
-                       }
-
-                       config.callbacks[ key ].push( callback );
-               };
-
-               // DEPRECATED: This will be removed on QUnit 2.0.0+
-               // Stores the registered functions allowing restoring
-               // at verifyLoggingCallbacks() if modified
-               loggingCallbacks[ key ] = loggingCallback;
-
-               return loggingCallback;
-       }
-
-       for ( i = 0, l = callbacks.length; i < l; i++ ) {
-               key = callbacks[ i ];
-
-               // Initialize key collection of logging callback
-               if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
-                       config.callbacks[ key ] = [];
-               }
-
-               QUnit[ key ] = registerLoggingCallback( key );
-       }
-})();
-
-// `onErrorFnPrev` initialized at top of scope
-// Preserve other handlers
-onErrorFnPrev = window.onerror;
-
-// Cover uncaught exceptions
-// Returning true will suppress the default browser handler,
-// returning false will let it run.
-window.onerror = function( error, filePath, linerNr ) {
-       var ret = false;
-       if ( onErrorFnPrev ) {
-               ret = onErrorFnPrev( error, filePath, linerNr );
-       }
-
-       // Treat return value as window.onerror itself does,
-       // Only do our handling if not suppressed.
-       if ( ret !== true ) {
-               if ( QUnit.config.current ) {
-                       if ( QUnit.config.current.ignoreGlobalErrors ) {
-                               return true;
-                       }
-                       QUnit.pushFailure( error, filePath + ":" + linerNr );
-               } else {
-                       QUnit.test( "global failure", extend(function() {
-                               QUnit.pushFailure( error, filePath + ":" + linerNr );
-                       }, { validTest: true } ) );
-               }
-               return false;
-       }
-
-       return ret;
-};
-
-function done() {
-       var runtime, passed;
-
-       config.autorun = true;
-
-       // Log the last module results
-       if ( config.previousModule ) {
-               runLoggingCallbacks( "moduleDone", {
-                       name: config.previousModule.name,
-                       tests: config.previousModule.tests,
-                       failed: config.moduleStats.bad,
-                       passed: config.moduleStats.all - config.moduleStats.bad,
-                       total: config.moduleStats.all,
-                       runtime: now() - config.moduleStats.started
-               });
-       }
-       delete config.previousModule;
-
-       runtime = now() - config.started;
-       passed = config.stats.all - config.stats.bad;
-
-       runLoggingCallbacks( "done", {
-               failed: config.stats.bad,
-               passed: passed,
-               total: config.stats.all,
-               runtime: runtime
-       });
-}
-
-// Doesn't support IE6 to IE9
-// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
-function extractStacktrace( e, offset ) {
-       offset = offset === undefined ? 4 : offset;
-
-       var stack, include, i;
-
-       if ( e.stacktrace ) {
-
-               // Opera 12.x
-               return e.stacktrace.split( "\n" )[ offset + 3 ];
-       } else if ( e.stack ) {
-
-               // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
-               stack = e.stack.split( "\n" );
-               if ( /^error$/i.test( stack[ 0 ] ) ) {
-                       stack.shift();
-               }
-               if ( fileName ) {
-                       include = [];
-                       for ( i = offset; i < stack.length; i++ ) {
-                               if ( stack[ i ].indexOf( fileName ) !== -1 ) {
-                                       break;
-                               }
-                               include.push( stack[ i ] );
-                       }
-                       if ( include.length ) {
-                               return include.join( "\n" );
-                       }
-               }
-               return stack[ offset ];
-       } else if ( e.sourceURL ) {
-
-               // Safari < 6
-               // exclude useless self-reference for generated Error objects
-               if ( /qunit.js$/.test( e.sourceURL ) ) {
-                       return;
-               }
-
-               // for actual exceptions, this is useful
-               return e.sourceURL + ":" + e.line;
-       }
-}
-
-function sourceFromStacktrace( offset ) {
-       var e = new Error();
-       if ( !e.stack ) {
-               try {
-                       throw e;
-               } catch ( err ) {
-                       // This should already be true in most browsers
-                       e = err;
-               }
-       }
-       return extractStacktrace( e, offset );
-}
-
-function synchronize( callback, last ) {
-       if ( QUnit.objectType( callback ) === "array" ) {
-               while ( callback.length ) {
-                       synchronize( callback.shift() );
-               }
-               return;
-       }
-       config.queue.push( callback );
-
-       if ( config.autorun && !config.blocking ) {
-               process( last );
-       }
-}
-
-function process( last ) {
-       function next() {
-               process( last );
-       }
-       var start = now();
-       config.depth = config.depth ? config.depth + 1 : 1;
-
-       while ( config.queue.length && !config.blocking ) {
-               if ( !defined.setTimeout || config.updateRate <= 0 ||
-                               ( ( now() - start ) < config.updateRate ) ) {
-                       if ( config.current ) {
-
-                               // Reset async tracking for each phase of the Test lifecycle
-                               config.current.usedAsync = false;
-                       }
-                       config.queue.shift()();
-               } else {
-                       setTimeout( next, 13 );
-                       break;
-               }
-       }
-       config.depth--;
-       if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
-               done();
-       }
-}
-
-function begin() {
-       var i, l,
-               modulesLog = [];
-
-       // If the test run hasn't officially begun yet
-       if ( !config.started ) {
-
-               // Record the time of the test run's beginning
-               config.started = now();
-
-               verifyLoggingCallbacks();
-
-               // Delete the loose unnamed module if unused.
-               if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
-                       config.modules.shift();
-               }
-
-               // Avoid unnecessary information by not logging modules' test environments
-               for ( i = 0, l = config.modules.length; i < l; i++ ) {
-                       modulesLog.push({
-                               name: config.modules[ i ].name,
-                               tests: config.modules[ i ].tests
-                       });
-               }
-
-               // The test run is officially beginning now
-               runLoggingCallbacks( "begin", {
-                       totalTests: Test.count,
-                       modules: modulesLog
-               });
-       }
-
-       config.blocking = false;
-       process( true );
-}
-
-function resumeProcessing() {
-       runStarted = true;
-
-       // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
-       if ( defined.setTimeout ) {
-               setTimeout(function() {
-                       if ( config.current && config.current.semaphore > 0 ) {
-                               return;
-                       }
-                       if ( config.timeout ) {
-                               clearTimeout( config.timeout );
-                       }
-
-                       begin();
-               }, 13 );
-       } else {
-               begin();
-       }
-}
-
-function pauseProcessing() {
-       config.blocking = true;
-
-       if ( config.testTimeout && defined.setTimeout ) {
-               clearTimeout( config.timeout );
-               config.timeout = setTimeout(function() {
-                       if ( config.current ) {
-                               config.current.semaphore = 0;
-                               QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
-                       } else {
-                               throw new Error( "Test timed out" );
-                       }
-                       resumeProcessing();
-               }, config.testTimeout );
-       }
-}
-
-function saveGlobal() {
-       config.pollution = [];
-
-       if ( config.noglobals ) {
-               for ( var key in window ) {
-                       if ( hasOwn.call( window, key ) ) {
-                               // in Opera sometimes DOM element ids show up here, ignore them
-                               if ( /^qunit-test-output/.test( key ) ) {
-                                       continue;
-                               }
-                               config.pollution.push( key );
-                       }
-               }
-       }
-}
-
-function checkPollution() {
-       var newGlobals,
-               deletedGlobals,
-               old = config.pollution;
-
-       saveGlobal();
-
-       newGlobals = diff( config.pollution, old );
-       if ( newGlobals.length > 0 ) {
-               QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
-       }
-
-       deletedGlobals = diff( old, config.pollution );
-       if ( deletedGlobals.length > 0 ) {
-               QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
-       }
-}
-
-// returns a new Array with the elements that are in a but not in b
-function diff( a, b ) {
-       var i, j,
-               result = a.slice();
-
-       for ( i = 0; i < result.length; i++ ) {
-               for ( j = 0; j < b.length; j++ ) {
-                       if ( result[ i ] === b[ j ] ) {
-                               result.splice( i, 1 );
-                               i--;
-                               break;
-                       }
-               }
-       }
-       return result;
-}
-
-function extend( a, b, undefOnly ) {
-       for ( var prop in b ) {
-               if ( hasOwn.call( b, prop ) ) {
-
-                       // Avoid "Member not found" error in IE8 caused by messing with window.constructor
-                       if ( !( prop === "constructor" && a === window ) ) {
-                               if ( b[ prop ] === undefined ) {
-                                       delete a[ prop ];
-                               } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
-                                       a[ prop ] = b[ prop ];
-                               }
-                       }
-               }
-       }
-
-       return a;
-}
-
-function runLoggingCallbacks( key, args ) {
-       var i, l, callbacks;
-
-       callbacks = config.callbacks[ key ];
-       for ( i = 0, l = callbacks.length; i < l; i++ ) {
-               callbacks[ i ]( args );
-       }
-}
-
-// DEPRECATED: This will be removed on 2.0.0+
-// This function verifies if the loggingCallbacks were modified by the user
-// If so, it will restore it, assign the given callback and print a console warning
-function verifyLoggingCallbacks() {
-       var loggingCallback, userCallback;
-
-       for ( loggingCallback in loggingCallbacks ) {
-               if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
-
-                       userCallback = QUnit[ loggingCallback ];
-
-                       // Restore the callback function
-                       QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
-
-                       // Assign the deprecated given callback
-                       QUnit[ loggingCallback ]( userCallback );
-
-                       if ( window.console && window.console.warn ) {
-                               window.console.warn(
-                                       "QUnit." + loggingCallback + " was replaced with a new value.\n" +
-                                       "Please, check out the documentation on how to apply logging callbacks.\n" +
-                                       "Reference: http://api.qunitjs.com/category/callbacks/"
-                               );
-                       }
-               }
-       }
-}
-
-// from jquery.js
-function inArray( elem, array ) {
-       if ( array.indexOf ) {
-               return array.indexOf( elem );
-       }
-
-       for ( var i = 0, length = array.length; i < length; i++ ) {
-               if ( array[ i ] === elem ) {
-                       return i;
-               }
-       }
-
-       return -1;
-}
-
-function Test( settings ) {
-       var i, l;
-
-       ++Test.count;
-
-       extend( this, settings );
-       this.assertions = [];
-       this.semaphore = 0;
-       this.usedAsync = false;
-       this.module = config.currentModule;
-       this.stack = sourceFromStacktrace( 3 );
-
-       // Register unique strings
-       for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
-               if ( this.module.tests[ i ].name === this.testName ) {
-                       this.testName += " ";
-               }
-       }
-
-       this.testId = generateHash( this.module.name, this.testName );
-
-       this.module.tests.push({
-               name: this.testName,
-               testId: this.testId
-       });
-
-       if ( settings.skip ) {
-
-               // Skipped tests will fully ignore any sent callback
-               this.callback = function() {};
-               this.async = false;
-               this.expected = 0;
-       } else {
-               this.assert = new Assert( this );
-       }
-}
-
-Test.count = 0;
-
-Test.prototype = {
-       before: function() {
-               if (
-
-                       // Emit moduleStart when we're switching from one module to another
-                       this.module !== config.previousModule ||
-
-                               // They could be equal (both undefined) but if the previousModule property doesn't
-                               // yet exist it means this is the first test in a suite that isn't wrapped in a
-                               // module, in which case we'll just emit a moduleStart event for 'undefined'.
-                               // Without this, reporters can get testStart before moduleStart  which is a problem.
-                               !hasOwn.call( config, "previousModule" )
-               ) {
-                       if ( hasOwn.call( config, "previousModule" ) ) {
-                               runLoggingCallbacks( "moduleDone", {
-                                       name: config.previousModule.name,
-                                       tests: config.previousModule.tests,
-                                       failed: config.moduleStats.bad,
-                                       passed: config.moduleStats.all - config.moduleStats.bad,
-                                       total: config.moduleStats.all,
-                                       runtime: now() - config.moduleStats.started
-                               });
-                       }
-                       config.previousModule = this.module;
-                       config.moduleStats = { all: 0, bad: 0, started: now() };
-                       runLoggingCallbacks( "moduleStart", {
-                               name: this.module.name,
-                               tests: this.module.tests
-                       });
-               }
-
-               config.current = this;
-
-               this.testEnvironment = extend( {}, this.module.testEnvironment );
-               delete this.testEnvironment.beforeEach;
-               delete this.testEnvironment.afterEach;
-
-               this.started = now();
-               runLoggingCallbacks( "testStart", {
-                       name: this.testName,
-                       module: this.module.name,
-                       testId: this.testId
-               });
-
-               if ( !config.pollution ) {
-                       saveGlobal();
-               }
-       },
-
-       run: function() {
-               var promise;
-
-               config.current = this;
-
-               if ( this.async ) {
-                       QUnit.stop();
-               }
-
-               this.callbackStarted = now();
-
-               if ( config.notrycatch ) {
-                       promise = this.callback.call( this.testEnvironment, this.assert );
-                       this.resolvePromise( promise );
-                       return;
-               }
-
-               try {
-                       promise = this.callback.call( this.testEnvironment, this.assert );
-                       this.resolvePromise( promise );
-               } catch ( e ) {
-                       this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
-                               this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-
-                       // else next test will carry the responsibility
-                       saveGlobal();
-
-                       // Restart the tests if they're blocking
-                       if ( config.blocking ) {
-                               QUnit.start();
-                       }
-               }
-       },
-
-       after: function() {
-               checkPollution();
-       },
-
-       queueHook: function( hook, hookName ) {
-               var promise,
-                       test = this;
-               return function runHook() {
-                       config.current = test;
-                       if ( config.notrycatch ) {
-                               promise = hook.call( test.testEnvironment, test.assert );
-                               test.resolvePromise( promise, hookName );
-                               return;
-                       }
-                       try {
-                               promise = hook.call( test.testEnvironment, test.assert );
-                               test.resolvePromise( promise, hookName );
-                       } catch ( error ) {
-                               test.pushFailure( hookName + " failed on " + test.testName + ": " +
-                                       ( error.message || error ), extractStacktrace( error, 0 ) );
-                       }
-               };
-       },
-
-       // Currently only used for module level hooks, can be used to add global level ones
-       hooks: function( handler ) {
-               var hooks = [];
-
-               // Hooks are ignored on skipped tests
-               if ( this.skip ) {
-                       return hooks;
-               }
-
-               if ( this.module.testEnvironment &&
-                               QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
-                       hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
-               }
-
-               return hooks;
-       },
-
-       finish: function() {
-               config.current = this;
-               if ( config.requireExpects && this.expected === null ) {
-                       this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
-                               "not called.", this.stack );
-               } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
-                       this.pushFailure( "Expected " + this.expected + " assertions, but " +
-                               this.assertions.length + " were run", this.stack );
-               } else if ( this.expected === null && !this.assertions.length ) {
-                       this.pushFailure( "Expected at least one assertion, but none were run - call " +
-                               "expect(0) to accept zero assertions.", this.stack );
-               }
-
-               var i,
-                       bad = 0;
-
-               this.runtime = now() - this.started;
-               config.stats.all += this.assertions.length;
-               config.moduleStats.all += this.assertions.length;
-
-               for ( i = 0; i < this.assertions.length; i++ ) {
-                       if ( !this.assertions[ i ].result ) {
-                               bad++;
-                               config.stats.bad++;
-                               config.moduleStats.bad++;
-                       }
-               }
-
-               runLoggingCallbacks( "testDone", {
-                       name: this.testName,
-                       module: this.module.name,
-                       skipped: !!this.skip,
-                       failed: bad,
-                       passed: this.assertions.length - bad,
-                       total: this.assertions.length,
-                       runtime: this.runtime,
-
-                       // HTML Reporter use
-                       assertions: this.assertions,
-                       testId: this.testId,
-
-                       // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
-                       duration: this.runtime
-               });
-
-               // QUnit.reset() is deprecated and will be replaced for a new
-               // fixture reset function on QUnit 2.0/2.1.
-               // It's still called here for backwards compatibility handling
-               QUnit.reset();
-
-               config.current = undefined;
-       },
-
-       queue: function() {
-               var bad,
-                       test = this;
-
-               if ( !this.valid() ) {
-                       return;
-               }
-
-               function run() {
-
-                       // each of these can by async
-                       synchronize([
-                               function() {
-                                       test.before();
-                               },
-
-                               test.hooks( "beforeEach" ),
-
-                               function() {
-                                       test.run();
-                               },
-
-                               test.hooks( "afterEach" ).reverse(),
-
-                               function() {
-                                       test.after();
-                               },
-                               function() {
-                                       test.finish();
-                               }
-                       ]);
-               }
-
-               // `bad` initialized at top of scope
-               // defer when previous test run passed, if storage is available
-               bad = QUnit.config.reorder && defined.sessionStorage &&
-                               +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
-
-               if ( bad ) {
-                       run();
-               } else {
-                       synchronize( run, true );
-               }
-       },
-
-       push: function( result, actual, expected, message ) {
-               var source,
-                       details = {
-                               module: this.module.name,
-                               name: this.testName,
-                               result: result,
-                               message: message,
-                               actual: actual,
-                               expected: expected,
-                               testId: this.testId,
-                               runtime: now() - this.started
-                       };
-
-               if ( !result ) {
-                       source = sourceFromStacktrace();
-
-                       if ( source ) {
-                               details.source = source;
-                       }
-               }
-
-               runLoggingCallbacks( "log", details );
-
-               this.assertions.push({
-                       result: !!result,
-                       message: message
-               });
-       },
-
-       pushFailure: function( message, source, actual ) {
-               if ( !this instanceof Test ) {
-                       throw new Error( "pushFailure() assertion outside test context, was " +
-                               sourceFromStacktrace( 2 ) );
-               }
-
-               var details = {
-                               module: this.module.name,
-                               name: this.testName,
-                               result: false,
-                               message: message || "error",
-                               actual: actual || null,
-                               testId: this.testId,
-                               runtime: now() - this.started
-                       };
-
-               if ( source ) {
-                       details.source = source;
-               }
-
-               runLoggingCallbacks( "log", details );
-
-               this.assertions.push({
-                       result: false,
-                       message: message
-               });
-       },
-
-       resolvePromise: function( promise, phase ) {
-               var then, message,
-                       test = this;
-               if ( promise != null ) {
-                       then = promise.then;
-                       if ( QUnit.objectType( then ) === "function" ) {
-                               QUnit.stop();
-                               then.call(
-                                       promise,
-                                       QUnit.start,
-                                       function( error ) {
-                                               message = "Promise rejected " +
-                                                       ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
-                                                       " " + test.testName + ": " + ( error.message || error );
-                                               test.pushFailure( message, extractStacktrace( error, 0 ) );
-
-                                               // else next test will carry the responsibility
-                                               saveGlobal();
-
-                                               // Unblock
-                                               QUnit.start();
-                                       }
-                               );
-                       }
-               }
-       },
-
-       valid: function() {
-               var include,
-                       filter = config.filter && config.filter.toLowerCase(),
-                       module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
-                       fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
-
-               // Internally-generated tests are always valid
-               if ( this.callback && this.callback.validTest ) {
-                       return true;
-               }
-
-               if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
-                       return false;
-               }
-
-               if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
-                       return false;
-               }
-
-               if ( !filter ) {
-                       return true;
-               }
-
-               include = filter.charAt( 0 ) !== "!";
-               if ( !include ) {
-                       filter = filter.slice( 1 );
-               }
-
-               // If the filter matches, we need to honour include
-               if ( fullName.indexOf( filter ) !== -1 ) {
-                       return include;
-               }
-
-               // Otherwise, do the opposite
-               return !include;
-       }
-
-};
-
-// Resets the test setup. Useful for tests that modify the DOM.
-/*
-DEPRECATED: Use multiple tests instead of resetting inside a test.
-Use testStart or testDone for custom cleanup.
-This method will throw an error in 2.0, and will be removed in 2.1
-*/
-QUnit.reset = function() {
-
-       // Return on non-browser environments
-       // This is necessary to not break on node tests
-       if ( typeof window === "undefined" ) {
-               return;
-       }
-
-       var fixture = defined.document && document.getElementById &&
-                       document.getElementById( "qunit-fixture" );
-
-       if ( fixture ) {
-               fixture.innerHTML = config.fixture;
-       }
-};
-
-QUnit.pushFailure = function() {
-       if ( !QUnit.config.current ) {
-               throw new Error( "pushFailure() assertion outside test context, in " +
-                       sourceFromStacktrace( 2 ) );
-       }
-
-       // Gets current test obj
-       var currentTest = QUnit.config.current;
-
-       return currentTest.pushFailure.apply( currentTest, arguments );
-};
-
-// Based on Java's String.hashCode, a simple but not
-// rigorously collision resistant hashing function
-function generateHash( module, testName ) {
-       var hex,
-               i = 0,
-               hash = 0,
-               str = module + "\x1C" + testName,
-               len = str.length;
-
-       for ( ; i < len; i++ ) {
-               hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
-               hash |= 0;
-       }
-
-       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
-       // strictly necessary but increases user understanding that the id is a SHA-like hash
-       hex = ( 0x100000000 + hash ).toString( 16 );
-       if ( hex.length < 8 ) {
-               hex = "0000000" + hex;
-       }
-
-       return hex.slice( -8 );
-}
-
-function Assert( testContext ) {
-       this.test = testContext;
-}
-
-// Assert helpers
-QUnit.assert = Assert.prototype = {
-
-       // Specify the number of expected assertions to guarantee that failed test
-       // (no assertions are run at all) don't slip through.
-       expect: function( asserts ) {
-               if ( arguments.length === 1 ) {
-                       this.test.expected = asserts;
-               } else {
-                       return this.test.expected;
-               }
-       },
-
-       // Increment this Test's semaphore counter, then return a single-use function that
-       // decrements that counter a maximum of once.
-       async: function() {
-               var test = this.test,
-                       popped = false;
-
-               test.semaphore += 1;
-               test.usedAsync = true;
-               pauseProcessing();
-
-               return function done() {
-                       if ( !popped ) {
-                               test.semaphore -= 1;
-                               popped = true;
-                               resumeProcessing();
-                       } else {
-                               test.pushFailure( "Called the callback returned from `assert.async` more than once",
-                                       sourceFromStacktrace( 2 ) );
-                       }
-               };
-       },
-
-       // Exports test.push() to the user API
-       push: function( /* result, actual, expected, message */ ) {
-               var assert = this,
-                       currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
-
-               // Backwards compatibility fix.
-               // Allows the direct use of global exported assertions and QUnit.assert.*
-               // Although, it's use is not recommended as it can leak assertions
-               // to other tests from async tests, because we only get a reference to the current test,
-               // not exactly the test where assertion were intended to be called.
-               if ( !currentTest ) {
-                       throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
-               }
-
-               if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
-                       currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
-                               sourceFromStacktrace( 2 ) );
-
-                       // Allow this assertion to continue running anyway...
-               }
-
-               if ( !( assert instanceof Assert ) ) {
-                       assert = currentTest.assert;
-               }
-               return assert.test.push.apply( assert.test, arguments );
-       },
-
-       /**
-        * Asserts rough true-ish result.
-        * @name ok
-        * @function
-        * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
-        */
-       ok: function( result, message ) {
-               message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
-                       QUnit.dump.parse( result ) );
-               this.push( !!result, result, true, message );
-       },
-
-       /**
-        * Assert that the first two arguments are equal, with an optional message.
-        * Prints out both actual and expected values.
-        * @name equal
-        * @function
-        * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
-        */
-       equal: function( actual, expected, message ) {
-               /*jshint eqeqeq:false */
-               this.push( expected == actual, actual, expected, message );
-       },
-
-       /**
-        * @name notEqual
-        * @function
-        */
-       notEqual: function( actual, expected, message ) {
-               /*jshint eqeqeq:false */
-               this.push( expected != actual, actual, expected, message );
-       },
-
-       /**
-        * @name propEqual
-        * @function
-        */
-       propEqual: function( actual, expected, message ) {
-               actual = objectValues( actual );
-               expected = objectValues( expected );
-               this.push( QUnit.equiv( actual, expected ), actual, expected, message );
-       },
-
-       /**
-        * @name notPropEqual
-        * @function
-        */
-       notPropEqual: function( actual, expected, message ) {
-               actual = objectValues( actual );
-               expected = objectValues( expected );
-               this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
-       },
-
-       /**
-        * @name deepEqual
-        * @function
-        */
-       deepEqual: function( actual, expected, message ) {
-               this.push( QUnit.equiv( actual, expected ), actual, expected, message );
-       },
-
-       /**
-        * @name notDeepEqual
-        * @function
-        */
-       notDeepEqual: function( actual, expected, message ) {
-               this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
-       },
-
-       /**
-        * @name strictEqual
-        * @function
-        */
-       strictEqual: function( actual, expected, message ) {
-               this.push( expected === actual, actual, expected, message );
-       },
-
-       /**
-        * @name notStrictEqual
-        * @function
-        */
-       notStrictEqual: function( actual, expected, message ) {
-               this.push( expected !== actual, actual, expected, message );
-       },
-
-       "throws": function( block, expected, message ) {
-               var actual, expectedType,
-                       expectedOutput = expected,
-                       ok = false;
-
-               // 'expected' is optional unless doing string comparison
-               if ( message == null && typeof expected === "string" ) {
-                       message = expected;
-                       expected = null;
-               }
-
-               this.test.ignoreGlobalErrors = true;
-               try {
-                       block.call( this.test.testEnvironment );
-               } catch (e) {
-                       actual = e;
-               }
-               this.test.ignoreGlobalErrors = false;
-
-               if ( actual ) {
-                       expectedType = QUnit.objectType( expected );
-
-                       // we don't want to validate thrown error
-                       if ( !expected ) {
-                               ok = true;
-                               expectedOutput = null;
-
-                       // expected is a regexp
-                       } else if ( expectedType === "regexp" ) {
-                               ok = expected.test( errorString( actual ) );
-
-                       // expected is a string
-                       } else if ( expectedType === "string" ) {
-                               ok = expected === errorString( actual );
-
-                       // expected is a constructor, maybe an Error constructor
-                       } else if ( expectedType === "function" && actual instanceof expected ) {
-                               ok = true;
-
-                       // expected is an Error object
-                       } else if ( expectedType === "object" ) {
-                               ok = actual instanceof expected.constructor &&
-                                       actual.name === expected.name &&
-                                       actual.message === expected.message;
-
-                       // expected is a validation function which returns true if validation passed
-                       } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
-                               expectedOutput = null;
-                               ok = true;
-                       }
-
-                       this.push( ok, actual, expectedOutput, message );
-               } else {
-                       this.test.pushFailure( message, null, "No exception was thrown." );
-               }
-       }
-};
-
-// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
-// Known to us are: Closure Compiler, Narwhal
-(function() {
-       /*jshint sub:true */
-       Assert.prototype.raises = Assert.prototype[ "throws" ];
-}());
-
-// Test for equality any JavaScript type.
-// Author: Philippe Rathé <prathe@gmail.com>
-QUnit.equiv = (function() {
-
-       // Call the o related callback with the given arguments.
-       function bindCallbacks( o, callbacks, args ) {
-               var prop = QUnit.objectType( o );
-               if ( prop ) {
-                       if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
-                               return callbacks[ prop ].apply( callbacks, args );
-                       } else {
-                               return callbacks[ prop ]; // or undefined
-                       }
-               }
-       }
-
-       // the real equiv function
-       var innerEquiv,
-
-               // stack to decide between skip/abort functions
-               callers = [],
-
-               // stack to avoiding loops from circular referencing
-               parents = [],
-               parentsB = [],
-
-               getProto = Object.getPrototypeOf || function( obj ) {
-                       /* jshint camelcase: false, proto: true */
-                       return obj.__proto__;
-               },
-               callbacks = (function() {
-
-                       // for string, boolean, number and null
-                       function useStrictEquality( b, a ) {
-
-                               /*jshint eqeqeq:false */
-                               if ( b instanceof a.constructor || a instanceof b.constructor ) {
-
-                                       // to catch short annotation VS 'new' annotation of a
-                                       // declaration
-                                       // e.g. var i = 1;
-                                       // var j = new Number(1);
-                                       return a == b;
-                               } else {
-                                       return a === b;
-                               }
-                       }
-
-                       return {
-                               "string": useStrictEquality,
-                               "boolean": useStrictEquality,
-                               "number": useStrictEquality,
-                               "null": useStrictEquality,
-                               "undefined": useStrictEquality,
-
-                               "nan": function( b ) {
-                                       return isNaN( b );
-                               },
-
-                               "date": function( b, a ) {
-                                       return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
-                               },
-
-                               "regexp": function( b, a ) {
-                                       return QUnit.objectType( b ) === "regexp" &&
-
-                                               // the regex itself
-                                               a.source === b.source &&
-
-                                               // and its modifiers
-                                               a.global === b.global &&
-
-                                               // (gmi) ...
-                                               a.ignoreCase === b.ignoreCase &&
-                                               a.multiline === b.multiline &&
-                                               a.sticky === b.sticky;
-                               },
-
-                               // - skip when the property is a method of an instance (OOP)
-                               // - abort otherwise,
-                               // initial === would have catch identical references anyway
-                               "function": function() {
-                                       var caller = callers[ callers.length - 1 ];
-                                       return caller !== Object && typeof caller !== "undefined";
-                               },
-
-                               "array": function( b, a ) {
-                                       var i, j, len, loop, aCircular, bCircular;
-
-                                       // b could be an object literal here
-                                       if ( QUnit.objectType( b ) !== "array" ) {
-                                               return false;
-                                       }
-
-                                       len = a.length;
-                                       if ( len !== b.length ) {
-                                               // safe and faster
-                                               return false;
-                                       }
-
-                                       // track reference to avoid circular references
-                                       parents.push( a );
-                                       parentsB.push( b );
-                                       for ( i = 0; i < len; i++ ) {
-                                               loop = false;
-                                               for ( j = 0; j < parents.length; j++ ) {
-                                                       aCircular = parents[ j ] === a[ i ];
-                                                       bCircular = parentsB[ j ] === b[ i ];
-                                                       if ( aCircular || bCircular ) {
-                                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-                                                                       loop = true;
-                                                               } else {
-                                                                       parents.pop();
-                                                                       parentsB.pop();
-                                                                       return false;
-                                                               }
-                                                       }
-                                               }
-                                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-                                                       parents.pop();
-                                                       parentsB.pop();
-                                                       return false;
-                                               }
-                                       }
-                                       parents.pop();
-                                       parentsB.pop();
-                                       return true;
-                               },
-
-                               "object": function( b, a ) {
-
-                                       /*jshint forin:false */
-                                       var i, j, loop, aCircular, bCircular,
-                                               // Default to true
-                                               eq = true,
-                                               aProperties = [],
-                                               bProperties = [];
-
-                                       // comparing constructors is more strict than using
-                                       // instanceof
-                                       if ( a.constructor !== b.constructor ) {
-
-                                               // Allow objects with no prototype to be equivalent to
-                                               // objects with Object as their constructor.
-                                               if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
-                                                       ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
-                                                       return false;
-                                               }
-                                       }
-
-                                       // stack constructor before traversing properties
-                                       callers.push( a.constructor );
-
-                                       // track reference to avoid circular references
-                                       parents.push( a );
-                                       parentsB.push( b );
-
-                                       // be strict: don't ensure hasOwnProperty and go deep
-                                       for ( i in a ) {
-                                               loop = false;
-                                               for ( j = 0; j < parents.length; j++ ) {
-                                                       aCircular = parents[ j ] === a[ i ];
-                                                       bCircular = parentsB[ j ] === b[ i ];
-                                                       if ( aCircular || bCircular ) {
-                                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-                                                                       loop = true;
-                                                               } else {
-                                                                       eq = false;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-                                               aProperties.push( i );
-                                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-                                                       eq = false;
-                                                       break;
-                                               }
-                                       }
-
-                                       parents.pop();
-                                       parentsB.pop();
-                                       callers.pop(); // unstack, we are done
-
-                                       for ( i in b ) {
-                                               bProperties.push( i ); // collect b's properties
-                                       }
-
-                                       // Ensures identical properties name
-                                       return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
-                               }
-                       };
-               }());
-
-       innerEquiv = function() { // can take multiple arguments
-               var args = [].slice.apply( arguments );
-               if ( args.length < 2 ) {
-                       return true; // end transition
-               }
-
-               return ( (function( a, b ) {
-                       if ( a === b ) {
-                               return true; // catch the most you can
-                       } else if ( a === null || b === null || typeof a === "undefined" ||
-                                       typeof b === "undefined" ||
-                                       QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
-
-                               // don't lose time with error prone cases
-                               return false;
-                       } else {
-                               return bindCallbacks( a, callbacks, [ b, a ] );
-                       }
-
-                       // apply transition with (1..n) arguments
-               }( args[ 0 ], args[ 1 ] ) ) &&
-                       innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
-       };
-
-       return innerEquiv;
-}());
-
-// Based on jsDump by Ariel Flesler
-// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
-QUnit.dump = (function() {
-       function quote( str ) {
-               return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
-       }
-       function literal( o ) {
-               return o + "";
-       }
-       function join( pre, arr, post ) {
-               var s = dump.separator(),
-                       base = dump.indent(),
-                       inner = dump.indent( 1 );
-               if ( arr.join ) {
-                       arr = arr.join( "," + s + inner );
-               }
-               if ( !arr ) {
-                       return pre + post;
-               }
-               return [ pre, inner + arr, base + post ].join( s );
-       }
-       function array( arr, stack ) {
-               var i = arr.length,
-                       ret = new Array( i );
-
-               if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
-                       return "[object Array]";
-               }
-
-               this.up();
-               while ( i-- ) {
-                       ret[ i ] = this.parse( arr[ i ], undefined, stack );
-               }
-               this.down();
-               return join( "[", ret, "]" );
-       }
-
-       var reName = /^function (\w+)/,
-               dump = {
-
-                       // objType is used mostly internally, you can fix a (custom) type in advance
-                       parse: function( obj, objType, stack ) {
-                               stack = stack || [];
-                               var res, parser, parserType,
-                                       inStack = inArray( obj, stack );
-
-                               if ( inStack !== -1 ) {
-                                       return "recursion(" + ( inStack - stack.length ) + ")";
-                               }
-
-                               objType = objType || this.typeOf( obj  );
-                               parser = this.parsers[ objType ];
-                               parserType = typeof parser;
-
-                               if ( parserType === "function" ) {
-                                       stack.push( obj );
-                                       res = parser.call( this, obj, stack );
-                                       stack.pop();
-                                       return res;
-                               }
-                               return ( parserType === "string" ) ? parser : this.parsers.error;
-                       },
-                       typeOf: function( obj ) {
-                               var type;
-                               if ( obj === null ) {
-                                       type = "null";
-                               } else if ( typeof obj === "undefined" ) {
-                                       type = "undefined";
-                               } else if ( QUnit.is( "regexp", obj ) ) {
-                                       type = "regexp";
-                               } else if ( QUnit.is( "date", obj ) ) {
-                                       type = "date";
-                               } else if ( QUnit.is( "function", obj ) ) {
-                                       type = "function";
-                               } else if ( obj.setInterval !== undefined &&
-                                               obj.document !== undefined &&
-                                               obj.nodeType === undefined ) {
-                                       type = "window";
-                               } else if ( obj.nodeType === 9 ) {
-                                       type = "document";
-                               } else if ( obj.nodeType ) {
-                                       type = "node";
-                               } else if (
-
-                                       // native arrays
-                                       toString.call( obj ) === "[object Array]" ||
-
-                                       // NodeList objects
-                                       ( typeof obj.length === "number" && obj.item !== undefined &&
-                                       ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
-                                       obj[ 0 ] === undefined ) ) )
-                               ) {
-                                       type = "array";
-                               } else if ( obj.constructor === Error.prototype.constructor ) {
-                                       type = "error";
-                               } else {
-                                       type = typeof obj;
-                               }
-                               return type;
-                       },
-                       separator: function() {
-                               return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
-                       },
-                       // extra can be a number, shortcut for increasing-calling-decreasing
-                       indent: function( extra ) {
-                               if ( !this.multiline ) {
-                                       return "";
-                               }
-                               var chr = this.indentChar;
-                               if ( this.HTML ) {
-                                       chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
-                               }
-                               return new Array( this.depth + ( extra || 0 ) ).join( chr );
-                       },
-                       up: function( a ) {
-                               this.depth += a || 1;
-                       },
-                       down: function( a ) {
-                               this.depth -= a || 1;
-                       },
-                       setParser: function( name, parser ) {
-                               this.parsers[ name ] = parser;
-                       },
-                       // The next 3 are exposed so you can use them
-                       quote: quote,
-                       literal: literal,
-                       join: join,
-                       //
-                       depth: 1,
-                       maxDepth: 5,
-
-                       // This is the list of parsers, to modify them, use dump.setParser
-                       parsers: {
-                               window: "[Window]",
-                               document: "[Document]",
-                               error: function( error ) {
-                                       return "Error(\"" + error.message + "\")";
-                               },
-                               unknown: "[Unknown]",
-                               "null": "null",
-                               "undefined": "undefined",
-                               "function": function( fn ) {
-                                       var ret = "function",
-
-                                               // functions never have name in IE
-                                               name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
-
-                                       if ( name ) {
-                                               ret += " " + name;
-                                       }
-                                       ret += "( ";
-
-                                       ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
-                                       return join( ret, dump.parse( fn, "functionCode" ), "}" );
-                               },
-                               array: array,
-                               nodelist: array,
-                               "arguments": array,
-                               object: function( map, stack ) {
-                                       var keys, key, val, i, nonEnumerableProperties,
-                                               ret = [];
-
-                                       if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
-                                               return "[object Object]";
-                                       }
-
-                                       dump.up();
-                                       keys = [];
-                                       for ( key in map ) {
-                                               keys.push( key );
-                                       }
-
-                                       // Some properties are not always enumerable on Error objects.
-                                       nonEnumerableProperties = [ "message", "name" ];
-                                       for ( i in nonEnumerableProperties ) {
-                                               key = nonEnumerableProperties[ i ];
-                                               if ( key in map && !( key in keys ) ) {
-                                                       keys.push( key );
-                                               }
-                                       }
-                                       keys.sort();
-                                       for ( i = 0; i < keys.length; i++ ) {
-                                               key = keys[ i ];
-                                               val = map[ key ];
-                                               ret.push( dump.parse( key, "key" ) + ": " +
-                                                       dump.parse( val, undefined, stack ) );
-                                       }
-                                       dump.down();
-                                       return join( "{", ret, "}" );
-                               },
-                               node: function( node ) {
-                                       var len, i, val,
-                                               open = dump.HTML ? "&lt;" : "<",
-                                               close = dump.HTML ? "&gt;" : ">",
-                                               tag = node.nodeName.toLowerCase(),
-                                               ret = open + tag,
-                                               attrs = node.attributes;
-
-                                       if ( attrs ) {
-                                               for ( i = 0, len = attrs.length; i < len; i++ ) {
-                                                       val = attrs[ i ].nodeValue;
-
-                                                       // IE6 includes all attributes in .attributes, even ones not explicitly
-                                                       // set. Those have values like undefined, null, 0, false, "" or
-                                                       // "inherit".
-                                                       if ( val && val !== "inherit" ) {
-                                                               ret += " " + attrs[ i ].nodeName + "=" +
-                                                                       dump.parse( val, "attribute" );
-                                                       }
-                                               }
-                                       }
-                                       ret += close;
-
-                                       // Show content of TextNode or CDATASection
-                                       if ( node.nodeType === 3 || node.nodeType === 4 ) {
-                                               ret += node.nodeValue;
-                                       }
-
-                                       return ret + open + "/" + tag + close;
-                               },
-
-                               // function calls it internally, it's the arguments part of the function
-                               functionArgs: function( fn ) {
-                                       var args,
-                                               l = fn.length;
-
-                                       if ( !l ) {
-                                               return "";
-                                       }
-
-                                       args = new Array( l );
-                                       while ( l-- ) {
-
-                                               // 97 is 'a'
-                                               args[ l ] = String.fromCharCode( 97 + l );
-                                       }
-                                       return " " + args.join( ", " ) + " ";
-                               },
-                               // object calls it internally, the key part of an item in a map
-                               key: quote,
-                               // function calls it internally, it's the content of the function
-                               functionCode: "[code]",
-                               // node calls it internally, it's an html attribute value
-                               attribute: quote,
-                               string: quote,
-                               date: quote,
-                               regexp: literal,
-                               number: literal,
-                               "boolean": literal
-                       },
-                       // if true, entities are escaped ( <, >, \t, space and \n )
-                       HTML: false,
-                       // indentation unit
-                       indentChar: "  ",
-                       // if true, items in a collection, are separated by a \n, else just a space.
-                       multiline: true
-               };
-
-       return dump;
-}());
-
-// back compat
-QUnit.jsDump = QUnit.dump;
-
-// For browser, export only select globals
-if ( typeof window !== "undefined" ) {
-
-       // Deprecated
-       // Extend assert methods to QUnit and Global scope through Backwards compatibility
-       (function() {
-               var i,
-                       assertions = Assert.prototype;
-
-               function applyCurrent( current ) {
-                       return function() {
-                               var assert = new Assert( QUnit.config.current );
-                               current.apply( assert, arguments );
-                       };
-               }
-
-               for ( i in assertions ) {
-                       QUnit[ i ] = applyCurrent( assertions[ i ] );
-               }
-       })();
-
-       (function() {
-               var i, l,
-                       keys = [
-                               "test",
-                               "module",
-                               "expect",
-                               "asyncTest",
-                               "start",
-                               "stop",
-                               "ok",
-                               "equal",
-                               "notEqual",
-                               "propEqual",
-                               "notPropEqual",
-                               "deepEqual",
-                               "notDeepEqual",
-                               "strictEqual",
-                               "notStrictEqual",
-                               "throws"
-                       ];
-
-               for ( i = 0, l = keys.length; i < l; i++ ) {
-                       window[ keys[ i ] ] = QUnit[ keys[ i ] ];
-               }
-       })();
-
-       window.QUnit = QUnit;
-}
-
-// For nodejs
-if ( typeof module !== "undefined" && module.exports ) {
-       module.exports = QUnit;
-}
-
-// For CommonJS with exports, but without module.exports, like Rhino
-if ( typeof exports !== "undefined" ) {
-       exports.QUnit = QUnit;
-}
-
-// Get a reference to the global object, like window in browsers
-}( (function() {
-       return this;
-})() ));
-
-/*istanbul ignore next */
-// jscs:disable maximumLineLength
-/*
- * Javascript Diff Algorithm
- *  By John Resig (http://ejohn.org/)
- *  Modified by Chu Alan "sprite"
- *
- * Released under the MIT license.
- *
- * More Info:
- *  http://ejohn.org/projects/javascript-diff-algorithm/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- * 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"
- */
-QUnit.diff = (function() {
-       var hasOwn = Object.prototype.hasOwnProperty;
-
-       /*jshint eqeqeq:false, eqnull:true */
-       function diff( o, n ) {
-               var i,
-                       ns = {},
-                       os = {};
-
-               for ( i = 0; i < n.length; i++ ) {
-                       if ( !hasOwn.call( ns, n[ i ] ) ) {
-                               ns[ n[ i ] ] = {
-                                       rows: [],
-                                       o: null
-                               };
-                       }
-                       ns[ n[ i ] ].rows.push( i );
-               }
-
-               for ( i = 0; i < o.length; i++ ) {
-                       if ( !hasOwn.call( os, o[ i ] ) ) {
-                               os[ o[ i ] ] = {
-                                       rows: [],
-                                       n: null
-                               };
-                       }
-                       os[ o[ i ] ].rows.push( i );
-               }
-
-               for ( i in ns ) {
-                       if ( hasOwn.call( ns, i ) ) {
-                               if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
-                                       n[ ns[ i ].rows[ 0 ] ] = {
-                                               text: n[ ns[ i ].rows[ 0 ] ],
-                                               row: os[ i ].rows[ 0 ]
-                                       };
-                                       o[ os[ i ].rows[ 0 ] ] = {
-                                               text: o[ os[ i ].rows[ 0 ] ],
-                                               row: ns[ i ].rows[ 0 ]
-                                       };
-                               }
-                       }
-               }
-
-               for ( i = 0; i < n.length - 1; i++ ) {
-                       if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
-                               n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
-
-                               n[ i + 1 ] = {
-                                       text: n[ i + 1 ],
-                                       row: n[ i ].row + 1
-                               };
-                               o[ n[ i ].row + 1 ] = {
-                                       text: o[ n[ i ].row + 1 ],
-                                       row: i + 1
-                               };
-                       }
-               }
-
-               for ( i = n.length - 1; i > 0; i-- ) {
-                       if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
-                               n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
-
-                               n[ i - 1 ] = {
-                                       text: n[ i - 1 ],
-                                       row: n[ i ].row - 1
-                               };
-                               o[ n[ i ].row - 1 ] = {
-                                       text: o[ n[ i ].row - 1 ],
-                                       row: i - 1
-                               };
-                       }
-               }
-
-               return {
-                       o: o,
-                       n: n
-               };
-       }
-
-       return function( o, n ) {
-               o = o.replace( /\s+$/, "" );
-               n = n.replace( /\s+$/, "" );
-
-               var i, pre,
-                       str = "",
-                       out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
-                       oSpace = o.match( /\s+/g ),
-                       nSpace = n.match( /\s+/g );
-
-               if ( oSpace == null ) {
-                       oSpace = [ " " ];
-               } else {
-                       oSpace.push( " " );
-               }
-
-               if ( nSpace == null ) {
-                       nSpace = [ " " ];
-               } else {
-                       nSpace.push( " " );
-               }
-
-               if ( out.n.length === 0 ) {
-                       for ( i = 0; i < out.o.length; i++ ) {
-                               str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
-                       }
-               } else {
-                       if ( out.n[ 0 ].text == null ) {
-                               for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
-                                       str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
-                               }
-                       }
-
-                       for ( i = 0; i < out.n.length; i++ ) {
-                               if ( out.n[ i ].text == null ) {
-                                       str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
-                               } else {
-
-                                       // `pre` initialized at top of scope
-                                       pre = "";
-
-                                       for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
-                                               pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
-                                       }
-                                       str += " " + out.n[ i ].text + nSpace[ i ] + pre;
-                               }
-                       }
-               }
-
-               return str;
-       };
-}());
-// jscs:enable
-
-(function() {
-
-// Deprecated QUnit.init - Ref #530
-// Re-initialize the configuration options
-QUnit.init = function() {
-       var tests, banner, result, qunit,
-               config = QUnit.config;
-
-       config.stats = { all: 0, bad: 0 };
-       config.moduleStats = { all: 0, bad: 0 };
-       config.started = 0;
-       config.updateRate = 1000;
-       config.blocking = false;
-       config.autostart = true;
-       config.autorun = false;
-       config.filter = "";
-       config.queue = [];
-
-       // Return on non-browser environments
-       // This is necessary to not break on node tests
-       if ( typeof window === "undefined" ) {
-               return;
-       }
-
-       qunit = id( "qunit" );
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
-
-       tests = id( "qunit-tests" );
-       banner = id( "qunit-banner" );
-       result = id( "qunit-testresult" );
-
-       if ( tests ) {
-               tests.innerHTML = "";
-       }
-
-       if ( banner ) {
-               banner.className = "";
-       }
-
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
-
-       if ( tests ) {
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
-       }
-};
-
-// Don't load the HTML Reporter on non-Browser environments
-if ( typeof window === "undefined" ) {
-       return;
-}
-
-var config = QUnit.config,
-       hasOwn = Object.prototype.hasOwnProperty,
-       defined = {
-               document: window.document !== undefined,
-               sessionStorage: (function() {
-                       var x = "qunit-test-string";
-                       try {
-                               sessionStorage.setItem( x, x );
-                               sessionStorage.removeItem( x );
-                               return true;
-                       } catch ( e ) {
-                               return false;
-                       }
-               }())
-       },
-       modulesList = [];
-
-/**
-* Escape text for attribute or text content.
-*/
-function escapeText( s ) {
-       if ( !s ) {
-               return "";
-       }
-       s = s + "";
-
-       // Both single quotes and double quotes (for attributes)
-       return s.replace( /['"<>&]/g, function( s ) {
-               switch ( s ) {
-               case "'":
-                       return "&#039;";
-               case "\"":
-                       return "&quot;";
-               case "<":
-                       return "&lt;";
-               case ">":
-                       return "&gt;";
-               case "&":
-                       return "&amp;";
-               }
-       });
-}
-
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
-       if ( elem.addEventListener ) {
-
-               // Standards-based browsers
-               elem.addEventListener( type, fn, false );
-       } else if ( elem.attachEvent ) {
-
-               // support: IE <9
-               elem.attachEvent( "on" + type, fn );
-       }
-}
-
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
-       var i = elems.length;
-       while ( i-- ) {
-               addEvent( elems[ i ], type, fn );
-       }
-}
-
-function hasClass( elem, name ) {
-       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
-}
-
-function addClass( elem, name ) {
-       if ( !hasClass( elem, name ) ) {
-               elem.className += ( elem.className ? " " : "" ) + name;
-       }
-}
-
-function toggleClass( elem, name ) {
-       if ( hasClass( elem, name ) ) {
-               removeClass( elem, name );
-       } else {
-               addClass( elem, name );
-       }
-}
-
-function removeClass( elem, name ) {
-       var set = " " + elem.className + " ";
-
-       // Class name may appear multiple times
-       while ( set.indexOf( " " + name + " " ) >= 0 ) {
-               set = set.replace( " " + name + " ", " " );
-       }
-
-       // trim for prettiness
-       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
-}
-
-function id( name ) {
-       return defined.document && document.getElementById && document.getElementById( name );
-}
-
-function getUrlConfigHtml() {
-       var i, j, val,
-               escaped, escapedTooltip,
-               selection = false,
-               len = config.urlConfig.length,
-               urlConfigHtml = "";
-
-       for ( i = 0; i < len; i++ ) {
-               val = config.urlConfig[ i ];
-               if ( typeof val === "string" ) {
-                       val = {
-                               id: val,
-                               label: val
-                       };
-               }
-
-               escaped = escapeText( val.id );
-               escapedTooltip = escapeText( val.tooltip );
-
-               config[ val.id ] = QUnit.urlParams[ val.id ];
-               if ( !val.value || typeof val.value === "string" ) {
-                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' type='checkbox'" +
-                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
-                               ( config[ val.id ] ? " checked='checked'" : "" ) +
-                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
-               } else {
-                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label +
-                               ": </label><select id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
-
-                       if ( QUnit.is( "array", val.value ) ) {
-                               for ( j = 0; j < val.value.length; j++ ) {
-                                       escaped = escapeText( val.value[ j ] );
-                                       urlConfigHtml += "<option value='" + escaped + "'" +
-                                               ( config[ val.id ] === val.value[ j ] ?
-                                                       ( selection = true ) && " selected='selected'" : "" ) +
-                                               ">" + escaped + "</option>";
-                               }
-                       } else {
-                               for ( j in val.value ) {
-                                       if ( hasOwn.call( val.value, j ) ) {
-                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
-                                                       ( config[ val.id ] === j ?
-                                                               ( selection = true ) && " selected='selected'" : "" ) +
-                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
-                                       }
-                               }
-                       }
-                       if ( config[ val.id ] && !selection ) {
-                               escaped = escapeText( config[ val.id ] );
-                               urlConfigHtml += "<option value='" + escaped +
-                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
-                       }
-                       urlConfigHtml += "</select>";
-               }
-       }
-
-       return urlConfigHtml;
-}
-
-// Handle "click" events on toolbar checkboxes and "change" for select menus.
-// Updates the URL with the new state of `config.urlConfig` values.
-function toolbarChanged() {
-       var updatedUrl, value,
-               field = this,
-               params = {};
-
-       // Detect if field is a select menu or a checkbox
-       if ( "selectedIndex" in field ) {
-               value = field.options[ field.selectedIndex ].value || undefined;
-       } else {
-               value = field.checked ? ( field.defaultValue || true ) : undefined;
-       }
-
-       params[ field.name ] = value;
-       updatedUrl = QUnit.url( params );
-
-       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
-               config[ field.name ] = value || false;
-               if ( value ) {
-                       addClass( id( "qunit-tests" ), "hidepass" );
-               } else {
-                       removeClass( id( "qunit-tests" ), "hidepass" );
-               }
-
-               // It is not necessary to refresh the whole page
-               window.history.replaceState( null, "", updatedUrl );
-       } else {
-               window.location = updatedUrl;
-       }
-}
-
-function toolbarUrlConfigContainer() {
-       var urlConfigContainer = document.createElement( "span" );
-
-       urlConfigContainer.innerHTML = getUrlConfigHtml();
-
-       // For oldIE support:
-       // * Add handlers to the individual elements instead of the container
-       // * Use "click" instead of "change" for checkboxes
-       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
-       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
-
-       return urlConfigContainer;
-}
-
-function toolbarModuleFilterHtml() {
-       var i,
-               moduleFilterHtml = "";
-
-       if ( !modulesList.length ) {
-               return false;
-       }
-
-       modulesList.sort(function( a, b ) {
-               return a.localeCompare( b );
-       });
-
-       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
-               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
-               ">< All Modules ></option>";
-
-       for ( i = 0; i < modulesList.length; i++ ) {
-               moduleFilterHtml += "<option value='" +
-                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
-                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
-                       ">" + escapeText( modulesList[ i ] ) + "</option>";
-       }
-       moduleFilterHtml += "</select>";
-
-       return moduleFilterHtml;
-}
-
-function toolbarModuleFilter() {
-       var toolbar = id( "qunit-testrunner-toolbar" ),
-               moduleFilter = document.createElement( "span" ),
-               moduleFilterHtml = toolbarModuleFilterHtml();
-
-       if ( !moduleFilterHtml ) {
-               return false;
-       }
-
-       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
-       moduleFilter.innerHTML = moduleFilterHtml;
-
-       addEvent( moduleFilter.lastChild, "change", function() {
-               var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
-                       selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
-
-               window.location = QUnit.url({
-                       module: ( selection === "" ) ? undefined : selection,
-
-                       // Remove any existing filters
-                       filter: undefined,
-                       testId: undefined
-               });
-       });
-
-       toolbar.appendChild( moduleFilter );
-}
-
-function appendToolbar() {
-       var toolbar = id( "qunit-testrunner-toolbar" );
-
-       if ( toolbar ) {
-               toolbar.appendChild( toolbarUrlConfigContainer() );
-       }
-}
-
-function appendBanner() {
-       var banner = id( "qunit-banner" );
-
-       if ( banner ) {
-               banner.className = "";
-               banner.innerHTML = "<a href='" +
-                       QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
-                       "'>" + banner.innerHTML + "</a> ";
-       }
-}
-
-function appendTestResults() {
-       var tests = id( "qunit-tests" ),
-               result = id( "qunit-testresult" );
-
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
-
-       if ( tests ) {
-               tests.innerHTML = "";
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
-       }
-}
-
-function storeFixture() {
-       var fixture = id( "qunit-fixture" );
-       if ( fixture ) {
-               config.fixture = fixture.innerHTML;
-       }
-}
-
-function appendUserAgent() {
-       var userAgent = id( "qunit-userAgent" );
-       if ( userAgent ) {
-               userAgent.innerHTML = navigator.userAgent;
-       }
-}
-
-function appendTestsList( modules ) {
-       var i, l, x, z, test, moduleObj;
-
-       for ( i = 0, l = modules.length; i < l; i++ ) {
-               moduleObj = modules[ i ];
-
-               if ( moduleObj.name ) {
-                       modulesList.push( moduleObj.name );
-               }
-
-               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
-                       test = moduleObj.tests[ x ];
-
-                       appendTest( test.name, test.testId, moduleObj.name );
-               }
-       }
-}
-
-function appendTest( name, testId, moduleName ) {
-       var title, rerunTrigger, testBlock, assertList,
-               tests = id( "qunit-tests" );
-
-       if ( !tests ) {
-               return;
-       }
-
-       title = document.createElement( "strong" );
-       title.innerHTML = getNameHtml( name, moduleName );
-
-       rerunTrigger = document.createElement( "a" );
-       rerunTrigger.innerHTML = "Rerun";
-       rerunTrigger.href = QUnit.url({ testId: testId });
-
-       testBlock = document.createElement( "li" );
-       testBlock.appendChild( title );
-       testBlock.appendChild( rerunTrigger );
-       testBlock.id = "qunit-test-output-" + testId;
-
-       assertList = document.createElement( "ol" );
-       assertList.className = "qunit-assert-list";
-
-       testBlock.appendChild( assertList );
-
-       tests.appendChild( testBlock );
-}
-
-// HTML Reporter initialization and load
-QUnit.begin(function( details ) {
-       var qunit = id( "qunit" );
-
-       // Fixture is the only one necessary to run without the #qunit element
-       storeFixture();
-
-       if ( !qunit ) {
-               return;
-       }
-
-       qunit.innerHTML =
-               "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-               "<h2 id='qunit-banner'></h2>" +
-               "<div id='qunit-testrunner-toolbar'></div>" +
-               "<h2 id='qunit-userAgent'></h2>" +
-               "<ol id='qunit-tests'></ol>";
-
-       appendBanner();
-       appendTestResults();
-       appendUserAgent();
-       appendToolbar();
-       appendTestsList( details.modules );
-       toolbarModuleFilter();
-
-       if ( config.hidepassed ) {
-               addClass( qunit.lastChild, "hidepass" );
-       }
-});
-
-QUnit.done(function( details ) {
-       var i, key,
-               banner = id( "qunit-banner" ),
-               tests = id( "qunit-tests" ),
-               html = [
-                       "Tests completed in ",
-                       details.runtime,
-                       " milliseconds.<br />",
-                       "<span class='passed'>",
-                       details.passed,
-                       "</span> assertions of <span class='total'>",
-                       details.total,
-                       "</span> passed, <span class='failed'>",
-                       details.failed,
-                       "</span> failed."
-               ].join( "" );
-
-       if ( banner ) {
-               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
-       }
-
-       if ( tests ) {
-               id( "qunit-testresult" ).innerHTML = html;
-       }
-
-       if ( config.altertitle && defined.document && document.title ) {
-
-               // show âœ– for good, âœ” for bad suite result in title
-               // use escape sequences in case file gets loaded with non-utf-8-charset
-               document.title = [
-                       ( details.failed ? "\u2716" : "\u2714" ),
-                       document.title.replace( /^[\u2714\u2716] /i, "" )
-               ].join( " " );
-       }
-
-       // clear own sessionStorage items if all tests passed
-       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
-               for ( i = 0; i < sessionStorage.length; i++ ) {
-                       key = sessionStorage.key( i++ );
-                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
-                               sessionStorage.removeItem( key );
-                       }
-               }
-       }
-
-       // scroll back to top to show results
-       if ( config.scrolltop && window.scrollTo ) {
-               window.scrollTo( 0, 0 );
-       }
-});
-
-function getNameHtml( name, module ) {
-       var nameHtml = "";
-
-       if ( module ) {
-               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
-       }
-
-       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
-
-       return nameHtml;
-}
-
-QUnit.testStart(function( details ) {
-       var running, testBlock;
-
-       testBlock = id( "qunit-test-output-" + details.testId );
-       if ( testBlock ) {
-               testBlock.className = "running";
-       } else {
-
-               // Report later registered tests
-               appendTest( details.name, details.testId, details.module );
-       }
-
-       running = id( "qunit-testresult" );
-       if ( running ) {
-               running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
-       }
-
-});
-
-QUnit.log(function( details ) {
-       var assertList, assertLi,
-               message, expected, actual,
-               testItem = id( "qunit-test-output-" + details.testId );
-
-       if ( !testItem ) {
-               return;
-       }
-
-       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
-       message = "<span class='test-message'>" + message + "</span>";
-       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
-
-       // pushFailure doesn't provide details.expected
-       // when it calls, it's implicit to also not show expected and diff stuff
-       // Also, we need to check details.expected existence, as it can exist and be undefined
-       if ( !details.result && hasOwn.call( details, "expected" ) ) {
-               expected = escapeText( QUnit.dump.parse( details.expected ) );
-               actual = escapeText( QUnit.dump.parse( details.actual ) );
-               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
-                       expected +
-                       "</pre></td></tr>";
-
-               if ( actual !== expected ) {
-                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
-                               actual + "</pre></td></tr>" +
-                               "<tr class='test-diff'><th>Diff: </th><td><pre>" +
-                               QUnit.diff( expected, actual ) + "</pre></td></tr>";
-               }
-
-               if ( details.source ) {
-                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
-                               escapeText( details.source ) + "</pre></td></tr>";
-               }
-
-               message += "</table>";
-
-       // this occours when pushFailure is set and we have an extracted stack trace
-       } else if ( !details.result && details.source ) {
-               message += "<table>" +
-                       "<tr class='test-source'><th>Source: </th><td><pre>" +
-                       escapeText( details.source ) + "</pre></td></tr>" +
-                       "</table>";
-       }
-
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-       assertLi = document.createElement( "li" );
-       assertLi.className = details.result ? "pass" : "fail";
-       assertLi.innerHTML = message;
-       assertList.appendChild( assertLi );
-});
-
-QUnit.testDone(function( details ) {
-       var testTitle, time, testItem, assertList,
-               good, bad, testCounts, skipped,
-               tests = id( "qunit-tests" );
-
-       if ( !tests ) {
-               return;
-       }
-
-       testItem = id( "qunit-test-output-" + details.testId );
-
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-       good = details.passed;
-       bad = details.failed;
-
-       // store result when possible
-       if ( config.reorder && defined.sessionStorage ) {
-               if ( bad ) {
-                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
-               } else {
-                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
-               }
-       }
-
-       if ( bad === 0 ) {
-               addClass( assertList, "qunit-collapsed" );
-       }
-
-       // testItem.firstChild is the test name
-       testTitle = testItem.firstChild;
-
-       testCounts = bad ?
-               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
-               "";
-
-       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
-               details.assertions.length + ")</b>";
-
-       if ( details.skipped ) {
-               addClass( testItem, "skipped" );
-               skipped = document.createElement( "em" );
-               skipped.className = "qunit-skipped-label";
-               skipped.innerHTML = "skipped";
-               testItem.insertBefore( skipped, testTitle );
-       } else {
-               addEvent( testTitle, "click", function() {
-                       toggleClass( assertList, "qunit-collapsed" );
-               });
-
-               testItem.className = bad ? "fail" : "pass";
-
-               time = document.createElement( "span" );
-               time.className = "runtime";
-               time.innerHTML = details.runtime + " ms";
-               testItem.insertBefore( time, assertList );
-       }
-});
-
-if ( !defined.document || document.readyState === "complete" ) {
-       config.pageLoaded = true;
-       config.autorun = true;
-}
-
-if ( defined.document ) {
-       addEvent( window, "load", QUnit.load );
-}
-
-})();
diff --git a/resources/lib/qunitjs/qunit.css b/resources/lib/qunitjs/qunit.css
new file mode 100644 (file)
index 0000000..0eb0b01
--- /dev/null
@@ -0,0 +1,280 @@
+/*!
+ * QUnit 1.17.1
+ * http://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-01-20T19:39Z
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+       font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+       margin: 0;
+       padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+       padding: 0.5em 0 0.5em 1em;
+
+       color: #8699A4;
+       background-color: #0D3349;
+
+       font-size: 1.5em;
+       line-height: 1em;
+       font-weight: 400;
+
+       border-radius: 5px 5px 0 0;
+}
+
+#qunit-header a {
+       text-decoration: none;
+       color: #C2CCD1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+       color: #FFF;
+}
+
+#qunit-testrunner-toolbar label {
+       display: inline-block;
+       padding: 0 0.5em 0 0.1em;
+}
+
+#qunit-banner {
+       height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+       padding: 0.5em 1em 0.5em 1em;
+       color: #5E740B;
+       background-color: #EEE;
+       overflow: hidden;
+}
+
+#qunit-userAgent {
+       padding: 0.5em 1em 0.5em 1em;
+       background-color: #2B81AF;
+       color: #FFF;
+       text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+       float: right;
+       padding: 0.2em;
+}
+
+.qunit-url-config {
+       display: inline-block;
+       padding: 0.1em;
+}
+
+.qunit-filter {
+       display: block;
+       float: right;
+       margin-left: 1em;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+       list-style-position: inside;
+}
+
+#qunit-tests li {
+       padding: 0.4em 1em 0.4em 1em;
+       border-bottom: 1px solid #FFF;
+       list-style-position: inside;
+}
+
+#qunit-tests > li {
+       display: none;
+}
+
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped {
+       display: list-item;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass {
+       display: none;
+}
+
+#qunit-tests li strong {
+       cursor: pointer;
+}
+
+#qunit-tests li.skipped strong {
+       cursor: default;
+}
+
+#qunit-tests li a {
+       padding: 0.5em;
+       color: #C2CCD1;
+       text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+       color: #000;
+}
+
+#qunit-tests li .runtime {
+       float: right;
+       font-size: smaller;
+}
+
+.qunit-assert-list {
+       margin-top: 0.5em;
+       padding: 0.5em;
+
+       background-color: #FFF;
+
+       border-radius: 5px;
+}
+
+.qunit-collapsed {
+       display: none;
+}
+
+#qunit-tests table {
+       border-collapse: collapse;
+       margin-top: 0.2em;
+}
+
+#qunit-tests th {
+       text-align: right;
+       vertical-align: top;
+       padding: 0 0.5em 0 0;
+}
+
+#qunit-tests td {
+       vertical-align: top;
+}
+
+#qunit-tests pre {
+       margin: 0;
+       white-space: pre-wrap;
+       word-wrap: break-word;
+}
+
+#qunit-tests del {
+       background-color: #E0F2BE;
+       color: #374E0C;
+       text-decoration: none;
+}
+
+#qunit-tests ins {
+       background-color: #FFCACA;
+       color: #500;
+       text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts                       { color: #000; }
+#qunit-tests b.passed                       { color: #5E740B; }
+#qunit-tests b.failed                       { color: #710909; }
+
+#qunit-tests li li {
+       padding: 5px;
+       background-color: #FFF;
+       border-bottom: none;
+       list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+       color: #3C510C;
+       background-color: #FFF;
+       border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name               { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected           { color: #999; }
+
+#qunit-banner.qunit-pass                    { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+       color: #710909;
+       background-color: #FFF;
+       border-left: 10px solid #EE5757;
+       white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+       border-radius: 0 0 5px 5px;
+}
+
+#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name             { color: #000; }
+
+#qunit-tests .fail .test-actual             { color: #EE5757; }
+#qunit-tests .fail .test-expected           { color: #008000; }
+
+#qunit-banner.qunit-fail                    { background-color: #EE5757; }
+
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+       background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-skipped-label {
+       background-color: #F4FF77;
+       display: inline-block;
+       font-style: normal;
+       color: #366097;
+       line-height: 1.8em;
+       padding: 0 0.5em;
+       margin: -0.4em 0.4em -0.4em 0;
+}
+
+/** Result */
+
+#qunit-testresult {
+       padding: 0.5em 1em 0.5em 1em;
+
+       color: #2B81AF;
+       background-color: #D2E0E6;
+
+       border-bottom: 1px solid #FFF;
+}
+#qunit-testresult .module-name {
+       font-weight: 700;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+       position: absolute;
+       top: -10000px;
+       left: -10000px;
+       width: 1000px;
+       height: 1000px;
+}
diff --git a/resources/lib/qunitjs/qunit.js b/resources/lib/qunitjs/qunit.js
new file mode 100644 (file)
index 0000000..006ca47
--- /dev/null
@@ -0,0 +1,2875 @@
+/*!
+ * QUnit 1.17.1
+ * http://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-01-20T19:39Z
+ */
+
+(function( window ) {
+
+var QUnit,
+       config,
+       onErrorFnPrev,
+       loggingCallbacks = {},
+       fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
+       toString = Object.prototype.toString,
+       hasOwn = Object.prototype.hasOwnProperty,
+       // Keep a local reference to Date (GH-283)
+       Date = window.Date,
+       now = Date.now || function() {
+               return new Date().getTime();
+       },
+       globalStartCalled = false,
+       runStarted = false,
+       setTimeout = window.setTimeout,
+       clearTimeout = window.clearTimeout,
+       defined = {
+               document: window.document !== undefined,
+               setTimeout: window.setTimeout !== undefined,
+               sessionStorage: (function() {
+                       var x = "qunit-test-string";
+                       try {
+                               sessionStorage.setItem( x, x );
+                               sessionStorage.removeItem( x );
+                               return true;
+                       } catch ( e ) {
+                               return false;
+                       }
+               }())
+       },
+       /**
+        * Provides a normalized error string, correcting an issue
+        * with IE 7 (and prior) where Error.prototype.toString is
+        * not properly implemented
+        *
+        * Based on http://es5.github.com/#x15.11.4.4
+        *
+        * @param {String|Error} error
+        * @return {String} error message
+        */
+       errorString = function( error ) {
+               var name, message,
+                       errorString = error.toString();
+               if ( errorString.substring( 0, 7 ) === "[object" ) {
+                       name = error.name ? error.name.toString() : "Error";
+                       message = error.message ? error.message.toString() : "";
+                       if ( name && message ) {
+                               return name + ": " + message;
+                       } else if ( name ) {
+                               return name;
+                       } else if ( message ) {
+                               return message;
+                       } else {
+                               return "Error";
+                       }
+               } else {
+                       return errorString;
+               }
+       },
+       /**
+        * Makes a clone of an object using only Array or Object as base,
+        * and copies over the own enumerable properties.
+        *
+        * @param {Object} obj
+        * @return {Object} New object with only the own properties (recursively).
+        */
+       objectValues = function( obj ) {
+               var key, val,
+                       vals = QUnit.is( "array", obj ) ? [] : {};
+               for ( key in obj ) {
+                       if ( hasOwn.call( obj, key ) ) {
+                               val = obj[ key ];
+                               vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
+                       }
+               }
+               return vals;
+       };
+
+QUnit = {};
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+       // The queue of tests to run
+       queue: [],
+
+       // block until document ready
+       blocking: true,
+
+       // by default, run previously failed tests first
+       // very useful in combination with "Hide passed tests" checked
+       reorder: true,
+
+       // by default, modify document.title when suite is done
+       altertitle: true,
+
+       // by default, scroll to top of the page when suite is done
+       scrolltop: true,
+
+       // when enabled, all tests must call expect()
+       requireExpects: false,
+
+       // add checkboxes that are persisted in the query-string
+       // when enabled, the id is set to `true` as a `QUnit.config` property
+       urlConfig: [
+               {
+                       id: "hidepassed",
+                       label: "Hide passed tests",
+                       tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+               },
+               {
+                       id: "noglobals",
+                       label: "Check for Globals",
+                       tooltip: "Enabling this will test if any test introduces new properties on the " +
+                               "`window` object. Stored as query-strings."
+               },
+               {
+                       id: "notrycatch",
+                       label: "No try-catch",
+                       tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+                               "exceptions in IE reasonable. Stored as query-strings."
+               }
+       ],
+
+       // Set of all modules.
+       modules: [],
+
+       // The first unnamed module
+       currentModule: {
+               name: "",
+               tests: []
+       },
+
+       callbacks: {}
+};
+
+// Push a loose unnamed module to the modules collection
+config.modules.push( config.currentModule );
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+       var i, current,
+               location = window.location || { search: "", protocol: "file:" },
+               params = location.search.slice( 1 ).split( "&" ),
+               length = params.length,
+               urlParams = {};
+
+       if ( params[ 0 ] ) {
+               for ( i = 0; i < length; i++ ) {
+                       current = params[ i ].split( "=" );
+                       current[ 0 ] = decodeURIComponent( current[ 0 ] );
+
+                       // allow just a key to turn on a flag, e.g., test.html?noglobals
+                       current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+                       if ( urlParams[ current[ 0 ] ] ) {
+                               urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
+                       } else {
+                               urlParams[ current[ 0 ] ] = current[ 1 ];
+                       }
+               }
+       }
+
+       if ( urlParams.filter === true ) {
+               delete urlParams.filter;
+       }
+
+       QUnit.urlParams = urlParams;
+
+       // String search anywhere in moduleName+testName
+       config.filter = urlParams.filter;
+
+       config.testId = [];
+       if ( urlParams.testId ) {
+
+               // Ensure that urlParams.testId is an array
+               urlParams.testId = [].concat( urlParams.testId );
+               for ( i = 0; i < urlParams.testId.length; i++ ) {
+                       config.testId.push( urlParams.testId[ i ] );
+               }
+       }
+
+       // Figure out if we're running the tests from a server or not
+       QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+extend( QUnit, {
+
+       // call on start of module test to prepend name to all tests
+       module: function( name, testEnvironment ) {
+               var currentModule = {
+                       name: name,
+                       testEnvironment: testEnvironment,
+                       tests: []
+               };
+
+               // DEPRECATED: handles setup/teardown functions,
+               // beforeEach and afterEach should be used instead
+               if ( testEnvironment && testEnvironment.setup ) {
+                       testEnvironment.beforeEach = testEnvironment.setup;
+                       delete testEnvironment.setup;
+               }
+               if ( testEnvironment && testEnvironment.teardown ) {
+                       testEnvironment.afterEach = testEnvironment.teardown;
+                       delete testEnvironment.teardown;
+               }
+
+               config.modules.push( currentModule );
+               config.currentModule = currentModule;
+       },
+
+       // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
+       asyncTest: function( testName, expected, callback ) {
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = null;
+               }
+
+               QUnit.test( testName, expected, callback, true );
+       },
+
+       test: function( testName, expected, callback, async ) {
+               var test;
+
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = null;
+               }
+
+               test = new Test({
+                       testName: testName,
+                       expected: expected,
+                       async: async,
+                       callback: callback
+               });
+
+               test.queue();
+       },
+
+       skip: function( testName ) {
+               var test = new Test({
+                       testName: testName,
+                       skip: true
+               });
+
+               test.queue();
+       },
+
+       // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
+       // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
+       start: function( count ) {
+               var globalStartAlreadyCalled = globalStartCalled;
+
+               if ( !config.current ) {
+                       globalStartCalled = true;
+
+                       if ( runStarted ) {
+                               throw new Error( "Called start() outside of a test context while already started" );
+                       } else if ( globalStartAlreadyCalled || count > 1 ) {
+                               throw new Error( "Called start() outside of a test context too many times" );
+                       } else if ( config.autostart ) {
+                               throw new Error( "Called start() outside of a test context when " +
+                                       "QUnit.config.autostart was true" );
+                       } else if ( !config.pageLoaded ) {
+
+                               // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+                               config.autostart = true;
+                               return;
+                       }
+               } else {
+
+                       // If a test is running, adjust its semaphore
+                       config.current.semaphore -= count || 1;
+
+                       // Don't start until equal number of stop-calls
+                       if ( config.current.semaphore > 0 ) {
+                               return;
+                       }
+
+                       // throw an Error if start is called more often than stop
+                       if ( config.current.semaphore < 0 ) {
+                               config.current.semaphore = 0;
+
+                               QUnit.pushFailure(
+                                       "Called start() while already started (test's semaphore was 0 already)",
+                                       sourceFromStacktrace( 2 )
+                               );
+                               return;
+                       }
+               }
+
+               resumeProcessing();
+       },
+
+       // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
+       stop: function( count ) {
+
+               // If there isn't a test running, don't allow QUnit.stop() to be called
+               if ( !config.current ) {
+                       throw new Error( "Called stop() outside of a test context" );
+               }
+
+               // If a test is running, adjust its semaphore
+               config.current.semaphore += count || 1;
+
+               pauseProcessing();
+       },
+
+       config: config,
+
+       // Safe object type checking
+       is: function( type, obj ) {
+               return QUnit.objectType( obj ) === type;
+       },
+
+       objectType: function( obj ) {
+               if ( typeof obj === "undefined" ) {
+                       return "undefined";
+               }
+
+               // Consider: typeof null === object
+               if ( obj === null ) {
+                       return "null";
+               }
+
+               var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
+                       type = match && match[ 1 ] || "";
+
+               switch ( type ) {
+                       case "Number":
+                               if ( isNaN( obj ) ) {
+                                       return "nan";
+                               }
+                               return "number";
+                       case "String":
+                       case "Boolean":
+                       case "Array":
+                       case "Date":
+                       case "RegExp":
+                       case "Function":
+                               return type.toLowerCase();
+               }
+               if ( typeof obj === "object" ) {
+                       return "object";
+               }
+               return undefined;
+       },
+
+       extend: extend,
+
+       load: function() {
+               config.pageLoaded = true;
+
+               // Initialize the configuration options
+               extend( config, {
+                       stats: { all: 0, bad: 0 },
+                       moduleStats: { all: 0, bad: 0 },
+                       started: 0,
+                       updateRate: 1000,
+                       autostart: true,
+                       filter: ""
+               }, true );
+
+               config.blocking = false;
+
+               if ( config.autostart ) {
+                       resumeProcessing();
+               }
+       }
+});
+
+// Register logging callbacks
+(function() {
+       var i, l, key,
+               callbacks = [ "begin", "done", "log", "testStart", "testDone",
+                       "moduleStart", "moduleDone" ];
+
+       function registerLoggingCallback( key ) {
+               var loggingCallback = function( callback ) {
+                       if ( QUnit.objectType( callback ) !== "function" ) {
+                               throw new Error(
+                                       "QUnit logging methods require a callback function as their first parameters."
+                               );
+                       }
+
+                       config.callbacks[ key ].push( callback );
+               };
+
+               // DEPRECATED: This will be removed on QUnit 2.0.0+
+               // Stores the registered functions allowing restoring
+               // at verifyLoggingCallbacks() if modified
+               loggingCallbacks[ key ] = loggingCallback;
+
+               return loggingCallback;
+       }
+
+       for ( i = 0, l = callbacks.length; i < l; i++ ) {
+               key = callbacks[ i ];
+
+               // Initialize key collection of logging callback
+               if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
+                       config.callbacks[ key ] = [];
+               }
+
+               QUnit[ key ] = registerLoggingCallback( key );
+       }
+})();
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will suppress the default browser handler,
+// returning false will let it run.
+window.onerror = function( error, filePath, linerNr ) {
+       var ret = false;
+       if ( onErrorFnPrev ) {
+               ret = onErrorFnPrev( error, filePath, linerNr );
+       }
+
+       // Treat return value as window.onerror itself does,
+       // Only do our handling if not suppressed.
+       if ( ret !== true ) {
+               if ( QUnit.config.current ) {
+                       if ( QUnit.config.current.ignoreGlobalErrors ) {
+                               return true;
+                       }
+                       QUnit.pushFailure( error, filePath + ":" + linerNr );
+               } else {
+                       QUnit.test( "global failure", extend(function() {
+                               QUnit.pushFailure( error, filePath + ":" + linerNr );
+                       }, { validTest: true } ) );
+               }
+               return false;
+       }
+
+       return ret;
+};
+
+function done() {
+       var runtime, passed;
+
+       config.autorun = true;
+
+       // Log the last module results
+       if ( config.previousModule ) {
+               runLoggingCallbacks( "moduleDone", {
+                       name: config.previousModule.name,
+                       tests: config.previousModule.tests,
+                       failed: config.moduleStats.bad,
+                       passed: config.moduleStats.all - config.moduleStats.bad,
+                       total: config.moduleStats.all,
+                       runtime: now() - config.moduleStats.started
+               });
+       }
+       delete config.previousModule;
+
+       runtime = now() - config.started;
+       passed = config.stats.all - config.stats.bad;
+
+       runLoggingCallbacks( "done", {
+               failed: config.stats.bad,
+               passed: passed,
+               total: config.stats.all,
+               runtime: runtime
+       });
+}
+
+// Doesn't support IE6 to IE9
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+       offset = offset === undefined ? 4 : offset;
+
+       var stack, include, i;
+
+       if ( e.stacktrace ) {
+
+               // Opera 12.x
+               return e.stacktrace.split( "\n" )[ offset + 3 ];
+       } else if ( e.stack ) {
+
+               // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
+               stack = e.stack.split( "\n" );
+               if ( /^error$/i.test( stack[ 0 ] ) ) {
+                       stack.shift();
+               }
+               if ( fileName ) {
+                       include = [];
+                       for ( i = offset; i < stack.length; i++ ) {
+                               if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+                                       break;
+                               }
+                               include.push( stack[ i ] );
+                       }
+                       if ( include.length ) {
+                               return include.join( "\n" );
+                       }
+               }
+               return stack[ offset ];
+       } else if ( e.sourceURL ) {
+
+               // Safari < 6
+               // exclude useless self-reference for generated Error objects
+               if ( /qunit.js$/.test( e.sourceURL ) ) {
+                       return;
+               }
+
+               // for actual exceptions, this is useful
+               return e.sourceURL + ":" + e.line;
+       }
+}
+
+function sourceFromStacktrace( offset ) {
+       var e = new Error();
+       if ( !e.stack ) {
+               try {
+                       throw e;
+               } catch ( err ) {
+                       // This should already be true in most browsers
+                       e = err;
+               }
+       }
+       return extractStacktrace( e, offset );
+}
+
+function synchronize( callback, last ) {
+       if ( QUnit.objectType( callback ) === "array" ) {
+               while ( callback.length ) {
+                       synchronize( callback.shift() );
+               }
+               return;
+       }
+       config.queue.push( callback );
+
+       if ( config.autorun && !config.blocking ) {
+               process( last );
+       }
+}
+
+function process( last ) {
+       function next() {
+               process( last );
+       }
+       var start = now();
+       config.depth = ( config.depth || 0 ) + 1;
+
+       while ( config.queue.length && !config.blocking ) {
+               if ( !defined.setTimeout || config.updateRate <= 0 ||
+                               ( ( now() - start ) < config.updateRate ) ) {
+                       if ( config.current ) {
+
+                               // Reset async tracking for each phase of the Test lifecycle
+                               config.current.usedAsync = false;
+                       }
+                       config.queue.shift()();
+               } else {
+                       setTimeout( next, 13 );
+                       break;
+               }
+       }
+       config.depth--;
+       if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+               done();
+       }
+}
+
+function begin() {
+       var i, l,
+               modulesLog = [];
+
+       // If the test run hasn't officially begun yet
+       if ( !config.started ) {
+
+               // Record the time of the test run's beginning
+               config.started = now();
+
+               verifyLoggingCallbacks();
+
+               // Delete the loose unnamed module if unused.
+               if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
+                       config.modules.shift();
+               }
+
+               // Avoid unnecessary information by not logging modules' test environments
+               for ( i = 0, l = config.modules.length; i < l; i++ ) {
+                       modulesLog.push({
+                               name: config.modules[ i ].name,
+                               tests: config.modules[ i ].tests
+                       });
+               }
+
+               // The test run is officially beginning now
+               runLoggingCallbacks( "begin", {
+                       totalTests: Test.count,
+                       modules: modulesLog
+               });
+       }
+
+       config.blocking = false;
+       process( true );
+}
+
+function resumeProcessing() {
+       runStarted = true;
+
+       // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
+       if ( defined.setTimeout ) {
+               setTimeout(function() {
+                       if ( config.current && config.current.semaphore > 0 ) {
+                               return;
+                       }
+                       if ( config.timeout ) {
+                               clearTimeout( config.timeout );
+                       }
+
+                       begin();
+               }, 13 );
+       } else {
+               begin();
+       }
+}
+
+function pauseProcessing() {
+       config.blocking = true;
+
+       if ( config.testTimeout && defined.setTimeout ) {
+               clearTimeout( config.timeout );
+               config.timeout = setTimeout(function() {
+                       if ( config.current ) {
+                               config.current.semaphore = 0;
+                               QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
+                       } else {
+                               throw new Error( "Test timed out" );
+                       }
+                       resumeProcessing();
+               }, config.testTimeout );
+       }
+}
+
+function saveGlobal() {
+       config.pollution = [];
+
+       if ( config.noglobals ) {
+               for ( var key in window ) {
+                       if ( hasOwn.call( window, key ) ) {
+                               // in Opera sometimes DOM element ids show up here, ignore them
+                               if ( /^qunit-test-output/.test( key ) ) {
+                                       continue;
+                               }
+                               config.pollution.push( key );
+                       }
+               }
+       }
+}
+
+function checkPollution() {
+       var newGlobals,
+               deletedGlobals,
+               old = config.pollution;
+
+       saveGlobal();
+
+       newGlobals = diff( config.pollution, old );
+       if ( newGlobals.length > 0 ) {
+               QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
+       }
+
+       deletedGlobals = diff( old, config.pollution );
+       if ( deletedGlobals.length > 0 ) {
+               QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
+       }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+       var i, j,
+               result = a.slice();
+
+       for ( i = 0; i < result.length; i++ ) {
+               for ( j = 0; j < b.length; j++ ) {
+                       if ( result[ i ] === b[ j ] ) {
+                               result.splice( i, 1 );
+                               i--;
+                               break;
+                       }
+               }
+       }
+       return result;
+}
+
+function extend( a, b, undefOnly ) {
+       for ( var prop in b ) {
+               if ( hasOwn.call( b, prop ) ) {
+
+                       // Avoid "Member not found" error in IE8 caused by messing with window.constructor
+                       if ( !( prop === "constructor" && a === window ) ) {
+                               if ( b[ prop ] === undefined ) {
+                                       delete a[ prop ];
+                               } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
+                                       a[ prop ] = b[ prop ];
+                               }
+                       }
+               }
+       }
+
+       return a;
+}
+
+function runLoggingCallbacks( key, args ) {
+       var i, l, callbacks;
+
+       callbacks = config.callbacks[ key ];
+       for ( i = 0, l = callbacks.length; i < l; i++ ) {
+               callbacks[ i ]( args );
+       }
+}
+
+// DEPRECATED: This will be removed on 2.0.0+
+// This function verifies if the loggingCallbacks were modified by the user
+// If so, it will restore it, assign the given callback and print a console warning
+function verifyLoggingCallbacks() {
+       var loggingCallback, userCallback;
+
+       for ( loggingCallback in loggingCallbacks ) {
+               if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
+
+                       userCallback = QUnit[ loggingCallback ];
+
+                       // Restore the callback function
+                       QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
+
+                       // Assign the deprecated given callback
+                       QUnit[ loggingCallback ]( userCallback );
+
+                       if ( window.console && window.console.warn ) {
+                               window.console.warn(
+                                       "QUnit." + loggingCallback + " was replaced with a new value.\n" +
+                                       "Please, check out the documentation on how to apply logging callbacks.\n" +
+                                       "Reference: http://api.qunitjs.com/category/callbacks/"
+                               );
+                       }
+               }
+       }
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+       if ( array.indexOf ) {
+               return array.indexOf( elem );
+       }
+
+       for ( var i = 0, length = array.length; i < length; i++ ) {
+               if ( array[ i ] === elem ) {
+                       return i;
+               }
+       }
+
+       return -1;
+}
+
+function Test( settings ) {
+       var i, l;
+
+       ++Test.count;
+
+       extend( this, settings );
+       this.assertions = [];
+       this.semaphore = 0;
+       this.usedAsync = false;
+       this.module = config.currentModule;
+       this.stack = sourceFromStacktrace( 3 );
+
+       // Register unique strings
+       for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
+               if ( this.module.tests[ i ].name === this.testName ) {
+                       this.testName += " ";
+               }
+       }
+
+       this.testId = generateHash( this.module.name, this.testName );
+
+       this.module.tests.push({
+               name: this.testName,
+               testId: this.testId
+       });
+
+       if ( settings.skip ) {
+
+               // Skipped tests will fully ignore any sent callback
+               this.callback = function() {};
+               this.async = false;
+               this.expected = 0;
+       } else {
+               this.assert = new Assert( this );
+       }
+}
+
+Test.count = 0;
+
+Test.prototype = {
+       before: function() {
+               if (
+
+                       // Emit moduleStart when we're switching from one module to another
+                       this.module !== config.previousModule ||
+
+                               // They could be equal (both undefined) but if the previousModule property doesn't
+                               // yet exist it means this is the first test in a suite that isn't wrapped in a
+                               // module, in which case we'll just emit a moduleStart event for 'undefined'.
+                               // Without this, reporters can get testStart before moduleStart  which is a problem.
+                               !hasOwn.call( config, "previousModule" )
+               ) {
+                       if ( hasOwn.call( config, "previousModule" ) ) {
+                               runLoggingCallbacks( "moduleDone", {
+                                       name: config.previousModule.name,
+                                       tests: config.previousModule.tests,
+                                       failed: config.moduleStats.bad,
+                                       passed: config.moduleStats.all - config.moduleStats.bad,
+                                       total: config.moduleStats.all,
+                                       runtime: now() - config.moduleStats.started
+                               });
+                       }
+                       config.previousModule = this.module;
+                       config.moduleStats = { all: 0, bad: 0, started: now() };
+                       runLoggingCallbacks( "moduleStart", {
+                               name: this.module.name,
+                               tests: this.module.tests
+                       });
+               }
+
+               config.current = this;
+
+               this.testEnvironment = extend( {}, this.module.testEnvironment );
+               delete this.testEnvironment.beforeEach;
+               delete this.testEnvironment.afterEach;
+
+               this.started = now();
+               runLoggingCallbacks( "testStart", {
+                       name: this.testName,
+                       module: this.module.name,
+                       testId: this.testId
+               });
+
+               if ( !config.pollution ) {
+                       saveGlobal();
+               }
+       },
+
+       run: function() {
+               var promise;
+
+               config.current = this;
+
+               if ( this.async ) {
+                       QUnit.stop();
+               }
+
+               this.callbackStarted = now();
+
+               if ( config.notrycatch ) {
+                       promise = this.callback.call( this.testEnvironment, this.assert );
+                       this.resolvePromise( promise );
+                       return;
+               }
+
+               try {
+                       promise = this.callback.call( this.testEnvironment, this.assert );
+                       this.resolvePromise( promise );
+               } catch ( e ) {
+                       this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
+                               this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+
+                       // else next test will carry the responsibility
+                       saveGlobal();
+
+                       // Restart the tests if they're blocking
+                       if ( config.blocking ) {
+                               QUnit.start();
+                       }
+               }
+       },
+
+       after: function() {
+               checkPollution();
+       },
+
+       queueHook: function( hook, hookName ) {
+               var promise,
+                       test = this;
+               return function runHook() {
+                       config.current = test;
+                       if ( config.notrycatch ) {
+                               promise = hook.call( test.testEnvironment, test.assert );
+                               test.resolvePromise( promise, hookName );
+                               return;
+                       }
+                       try {
+                               promise = hook.call( test.testEnvironment, test.assert );
+                               test.resolvePromise( promise, hookName );
+                       } catch ( error ) {
+                               test.pushFailure( hookName + " failed on " + test.testName + ": " +
+                                       ( error.message || error ), extractStacktrace( error, 0 ) );
+                       }
+               };
+       },
+
+       // Currently only used for module level hooks, can be used to add global level ones
+       hooks: function( handler ) {
+               var hooks = [];
+
+               // Hooks are ignored on skipped tests
+               if ( this.skip ) {
+                       return hooks;
+               }
+
+               if ( this.module.testEnvironment &&
+                               QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
+                       hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
+               }
+
+               return hooks;
+       },
+
+       finish: function() {
+               config.current = this;
+               if ( config.requireExpects && this.expected === null ) {
+                       this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
+                               "not called.", this.stack );
+               } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+                       this.pushFailure( "Expected " + this.expected + " assertions, but " +
+                               this.assertions.length + " were run", this.stack );
+               } else if ( this.expected === null && !this.assertions.length ) {
+                       this.pushFailure( "Expected at least one assertion, but none were run - call " +
+                               "expect(0) to accept zero assertions.", this.stack );
+               }
+
+               var i,
+                       bad = 0;
+
+               this.runtime = now() - this.started;
+               config.stats.all += this.assertions.length;
+               config.moduleStats.all += this.assertions.length;
+
+               for ( i = 0; i < this.assertions.length; i++ ) {
+                       if ( !this.assertions[ i ].result ) {
+                               bad++;
+                               config.stats.bad++;
+                               config.moduleStats.bad++;
+                       }
+               }
+
+               runLoggingCallbacks( "testDone", {
+                       name: this.testName,
+                       module: this.module.name,
+                       skipped: !!this.skip,
+                       failed: bad,
+                       passed: this.assertions.length - bad,
+                       total: this.assertions.length,
+                       runtime: this.runtime,
+
+                       // HTML Reporter use
+                       assertions: this.assertions,
+                       testId: this.testId,
+
+                       // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
+                       duration: this.runtime
+               });
+
+               // QUnit.reset() is deprecated and will be replaced for a new
+               // fixture reset function on QUnit 2.0/2.1.
+               // It's still called here for backwards compatibility handling
+               QUnit.reset();
+
+               config.current = undefined;
+       },
+
+       queue: function() {
+               var bad,
+                       test = this;
+
+               if ( !this.valid() ) {
+                       return;
+               }
+
+               function run() {
+
+                       // each of these can by async
+                       synchronize([
+                               function() {
+                                       test.before();
+                               },
+
+                               test.hooks( "beforeEach" ),
+
+                               function() {
+                                       test.run();
+                               },
+
+                               test.hooks( "afterEach" ).reverse(),
+
+                               function() {
+                                       test.after();
+                               },
+                               function() {
+                                       test.finish();
+                               }
+                       ]);
+               }
+
+               // `bad` initialized at top of scope
+               // defer when previous test run passed, if storage is available
+               bad = QUnit.config.reorder && defined.sessionStorage &&
+                               +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
+
+               if ( bad ) {
+                       run();
+               } else {
+                       synchronize( run, true );
+               }
+       },
+
+       push: function( result, actual, expected, message ) {
+               var source,
+                       details = {
+                               module: this.module.name,
+                               name: this.testName,
+                               result: result,
+                               message: message,
+                               actual: actual,
+                               expected: expected,
+                               testId: this.testId,
+                               runtime: now() - this.started
+                       };
+
+               if ( !result ) {
+                       source = sourceFromStacktrace();
+
+                       if ( source ) {
+                               details.source = source;
+                       }
+               }
+
+               runLoggingCallbacks( "log", details );
+
+               this.assertions.push({
+                       result: !!result,
+                       message: message
+               });
+       },
+
+       pushFailure: function( message, source, actual ) {
+               if ( !this instanceof Test ) {
+                       throw new Error( "pushFailure() assertion outside test context, was " +
+                               sourceFromStacktrace( 2 ) );
+               }
+
+               var details = {
+                               module: this.module.name,
+                               name: this.testName,
+                               result: false,
+                               message: message || "error",
+                               actual: actual || null,
+                               testId: this.testId,
+                               runtime: now() - this.started
+                       };
+
+               if ( source ) {
+                       details.source = source;
+               }
+
+               runLoggingCallbacks( "log", details );
+
+               this.assertions.push({
+                       result: false,
+                       message: message
+               });
+       },
+
+       resolvePromise: function( promise, phase ) {
+               var then, message,
+                       test = this;
+               if ( promise != null ) {
+                       then = promise.then;
+                       if ( QUnit.objectType( then ) === "function" ) {
+                               QUnit.stop();
+                               then.call(
+                                       promise,
+                                       QUnit.start,
+                                       function( error ) {
+                                               message = "Promise rejected " +
+                                                       ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
+                                                       " " + test.testName + ": " + ( error.message || error );
+                                               test.pushFailure( message, extractStacktrace( error, 0 ) );
+
+                                               // else next test will carry the responsibility
+                                               saveGlobal();
+
+                                               // Unblock
+                                               QUnit.start();
+                                       }
+                               );
+                       }
+               }
+       },
+
+       valid: function() {
+               var include,
+                       filter = config.filter,
+                       module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+                       fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
+
+               // Internally-generated tests are always valid
+               if ( this.callback && this.callback.validTest ) {
+                       return true;
+               }
+
+               if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+                       return false;
+               }
+
+               if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
+                       return false;
+               }
+
+               if ( !filter ) {
+                       return true;
+               }
+
+               include = filter.charAt( 0 ) !== "!";
+               if ( !include ) {
+                       filter = filter.toLowerCase().slice( 1 );
+               }
+
+               // If the filter matches, we need to honour include
+               if ( fullName.indexOf( filter ) !== -1 ) {
+                       return include;
+               }
+
+               // Otherwise, do the opposite
+               return !include;
+       }
+
+};
+
+// Resets the test setup. Useful for tests that modify the DOM.
+/*
+DEPRECATED: Use multiple tests instead of resetting inside a test.
+Use testStart or testDone for custom cleanup.
+This method will throw an error in 2.0, and will be removed in 2.1
+*/
+QUnit.reset = function() {
+
+       // Return on non-browser environments
+       // This is necessary to not break on node tests
+       if ( typeof window === "undefined" ) {
+               return;
+       }
+
+       var fixture = defined.document && document.getElementById &&
+                       document.getElementById( "qunit-fixture" );
+
+       if ( fixture ) {
+               fixture.innerHTML = config.fixture;
+       }
+};
+
+QUnit.pushFailure = function() {
+       if ( !QUnit.config.current ) {
+               throw new Error( "pushFailure() assertion outside test context, in " +
+                       sourceFromStacktrace( 2 ) );
+       }
+
+       // Gets current test obj
+       var currentTest = QUnit.config.current;
+
+       return currentTest.pushFailure.apply( currentTest, arguments );
+};
+
+// Based on Java's String.hashCode, a simple but not
+// rigorously collision resistant hashing function
+function generateHash( module, testName ) {
+       var hex,
+               i = 0,
+               hash = 0,
+               str = module + "\x1C" + testName,
+               len = str.length;
+
+       for ( ; i < len; i++ ) {
+               hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
+               hash |= 0;
+       }
+
+       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+       // strictly necessary but increases user understanding that the id is a SHA-like hash
+       hex = ( 0x100000000 + hash ).toString( 16 );
+       if ( hex.length < 8 ) {
+               hex = "0000000" + hex;
+       }
+
+       return hex.slice( -8 );
+}
+
+function Assert( testContext ) {
+       this.test = testContext;
+}
+
+// Assert helpers
+QUnit.assert = Assert.prototype = {
+
+       // Specify the number of expected assertions to guarantee that failed test
+       // (no assertions are run at all) don't slip through.
+       expect: function( asserts ) {
+               if ( arguments.length === 1 ) {
+                       this.test.expected = asserts;
+               } else {
+                       return this.test.expected;
+               }
+       },
+
+       // Increment this Test's semaphore counter, then return a single-use function that
+       // decrements that counter a maximum of once.
+       async: function() {
+               var test = this.test,
+                       popped = false;
+
+               test.semaphore += 1;
+               test.usedAsync = true;
+               pauseProcessing();
+
+               return function done() {
+                       if ( !popped ) {
+                               test.semaphore -= 1;
+                               popped = true;
+                               resumeProcessing();
+                       } else {
+                               test.pushFailure( "Called the callback returned from `assert.async` more than once",
+                                       sourceFromStacktrace( 2 ) );
+                       }
+               };
+       },
+
+       // Exports test.push() to the user API
+       push: function( /* result, actual, expected, message */ ) {
+               var assert = this,
+                       currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
+
+               // Backwards compatibility fix.
+               // Allows the direct use of global exported assertions and QUnit.assert.*
+               // Although, it's use is not recommended as it can leak assertions
+               // to other tests from async tests, because we only get a reference to the current test,
+               // not exactly the test where assertion were intended to be called.
+               if ( !currentTest ) {
+                       throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
+               }
+
+               if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
+                       currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
+                               sourceFromStacktrace( 2 ) );
+
+                       // Allow this assertion to continue running anyway...
+               }
+
+               if ( !( assert instanceof Assert ) ) {
+                       assert = currentTest.assert;
+               }
+               return assert.test.push.apply( assert.test, arguments );
+       },
+
+       /**
+        * Asserts rough true-ish result.
+        * @name ok
+        * @function
+        * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+        */
+       ok: function( result, message ) {
+               message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
+                       QUnit.dump.parse( result ) );
+               this.push( !!result, result, true, message );
+       },
+
+       /**
+        * Assert that the first two arguments are equal, with an optional message.
+        * Prints out both actual and expected values.
+        * @name equal
+        * @function
+        * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
+        */
+       equal: function( actual, expected, message ) {
+               /*jshint eqeqeq:false */
+               this.push( expected == actual, actual, expected, message );
+       },
+
+       /**
+        * @name notEqual
+        * @function
+        */
+       notEqual: function( actual, expected, message ) {
+               /*jshint eqeqeq:false */
+               this.push( expected != actual, actual, expected, message );
+       },
+
+       /**
+        * @name propEqual
+        * @function
+        */
+       propEqual: function( actual, expected, message ) {
+               actual = objectValues( actual );
+               expected = objectValues( expected );
+               this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+       },
+
+       /**
+        * @name notPropEqual
+        * @function
+        */
+       notPropEqual: function( actual, expected, message ) {
+               actual = objectValues( actual );
+               expected = objectValues( expected );
+               this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+       },
+
+       /**
+        * @name deepEqual
+        * @function
+        */
+       deepEqual: function( actual, expected, message ) {
+               this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+       },
+
+       /**
+        * @name notDeepEqual
+        * @function
+        */
+       notDeepEqual: function( actual, expected, message ) {
+               this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+       },
+
+       /**
+        * @name strictEqual
+        * @function
+        */
+       strictEqual: function( actual, expected, message ) {
+               this.push( expected === actual, actual, expected, message );
+       },
+
+       /**
+        * @name notStrictEqual
+        * @function
+        */
+       notStrictEqual: function( actual, expected, message ) {
+               this.push( expected !== actual, actual, expected, message );
+       },
+
+       "throws": function( block, expected, message ) {
+               var actual, expectedType,
+                       expectedOutput = expected,
+                       ok = false;
+
+               // 'expected' is optional unless doing string comparison
+               if ( message == null && typeof expected === "string" ) {
+                       message = expected;
+                       expected = null;
+               }
+
+               this.test.ignoreGlobalErrors = true;
+               try {
+                       block.call( this.test.testEnvironment );
+               } catch (e) {
+                       actual = e;
+               }
+               this.test.ignoreGlobalErrors = false;
+
+               if ( actual ) {
+                       expectedType = QUnit.objectType( expected );
+
+                       // we don't want to validate thrown error
+                       if ( !expected ) {
+                               ok = true;
+                               expectedOutput = null;
+
+                       // expected is a regexp
+                       } else if ( expectedType === "regexp" ) {
+                               ok = expected.test( errorString( actual ) );
+
+                       // expected is a string
+                       } else if ( expectedType === "string" ) {
+                               ok = expected === errorString( actual );
+
+                       // expected is a constructor, maybe an Error constructor
+                       } else if ( expectedType === "function" && actual instanceof expected ) {
+                               ok = true;
+
+                       // expected is an Error object
+                       } else if ( expectedType === "object" ) {
+                               ok = actual instanceof expected.constructor &&
+                                       actual.name === expected.name &&
+                                       actual.message === expected.message;
+
+                       // expected is a validation function which returns true if validation passed
+                       } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
+                               expectedOutput = null;
+                               ok = true;
+                       }
+
+                       this.push( ok, actual, expectedOutput, message );
+               } else {
+                       this.test.pushFailure( message, null, "No exception was thrown." );
+               }
+       }
+};
+
+// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
+// Known to us are: Closure Compiler, Narwhal
+(function() {
+       /*jshint sub:true */
+       Assert.prototype.raises = Assert.prototype[ "throws" ];
+}());
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+       // Call the o related callback with the given arguments.
+       function bindCallbacks( o, callbacks, args ) {
+               var prop = QUnit.objectType( o );
+               if ( prop ) {
+                       if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+                               return callbacks[ prop ].apply( callbacks, args );
+                       } else {
+                               return callbacks[ prop ]; // or undefined
+                       }
+               }
+       }
+
+       // the real equiv function
+       var innerEquiv,
+
+               // stack to decide between skip/abort functions
+               callers = [],
+
+               // stack to avoiding loops from circular referencing
+               parents = [],
+               parentsB = [],
+
+               getProto = Object.getPrototypeOf || function( obj ) {
+                       /* jshint camelcase: false, proto: true */
+                       return obj.__proto__;
+               },
+               callbacks = (function() {
+
+                       // for string, boolean, number and null
+                       function useStrictEquality( b, a ) {
+
+                               /*jshint eqeqeq:false */
+                               if ( b instanceof a.constructor || a instanceof b.constructor ) {
+
+                                       // to catch short annotation VS 'new' annotation of a
+                                       // declaration
+                                       // e.g. var i = 1;
+                                       // var j = new Number(1);
+                                       return a == b;
+                               } else {
+                                       return a === b;
+                               }
+                       }
+
+                       return {
+                               "string": useStrictEquality,
+                               "boolean": useStrictEquality,
+                               "number": useStrictEquality,
+                               "null": useStrictEquality,
+                               "undefined": useStrictEquality,
+
+                               "nan": function( b ) {
+                                       return isNaN( b );
+                               },
+
+                               "date": function( b, a ) {
+                                       return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+                               },
+
+                               "regexp": function( b, a ) {
+                                       return QUnit.objectType( b ) === "regexp" &&
+
+                                               // the regex itself
+                                               a.source === b.source &&
+
+                                               // and its modifiers
+                                               a.global === b.global &&
+
+                                               // (gmi) ...
+                                               a.ignoreCase === b.ignoreCase &&
+                                               a.multiline === b.multiline &&
+                                               a.sticky === b.sticky;
+                               },
+
+                               // - skip when the property is a method of an instance (OOP)
+                               // - abort otherwise,
+                               // initial === would have catch identical references anyway
+                               "function": function() {
+                                       var caller = callers[ callers.length - 1 ];
+                                       return caller !== Object && typeof caller !== "undefined";
+                               },
+
+                               "array": function( b, a ) {
+                                       var i, j, len, loop, aCircular, bCircular;
+
+                                       // b could be an object literal here
+                                       if ( QUnit.objectType( b ) !== "array" ) {
+                                               return false;
+                                       }
+
+                                       len = a.length;
+                                       if ( len !== b.length ) {
+                                               // safe and faster
+                                               return false;
+                                       }
+
+                                       // track reference to avoid circular references
+                                       parents.push( a );
+                                       parentsB.push( b );
+                                       for ( i = 0; i < len; i++ ) {
+                                               loop = false;
+                                               for ( j = 0; j < parents.length; j++ ) {
+                                                       aCircular = parents[ j ] === a[ i ];
+                                                       bCircular = parentsB[ j ] === b[ i ];
+                                                       if ( aCircular || bCircular ) {
+                                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+                                                                       loop = true;
+                                                               } else {
+                                                                       parents.pop();
+                                                                       parentsB.pop();
+                                                                       return false;
+                                                               }
+                                                       }
+                                               }
+                                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+                                                       parents.pop();
+                                                       parentsB.pop();
+                                                       return false;
+                                               }
+                                       }
+                                       parents.pop();
+                                       parentsB.pop();
+                                       return true;
+                               },
+
+                               "object": function( b, a ) {
+
+                                       /*jshint forin:false */
+                                       var i, j, loop, aCircular, bCircular,
+                                               // Default to true
+                                               eq = true,
+                                               aProperties = [],
+                                               bProperties = [];
+
+                                       // comparing constructors is more strict than using
+                                       // instanceof
+                                       if ( a.constructor !== b.constructor ) {
+
+                                               // Allow objects with no prototype to be equivalent to
+                                               // objects with Object as their constructor.
+                                               if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
+                                                       ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
+                                                       return false;
+                                               }
+                                       }
+
+                                       // stack constructor before traversing properties
+                                       callers.push( a.constructor );
+
+                                       // track reference to avoid circular references
+                                       parents.push( a );
+                                       parentsB.push( b );
+
+                                       // be strict: don't ensure hasOwnProperty and go deep
+                                       for ( i in a ) {
+                                               loop = false;
+                                               for ( j = 0; j < parents.length; j++ ) {
+                                                       aCircular = parents[ j ] === a[ i ];
+                                                       bCircular = parentsB[ j ] === b[ i ];
+                                                       if ( aCircular || bCircular ) {
+                                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+                                                                       loop = true;
+                                                               } else {
+                                                                       eq = false;
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                               aProperties.push( i );
+                                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+                                                       eq = false;
+                                                       break;
+                                               }
+                                       }
+
+                                       parents.pop();
+                                       parentsB.pop();
+                                       callers.pop(); // unstack, we are done
+
+                                       for ( i in b ) {
+                                               bProperties.push( i ); // collect b's properties
+                                       }
+
+                                       // Ensures identical properties name
+                                       return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+                               }
+                       };
+               }());
+
+       innerEquiv = function() { // can take multiple arguments
+               var args = [].slice.apply( arguments );
+               if ( args.length < 2 ) {
+                       return true; // end transition
+               }
+
+               return ( (function( a, b ) {
+                       if ( a === b ) {
+                               return true; // catch the most you can
+                       } else if ( a === null || b === null || typeof a === "undefined" ||
+                                       typeof b === "undefined" ||
+                                       QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
+
+                               // don't lose time with error prone cases
+                               return false;
+                       } else {
+                               return bindCallbacks( a, callbacks, [ b, a ] );
+                       }
+
+                       // apply transition with (1..n) arguments
+               }( args[ 0 ], args[ 1 ] ) ) &&
+                       innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
+       };
+
+       return innerEquiv;
+}());
+
+// Based on jsDump by Ariel Flesler
+// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+QUnit.dump = (function() {
+       function quote( str ) {
+               return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+       }
+       function literal( o ) {
+               return o + "";
+       }
+       function join( pre, arr, post ) {
+               var s = dump.separator(),
+                       base = dump.indent(),
+                       inner = dump.indent( 1 );
+               if ( arr.join ) {
+                       arr = arr.join( "," + s + inner );
+               }
+               if ( !arr ) {
+                       return pre + post;
+               }
+               return [ pre, inner + arr, base + post ].join( s );
+       }
+       function array( arr, stack ) {
+               var i = arr.length,
+                       ret = new Array( i );
+
+               if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+                       return "[object Array]";
+               }
+
+               this.up();
+               while ( i-- ) {
+                       ret[ i ] = this.parse( arr[ i ], undefined, stack );
+               }
+               this.down();
+               return join( "[", ret, "]" );
+       }
+
+       var reName = /^function (\w+)/,
+               dump = {
+
+                       // objType is used mostly internally, you can fix a (custom) type in advance
+                       parse: function( obj, objType, stack ) {
+                               stack = stack || [];
+                               var res, parser, parserType,
+                                       inStack = inArray( obj, stack );
+
+                               if ( inStack !== -1 ) {
+                                       return "recursion(" + ( inStack - stack.length ) + ")";
+                               }
+
+                               objType = objType || this.typeOf( obj  );
+                               parser = this.parsers[ objType ];
+                               parserType = typeof parser;
+
+                               if ( parserType === "function" ) {
+                                       stack.push( obj );
+                                       res = parser.call( this, obj, stack );
+                                       stack.pop();
+                                       return res;
+                               }
+                               return ( parserType === "string" ) ? parser : this.parsers.error;
+                       },
+                       typeOf: function( obj ) {
+                               var type;
+                               if ( obj === null ) {
+                                       type = "null";
+                               } else if ( typeof obj === "undefined" ) {
+                                       type = "undefined";
+                               } else if ( QUnit.is( "regexp", obj ) ) {
+                                       type = "regexp";
+                               } else if ( QUnit.is( "date", obj ) ) {
+                                       type = "date";
+                               } else if ( QUnit.is( "function", obj ) ) {
+                                       type = "function";
+                               } else if ( obj.setInterval !== undefined &&
+                                               obj.document !== undefined &&
+                                               obj.nodeType === undefined ) {
+                                       type = "window";
+                               } else if ( obj.nodeType === 9 ) {
+                                       type = "document";
+                               } else if ( obj.nodeType ) {
+                                       type = "node";
+                               } else if (
+
+                                       // native arrays
+                                       toString.call( obj ) === "[object Array]" ||
+
+                                       // NodeList objects
+                                       ( typeof obj.length === "number" && obj.item !== undefined &&
+                                       ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
+                                       obj[ 0 ] === undefined ) ) )
+                               ) {
+                                       type = "array";
+                               } else if ( obj.constructor === Error.prototype.constructor ) {
+                                       type = "error";
+                               } else {
+                                       type = typeof obj;
+                               }
+                               return type;
+                       },
+                       separator: function() {
+                               return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
+                       },
+                       // extra can be a number, shortcut for increasing-calling-decreasing
+                       indent: function( extra ) {
+                               if ( !this.multiline ) {
+                                       return "";
+                               }
+                               var chr = this.indentChar;
+                               if ( this.HTML ) {
+                                       chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
+                               }
+                               return new Array( this.depth + ( extra || 0 ) ).join( chr );
+                       },
+                       up: function( a ) {
+                               this.depth += a || 1;
+                       },
+                       down: function( a ) {
+                               this.depth -= a || 1;
+                       },
+                       setParser: function( name, parser ) {
+                               this.parsers[ name ] = parser;
+                       },
+                       // The next 3 are exposed so you can use them
+                       quote: quote,
+                       literal: literal,
+                       join: join,
+                       //
+                       depth: 1,
+                       maxDepth: 5,
+
+                       // This is the list of parsers, to modify them, use dump.setParser
+                       parsers: {
+                               window: "[Window]",
+                               document: "[Document]",
+                               error: function( error ) {
+                                       return "Error(\"" + error.message + "\")";
+                               },
+                               unknown: "[Unknown]",
+                               "null": "null",
+                               "undefined": "undefined",
+                               "function": function( fn ) {
+                                       var ret = "function",
+
+                                               // functions never have name in IE
+                                               name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
+
+                                       if ( name ) {
+                                               ret += " " + name;
+                                       }
+                                       ret += "( ";
+
+                                       ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
+                                       return join( ret, dump.parse( fn, "functionCode" ), "}" );
+                               },
+                               array: array,
+                               nodelist: array,
+                               "arguments": array,
+                               object: function( map, stack ) {
+                                       var keys, key, val, i, nonEnumerableProperties,
+                                               ret = [];
+
+                                       if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+                                               return "[object Object]";
+                                       }
+
+                                       dump.up();
+                                       keys = [];
+                                       for ( key in map ) {
+                                               keys.push( key );
+                                       }
+
+                                       // Some properties are not always enumerable on Error objects.
+                                       nonEnumerableProperties = [ "message", "name" ];
+                                       for ( i in nonEnumerableProperties ) {
+                                               key = nonEnumerableProperties[ i ];
+                                               if ( key in map && !( key in keys ) ) {
+                                                       keys.push( key );
+                                               }
+                                       }
+                                       keys.sort();
+                                       for ( i = 0; i < keys.length; i++ ) {
+                                               key = keys[ i ];
+                                               val = map[ key ];
+                                               ret.push( dump.parse( key, "key" ) + ": " +
+                                                       dump.parse( val, undefined, stack ) );
+                                       }
+                                       dump.down();
+                                       return join( "{", ret, "}" );
+                               },
+                               node: function( node ) {
+                                       var len, i, val,
+                                               open = dump.HTML ? "&lt;" : "<",
+                                               close = dump.HTML ? "&gt;" : ">",
+                                               tag = node.nodeName.toLowerCase(),
+                                               ret = open + tag,
+                                               attrs = node.attributes;
+
+                                       if ( attrs ) {
+                                               for ( i = 0, len = attrs.length; i < len; i++ ) {
+                                                       val = attrs[ i ].nodeValue;
+
+                                                       // IE6 includes all attributes in .attributes, even ones not explicitly
+                                                       // set. Those have values like undefined, null, 0, false, "" or
+                                                       // "inherit".
+                                                       if ( val && val !== "inherit" ) {
+                                                               ret += " " + attrs[ i ].nodeName + "=" +
+                                                                       dump.parse( val, "attribute" );
+                                                       }
+                                               }
+                                       }
+                                       ret += close;
+
+                                       // Show content of TextNode or CDATASection
+                                       if ( node.nodeType === 3 || node.nodeType === 4 ) {
+                                               ret += node.nodeValue;
+                                       }
+
+                                       return ret + open + "/" + tag + close;
+                               },
+
+                               // function calls it internally, it's the arguments part of the function
+                               functionArgs: function( fn ) {
+                                       var args,
+                                               l = fn.length;
+
+                                       if ( !l ) {
+                                               return "";
+                                       }
+
+                                       args = new Array( l );
+                                       while ( l-- ) {
+
+                                               // 97 is 'a'
+                                               args[ l ] = String.fromCharCode( 97 + l );
+                                       }
+                                       return " " + args.join( ", " ) + " ";
+                               },
+                               // object calls it internally, the key part of an item in a map
+                               key: quote,
+                               // function calls it internally, it's the content of the function
+                               functionCode: "[code]",
+                               // node calls it internally, it's an html attribute value
+                               attribute: quote,
+                               string: quote,
+                               date: quote,
+                               regexp: literal,
+                               number: literal,
+                               "boolean": literal
+                       },
+                       // if true, entities are escaped ( <, >, \t, space and \n )
+                       HTML: false,
+                       // indentation unit
+                       indentChar: "  ",
+                       // if true, items in a collection, are separated by a \n, else just a space.
+                       multiline: true
+               };
+
+       return dump;
+}());
+
+// back compat
+QUnit.jsDump = QUnit.dump;
+
+// For browser, export only select globals
+if ( typeof window !== "undefined" ) {
+
+       // Deprecated
+       // Extend assert methods to QUnit and Global scope through Backwards compatibility
+       (function() {
+               var i,
+                       assertions = Assert.prototype;
+
+               function applyCurrent( current ) {
+                       return function() {
+                               var assert = new Assert( QUnit.config.current );
+                               current.apply( assert, arguments );
+                       };
+               }
+
+               for ( i in assertions ) {
+                       QUnit[ i ] = applyCurrent( assertions[ i ] );
+               }
+       })();
+
+       (function() {
+               var i, l,
+                       keys = [
+                               "test",
+                               "module",
+                               "expect",
+                               "asyncTest",
+                               "start",
+                               "stop",
+                               "ok",
+                               "equal",
+                               "notEqual",
+                               "propEqual",
+                               "notPropEqual",
+                               "deepEqual",
+                               "notDeepEqual",
+                               "strictEqual",
+                               "notStrictEqual",
+                               "throws"
+                       ];
+
+               for ( i = 0, l = keys.length; i < l; i++ ) {
+                       window[ keys[ i ] ] = QUnit[ keys[ i ] ];
+               }
+       })();
+
+       window.QUnit = QUnit;
+}
+
+// For nodejs
+if ( typeof module !== "undefined" && module && module.exports ) {
+       module.exports = QUnit;
+
+       // For consistency with CommonJS environments' exports
+       module.exports.QUnit = QUnit;
+}
+
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" && exports ) {
+       exports.QUnit = QUnit;
+}
+
+// Get a reference to the global object, like window in browsers
+}( (function() {
+       return this;
+})() ));
+
+/*istanbul ignore next */
+// jscs:disable maximumLineLength
+/*
+ * Javascript Diff Algorithm
+ *  By John Resig (http://ejohn.org/)
+ *  Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ *  http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * 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"
+ */
+QUnit.diff = (function() {
+       var hasOwn = Object.prototype.hasOwnProperty;
+
+       /*jshint eqeqeq:false, eqnull:true */
+       function diff( o, n ) {
+               var i,
+                       ns = {},
+                       os = {};
+
+               for ( i = 0; i < n.length; i++ ) {
+                       if ( !hasOwn.call( ns, n[ i ] ) ) {
+                               ns[ n[ i ] ] = {
+                                       rows: [],
+                                       o: null
+                               };
+                       }
+                       ns[ n[ i ] ].rows.push( i );
+               }
+
+               for ( i = 0; i < o.length; i++ ) {
+                       if ( !hasOwn.call( os, o[ i ] ) ) {
+                               os[ o[ i ] ] = {
+                                       rows: [],
+                                       n: null
+                               };
+                       }
+                       os[ o[ i ] ].rows.push( i );
+               }
+
+               for ( i in ns ) {
+                       if ( hasOwn.call( ns, i ) ) {
+                               if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
+                                       n[ ns[ i ].rows[ 0 ] ] = {
+                                               text: n[ ns[ i ].rows[ 0 ] ],
+                                               row: os[ i ].rows[ 0 ]
+                                       };
+                                       o[ os[ i ].rows[ 0 ] ] = {
+                                               text: o[ os[ i ].rows[ 0 ] ],
+                                               row: ns[ i ].rows[ 0 ]
+                                       };
+                               }
+                       }
+               }
+
+               for ( i = 0; i < n.length - 1; i++ ) {
+                       if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
+                               n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
+
+                               n[ i + 1 ] = {
+                                       text: n[ i + 1 ],
+                                       row: n[ i ].row + 1
+                               };
+                               o[ n[ i ].row + 1 ] = {
+                                       text: o[ n[ i ].row + 1 ],
+                                       row: i + 1
+                               };
+                       }
+               }
+
+               for ( i = n.length - 1; i > 0; i-- ) {
+                       if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
+                               n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
+
+                               n[ i - 1 ] = {
+                                       text: n[ i - 1 ],
+                                       row: n[ i ].row - 1
+                               };
+                               o[ n[ i ].row - 1 ] = {
+                                       text: o[ n[ i ].row - 1 ],
+                                       row: i - 1
+                               };
+                       }
+               }
+
+               return {
+                       o: o,
+                       n: n
+               };
+       }
+
+       return function( o, n ) {
+               o = o.replace( /\s+$/, "" );
+               n = n.replace( /\s+$/, "" );
+
+               var i, pre,
+                       str = "",
+                       out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
+                       oSpace = o.match( /\s+/g ),
+                       nSpace = n.match( /\s+/g );
+
+               if ( oSpace == null ) {
+                       oSpace = [ " " ];
+               } else {
+                       oSpace.push( " " );
+               }
+
+               if ( nSpace == null ) {
+                       nSpace = [ " " ];
+               } else {
+                       nSpace.push( " " );
+               }
+
+               if ( out.n.length === 0 ) {
+                       for ( i = 0; i < out.o.length; i++ ) {
+                               str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
+                       }
+               } else {
+                       if ( out.n[ 0 ].text == null ) {
+                               for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
+                                       str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
+                               }
+                       }
+
+                       for ( i = 0; i < out.n.length; i++ ) {
+                               if ( out.n[ i ].text == null ) {
+                                       str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
+                               } else {
+
+                                       // `pre` initialized at top of scope
+                                       pre = "";
+
+                                       for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
+                                               pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
+                                       }
+                                       str += " " + out.n[ i ].text + nSpace[ i ] + pre;
+                               }
+                       }
+               }
+
+               return str;
+       };
+}());
+// jscs:enable
+
+(function() {
+
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+       var tests, banner, result, qunit,
+               config = QUnit.config;
+
+       config.stats = { all: 0, bad: 0 };
+       config.moduleStats = { all: 0, bad: 0 };
+       config.started = 0;
+       config.updateRate = 1000;
+       config.blocking = false;
+       config.autostart = true;
+       config.autorun = false;
+       config.filter = "";
+       config.queue = [];
+
+       // Return on non-browser environments
+       // This is necessary to not break on node tests
+       if ( typeof window === "undefined" ) {
+               return;
+       }
+
+       qunit = id( "qunit" );
+       if ( qunit ) {
+               qunit.innerHTML =
+                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+                       "<h2 id='qunit-banner'></h2>" +
+                       "<div id='qunit-testrunner-toolbar'></div>" +
+                       "<h2 id='qunit-userAgent'></h2>" +
+                       "<ol id='qunit-tests'></ol>";
+       }
+
+       tests = id( "qunit-tests" );
+       banner = id( "qunit-banner" );
+       result = id( "qunit-testresult" );
+
+       if ( tests ) {
+               tests.innerHTML = "";
+       }
+
+       if ( banner ) {
+               banner.className = "";
+       }
+
+       if ( result ) {
+               result.parentNode.removeChild( result );
+       }
+
+       if ( tests ) {
+               result = document.createElement( "p" );
+               result.id = "qunit-testresult";
+               result.className = "result";
+               tests.parentNode.insertBefore( result, tests );
+               result.innerHTML = "Running...<br />&#160;";
+       }
+};
+
+// Don't load the HTML Reporter on non-Browser environments
+if ( typeof window === "undefined" ) {
+       return;
+}
+
+var config = QUnit.config,
+       hasOwn = Object.prototype.hasOwnProperty,
+       defined = {
+               document: window.document !== undefined,
+               sessionStorage: (function() {
+                       var x = "qunit-test-string";
+                       try {
+                               sessionStorage.setItem( x, x );
+                               sessionStorage.removeItem( x );
+                               return true;
+                       } catch ( e ) {
+                               return false;
+                       }
+               }())
+       },
+       modulesList = [];
+
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+       if ( !s ) {
+               return "";
+       }
+       s = s + "";
+
+       // Both single quotes and double quotes (for attributes)
+       return s.replace( /['"<>&]/g, function( s ) {
+               switch ( s ) {
+               case "'":
+                       return "&#039;";
+               case "\"":
+                       return "&quot;";
+               case "<":
+                       return "&lt;";
+               case ">":
+                       return "&gt;";
+               case "&":
+                       return "&amp;";
+               }
+       });
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+       if ( elem.addEventListener ) {
+
+               // Standards-based browsers
+               elem.addEventListener( type, fn, false );
+       } else if ( elem.attachEvent ) {
+
+               // support: IE <9
+               elem.attachEvent( "on" + type, fn );
+       }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+       var i = elems.length;
+       while ( i-- ) {
+               addEvent( elems[ i ], type, fn );
+       }
+}
+
+function hasClass( elem, name ) {
+       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
+
+function addClass( elem, name ) {
+       if ( !hasClass( elem, name ) ) {
+               elem.className += ( elem.className ? " " : "" ) + name;
+       }
+}
+
+function toggleClass( elem, name ) {
+       if ( hasClass( elem, name ) ) {
+               removeClass( elem, name );
+       } else {
+               addClass( elem, name );
+       }
+}
+
+function removeClass( elem, name ) {
+       var set = " " + elem.className + " ";
+
+       // Class name may appear multiple times
+       while ( set.indexOf( " " + name + " " ) >= 0 ) {
+               set = set.replace( " " + name + " ", " " );
+       }
+
+       // trim for prettiness
+       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+       return defined.document && document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+       var i, j, val,
+               escaped, escapedTooltip,
+               selection = false,
+               len = config.urlConfig.length,
+               urlConfigHtml = "";
+
+       for ( i = 0; i < len; i++ ) {
+               val = config.urlConfig[ i ];
+               if ( typeof val === "string" ) {
+                       val = {
+                               id: val,
+                               label: val
+                       };
+               }
+
+               escaped = escapeText( val.id );
+               escapedTooltip = escapeText( val.tooltip );
+
+               if ( config[ val.id ] === undefined ) {
+                       config[ val.id ] = QUnit.urlParams[ val.id ];
+               }
+
+               if ( !val.value || typeof val.value === "string" ) {
+                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' type='checkbox'" +
+                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+                               ( config[ val.id ] ? " checked='checked'" : "" ) +
+                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
+               } else {
+                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label +
+                               ": </label><select id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+                       if ( QUnit.is( "array", val.value ) ) {
+                               for ( j = 0; j < val.value.length; j++ ) {
+                                       escaped = escapeText( val.value[ j ] );
+                                       urlConfigHtml += "<option value='" + escaped + "'" +
+                                               ( config[ val.id ] === val.value[ j ] ?
+                                                       ( selection = true ) && " selected='selected'" : "" ) +
+                                               ">" + escaped + "</option>";
+                               }
+                       } else {
+                               for ( j in val.value ) {
+                                       if ( hasOwn.call( val.value, j ) ) {
+                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+                                                       ( config[ val.id ] === j ?
+                                                               ( selection = true ) && " selected='selected'" : "" ) +
+                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
+                                       }
+                               }
+                       }
+                       if ( config[ val.id ] && !selection ) {
+                               escaped = escapeText( config[ val.id ] );
+                               urlConfigHtml += "<option value='" + escaped +
+                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
+                       }
+                       urlConfigHtml += "</select>";
+               }
+       }
+
+       return urlConfigHtml;
+}
+
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+       var updatedUrl, value,
+               field = this,
+               params = {};
+
+       // Detect if field is a select menu or a checkbox
+       if ( "selectedIndex" in field ) {
+               value = field.options[ field.selectedIndex ].value || undefined;
+       } else {
+               value = field.checked ? ( field.defaultValue || true ) : undefined;
+       }
+
+       params[ field.name ] = value;
+       updatedUrl = setUrl( params );
+
+       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+               config[ field.name ] = value || false;
+               if ( value ) {
+                       addClass( id( "qunit-tests" ), "hidepass" );
+               } else {
+                       removeClass( id( "qunit-tests" ), "hidepass" );
+               }
+
+               // It is not necessary to refresh the whole page
+               window.history.replaceState( null, "", updatedUrl );
+       } else {
+               window.location = updatedUrl;
+       }
+}
+
+function setUrl( params ) {
+       var key,
+               querystring = "?";
+
+       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+
+       for ( key in params ) {
+               if ( hasOwn.call( params, key ) ) {
+                       if ( params[ key ] === undefined ) {
+                               continue;
+                       }
+                       querystring += encodeURIComponent( key );
+                       if ( params[ key ] !== true ) {
+                               querystring += "=" + encodeURIComponent( params[ key ] );
+                       }
+                       querystring += "&";
+               }
+       }
+       return location.protocol + "//" + location.host +
+               location.pathname + querystring.slice( 0, -1 );
+}
+
+function applyUrlParams() {
+       var selectBox = id( "qunit-modulefilter" ),
+               selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
+               filter = id( "qunit-filter-input" ).value;
+
+       window.location = setUrl({
+               module: ( selection === "" ) ? undefined : selection,
+               filter: ( filter === "" ) ? undefined : filter,
+
+               // Remove testId filter
+               testId: undefined
+       });
+}
+
+function toolbarUrlConfigContainer() {
+       var urlConfigContainer = document.createElement( "span" );
+
+       urlConfigContainer.innerHTML = getUrlConfigHtml();
+       addClass( urlConfigContainer, "qunit-url-config" );
+
+       // For oldIE support:
+       // * Add handlers to the individual elements instead of the container
+       // * Use "click" instead of "change" for checkboxes
+       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+       return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+       var filter = document.createElement( "form" ),
+               label = document.createElement( "label" ),
+               input = document.createElement( "input" ),
+               button = document.createElement( "button" );
+
+       addClass( filter, "qunit-filter" );
+
+       label.innerHTML = "Filter: ";
+
+       input.type = "text";
+       input.value = config.filter || "";
+       input.name = "filter";
+       input.id = "qunit-filter-input";
+
+       button.innerHTML = "Go";
+
+       label.appendChild( input );
+
+       filter.appendChild( label );
+       filter.appendChild( button );
+       addEvent( filter, "submit", function( ev ) {
+               applyUrlParams();
+
+               if ( ev && ev.preventDefault ) {
+                       ev.preventDefault();
+               }
+
+               return false;
+       });
+
+       return filter;
+}
+
+function toolbarModuleFilterHtml() {
+       var i,
+               moduleFilterHtml = "";
+
+       if ( !modulesList.length ) {
+               return false;
+       }
+
+       modulesList.sort(function( a, b ) {
+               return a.localeCompare( b );
+       });
+
+       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
+               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
+               ">< All Modules ></option>";
+
+       for ( i = 0; i < modulesList.length; i++ ) {
+               moduleFilterHtml += "<option value='" +
+                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+                       ">" + escapeText( modulesList[ i ] ) + "</option>";
+       }
+       moduleFilterHtml += "</select>";
+
+       return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+       var toolbar = id( "qunit-testrunner-toolbar" ),
+               moduleFilter = document.createElement( "span" ),
+               moduleFilterHtml = toolbarModuleFilterHtml();
+
+       if ( !toolbar || !moduleFilterHtml ) {
+               return false;
+       }
+
+       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+       moduleFilter.innerHTML = moduleFilterHtml;
+
+       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+       toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+       var toolbar = id( "qunit-testrunner-toolbar" );
+
+       if ( toolbar ) {
+               toolbar.appendChild( toolbarUrlConfigContainer() );
+               toolbar.appendChild( toolbarLooseFilter() );
+       }
+}
+
+function appendHeader() {
+       var header = id( "qunit-header" );
+
+       if ( header ) {
+               header.innerHTML = "<a href='" +
+                       setUrl({ filter: undefined, module: undefined, testId: undefined }) +
+                       "'>" + header.innerHTML + "</a> ";
+       }
+}
+
+function appendBanner() {
+       var banner = id( "qunit-banner" );
+
+       if ( banner ) {
+               banner.className = "";
+       }
+}
+
+function appendTestResults() {
+       var tests = id( "qunit-tests" ),
+               result = id( "qunit-testresult" );
+
+       if ( result ) {
+               result.parentNode.removeChild( result );
+       }
+
+       if ( tests ) {
+               tests.innerHTML = "";
+               result = document.createElement( "p" );
+               result.id = "qunit-testresult";
+               result.className = "result";
+               tests.parentNode.insertBefore( result, tests );
+               result.innerHTML = "Running...<br />&#160;";
+       }
+}
+
+function storeFixture() {
+       var fixture = id( "qunit-fixture" );
+       if ( fixture ) {
+               config.fixture = fixture.innerHTML;
+       }
+}
+
+function appendUserAgent() {
+       var userAgent = id( "qunit-userAgent" );
+       if ( userAgent ) {
+               userAgent.innerHTML = "";
+               userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
+       }
+}
+
+function appendTestsList( modules ) {
+       var i, l, x, z, test, moduleObj;
+
+       for ( i = 0, l = modules.length; i < l; i++ ) {
+               moduleObj = modules[ i ];
+
+               if ( moduleObj.name ) {
+                       modulesList.push( moduleObj.name );
+               }
+
+               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+                       test = moduleObj.tests[ x ];
+
+                       appendTest( test.name, test.testId, moduleObj.name );
+               }
+       }
+}
+
+function appendTest( name, testId, moduleName ) {
+       var title, rerunTrigger, testBlock, assertList,
+               tests = id( "qunit-tests" );
+
+       if ( !tests ) {
+               return;
+       }
+
+       title = document.createElement( "strong" );
+       title.innerHTML = getNameHtml( name, moduleName );
+
+       rerunTrigger = document.createElement( "a" );
+       rerunTrigger.innerHTML = "Rerun";
+       rerunTrigger.href = setUrl({ testId: testId });
+
+       testBlock = document.createElement( "li" );
+       testBlock.appendChild( title );
+       testBlock.appendChild( rerunTrigger );
+       testBlock.id = "qunit-test-output-" + testId;
+
+       assertList = document.createElement( "ol" );
+       assertList.className = "qunit-assert-list";
+
+       testBlock.appendChild( assertList );
+
+       tests.appendChild( testBlock );
+}
+
+// HTML Reporter initialization and load
+QUnit.begin(function( details ) {
+       var qunit = id( "qunit" );
+
+       // Fixture is the only one necessary to run without the #qunit element
+       storeFixture();
+
+       if ( qunit ) {
+               qunit.innerHTML =
+                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+                       "<h2 id='qunit-banner'></h2>" +
+                       "<div id='qunit-testrunner-toolbar'></div>" +
+                       "<h2 id='qunit-userAgent'></h2>" +
+                       "<ol id='qunit-tests'></ol>";
+       }
+
+       appendHeader();
+       appendBanner();
+       appendTestResults();
+       appendUserAgent();
+       appendToolbar();
+       appendTestsList( details.modules );
+       toolbarModuleFilter();
+
+       if ( qunit && config.hidepassed ) {
+               addClass( qunit.lastChild, "hidepass" );
+       }
+});
+
+QUnit.done(function( details ) {
+       var i, key,
+               banner = id( "qunit-banner" ),
+               tests = id( "qunit-tests" ),
+               html = [
+                       "Tests completed in ",
+                       details.runtime,
+                       " milliseconds.<br />",
+                       "<span class='passed'>",
+                       details.passed,
+                       "</span> assertions of <span class='total'>",
+                       details.total,
+                       "</span> passed, <span class='failed'>",
+                       details.failed,
+                       "</span> failed."
+               ].join( "" );
+
+       if ( banner ) {
+               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+       }
+
+       if ( tests ) {
+               id( "qunit-testresult" ).innerHTML = html;
+       }
+
+       if ( config.altertitle && defined.document && document.title ) {
+
+               // show âœ– for good, âœ” for bad suite result in title
+               // use escape sequences in case file gets loaded with non-utf-8-charset
+               document.title = [
+                       ( details.failed ? "\u2716" : "\u2714" ),
+                       document.title.replace( /^[\u2714\u2716] /i, "" )
+               ].join( " " );
+       }
+
+       // clear own sessionStorage items if all tests passed
+       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+               for ( i = 0; i < sessionStorage.length; i++ ) {
+                       key = sessionStorage.key( i++ );
+                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
+                               sessionStorage.removeItem( key );
+                       }
+               }
+       }
+
+       // scroll back to top to show results
+       if ( config.scrolltop && window.scrollTo ) {
+               window.scrollTo( 0, 0 );
+       }
+});
+
+function getNameHtml( name, module ) {
+       var nameHtml = "";
+
+       if ( module ) {
+               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
+       }
+
+       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
+
+       return nameHtml;
+}
+
+QUnit.testStart(function( details ) {
+       var running, testBlock;
+
+       testBlock = id( "qunit-test-output-" + details.testId );
+       if ( testBlock ) {
+               testBlock.className = "running";
+       } else {
+
+               // Report later registered tests
+               appendTest( details.name, details.testId, details.module );
+       }
+
+       running = id( "qunit-testresult" );
+       if ( running ) {
+               running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
+       }
+
+});
+
+QUnit.log(function( details ) {
+       var assertList, assertLi,
+               message, expected, actual,
+               testItem = id( "qunit-test-output-" + details.testId );
+
+       if ( !testItem ) {
+               return;
+       }
+
+       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+       message = "<span class='test-message'>" + message + "</span>";
+       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+
+       // pushFailure doesn't provide details.expected
+       // when it calls, it's implicit to also not show expected and diff stuff
+       // Also, we need to check details.expected existence, as it can exist and be undefined
+       if ( !details.result && hasOwn.call( details, "expected" ) ) {
+               expected = escapeText( QUnit.dump.parse( details.expected ) );
+               actual = escapeText( QUnit.dump.parse( details.actual ) );
+               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
+                       expected +
+                       "</pre></td></tr>";
+
+               if ( actual !== expected ) {
+                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
+                               actual + "</pre></td></tr>" +
+                               "<tr class='test-diff'><th>Diff: </th><td><pre>" +
+                               QUnit.diff( expected, actual ) + "</pre></td></tr>";
+               }
+
+               if ( details.source ) {
+                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
+                               escapeText( details.source ) + "</pre></td></tr>";
+               }
+
+               message += "</table>";
+
+       // this occours when pushFailure is set and we have an extracted stack trace
+       } else if ( !details.result && details.source ) {
+               message += "<table>" +
+                       "<tr class='test-source'><th>Source: </th><td><pre>" +
+                       escapeText( details.source ) + "</pre></td></tr>" +
+                       "</table>";
+       }
+
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+       assertLi = document.createElement( "li" );
+       assertLi.className = details.result ? "pass" : "fail";
+       assertLi.innerHTML = message;
+       assertList.appendChild( assertLi );
+});
+
+QUnit.testDone(function( details ) {
+       var testTitle, time, testItem, assertList,
+               good, bad, testCounts, skipped,
+               tests = id( "qunit-tests" );
+
+       if ( !tests ) {
+               return;
+       }
+
+       testItem = id( "qunit-test-output-" + details.testId );
+
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+       good = details.passed;
+       bad = details.failed;
+
+       // store result when possible
+       if ( config.reorder && defined.sessionStorage ) {
+               if ( bad ) {
+                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+               } else {
+                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+               }
+       }
+
+       if ( bad === 0 ) {
+               addClass( assertList, "qunit-collapsed" );
+       }
+
+       // testItem.firstChild is the test name
+       testTitle = testItem.firstChild;
+
+       testCounts = bad ?
+               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
+               "";
+
+       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
+               details.assertions.length + ")</b>";
+
+       if ( details.skipped ) {
+               testItem.className = "skipped";
+               skipped = document.createElement( "em" );
+               skipped.className = "qunit-skipped-label";
+               skipped.innerHTML = "skipped";
+               testItem.insertBefore( skipped, testTitle );
+       } else {
+               addEvent( testTitle, "click", function() {
+                       toggleClass( assertList, "qunit-collapsed" );
+               });
+
+               testItem.className = bad ? "fail" : "pass";
+
+               time = document.createElement( "span" );
+               time.className = "runtime";
+               time.innerHTML = details.runtime + " ms";
+               testItem.insertBefore( time, assertList );
+       }
+});
+
+if ( !defined.document || document.readyState === "complete" ) {
+       config.pageLoaded = true;
+       config.autorun = true;
+}
+
+if ( defined.document ) {
+       addEvent( window, "load", QUnit.load );
+}
+
+})();
index dc7aaa4..e6e33ad 100644 (file)
                        return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
                },
                escapeRE: function ( str ) {
-                       return str.replace ( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+                       return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
                },
                isDomElement: function ( el ) {
                        return !!el && !!el.nodeType;
                },
                isEmpty: function ( v ) {
                        var key;
-                       if ( v === '' || v === 0 || v === '0' || v === null
-                               || v === false || v === undefined )
-                       {
+                       if (
+                               v === '' || v === 0 || v === '0' || v === null || v === false || v === undefined
+                       {
                                return true;
                        }
                        // the for-loop could potentially contain prototypes
index d458019..d50422e 100644 (file)
@@ -13,7 +13,7 @@
  * @version 2.1.0
  * @license MIT
  */
-(function ($) {
+( function ($) {
 
        var isInputSupported = 'placeholder' in document.createElement('input'),
                isTextareaSupported = 'placeholder' in document.createElement('textarea'),
@@ -49,7 +49,7 @@
 
                        $this
                                .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
-                               .filter(function () {
+                               .filter( function () {
                                        return !$(this).data('placeholder-enabled');
                                })
                                .bind({
                        propHooks.value = hooks;
                }
 
-               $(function () {
+               $( function () {
                        // Look for forms
                        $(document).delegate('form', 'submit.placeholder', function () {
                                // Clear the placeholder values so they don't get submitted
                                var $inputs = $('.placeholder', this).each(clearPlaceholder);
-                               setTimeout(function () {
+                               setTimeout( function () {
                                        $inputs.each(setPlaceholder);
                                }, 10);
                        });
 
                // Clear placeholder values upon page reload
                $(window).bind('beforeunload.placeholder', function () {
-                       $('.placeholder').each(function () {
+                       $('.placeholder').each( function () {
                                this.value = '';
                        });
                });
index bd6518d..e8fc8e4 100644 (file)
                                                        endPos = this.selectionEnd;
                                                        scrollTop = this.scrollTop;
                                                        checkSelectedText();
-                                                       if ( options.selectionStart !== undefined
-                                                                       && endPos - startPos !== options.selectionEnd - options.selectionStart )
-                                                       {
+                                                       if (
+                                                               options.selectionStart !== undefined &&
+                                                               endPos - startPos !== options.selectionEnd - options.selectionStart
+                                                       ) {
                                                                // This means there is a difference in the selection range returned by browser and what we passed.
                                                                // This happens for Chrome in the case of composite characters. Ref bug #30130
                                                                // Set the startPos to the correct position.
index 95ef62c..c008dfd 100644 (file)
@@ -76,4 +76,4 @@
                mw.cookie.set( cookieKey, null );
        }
 
-} ( mediaWiki, jQuery ) );
+}( mediaWiki, jQuery ) );
index ba7f734..7c2269f 100644 (file)
@@ -6,4 +6,4 @@
                        $( '#mw-pl-options-2' ).prop( 'checked', true );
                } );
        } );
-} ( jQuery ) );
+}( jQuery ) );
index 85f4ffa..73e7f6e 100644 (file)
                }
        };
 
-} ( mediaWiki, jQuery ) );
+}( mediaWiki, jQuery ) );
index bed5cb5..98a2f6a 100644 (file)
 
        // Attach to window and globally alias
        window.mw = window.mediaWiki = mw;
-
-       // Auto-register from pre-loaded startup scripts
-       if ( $.isFunction( window.startUp ) ) {
-               window.startUp();
-               window.startUp = undefined;
-       }
-
 }( jQuery ) );
diff --git a/resources/src/mediawiki/mediawiki.startUp.js b/resources/src/mediawiki/mediawiki.startUp.js
new file mode 100644 (file)
index 0000000..8032aa2
--- /dev/null
@@ -0,0 +1,12 @@
+/**
+ * Auto-register from pre-loaded startup scripts
+ * @ignore (this line will make JSDuck happy)
+ */
+( function ( $ ) {
+       'use strict';
+
+       if ( $.isFunction( window.startUp ) ) {
+               window.startUp();
+               window.startUp = undefined;
+       }
+}( jQuery ) );
index 809a65e..5e0cfcc 100644 (file)
                        var registration = mw.config.get( 'wgUserRegistration' );
                        if ( user.isAnon() ) {
                                return false;
-                       } else if ( registration === null ) {
+                       }
+                       if ( registration === null ) {
                                // Information may not be available if they signed up before
                                // MW began storing this.
                                return null;
-                       } else {
-                               return new Date( registration );
                        }
+                       return new Date( registration );
                },
 
                /**
index 0dde873..d202270 100644 (file)
@@ -88,7 +88,7 @@
                /**
                 * Get the link to a page name (relative to `wgServer`),
                 *
-                * @param {string} str Page name
+                * @param {string|null} [str=wgPageName] Page name
                 * @param {Object} [params] A mapping of query parameter names to values,
                 *  e.g. `{ action: 'edit' }`
                 * @return {string} Url of the page with name of `str`
index 628c59b..44463cf 100644 (file)
@@ -109,7 +109,9 @@ class StatusTest extends MediaWikiLangTestCase {
        public function testIsGood( $ok, $errors, $expected ) {
                $status = new Status();
                $status->ok = $ok;
-               $status->errors = $errors;
+               foreach ( $errors as $error ) {
+                       $status->warning( $error );
+               }
                $this->assertEquals( $expected, $status->isGood() );
        }
 
index 9461a16..d2254df 100644 (file)
@@ -162,6 +162,105 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
                }
        }
 
+       /**
+        * @covers ExtensionProcessor::extractResourceLoaderModules
+        * @dataProvider provideExtractResourceLoaderModules
+        */
+       public function testExtractResourceLoaderModules( $input, $expected ) {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, $input + self::$default );
+               $out = $processor->getExtractedInfo();
+               foreach ( $expected as $key => $value ) {
+                       $this->assertEquals( $value, $out['globals'][$key] );
+               }
+       }
+
+       public static function provideExtractResourceLoaderModules() {
+               $dir = __DIR__ . '/FooBar/';
+               return array(
+                       // Generic module with localBasePath/remoteExtPath specified
+                       array(
+                               // Input
+                               array(
+                                       'ResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => '',
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                       ),
+                               ),
+                               // Expected
+                               array(
+                                       'wgResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                       ),
+                               ),
+                       ),
+                       // ResourceFileModulePaths specified:
+                       array(
+                               // Input
+                               array(
+                                       'ResourceFileModulePaths' => array(
+                                               'localBasePath' => '',
+                                               'remoteExtPath' => 'FooBar',
+                                       ),
+                                       'ResourceModules' => array(
+                                               // No paths
+                                               'test.foo' => array(
+                                                       'styles' => 'foo.js',
+                                               ),
+                                               // Different paths set
+                                               'test.bar' => array(
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => 'subdir',
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ),
+                                               // Custom class with no paths set
+                                               'test.class' => array(
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                               ),
+                                               // Custom class with a localBasePath
+                                               'test.class.with.path' => array(
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => '',
+                                               )
+                                       ),
+                               ),
+                               // Expected
+                               array(
+                                       'wgResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foo.js',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                               'test.bar' => array(
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => $dir . 'subdir',
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ),
+                                               'test.class' => array(
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                               ),
+                                               'test.class.with.path' => array(
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => $dir,
+                                               )
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
        public static function provideSetToGlobal() {
                return array(
                        array(
index f6ea1b4..a87721b 100644 (file)
@@ -46,7 +46,7 @@
        } );
 
        QUnit.test( 'updateTooltipAccessKeys - current browser', 2, function ( assert ) {
-               var title = $( makeInput ( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ),
+               var title = $( makeInput( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ),
                        //The new title should be something like "Title [alt-a]", but the exact label will depend on the browser.
                        //The "a" could be capitalized, and the prefix could be anything, e.g. a simple "^" for ctrl-
                        //(no browser is known using such a short prefix, though) or "Alt+Umschalt+" in German Firefox.
index bbea829..78c185f 100644 (file)
@@ -1,10 +1,10 @@
-(function ($) {
+( function ($) {
 
        QUnit.module('jquery.placeholder', QUnit.newMwEnvironment());
 
        QUnit.test('caches results of feature tests', 2, function (assert) {
-               assert.strictEqual(typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input');
-               assert.strictEqual(typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea');
+               assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input');
+               assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea');
        });
 
        if ($.fn.placeholder.input && $.fn.placeholder.textarea) {
index ece5116..28d6d92 100644 (file)
@@ -80,7 +80,7 @@
                                only: 'scripts'
                        },
                        dataType: 'script'
-               } ).done(function () {
+               } ).done( function () {
                                mwLanguageCache[langCode].fire( mw.language );
                        } ).fail( function () {
                                mwLanguageCache[langCode].fire( false );
index 7aa9133..3f19a64 100644 (file)
                } );
        } );
 
-       QUnit.test( 'getUrl', 4, function ( assert ) {
+       QUnit.test( 'getUrl', 5, function ( assert ) {
                // Not part of startUp module
                mw.config.set( 'wgArticlePath', '/wiki/$1' );
                mw.config.set( 'wgPageName', 'Foobar' );
 
                var href = mw.util.getUrl( 'Sandbox' );
-               assert.equal( href, '/wiki/Sandbox', 'Simple title; Get link for "Sandbox"' );
+               assert.equal( href, '/wiki/Sandbox', 'simple title' );
 
-               href = mw.util.getUrl( 'Foo:Sandbox ? 5+5=10 ! (test)/subpage' );
-               assert.equal( href, '/wiki/Foo:Sandbox_%3F_5%2B5%3D10_!_(test)/subpage',
-                       'Advanced title; Get link for "Foo:Sandbox ? 5+5=10 ! (test)/subpage"' );
+               href = mw.util.getUrl( 'Foo:Sandbox? 5+5=10! (test)/sub ' );
+               assert.equal( href, '/wiki/Foo:Sandbox%3F_5%2B5%3D10!_(test)/sub_', 'advanced title' );
 
                href = mw.util.getUrl();
-               assert.equal( href, '/wiki/Foobar', 'Default title; Get link for current page ("Foobar")' );
+               assert.equal( href, '/wiki/Foobar', 'default title' );
+
+               href = mw.util.getUrl( null, { action: 'edit' } );
+               assert.equal( href, '/wiki/Foobar?action=edit', 'default title with query string' );
 
                href = mw.util.getUrl( 'Sandbox', { action: 'edit' } );
-               assert.equal( href, '/wiki/Sandbox?action=edit',
-                       'Simple title with query string; Get link for "Sandbox" with action=edit' );
+               assert.equal( href, '/wiki/Sandbox?action=edit', 'simple title with query string' );
        } );
 
        QUnit.test( 'wikiScript', 4, function ( assert ) {
index 88314bc..28f22fd 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -321,6 +321,7 @@ function wfStreamThumb( array $params ) {
 
        // Check for thumbnail generation errors...
        $msg = wfMessage( 'thumbnail_error' );
+       $errorCode = 500;
        if ( !$thumb ) {
                $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
        } elseif ( $thumb->isError() ) {
@@ -330,10 +331,11 @@ function wfStreamThumb( array $params ) {
        } elseif ( $thumb->fileIsSource() ) {
                $errorMsg = $msg->
                        rawParams( 'Image was not scaled, is the requested width bigger than the source?' )->escaped();
+               $errorCode = 400;
        }
 
        if ( $errorMsg !== false ) {
-               wfThumbError( 500, $errorMsg );
+               wfThumbError( $errorCode, $errorMsg );
        } else {
                // Stream the file if there were no errors
                $thumb->streamFile( $headers );
@@ -545,7 +547,9 @@ function wfThumbError( $status, $msg ) {
 
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );
-       if ( $status == 404 ) {
+       if ( $status == 400 ) {
+               header( 'HTTP/1.1 400 Bad request' );
+       } elseif ( $status == 404 ) {
                header( 'HTTP/1.1 404 Not found' );
        } elseif ( $status == 403 ) {
                header( 'HTTP/1.1 403 Forbidden' );