aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/URI.js/test
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-10 03:47:49 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-10 03:47:49 +0200
commit21e6b15991212e21a0bd9928890e8e8518f367e8 (patch)
tree3859320d916ff6c2e2fbe2f022375827fa28997c /thirdparty/URI.js/test
parent02fa518542328183f35ae04d389b61226f4c4e30 (diff)
parentd5194154335d6cb30edca9b648083069faf9778c (diff)
downloadwallet-core-21e6b15991212e21a0bd9928890e8e8518f367e8.tar.xz
Merge commit 'd5194154335d6cb30edca9b648083069faf9778c' as 'thirdparty/URI.js'
Diffstat (limited to 'thirdparty/URI.js/test')
-rw-r--r--thirdparty/URI.js/test/index.html26
-rw-r--r--thirdparty/URI.js/test/pre_libs.js7
-rw-r--r--thirdparty/URI.js/test/qunit/qunit-composite.css13
-rw-r--r--thirdparty/URI.js/test/qunit/qunit-composite.js167
-rw-r--r--thirdparty/URI.js/test/qunit/qunit.css244
-rw-r--r--thirdparty/URI.js/test/qunit/qunit.js2212
-rw-r--r--thirdparty/URI.js/test/test.URI.html26
-rw-r--r--thirdparty/URI.js/test/test.fragmentQuery.html31
-rw-r--r--thirdparty/URI.js/test/test.fragmentURI.html31
-rw-r--r--thirdparty/URI.js/test/test.jQuery-1.10.html31
-rw-r--r--thirdparty/URI.js/test/test.jQuery-1.7.html31
-rw-r--r--thirdparty/URI.js/test/test.jQuery-1.8.html31
-rw-r--r--thirdparty/URI.js/test/test.jQuery-1.9.html31
-rw-r--r--thirdparty/URI.js/test/test.js1837
-rw-r--r--thirdparty/URI.js/test/test_fragmentQuery.js59
-rw-r--r--thirdparty/URI.js/test/test_fragmentURI.js61
-rw-r--r--thirdparty/URI.js/test/test_jim.js146
-rw-r--r--thirdparty/URI.js/test/test_jquery.js141
-rw-r--r--thirdparty/URI.js/test/test_template.js411
-rw-r--r--thirdparty/URI.js/test/urls.js1892
20 files changed, 7428 insertions, 0 deletions
diff --git a/thirdparty/URI.js/test/index.html b/thirdparty/URI.js/test/index.html
new file mode 100644
index 000000000..4efc3393f
--- /dev/null
+++ b/thirdparty/URI.js/test/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <link rel="stylesheet" href="qunit/qunit-composite.css" type="text/css" media="screen">
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+ <script type="text/javascript" src="qunit/qunit-composite.js"></script>
+ <script>
+ QUnit.testSuites([
+ "test.URI.html",
+ "test.jQuery-1.10.html",
+ "test.jQuery-1.9.html",
+ "test.jQuery-1.8.html",
+ "test.jQuery-1.7.html",
+ "test.fragmentQuery.html",
+ "test.fragmentURI.html"
+ ]);
+ </script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/pre_libs.js b/thirdparty/URI.js/test/pre_libs.js
new file mode 100644
index 000000000..c12a06e76
--- /dev/null
+++ b/thirdparty/URI.js/test/pre_libs.js
@@ -0,0 +1,7 @@
+/*global window */
+// FIXME: v2.0.0 renamce non-camelCase properties to uppercase
+/*jshint camelcase: false */
+window.URI = window.URI_pre_lib = 'original URI, before loading URI.js library';
+window.URITemplate = window.URITemplate_pre_lib = 'original URITemplate, before loading URI.js library';
+window.IPv6 = window.IPv6_pre_lib = 'original IPv6, before loading URI.js library';
+window.SecondLevelDomains = window.SecondLevelDomains_pre_lib = 'original SecondLevelDomains, before loading URI.js library';
diff --git a/thirdparty/URI.js/test/qunit/qunit-composite.css b/thirdparty/URI.js/test/qunit/qunit-composite.css
new file mode 100644
index 000000000..54e791b13
--- /dev/null
+++ b/thirdparty/URI.js/test/qunit/qunit-composite.css
@@ -0,0 +1,13 @@
+.qunit-composite-suite {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+
+ margin: 0;
+ padding: 0;
+ border-width: 1px 0 0;
+ height: 45%;
+ width: 100%;
+
+ background: #fff;
+}
diff --git a/thirdparty/URI.js/test/qunit/qunit-composite.js b/thirdparty/URI.js/test/qunit/qunit-composite.js
new file mode 100644
index 000000000..b713f55c8
--- /dev/null
+++ b/thirdparty/URI.js/test/qunit/qunit-composite.js
@@ -0,0 +1,167 @@
+/**
+ * JUnit reporter for QUnit v1.0.1
+ *
+ * https://github.com/jquery/qunit-composite
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * https://jquery.org/license/
+ */
+(function( QUnit ) {
+var iframe, hasBound, addClass,
+ modules = 1,
+ executingComposite = false;
+
+// TODO: Kill this fallback method once QUnit 1.12 is released
+addClass = typeof QUnit.addClass === "function" ?
+ QUnit.addClass :
+ (function() {
+ var hasClass = typeof QUnit.hasClass === "function" ?
+ QUnit.hasClass :
+ function hasClass( elem, name ) {
+ return ( " " + elem.className + " " ).indexOf( " " + name + " " ) > -1;
+ };
+ return function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += ( elem.className ? " " : "" ) + name;
+ }
+ };
+ })();
+
+function runSuite( suite ) {
+ var path;
+
+ if ( QUnit.is( "object", suite ) ) {
+ path = suite.path;
+ suite = suite.name;
+ } else {
+ path = suite;
+ }
+
+ QUnit.asyncTest( suite, function() {
+ iframe.setAttribute( "src", path );
+ // QUnit.start is called from the child iframe's QUnit.done hook.
+ });
+}
+
+function initIframe() {
+ var iframeWin,
+ body = document.body;
+
+ function onIframeLoad() {
+ var moduleName, testName,
+ count = 0;
+
+ if ( !iframe.src ) {
+ return;
+ }
+
+ iframeWin.QUnit.moduleStart(function( data ) {
+ // Capture module name for messages
+ moduleName = data.name;
+ });
+
+ iframeWin.QUnit.testStart(function( data ) {
+ // Capture test name for messages
+ testName = data.name;
+ });
+ iframeWin.QUnit.testDone(function() {
+ testName = undefined;
+ });
+
+ iframeWin.QUnit.log(function( data ) {
+ if (testName === undefined) {
+ return;
+ }
+ // Pass all test details through to the main page
+ var message = ( moduleName ? moduleName + ": " : "" ) + testName + ": " + ( data.message || ( data.result ? "okay" : "failed" ) );
+ expect( ++count );
+ QUnit.push( data.result, data.actual, data.expected, message );
+ });
+
+ // Continue the outer test when the iframe's test is done
+ iframeWin.QUnit.done( QUnit.start );
+ }
+
+ iframe = document.createElement( "iframe" );
+ iframe.className = "qunit-composite-suite";
+ body.appendChild( iframe );
+
+ QUnit.addEvent( iframe, "load", onIframeLoad );
+
+ iframeWin = iframe.contentWindow;
+}
+
+/**
+ * @param {string} [name] Module name to group these test suites.
+ * @param {Array} suites List of suites where each suite
+ * may either be a string (path to the html test page),
+ * or an object with a path and name property.
+ */
+QUnit.testSuites = function( name, suites ) {
+ var i, suitesLen;
+
+ if ( arguments.length === 1 ) {
+ suites = name;
+ name = "Composition #" + modules++;
+ }
+ suitesLen = suites.length;
+
+ if ( !hasBound ) {
+ hasBound = true;
+ QUnit.begin( initIframe );
+
+ // TODO: Would be better to use something like QUnit.once( 'moduleDone' )
+ // after the last test suite.
+ QUnit.moduleDone( function () {
+ executingComposite = false;
+ } );
+
+ QUnit.done(function() {
+ iframe.style.display = "none";
+ });
+ }
+
+ QUnit.module( name, {
+ setup: function () {
+ executingComposite = true;
+ }
+ });
+
+ for ( i = 0; i < suitesLen; i++ ) {
+ runSuite( suites[ i ] );
+ }
+};
+
+QUnit.testDone(function() {
+ if ( !executingComposite ) {
+ return;
+ }
+
+ var i, len,
+ current = QUnit.id( this.config.current.id ),
+ children = current.children,
+ src = iframe.src;
+
+ QUnit.addEvent( current, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = src;
+ }
+ });
+
+ // Undo QUnit's auto-expansion for bad tests
+ for ( i = 0, len = children.length; i < len; i++ ) {
+ if ( children[ i ].nodeName.toLowerCase() === "ol" ) {
+ addClass( children[ i ], "qunit-collapsed" );
+ }
+ }
+
+ // Update Rerun link to point to the standalone test suite page
+ current.getElementsByTagName( "a" )[ 0 ].href = src;
+});
+
+})( QUnit );
diff --git a/thirdparty/URI.js/test/qunit/qunit.css b/thirdparty/URI.js/test/qunit/qunit.css
new file mode 100644
index 000000000..7ba3f9a30
--- /dev/null
+++ b/thirdparty/URI.js/test/qunit/qunit.css
@@ -0,0 +1,244 @@
+/**
+ * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+/** 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: normal;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+}
+
+#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 .5em 0 .1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ 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 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#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;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 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: black; }
+#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: #999999; }
+
+#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;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/thirdparty/URI.js/test/qunit/qunit.js b/thirdparty/URI.js/test/qunit/qunit.js
new file mode 100644
index 000000000..84c73907d
--- /dev/null
+++ b/thirdparty/URI.js/test/qunit/qunit.js
@@ -0,0 +1,2212 @@
+/**
+ * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * https://jquery.org/license/
+ */
+
+(function( window ) {
+
+var QUnit,
+ assert,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ 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,
+ setTimeout = window.setTimeout,
+ defined = {
+ setTimeout: typeof 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 ) {
+ // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
+ /*jshint newcap: false */
+ var key, val,
+ vals = QUnit.is( "array", obj ) ? [] : {};
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ val = obj[key];
+ vals[key] = val === Object(val) ? objectValues(val) : val;
+ }
+ }
+ return vals;
+ };
+
+function Test( settings ) {
+ extend( this, settings );
+ this.assertions = [];
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ init: function() {
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ testNumber: this.testNumber });
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
+ tests.appendChild( li );
+ }
+ },
+ setup: 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", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment );
+
+ this.started = +new Date();
+ runLoggingCallbacks( "testStart", QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ /*jshint camelcase:false */
+
+
+ /**
+ * Expose the current test environment.
+ *
+ * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
+ */
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ /*jshint camelcase:true */
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ return;
+ }
+ try {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ },
+ run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: <br/>" + this.nameHtml;
+ }
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = +new Date();
+
+ if ( config.notrycatch ) {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ return;
+ }
+
+ try {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ } catch( e ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+
+ QUnit.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();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ if ( config.notrycatch ) {
+ if ( typeof this.callbackRuntime === "undefined" ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ }
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ }
+ checkPollution();
+ },
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ QUnit.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 ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i, assertion, a, b, time, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
+
+ this.runtime = +new Date() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ ol = document.createElement( "ol" );
+ ol.className = "qunit-assert-list";
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
+
+ li = document.createElement( "li" );
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( ol, "qunit-collapsed" );
+ }
+
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+ addEvent(b, "click", function() {
+ var next = b.parentNode.lastChild,
+ collapsed = hasClass( next, "qunit-collapsed" );
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
+ });
+
+ addEvent(b, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ testNumber: test.testNumber });
+ }
+ });
+
+ // `time` initialized at top of scope
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = this.runtime + " ms";
+
+ // `li` initialized at top of scope
+ li = id( this.id );
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ a = li.firstChild;
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( time );
+ li.appendChild( ol );
+
+ } else {
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ runLoggingCallbacks( "testDone", QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ duration: this.runtime
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(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 + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ }
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ config.currentModule = name;
+ config.currentModuleTestEnvironment = testEnvironment;
+ config.modules[name] = true;
+ },
+
+ 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,
+ nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
+ }
+
+ test = new Test({
+ nameHtml: nameHtml,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
+ return;
+ }
+
+ test.queue();
+ },
+
+ // 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) {
+ config.current.expected = asserts;
+ } else {
+ return config.current.expected;
+ }
+ },
+
+ start: function( count ) {
+ // QUnit hasn't been initialized yet.
+ // Note: RequireJS (et al) may delay onLoad
+ if ( config.semaphore === undefined ) {
+ QUnit.begin(function() {
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
+ setTimeout(function() {
+ QUnit.start( count );
+ });
+ });
+ return;
+ }
+
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
+ return;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ setTimeout(function() {
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ config.blocking = false;
+ process( true );
+ }, 13);
+ } else {
+ config.blocking = false;
+ process( true );
+ }
+ },
+
+ stop: function( count ) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// `assert` initialized at top of scope
+// Assert helpers
+// All of these must either call QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+// We attach it to the QUnit object *after* we expose the public API,
+// otherwise `assert` will become a global variable in browsers (#341).
+assert = {
+ /**
+ * Asserts rough true-ish result.
+ * @name ok
+ * @function
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+ msg = msg || (result ? "okay" : "failed" );
+
+ var source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: msg
+ };
+
+ msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
+ config.current.assertions.push({
+ result: result,
+ message: msg
+ });
+ },
+
+ /**
+ * 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( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+ */
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected == actual, actual, expected, message );
+ },
+
+ /**
+ * @name notEqual
+ * @function
+ */
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected != actual, actual, expected, message );
+ },
+
+ /**
+ * @name propEqual
+ * @function
+ */
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notPropEqual
+ * @function
+ */
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name deepEqual
+ * @function
+ */
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notDeepEqual
+ * @function
+ */
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name strictEqual
+ * @function
+ */
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
+ },
+
+ /**
+ * @name notStrictEqual
+ * @function
+ */
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual,
+ expectedOutput = expected,
+ ok = false;
+
+ // 'expected' is optional
+ if ( typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ config.current.ignoreGlobalErrors = true;
+ try {
+ block.call( config.current.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ config.current.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+ // expected is a regexp
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+ // expected is a constructor
+ } else if ( actual instanceof expected ) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if ( expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+
+ QUnit.push( ok, actual, expectedOutput, message );
+ } else {
+ QUnit.pushFailure( message, null, "No exception was thrown." );
+ }
+ }
+};
+
+/**
+ * @deprecated since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility.
+ */
+extend( QUnit, assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept root "raises()" for backwards compatibility.
+ * (Note that we don't introduce assert.raises).
+ */
+QUnit.raises = assert[ "throws" ];
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+ function F() {}
+ F.prototype = QUnit;
+ QUnit = new F();
+ // Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+}());
+
+/**
+ * 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,
+
+ // 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: "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: {},
+
+ // logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+ extend( window, QUnit.constructor.prototype );
+
+ // Expose QUnit object
+ window.QUnit = QUnit;
+}
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ 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;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ // Exact match of the module name
+ config.module = urlParams.module;
+
+ config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+ assert: assert,
+
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date(),
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 1
+ });
+
+ var tests, banner, result,
+ 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/>&nbsp;";
+ }
+ },
+
+ // 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
+ */
+ reset: function() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+ },
+
+ // Trigger an event on an element.
+ // @example triggerEvent( document.body, "click" );
+ triggerEvent: function( elem, type, event ) {
+ if ( document.createEvent ) {
+ event = document.createEvent( "MouseEvents" );
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+ elem.dispatchEvent( event );
+ } else if ( elem.fireEvent ) {
+ elem.fireEvent( "on" + type );
+ }
+ },
+
+ // 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;
+ },
+
+ push: function( result, actual, expected, message ) {
+ if ( !config.current ) {
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+ }
+
+ var output, source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ if ( !result ) {
+ expected = escapeText( QUnit.jsDump.parse(expected) );
+ actual = escapeText( QUnit.jsDump.parse(actual) );
+ output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+ if ( actual !== expected ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+ output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+ }
+
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
+ }
+
+ output += "</table>";
+ }
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+
+ var output,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: false,
+ message: message
+ };
+
+ message = escapeText( message ) || "error";
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ output += "<table>";
+
+ if ( actual ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
+ }
+
+ if ( source ) {
+ details.source = source;
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
+ }
+
+ output += "</table>";
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: false,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var key,
+ querystring = "?";
+
+ for ( key in params ) {
+ if ( hasOwn.call( params, key ) ) {
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ }
+ return window.location.protocol + "//" + window.location.host +
+ window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ extend: extend,
+ id: id,
+ addEvent: addEvent,
+ addClass: addClass,
+ hasClass: hasClass,
+ removeClass: removeClass
+ // load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: registerLoggingCallback( "begin" ),
+
+ // done: { failed, passed, total, runtime }
+ done: registerLoggingCallback( "done" ),
+
+ // log: { result, actual, expected, message }
+ log: registerLoggingCallback( "log" ),
+
+ // testStart: { name }
+ testStart: registerLoggingCallback( "testStart" ),
+
+ // testDone: { name, failed, passed, total, duration }
+ testDone: registerLoggingCallback( "testDone" ),
+
+ // moduleStart: { name }
+ moduleStart: registerLoggingCallback( "moduleStart" ),
+
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+QUnit.load = function() {
+ runLoggingCallbacks( "begin", QUnit, {} );
+
+ // Initialize the config, saving the execution queue
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
+ urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
+ numModules = 0,
+ moduleNames = [],
+ moduleFilterHtml = "",
+ urlConfigHtml = "",
+ oldconfig = extend( {}, config );
+
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ len = config.urlConfig.length;
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[i];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val,
+ tooltip: "[no tooltip available]"
+ };
+ }
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
+ "' name='" + escapeText( val.id ) +
+ "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
+ " title='" + escapeText( val.tooltip ) +
+ "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
+ "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
+ }
+ for ( i in config.modules ) {
+ if ( config.modules.hasOwnProperty( i ) ) {
+ moduleNames.push(i);
+ }
+ }
+ numModules = moduleNames.length;
+ moduleNames.sort( function( a, b ) {
+ return a.localeCompare( b );
+ });
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+ ( config.module === undefined ? "selected='selected'" : "" ) +
+ ">< All Modules ></option>";
+
+
+ for ( i = 0; i < numModules; i++) {
+ moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
+ ( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
+ ">" + escapeText(moduleNames[i]) + "</option>";
+ }
+ moduleFilterHtml += "</select>";
+
+ // `userAgent` initialized at top of scope
+ userAgent = id( "qunit-userAgent" );
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+
+ // `banner` initialized at top of scope
+ banner = id( "qunit-header" );
+ if ( banner ) {
+ banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
+ }
+
+ // `toolbar` initialized at top of scope
+ toolbar = id( "qunit-testrunner-toolbar" );
+ if ( toolbar ) {
+ // `filter` initialized at top of scope
+ filter = document.createElement( "input" );
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+
+ addEvent( filter, "click", function() {
+ var tmp,
+ ol = document.getElementById( "qunit-tests" );
+
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace( / hidepass /, " " );
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+ } else {
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
+ }
+ }
+ });
+
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+ filter.checked = true;
+ // `ol` initialized at top of scope
+ ol = document.getElementById( "qunit-tests" );
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ // `label` initialized at top of scope
+ label = document.createElement( "label" );
+ label.setAttribute( "for", "qunit-filter-pass" );
+ label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+
+ urlConfigCheckboxesContainer = document.createElement("span");
+ urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
+ urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change"
+ // * Fallback from event.target to event.srcElement
+ addEvents( urlConfigCheckboxes, "click", function( event ) {
+ var params = {},
+ target = event.target || event.srcElement;
+ params[ target.name ] = target.checked ? true : undefined;
+ window.location = QUnit.url( params );
+ });
+ toolbar.appendChild( urlConfigCheckboxesContainer );
+
+ if (numModules > 1) {
+ moduleFilter = document.createElement( "span" );
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+ moduleFilter.innerHTML = moduleFilterHtml;
+ addEvent( moduleFilter.lastChild, "change", function() {
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+ window.location = QUnit.url({
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
+ // Remove any existing filters
+ filter: undefined,
+ testNumber: undefined
+ });
+ });
+ toolbar.appendChild(moduleFilter);
+ }
+ }
+
+ // `main` initialized at top of scope
+ main = id( "qunit-fixture" );
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if ( config.autostart ) {
+ QUnit.start();
+ }
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `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: validTest } ) );
+ }
+ return false;
+ }
+
+ return ret;
+};
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.currentModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.currentModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ delete config.previousModule;
+
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ runtime = +new Date() - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ "Tests completed in ",
+ runtime,
+ " milliseconds.<br/>",
+ "<span class='passed'>",
+ passed,
+ "</span> assertions of <span class='total'>",
+ config.stats.all,
+ "</span> passed, <span class='failed'>",
+ config.stats.bad,
+ "</span> failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && typeof document !== "undefined" && 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 = [
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ // `key` & `i` initialized at top of scope
+ 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 ( window.scrollTo ) {
+ window.scrollTo(0, 0);
+ }
+
+ runLoggingCallbacks( "done", QUnit, {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ });
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = config.module && config.module.toLowerCase(),
+ fullName = (test.module + ": " + test.testName).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( test.callback && test.callback.validTest === validTest ) {
+ delete test.callback.validTest;
+ return true;
+ }
+
+ if ( config.testNumber ) {
+ return test.testNumber === config.testNumber;
+ }
+
+ if ( module && ( !test.module || test.module.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;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+ offset = offset === undefined ? 3 : offset;
+
+ var stack, include, i;
+
+ if ( e.stacktrace ) {
+ // Opera
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
+ } else if ( e.stack ) {
+ // Firefox, Chrome
+ 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, PhantomJS
+ // hopefully one day Safari provides actual stacktraces
+ // 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 ) {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ return extractStacktrace( e, offset );
+ }
+}
+
+/**
+ * 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;";
+ }
+ });
+}
+
+function synchronize( callback, last ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process( last );
+ }
+}
+
+function process( last ) {
+ function next() {
+ process( last );
+ }
+ var start = new Date().getTime();
+ config.depth = config.depth ? config.depth + 1 : 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ config.queue.shift()();
+ } else {
+ setTimeout( next, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+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 ) {
+ 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 {
+ a[ prop ] = b[ prop ];
+ }
+ }
+ }
+ }
+
+ return a;
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ // Standards-based browsers
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, fn, false );
+ // IE
+ } else {
+ 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 + " ") > -1;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+ // Class name may appear multiple times
+ while ( set.indexOf(" " + name + " ") > -1 ) {
+ set = set.replace(" " + name + " " , " ");
+ }
+ // If possible, trim it for prettiness, but not necessarily
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+}
+
+function id( name ) {
+ return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+ document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+ return function( callback ) {
+ config[key].push( callback );
+ };
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+ var i, callbacks;
+ if ( QUnit.hasOwnProperty( key ) ) {
+ QUnit[ key ].call(scope, args );
+ } else {
+ callbacks = config[ key ];
+ for ( i = 0; i < callbacks.length; i++ ) {
+ callbacks[ i ].call( scope, args );
+ }
+ }
+}
+
+// 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 */
+ 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) ) {
+ return false; // don't lose time with error prone cases
+ } 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;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+ }
+ function literal( o ) {
+ return o + "";
+ }
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.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);
+ this.up();
+ while ( i-- ) {
+ ret[i] = this.parse( arr[i] , undefined , stack);
+ }
+ this.down();
+ return join( "[", ret, "]" );
+ }
+
+ var reName = /^function (\w+)/,
+ jsDump = {
+ // type is used mostly internally, you can fix a (custom)type in advance
+ parse: function( obj, type, stack ) {
+ stack = stack || [ ];
+ var inStack, res,
+ parser = this.parsers[ type || this.typeOf(obj) ];
+
+ type = typeof parser;
+ inStack = inArray( obj, stack );
+
+ if ( inStack !== -1 ) {
+ return "recursion(" + (inStack - stack.length) + ")";
+ }
+ if ( type === "function" ) {
+ stack.push( obj );
+ res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ return ( type === "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 ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof 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" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof 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 ? "&nbsp;" : " ";
+ },
+ // 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, "&nbsp;" );
+ }
+ 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,
+ // This is the list of parsers, to modify them, use jsDump.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, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function( map, stack ) {
+ /*jshint forin:false */
+ var ret = [ ], keys, key, val, i;
+ QUnit.jsDump.up();
+ keys = [];
+ for ( key in map ) {
+ keys.push( key );
+ }
+ keys.sort();
+ for ( i = 0; i < keys.length; i++ ) {
+ key = keys[ i ];
+ val = map[ key ];
+ ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+ }
+ QUnit.jsDump.down();
+ return join( "{", ret, "}" );
+ },
+ node: function( node ) {
+ var len, i, val,
+ open = QUnit.jsDump.HTML ? "&lt;" : "<",
+ close = QUnit.jsDump.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 + "=" + QUnit.jsDump.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 jsDump;
+}());
+
+// 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;
+}
+
+/*
+ * 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() {
+ /*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;
+ };
+}());
+
+// for CommonJS environments, export everything
+if ( typeof exports !== "undefined" ) {
+ extend( exports, QUnit.constructor.prototype );
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
diff --git a/thirdparty/URI.js/test/test.URI.html b/thirdparty/URI.js/test/test.URI.html
new file mode 100644
index 000000000..94f63c1c5
--- /dev/null
+++ b/thirdparty/URI.js/test/test.URI.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>URI.js - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../jquery-1.10.2.min.js"></script>
+ <script type="text/javascript" src="../src/jquery.URI.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.fragmentQuery.html b/thirdparty/URI.js/test/test.fragmentQuery.html
new file mode 100644
index 000000000..ec01640d8
--- /dev/null
+++ b/thirdparty/URI.js/test/test.fragmentQuery.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>fragmentQuery - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../src/URI.fragmentQuery.js"></script>
+ <script type="text/javascript" src="../jquery-1.9.1.min.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_fragmentQuery.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.fragmentURI.html b/thirdparty/URI.js/test/test.fragmentURI.html
new file mode 100644
index 000000000..50b5b9a79
--- /dev/null
+++ b/thirdparty/URI.js/test/test.fragmentURI.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>fragmentURI - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../src/URI.fragmentURI.js"></script>
+ <script type="text/javascript" src="../jquery-1.9.1.min.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_fragmentURI.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.jQuery-1.10.html b/thirdparty/URI.js/test/test.jQuery-1.10.html
new file mode 100644
index 000000000..5b7d50790
--- /dev/null
+++ b/thirdparty/URI.js/test/test.jQuery-1.10.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>jQuery Plugin 1.10 - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../jquery-1.10.2.min.js"></script>
+ <script type="text/javascript" src="../src/jquery.URI.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_jquery.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.jQuery-1.7.html b/thirdparty/URI.js/test/test.jQuery-1.7.html
new file mode 100644
index 000000000..a6283cc35
--- /dev/null
+++ b/thirdparty/URI.js/test/test.jQuery-1.7.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>jQuery Plugin 1.7 - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../jquery-1.7.2.min.js"></script>
+ <script type="text/javascript" src="../src/jquery.URI.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_jquery.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.jQuery-1.8.html b/thirdparty/URI.js/test/test.jQuery-1.8.html
new file mode 100644
index 000000000..e25782b38
--- /dev/null
+++ b/thirdparty/URI.js/test/test.jQuery-1.8.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>jQuery Plugin 1.8 - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../jquery-1.8.2.min.js"></script>
+ <script type="text/javascript" src="../src/jquery.URI.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_jquery.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.jQuery-1.9.html b/thirdparty/URI.js/test/test.jQuery-1.9.html
new file mode 100644
index 000000000..32f544f29
--- /dev/null
+++ b/thirdparty/URI.js/test/test.jQuery-1.9.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>jQuery Plugin 1.9 - URI Test Suite</title>
+ <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="pre_libs.js"></script>
+ <script type="text/javascript" src="../src/punycode.js"></script>
+ <script type="text/javascript" src="../src/IPv6.js"></script>
+ <script type="text/javascript" src="../src/SecondLevelDomains.js"></script>
+ <script type="text/javascript" src="../src/URI.js"></script>
+ <script type="text/javascript" src="../src/URITemplate.js"></script>
+ <script type="text/javascript" src="../jquery-1.9.1.min.js"></script>
+ <script type="text/javascript" src="../src/jquery.URI.js"></script>
+ <script type="text/javascript" src="qunit/qunit.js"></script>
+
+ <!--
+ as the plugins alter core URI functionality,
+ it is necessary to re-test URI itself was well
+ -->
+ <script type="text/javascript" src="urls.js"></script>
+ <script type="text/javascript" src="test.js"></script>
+ <script type="text/javascript" src="test_jim.js"></script>
+ <script type="text/javascript" src="test_template.js"></script>
+ <script type="text/javascript" src="test_jquery.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/thirdparty/URI.js/test/test.js b/thirdparty/URI.js/test/test.js
new file mode 100644
index 000000000..e1ade0c23
--- /dev/null
+++ b/thirdparty/URI.js/test/test.js
@@ -0,0 +1,1837 @@
+(function() {
+ 'use strict';
+ /*global window, document, location, URI, URI_pre_lib, IPv6, IPv6_pre_lib, URITemplate, URITemplate_pre_lib, SecondLevelDomains, SecondLevelDomains_pre_lib, urls, test, ok, equal, strictEqual, deepEqual, raises */
+ // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
+ /*jshint camelcase: false, loopfunc: true */
+
+ test('loaded', function() {
+ ok(window.URI);
+ });
+
+ module('constructing');
+ test('URI()', function() {
+ var u = URI();
+ ok(u instanceof URI, 'instanceof URI');
+ equal(u.toString(), window.location && window.location.href || '', 'is location (browser) or empty string (node)');
+ });
+ test('URI(undefined)', function() {
+ raises(function() {
+ URI(undefined);
+ }, TypeError, 'Failing undefined input');
+ });
+ test('new URI(string)', function() {
+ var u = new URI('http://example.org/');
+ ok(u instanceof URI, 'instanceof URI');
+ ok(u._parts.hostname !== undefined, 'host undefined');
+ });
+ test('new URI(object)', function() {
+ var u = new URI({protocol: 'http', hostname: 'example.org'});
+ ok(u instanceof URI, 'instanceof URI');
+ ok(u._parts.hostname !== undefined, 'host undefined');
+ });
+ test('new URI(Location)', function () {
+ var u = new URI(location);
+ equal(u.href(), String(location.href), 'location object');
+ });
+ test('new URI(undefined)', function() {
+ var u = new URI();
+ ok(u instanceof URI, 'instanceof URI');
+ equal(u.toString(), window.location && window.location.href || '', 'is location (browser) or empty string (node)');
+ raises(function() {
+ new URI(undefined);
+ }, TypeError, 'Failing undefined input');
+ });
+ (function() {
+ var element;
+
+ function testDomAttribute(element, attribute) {
+ test('new URI(Element ' + element.nodeName + ')', function() {
+ element[attribute] = 'http://example.org/foobar.html';
+
+ var u = new URI(element);
+ equal(u.scheme(), 'http', 'scheme');
+ equal(u.host(), 'example.org', 'host');
+ equal(u.path(), '/foobar.html', 'path');
+
+ element[attribute] = 'file:///C:/foo/bar.html';
+ u = new URI(element);
+ equal(u.href(), element[attribute], 'file');
+ });
+ }
+
+ function testUnsupportedDomAttribute(element, attribute) {
+ test('new URI(unsupported Element ' + element.nodeName + ')', function() {
+ element[attribute] = 'http://example.org/foobar.html';
+
+ var u = new URI(element);
+ equal(u.scheme(), '', 'scheme');
+ equal(u.host(), '', 'host');
+ equal(u.path(), '', 'path');
+
+ element[attribute] = 'file:///C:/foo/bar.html';
+ u = new URI(element);
+ equal(u.href(), '', 'file');
+ });
+ }
+
+ for (var nodeName in URI.domAttributes) {
+ if (!Object.prototype.hasOwnProperty.call(URI.domAttributes, nodeName) || nodeName === 'input') {
+ continue;
+ }
+
+ element = document.createElement(nodeName);
+ testDomAttribute(element, URI.domAttributes[nodeName]);
+ }
+
+ element = document.createElement('input');
+ element.type = 'image';
+ testDomAttribute(element, 'src');
+
+ element = document.createElement('input');
+ testUnsupportedDomAttribute(element, 'src');
+
+ element = document.createElement('div');
+ testUnsupportedDomAttribute(element, 'src');
+ })();
+ test('new URI(URI)', function() {
+ var u = new URI(new URI({protocol: 'http', hostname: 'example.org'}));
+ ok(u instanceof URI, 'instanceof URI');
+ ok(u._parts.hostname !== undefined, 'host undefined');
+ });
+ test('new URI(new Date())', function() {
+ raises(function() {
+ new URI(new Date());
+ }, TypeError, 'Failing unknown input');
+ });
+ test('new URI(undefined)', function() {
+ raises(function() {
+ new URI(undefined);
+ }, TypeError, 'Failing undefined input');
+ });
+ test('new URI()', function() {
+ var u = new URI();
+ ok(u instanceof URI, 'instanceof URI');
+ ok(u._parts.hostname === location.hostname || u._parts.hostname === null && location.hostname === '',
+ 'hostname == location.hostname');
+ });
+ test('function URI(string)', function() {
+ var u = new URI('http://example.org/');
+ ok(u instanceof URI, 'instanceof URI');
+ ok(u._parts.hostname !== undefined, 'host undefined');
+ });
+ test('new URI(string, string)', function() {
+ // see http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
+ var u = new URI('../foobar.html', 'http://example.org/hello/world.html');
+ equal(u+'', 'http://example.org/foobar.html', 'resolve on construct');
+ });
+
+ module('parsing');
+ // [].forEach() no IE, lacking interest in polyfilling this...
+ for (var i = 0, t; (t = urls[i]); i++) {
+ (function(t){
+ test('parse ' + t.name, function() {
+ var u = new URI(t.url),
+ key;
+
+ // test URL built from parts
+ equal(u + '', t._url || t.url, 'toString');
+
+ // test parsed parts
+ for (key in t.parts) {
+ if (Object.hasOwnProperty.call(t.parts, key)) {
+ equal(u._parts[key], t.parts[key], 'part: ' + key);
+ }
+ }
+
+ // test accessors
+ for (key in t.accessors) {
+ if (Object.hasOwnProperty.call(t.accessors, key)) {
+ equal(u[key](), t.accessors[key], 'accessor: ' + key);
+ }
+ }
+
+ // test is()
+ for (key in t.is) {
+ if (Object.hasOwnProperty.call(t.is, key)) {
+ equal(u.is(key), t.is[key], 'is: ' + key);
+ }
+ }
+ });
+ })(t);
+ }
+
+ module('mutating basics');
+ test('protocol', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.protocol('ftp');
+ equal(u.protocol(), 'ftp', 'ftp protocol');
+ equal(u+'', 'ftp://example.org/foo.html', 'ftp url');
+
+ u.protocol('');
+ equal(u.protocol(), '', 'relative protocol');
+ equal(u+'', '//example.org/foo.html', 'relative-scheme url');
+
+ u.protocol('f.t-p+0');
+ equal(u.protocol(), 'f.t-p+0', 'character profile');
+
+ try {
+ u.protocol('f:t');
+ ok(false, 'do not accept invalid protocol');
+ } catch(e) {}
+
+ u.protocol(null);
+ equal(u.protocol(), '', 'missing protocol');
+ equal(u+'', '//example.org/foo.html', 'missing-scheme url');
+ });
+ test('username', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.username('hello');
+ equal(u.username(), 'hello', 'changed username hello');
+ equal(u.password(), '', 'changed passowrd hello');
+ equal(u+'', 'http://hello@example.org/foo.html', 'changed url hello');
+
+ u.username('');
+ equal(u.username(), '', 'changed username ""');
+ equal(u.password(), '', 'changed passowrd ""');
+ equal(u+'', 'http://example.org/foo.html', 'changed url ""');
+ });
+ test('password', function() {
+ var u = new URI('http://hello@example.org/foo.html');
+ u.password('world');
+ equal(u.username(), 'hello', 'changed username world');
+ equal(u.password(), 'world', 'changed passowrd world');
+ equal(u+'', 'http://hello:world@example.org/foo.html', 'changed url world');
+
+ u.password('');
+ equal(u.username(), 'hello', 'changed username ""');
+ equal(u.password(), '', 'changed passowrd ""');
+ equal(u+'', 'http://hello@example.org/foo.html', 'changed url ""');
+
+ u.username('').password('hahaha');
+ equal(u.username(), '', 'changed username - password without username');
+ equal(u.password(), 'hahaha', 'changed password - password without username');
+ equal(u+'', 'http://:hahaha@example.org/foo.html', 'changed url - password without username');
+ });
+ test('hostname', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.hostname('abc.foobar.lala');
+ equal(u.hostname(), 'abc.foobar.lala', 'hostname changed');
+ equal(u+'', 'http://abc.foobar.lala/foo.html', 'hostname changed url');
+
+ u.hostname('');
+ equal(u.hostname(), '', 'hostname removed');
+ equal(u+'', 'http:///foo.html', 'hostname removed url');
+
+ raises(function() {
+ u.hostname('foo\\bar.com');
+ }, TypeError, 'Failing backslash detection in hostname');
+ });
+ test('port', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.port('80');
+ equal(u.port(), '80', 'changing port 80');
+ equal(u+'', 'http://example.org:80/foo.html', 'changing url 80');
+
+ u.port('');
+ equal(u.port(), '', 'changing port ""');
+ equal(u+'', 'http://example.org/foo.html', 'changing url ""');
+ });
+ test('path', function() {
+ var u = new URI('http://example.org/foobar.html?query=string');
+ u.pathname('/some/path/file.suffix');
+ equal(u.pathname(), '/some/path/file.suffix', 'changing pathname "/some/path/file.suffix"');
+ equal(u+'', 'http://example.org/some/path/file.suffix?query=string', 'changing url "/some/path/file.suffix"');
+
+ u.pathname('');
+ equal(u.pathname(), '/', 'changing pathname ""');
+ equal(u+'', 'http://example.org/?query=string', 'changing url ""');
+
+ u.pathname('/~userhome/@mine;is %2F and/');
+ equal(u.pathname(), '/~userhome/@mine;is%20%2F%20and/', 'path encoding');
+ equal(u.pathname(true), '/~userhome/@mine;is %2F and/', 'path decoded');
+
+ u = new URI('/a/b/c/').relativeTo('/a/b/c/');
+ equal(u.pathname(), '', 'empty relative path');
+ equal(u.toString(), '', 'empty relative path to string');
+
+ u.pathname('/');
+ equal(u.pathname(), '/', 'empty absolute path');
+ equal(u.toString(), '/', 'empty absolute path to string');
+ });
+ test('URN paths', function() {
+ var u = new URI('urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66?foo=bar');
+ u.pathname('uuid:de305d54-75b4-431b-adb2-eb6b9e546013');
+ equal(u.pathname(), 'uuid:de305d54-75b4-431b-adb2-eb6b9e546013');
+ equal(u + '', 'urn:uuid:de305d54-75b4-431b-adb2-eb6b9e546013?foo=bar');
+
+ u.pathname('');
+ equal(u.pathname(), '', 'changing pathname ""');
+ equal(u+'', 'urn:?foo=bar', 'changing url ""');
+
+ u.pathname('music:classical:Béla Bártok%3a Concerto for Orchestra');
+ equal(u.pathname(), 'music:classical:B%C3%A9la%20B%C3%A1rtok%3A%20Concerto%20for%20Orchestra', 'path encoding');
+ equal(u.pathname(true), 'music:classical:Béla Bártok%3A Concerto for Orchestra', 'path decoded');
+ });
+ test('query', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.query('foo=bar=foo');
+ equal(u.query(), 'foo=bar=foo', 'query: foo=bar=foo');
+ equal(u.search(), '?foo=bar=foo', 'query: foo=bar=foo - search');
+
+ u.query('?bar=foo');
+ equal(u.query(), 'bar=foo', 'query: ?bar=foo');
+ equal(u.search(), '?bar=foo', 'query: ?bar=foo - search');
+
+ u.query('');
+ equal(u.query(), '', 'query: ""');
+ equal(u.search(), '', 'query: "" - search');
+ equal(u.toString(), 'http://example.org/foo.html');
+
+ u.search('foo=bar=foo');
+ equal(u.query(), 'foo=bar=foo', 'search: foo=bar=foo');
+ equal(u.search(), '?foo=bar=foo', 'search: foo=bar=foo - query');
+
+ u.search('?bar=foo');
+ equal(u.query(), 'bar=foo', 'search: ?bar=foo');
+ equal(u.search(), '?bar=foo', 'search: ?bar=foo - query');
+
+ u.search('');
+ equal(u.query(), '', 'search: ""');
+ equal(u.search(), '', 'search: "" - query');
+
+ u.query('?foo');
+ equal(u.query(), 'foo', 'search: ""');
+ equal(u.search(), '?foo', 'search: "" - query');
+
+ u.search('foo=&foo=bar');
+ equal(u.query(), 'foo=&foo=bar', 'search: foo=&foo=bar');
+ equal(JSON.stringify(u.query(true)), JSON.stringify({foo: ['', 'bar']}), 'parsed query: {foo:["", "bar"]}');
+
+ u.search('foo=bar&foo=');
+ equal(u.query(), 'foo=bar&foo=', 'search: foo=bar&foo=');
+ equal(JSON.stringify(u.query(true)), JSON.stringify({foo: ['bar', '']}), 'parsed query: {foo:["bar", ""]}');
+
+ u.search('foo=bar&foo');
+ equal(u.query(), 'foo=bar&foo', 'search: foo=bar&foo');
+ equal(JSON.stringify(u.query(true)), JSON.stringify({foo: ['bar', null]}), 'parsed query: {foo:["bar", null]}');
+
+ u.search('foo&foo=bar');
+ equal(u.query(), 'foo&foo=bar', 'search: foo&foo=bar');
+ equal(JSON.stringify(u.query(true)), JSON.stringify({foo: [null, 'bar']}), 'parsed query: {foo:[null, "bar"]}');
+
+ // parsing empty query
+ var t;
+ t = u.query('?').query(true);
+ t = u.query('').query(true);
+ t = u.href('http://example.org').query(true);
+ });
+ test('fragment', function() {
+ var u = new URI('http://example.org/foo.html');
+ u.fragment('foo');
+ equal(u.fragment(), 'foo', 'fragment: foo');
+ equal(u.hash(), '#foo', 'fragment: foo - hash');
+
+ u.fragment('#bar');
+ equal(u.fragment(), 'bar', 'fragment: #bar');
+ equal(u.hash(), '#bar', 'fragment: #bar - hash');
+
+ u.fragment('');
+ equal(u.fragment(), '', 'fragment: ""');
+ equal(u.hash(), '', 'fragment: "" - hash');
+ equal(u.toString(), 'http://example.org/foo.html');
+
+ u.hash('foo');
+ equal(u.fragment(), 'foo', 'hash: foo');
+ equal(u.hash(), '#foo', 'hash: foo - fragment');
+
+ u.hash('#bar');
+ equal(u.fragment(), 'bar', 'hash: #bar');
+ equal(u.hash(), '#bar', 'hash: #bar - fragment');
+
+ u.hash('');
+ equal(u.fragment(), '', 'hash: ""');
+ equal(u.hash(), '', 'hash: "" - fragment');
+ });
+
+ module('mutating compounds');
+ test('host', function() {
+ var u = new URI('http://foo.bar/foo.html');
+
+ u.host('example.org:80');
+ equal(u.hostname(), 'example.org', 'host changed hostname');
+ equal(u.port(), '80', 'host changed port');
+ equal(u+'', 'http://example.org:80/foo.html', 'host changed url');
+
+ u.host('some-domain.com');
+ equal(u.hostname(), 'some-domain.com', 'host modified hostname');
+ equal(u.port(), '', 'host removed port');
+ equal(u+'', 'http://some-domain.com/foo.html', 'host modified url');
+
+ raises(function() {
+ u.host('foo\\bar.com');
+ }, TypeError, 'Failing backslash detection in host');
+ });
+ test('origin', function () {
+ var u = new URI('http://foo.bar/foo.html');
+ equal(u.origin(), 'http://foo.bar', 'invalid origin');
+
+ u.origin('http://bar.foo/bar.html');
+ equal(u.origin(), 'http://bar.foo', 'origin didnt change');
+ equal(u+'', 'http://bar.foo/foo.html', 'origin path changed');
+ });
+ test('authority', function() {
+ var u = new URI('http://foo.bar/foo.html');
+
+ u.authority('username:password@example.org:80');
+ equal(u.username(), 'username', 'authority changed username');
+ equal(u.password(), 'password', 'authority changed password');
+ equal(u.hostname(), 'example.org', 'authority changed hostname');
+ equal(u.port(), '80', 'authority changed port');
+ equal(u+'', 'http://username:password@example.org:80/foo.html', 'authority changed url');
+
+ u.authority('some-domain.com');
+ equal(u.username(), '', 'authority removed username');
+ equal(u.password(), '', 'authority removed password');
+ equal(u.hostname(), 'some-domain.com', 'authority modified hostname');
+ equal(u.port(), '', 'authority removed port');
+ equal(u+'', 'http://some-domain.com/foo.html', 'authority modified url');
+
+ raises(function() {
+ u.authority('username:password@foo\\bar.com:80');
+ }, TypeError, 'Failing backslash detection in authority');
+ });
+ test('userinfo', function() {
+ var u = new URI('http://foo.bar/foo.html');
+
+ u.userinfo('username:password');
+ equal(u.username(), 'username', 'userinfo changed username-only');
+ equal(u.password(), 'password', 'userinfo changed password');
+ equal(u+'', 'http://username:password@foo.bar/foo.html', 'userinfo changed url');
+
+ u.userinfo('walter');
+ equal(u.username(), 'walter', 'userinfo removed password');
+ equal(u.password(), '', 'userinfo removed password');
+ equal(u+'', 'http://walter@foo.bar/foo.html', 'userinfo changed url');
+
+ u.userinfo('');
+ equal(u.username(), '', 'userinfo removed username');
+ equal(u.password(), '', 'userinfo removed password');
+ equal(u+'', 'http://foo.bar/foo.html', 'userinfo changed url');
+ });
+ test('href', function() {
+ var u = new URI('http://foo.bar/foo.html');
+
+ u.href('ftp://u:p@example.org:123/directory/file.suffix?query=string#fragment');
+ equal(u.protocol(), 'ftp', 'href changed protocol');
+ equal(u.username(), 'u', 'href changed username');
+ equal(u.password(), 'p', 'href changed password');
+ equal(u.hostname(), 'example.org', 'href changed hostname');
+ equal(u.port(), '123', 'href changed port');
+ equal(u.pathname(), '/directory/file.suffix', 'href changed pathname');
+ equal(u.search(), '?query=string', 'href changed search');
+ equal(u.hash(), '#fragment', 'href changed hash');
+ equal(u.href(), 'ftp://u:p@example.org:123/directory/file.suffix?query=string#fragment', 'href removed url');
+
+ u.href('../path/index.html');
+ equal(u.protocol(), '', 'href removed protocol');
+ equal(u.username(), '', 'href removed username');
+ equal(u.password(), '', 'href removed password');
+ equal(u.hostname(), '', 'href removed hostname');
+ equal(u.port(), '', 'href removed port');
+ equal(u.pathname(), '../path/index.html', 'href removed pathname');
+ equal(u.search(), '', 'href removed search');
+ equal(u.hash(), '', 'href removed hash');
+ equal(u.href(), '../path/index.html', 'href removed url');
+
+ /*jshint -W053 */
+ u.href(new String('/narf'));
+ /*jshint +W053 */
+ equal(u.pathname(), '/narf', 'href from String instance');
+ });
+ test('resource', function() {
+ var u = new URI('http://foo.bar/foo.html?hello#world');
+
+ equal(u.resource(), '/foo.html?hello#world', 'get resource');
+
+ u.resource('/foo.html?hello#world');
+ equal(u.href(), 'http://foo.bar/foo.html?hello#world', 'set resource');
+
+ u.resource('/world.html');
+ equal(u.href(), 'http://foo.bar/world.html', 'set resource path');
+ equal(u.resource(), '/world.html', 'get resource path');
+
+ u.resource('?query');
+ equal(u.href(), 'http://foo.bar/?query', 'set resource query');
+ equal(u.resource(), '/?query', 'get resource query');
+
+ u.resource('#fragment');
+ equal(u.href(), 'http://foo.bar/#fragment', 'set resource fragment');
+ equal(u.resource(), '/#fragment', 'get resource fragment');
+
+ u.resource('?hello#world');
+ equal(u.href(), 'http://foo.bar/?hello#world', 'set resource query+fragment');
+ equal(u.resource(), '/?hello#world', 'get resource query+fragment');
+
+ u.resource('/mars.txt?planet=123');
+ equal(u.href(), 'http://foo.bar/mars.txt?planet=123', 'set resource path+query');
+ equal(u.resource(), '/mars.txt?planet=123', 'get resource path+query');
+
+ u.resource('/neptune.txt#foo');
+ equal(u.href(), 'http://foo.bar/neptune.txt#foo', 'set resource path+fragment');
+ equal(u.resource(), '/neptune.txt#foo', 'get resource path+fragment');
+ });
+
+ module('mutating fractions');
+ test('subdomain', function() {
+ var u = new URI('http://www.example.org/foo.html');
+ u.subdomain('foo.bar');
+ equal(u.hostname(), 'foo.bar.example.org', 'changed subdomain foo.bar');
+ equal(u+'', 'http://foo.bar.example.org/foo.html', 'changed url foo.bar');
+
+ u.subdomain('');
+ equal(u.hostname(), 'example.org', 'changed subdomain ""');
+ equal(u+'', 'http://example.org/foo.html', 'changed url ""');
+
+ u.subdomain('foo.');
+ equal(u.hostname(), 'foo.example.org', 'changed subdomain foo.');
+ equal(u+'', 'http://foo.example.org/foo.html', 'changed url foo.');
+
+ });
+ test('domain', function() {
+ var u = new URI('http://www.example.org/foo.html');
+ u.domain('foo.bar');
+ equal(u.hostname(), 'www.foo.bar', 'changed hostname foo.bar');
+ equal(u+'', 'http://www.foo.bar/foo.html', 'changed url foo.bar');
+
+ raises(function() {
+ u.domain('');
+ }, TypeError, 'Failing empty input');
+
+ u.hostname('www.example.co.uk');
+ equal(u.domain(), 'example.co.uk', 'domain after changed hostname www.example.co.uk');
+ equal(u+'', 'http://www.example.co.uk/foo.html', 'url after changed hostname www.example.co.uk');
+ equal(u.domain(true), 'co.uk', 'domain after changed hostname www.example.co.uk (TLD of SLD)');
+
+ u.domain('example.org');
+ equal(u.domain(), 'example.org', 'domain after changed domain example.org');
+ equal(u+'', 'http://www.example.org/foo.html', 'url after changed domain example.org');
+
+ u.domain('example.co.uk');
+ equal(u.domain(), 'example.co.uk', 'domain after changed domain example.co.uk');
+ equal(u+'', 'http://www.example.co.uk/foo.html', 'url after changed domain example.co.uk');
+
+ u.href('http://test/');
+ equal(u.domain(), 'test', 'domain (dot-less)');
+ equal(u.subdomain(), '', 'subdomain (dot-less)');
+
+ u.subdomain('foo');
+ equal(u.href(), 'http://foo.test/', 'subdomain set on (dot-less)');
+ });
+ test('tld', function() {
+ var u = new URI('http://www.example.org/foo.html');
+ u.tld('mine');
+ equal(u.tld(), 'mine', 'tld changed');
+ equal(u+'', 'http://www.example.mine/foo.html', 'changed url mine');
+
+ raises(function() {
+ u.tld('');
+ }, TypeError, 'Failing empty input');
+
+ raises(function() {
+ u.tld('foo.bar');
+ }, TypeError, 'Failing "foo.bar"');
+
+ u.tld('co.uk');
+ equal(u.tld(), 'co.uk', 'tld changed to sld');
+ equal(u+'', 'http://www.example.co.uk/foo.html', 'changed url to sld');
+ equal(u.tld(true), 'uk', 'TLD of SLD');
+
+ u.tld('org');
+ equal(u.tld(), 'org', 'sld changed to tld');
+ equal(u+'', 'http://www.example.org/foo.html', 'changed url to tld');
+
+ u.hostname('www.examplet.se');
+ equal(u.tld(), 'se', 'se tld');
+
+ });
+ test('sld', function() {
+ // Lets just test them all..
+ // Calling URI.is(), URI.domain(), URI.subdomain() allows us to indirectly
+ // test SLD.has(), SLD.is() and SLD.get()
+ var u = new URI('http://www.example.org/foo.html');
+ equal(u.is('sld'), false, 'is not sld');
+ var list = SecondLevelDomains.list;
+ var tlds = Object.keys(list);
+ var iTld = tlds.length;
+ var tld, slds, sld, iSld;
+ while ( iTld-- ) {
+ tld = tlds[iTld];
+ slds = list[tld].trim().split(/\s+/);
+ iSld = slds.length;
+ while ( iSld-- ) {
+ sld = slds[iSld].trim() + '.' + tld;
+ u.hostname('www.example.' + sld);
+ equal(u.is('sld'), true, 'is sld');
+ equal(u.domain(), 'example.' + sld, 'domain is example.' + sld);
+ equal(u.subdomain(), 'www', 'subdomain is www');
+ u.hostname('www.example.' + tld);
+ equal(u.is('sld'), false, 'is not sld');
+ }
+ }
+ });
+ test('directory', function() {
+ var u = new URI('http://www.example.org/some/directory/foo.html');
+ u.directory('/');
+ equal(u.path(), '/foo.html', 'changed path '/'');
+ equal(u+'', 'http://www.example.org/foo.html', 'changed url '/'');
+
+ u.directory('');
+ equal(u.path(), '/foo.html', 'changed path ""');
+ equal(u+'', 'http://www.example.org/foo.html', 'changed url ""');
+
+ u.directory('/bar');
+ equal(u.path(), '/bar/foo.html', 'changed path "/bar"');
+ equal(u+'', 'http://www.example.org/bar/foo.html', 'changed url "/bar"');
+
+ u.directory('baz');
+ equal(u.path(), '/baz/foo.html', 'changed path "baz"');
+ equal(u+'', 'http://www.example.org/baz/foo.html', 'changed url "baz"');
+
+ // relative paths
+ u = new URI('../some/directory/foo.html');
+ u.directory('../other/');
+ equal(u.path(), '../other/foo.html', 'changed path "../other/"');
+ equal(u+'', '../other/foo.html', 'changed url "../other/"');
+
+ u.directory('mine');
+ equal(u.path(), 'mine/foo.html', 'changed path "mine"');
+ equal(u+'', 'mine/foo.html', 'changed url "mine"');
+
+ u.directory('/');
+ equal(u.path(), '/foo.html', 'changed path "/"');
+ equal(u+'', '/foo.html', 'changed url "/"');
+
+ u.directory('');
+ equal(u.path(), 'foo.html', 'changed path ""');
+ equal(u+'', 'foo.html', 'changed url ""');
+
+ u.directory('../blubb');
+ equal(u.path(), '../blubb/foo.html', 'changed path "../blubb"');
+ equal(u+'', '../blubb/foo.html', 'changed url "../blubb"');
+
+ // encoding
+ u.path('/some/directory/foo.html');
+ u.directory('/~userhome/@mine;is %2F and/');
+ equal(u.path(), '/~userhome/@mine;is%20%2F%20and/foo.html', 'directory encoding');
+ equal(u.directory(true), '/~userhome/@mine;is %2F and', 'directory decoded');
+ });
+ test('filename', function() {
+ var u = new URI('http://www.example.org/some/directory/foo.html');
+ u.filename('hello.world');
+ equal(u.path(), '/some/directory/hello.world', 'changed path "hello.world"');
+ equal(u+'', 'http://www.example.org/some/directory/hello.world', 'changed url "hello.world"');
+
+ u.filename('hello');
+ equal(u.path(), '/some/directory/hello', 'changed path "hello"');
+ equal(u+'', 'http://www.example.org/some/directory/hello', 'changed url "hello"');
+
+ u.filename('');
+ equal(u.path(), '/some/directory/', 'changed path ""');
+ equal(u+'', 'http://www.example.org/some/directory/', 'changed url ""');
+
+ u.filename('world');
+ equal(u.path(), '/some/directory/world', 'changed path "world"');
+ equal(u+'', 'http://www.example.org/some/directory/world', 'changed url "world"');
+
+ // encoding
+ u.path('/some/directory/foo.html');
+ u.filename('hällo wörld.html');
+ equal(u.path(), '/some/directory/h%C3%A4llo%20w%C3%B6rld.html', 'filename encoding');
+ equal(u.filename(true), 'hällo wörld.html', 'filename decoded');
+ });
+ test('suffix', function() {
+ var u = new URI('http://www.example.org/some/directory/foo.html');
+ u.suffix('xml');
+ equal(u.path(), '/some/directory/foo.xml', 'changed path "xml"');
+ equal(u+'', 'http://www.example.org/some/directory/foo.xml', 'changed url "xml"');
+
+ u.suffix('');
+ equal(u.path(), '/some/directory/foo', 'changed path ""');
+ equal(u+'', 'http://www.example.org/some/directory/foo', 'changed url ""');
+
+ u.suffix('html');
+ equal(u.path(), '/some/directory/foo.html', 'changed path "html"');
+ equal(u+'', 'http://www.example.org/some/directory/foo.html', 'changed url "html"');
+
+ // encoding
+ u.suffix('cört');
+ equal(u.path(), '/some/directory/foo.c%C3%B6rt', 'suffix encoding');
+ equal(u.suffix(), 'c%C3%B6rt', 'suffix encoded'); // suffix is expected to be alnum!
+ equal(u.suffix(true), 'cört', 'suffix decoded'); // suffix is expected to be alnum!
+ });
+ test('segment', function() {
+ var u = new URI('http://www.example.org/some/directory/foo.html'),
+ s = u.segment();
+
+ equal(s.join('||'), 'some||directory||foo.html', 'segment get array');
+
+ u.segment(['hello', 'world', 'foo.html']);
+ equal(u.path(), '/hello/world/foo.html', 'segment set array');
+
+ equal(u.segment(0), 'hello', 'segment get 0');
+ equal(u.segment(2), 'foo.html', 'segment get 2');
+ equal(u.segment(3), undefined, 'segment get 3');
+
+ u.segment(0, 'goodbye');
+ equal(u.path(), '/goodbye/world/foo.html', 'segment set 0');
+ u.segment(2, 'bar.html');
+ equal(u.path(), '/goodbye/world/bar.html', 'segment set 2');
+ u.segment(3, 'zupp');
+ equal(u.path(), '/goodbye/world/bar.html/zupp', 'segment set 3');
+ u.segment('zapp');
+ equal(u.path(), '/goodbye/world/bar.html/zupp/zapp', 'segment append');
+
+ u.segment(3, '');
+ equal(u.path(), '/goodbye/world/bar.html/zapp', 'segment del 3 ""');
+ u.segment(3, null);
+ equal(u.path(), '/goodbye/world/bar.html', 'segment del 3 null');
+
+ u = new URI('http://www.example.org/some/directory/foo.html');
+ equal(u.segment(-1), 'foo.html', 'segment get -1');
+ u.segment(-1, 'world.html');
+ equal(u.path(), '/some/directory/world.html', 'segment set -1');
+
+ u = new URI('someurn:foo:bar:baz');
+ equal(u.segment().join('||'), 'foo||bar||baz', 'segment get array URN');
+ u.segment(1, 'mars');
+ equal(u.path(), 'foo:mars:baz', 'segment set 1 URN');
+ equal(u.toString(), 'someurn:foo:mars:baz', 'segment set 1 URN');
+
+ u = new URI('/foo/');
+ equal(u.segment().join('||'), 'foo||', 'segment get array trailing empty');
+
+ u.segment('test');
+ equal(u.path(), '/foo/test', 'segment append trailing empty');
+
+ u.segment('');
+ equal(u.path(), '/foo/test/', 'segment append empty trailing');
+ u.segment('');
+ equal(u.path(), '/foo/test/', 'segment append empty trailing unchanged');
+
+ u.segment(['', '', 'foo', '', '', 'bar', '', '']);
+ equal(u.path(), '/foo/bar/', 'segment collapsing empty parts');
+
+ u = new URI('https://google.com');
+ u.segment('//font.ttf//');
+ equal(u.path(), '/font.ttf', 'segment removes trailing and leading slashes');
+
+ u.segment(['/hello', '/world/', '//foo.html']);
+ equal(u.path(), '/hello/world/foo.html', 'segment set array trimming slashes');
+
+ u.segment(1, '/mars/');
+ equal(u.path(), '/hello/mars/foo.html', 'segment set index trimming slashes');
+ });
+ test('segmentCoded', function() {
+ var u = new URI('http://www.example.org/some%20thing/directory/foo.html'),
+ s = u.segmentCoded();
+
+ equal(s.join('||'), 'some thing||directory||foo.html', 'segmentCoded get array');
+
+ u.segmentCoded(['hello/world']);
+ equal(u.path(), '/hello%2Fworld', 'escape in array');
+
+ u.segmentCoded('hello/world');
+ equal(u.path(), '/hello%2Fworld/hello%2Fworld', 'escape appended value');
+
+ u.segmentCoded(['hello world', 'mars', 'foo.html']);
+ equal(u.path(), '/hello%20world/mars/foo.html', 'segmentCoded set array');
+
+ equal(u.segmentCoded(0), 'hello world', 'segmentCoded get 0');
+ equal(u.segmentCoded(2), 'foo.html', 'segmentCoded get 2');
+ equal(u.segmentCoded(3), undefined, 'segmentCoded get 3');
+
+ u.segmentCoded('zapp zerapp');
+ equal(u.path(), '/hello%20world/mars/foo.html/zapp%20zerapp', 'segmentCoded append');
+
+ u.segmentCoded(2, '');
+ equal(u.path(), '/hello%20world/mars/zapp%20zerapp', 'segmentCoded del 3 ""');
+ u.segmentCoded(2, null);
+ equal(u.path(), '/hello%20world/mars', 'segmentCoded del 3 null');
+
+ u.segmentCoded('');
+ equal(u.path(), '/hello%20world/mars/', 'segmentCoded append empty trailing');
+ u.segmentCoded('');
+ equal(u.path(), '/hello%20world/mars/', 'segmentCoded append empty trailing unchanged');
+ });
+
+ module('mutating query strings');
+ test('mutating object', function() {
+ var u = new URI('?foo=bar&baz=bam&baz=bau'),
+ q = u.query(true);
+
+ q.something = ['new', 'and', 'funky'];
+ u.query(q);
+ equal(u.query(), 'foo=bar&baz=bam&baz=bau&something=new&something=and&something=funky', 'adding array');
+
+ q.foo = undefined;
+ u.query(q);
+ equal(u.query(), 'baz=bam&baz=bau&something=new&something=and&something=funky', 'removing field');
+
+ q.baz = undefined;
+ u.query(q);
+ equal(u.query(), 'something=new&something=and&something=funky', 'removing array');
+ });
+ test('query callback', function() {
+ var u = URI('?foo=bar');
+ u.query(function(data) {
+ data.foo = 'bam';
+ });
+ equal(u.query(), 'foo=bam', 'augment argument');
+
+ u.query(function() {
+ return {
+ bla: 'blubb'
+ };
+ });
+ equal(u.query(), 'bla=blubb', 'overwrite returned value');
+ });
+ test('setQuery', function() {
+ var u = URI('?foo=bar');
+ u.setQuery('foo', 'bam');
+ equal(u.query(), 'foo=bam', 'set name, value');
+
+ u.setQuery('array', ['one', 'two']);
+ equal(u.query(), 'foo=bam&array=one&array=two', 'set name, array');
+
+ u.query('?foo=bar');
+ u.setQuery({'obj': 'bam', foo: 'baz'});
+ equal(u.query(), 'foo=baz&obj=bam', 'set {name: value}');
+
+ u.setQuery({'foo': 'foo', bar: ['1', '2']});
+ equal(u.query(), 'foo=foo&obj=bam&bar=1&bar=2', 'set {name: array}');
+
+ u.query('?foo=bar');
+ u.setQuery({'bam': null, 'baz': ''});
+ equal(u.query(), 'foo=bar&bam&baz=', 'set {name: null}');
+
+ u.query('?foo=bar');
+ u.setQuery('empty');
+ equal(u.query(), 'foo=bar&empty', 'set undefined');
+
+ u.query('?foo=bar');
+ u.setQuery('empty', '');
+ equal(u.query(), 'foo=bar&empty=', 'set empty string');
+
+ u.query('');
+ u.setQuery('some value', 'must be encoded because of = and ? and #');
+ equal(u.query(), 'some+value=must+be+encoded+because+of+%3D+and+%3F+and+%23', 'encoding');
+ equal(u.query(true)['some value'], 'must be encoded because of = and ? and #', 'decoding');
+ });
+ test('addQuery', function() {
+ var u = URI('?foo=bar');
+ u.addQuery('baz', 'bam');
+ equal(u.query(), 'foo=bar&baz=bam', 'add name, value');
+
+ u.addQuery('array', ['one', 'two']);
+ equal(u.query(), 'foo=bar&baz=bam&array=one&array=two', 'add name, array');
+
+ u.query('?foo=bar');
+ u.addQuery({'obj': 'bam', foo: 'baz'});
+ equal(u.query(), 'foo=bar&foo=baz&obj=bam', 'add {name: value}');
+
+ u.addQuery({'foo': 'bam', bar: ['1', '2']});
+ equal(u.query(), 'foo=bar&foo=baz&foo=bam&obj=bam&bar=1&bar=2', 'add {name: array}');
+
+ u.query('?foo=bar');
+ u.addQuery({'bam': null, 'baz': ''});
+ equal(u.query(), 'foo=bar&bam&baz=', 'add {name: null}');
+
+ u.query('?foo=bar');
+ u.addQuery('empty');
+ equal(u.query(), 'foo=bar&empty', 'add undefined');
+
+ u.query('?foo=bar');
+ u.addQuery('empty', '');
+ equal(u.query(), 'foo=bar&empty=', 'add empty string');
+
+ u.query('?foo');
+ u.addQuery('foo', 'bar');
+ equal(u.query(), 'foo=bar', 'add to null value');
+
+ u.query('');
+ u.addQuery('some value', 'must be encoded because of = and ? and #');
+ equal(u.query(), 'some+value=must+be+encoded+because+of+%3D+and+%3F+and+%23', 'encoding');
+ equal(u.query(true)['some value'], 'must be encoded because of = and ? and #', 'decoding');
+ });
+ test('removeQuery', function() {
+ var u = new URI('?foo=bar&foo=baz&foo=bam&obj=bam&bar=1&bar=2&bar=3');
+
+ u.removeQuery('foo', 'bar');
+ equal(u.query(), 'foo=baz&foo=bam&obj=bam&bar=1&bar=2&bar=3', 'removing name, value');
+
+ u.removeQuery('foo');
+ equal(u.query(), 'obj=bam&bar=1&bar=2&bar=3', 'removing name');
+
+ u.removeQuery('bar', ['1', '3']);
+ equal(u.query(), 'obj=bam&bar=2', 'removing name, array');
+
+ u.query('?obj=bam&bar=1&bar=2');
+ u.removeQuery('bar', ['2']);
+ equal(u.query(), 'obj=bam&bar=1', 'removing name, singleton array');
+
+ u.removeQuery('bar', ['1']);
+ equal(u.query(), 'obj=bam', 'removing the last value via name, singleton array');
+
+ u.query('?foo=one&foo=two').removeQuery('foo', ['one', 'two']);
+ equal(u.query(), '', 'removing name, array, finishes empty');
+
+ u.query('?foo=one,two').removeQuery('foo', ['one', 'two']);
+ equal(u.query(), 'foo=one%2Ctwo', 'not removing name, array');
+
+ u.query('?foo=one,two').removeQuery('foo', ['one,two']);
+ equal(u.query(), '', 'removing name, singleton array with comma in value');
+
+ u.query('?foo=bar&foo=baz&foo=bam&obj=bam&bar=1&bar=2&bar=3');
+ u.removeQuery(['foo', 'bar']);
+ equal(u.query(), 'obj=bam', 'removing array');
+
+ u.query('?bar=1&bar=2');
+ u.removeQuery({ bar: 1 });
+ equal(u.query(), 'bar=2', 'removing non-string value from array');
+
+ u.removeQuery({ bar: 2 });
+ equal(u.query(), '', 'removing a non-string value');
+
+ u.query('?foo=bar&foo=baz&foo=bam&obj=bam&bar=1&bar=2&bar=3');
+ u.removeQuery({foo: 'bar', obj: undefined, bar: ['1', '2']});
+ equal(u.query(), 'foo=baz&foo=bam&bar=3', 'removing object');
+
+ u.query('?foo=bar&foo=baz&foo=bam&obj=bam&bar=1&bar=2&bar=3');
+ u.removeQuery(/^bar/);
+ equal(u.query(), 'foo=bar&foo=baz&foo=bam&obj=bam', 'removing by RegExp');
+
+ u.query('?foo=bar&foo=baz&foo=bam&obj=bam&bar=bar&bar=baz&bar=bam');
+ u.removeQuery('foo', /[rz]$/);
+ equal(u.query(), 'foo=bam&obj=bam&bar=bar&bar=baz&bar=bam', 'removing by value RegExp');
+ });
+ test('duplicateQueryParameters', function() {
+ var u = new URI('?bar=1&bar=1&bar=1');
+
+ u.normalizeQuery();
+ equal(u.toString(), '?bar=1', 'parameters de-duplicated');
+
+ u = new URI('?bar=1&bar=1&bar=1');
+ u.duplicateQueryParameters(true);
+ ok(u._parts.duplicateQueryParameters, 'duplicateQueryParameters enabled');
+ u.normalizeQuery();
+ equal(u.toString(), '?bar=1&bar=1&bar=1', 'parameters NOT de-duplicated');
+ ok(u._parts.duplicateQueryParameters, 'duplicateQueryParameters still enabled after normalizeQuery()');
+
+ u.duplicateQueryParameters(false);
+ u.normalizeQuery();
+ equal(u.toString(), '?bar=1', 'parameters de-duplicated again');
+ ok(!u._parts.duplicateQueryParameters, 'duplicateQueryParameters still disabled after normalizeQuery()');
+
+ URI.duplicateQueryParameters = true;
+ u = new URI('?bar=1&bar=1&bar=1');
+ u.normalizeQuery();
+ equal(u.toString(), '?bar=1&bar=1&bar=1', 'global configuration');
+
+ URI.duplicateQueryParameters = false;
+
+ // test cloning
+ u = new URI('?bar=1&bar=1&bar=1');
+ u = u.duplicateQueryParameters(true).clone();
+ ok(u._parts.duplicateQueryParameters, 'duplicateQueryParameters still enabled after clone()');
+ u.normalizeQuery();
+ equal(u.toString(), '?bar=1&bar=1&bar=1', 'parameters NOT de-duplicated');
+
+ // test adding
+ u = new URI('?bar=1&bar=1&bar=1');
+ u.duplicateQueryParameters(true);
+ u.addQuery('bar', 1);
+ equal(u.toString(), '?bar=1&bar=1&bar=1&bar=1', 'parameters NOT de-duplicated after addQuery()');
+ });
+ test('escapeQuerySpace', function() {
+ var u = new URI('?bar=foo+bar&bam+baz=foo');
+ var data = u.query(true);
+
+ equal(data.bar, 'foo bar', 'value un-spac-escaped');
+ equal(data['bam baz'], 'foo', 'name un-spac-escaped');
+
+ u.escapeQuerySpace(false);
+ data = u.query(true);
+ equal(data.bar, 'foo+bar', 'value not un-spac-escaped');
+ equal(data['bam+baz'], 'foo', 'name not un-spac-escaped');
+
+ u.escapeQuerySpace(true);
+ data = u.query(true);
+
+ equal(data.bar, 'foo bar', 'value un-spac-escaped again');
+ equal(data['bam baz'], 'foo', 'name un-spac-escaped again');
+
+ u.escapeQuerySpace(false);
+
+ u.addQuery('alpha bravo', 'charlie delta');
+ equal(u.toString(), '?bar=foo%2Bbar&bam%2Bbaz=foo&alpha%20bravo=charlie%20delta', 'serialized un/escaped space');
+
+ URI.escapeQuerySpace = false;
+ u = new URI('?bar=foo+bar&bam+baz=foo');
+ data = u.query(true);
+ equal(data.bar, 'foo+bar', 'value not un-spac-escaped by default');
+ equal(data['bam+baz'], 'foo', 'name not un-spac-escaped by default');
+
+ // reset
+ URI.escapeQuerySpace = true;
+ });
+ test('hasQuery', function() {
+ var u = URI('?string=bar&list=one&list=two&number=123&null&empty=&nested[one]=1&nested[two]=2');
+
+ // exists
+ equal(u.hasQuery('string'), true, 'simple exists check - passing');
+ equal(u.hasQuery('nono'), false, 'simple exists check - failing');
+
+ // truthy value
+ equal(u.hasQuery('string', true), true, 'has truthy value check - passing string');
+ equal(u.hasQuery('number', true), true, 'has truthy value check - passing number');
+ equal(u.hasQuery('list', true), true, 'has truthy value check - passing list');
+ equal(u.hasQuery('empty', true), false, 'has truthy value check - failing empty');
+ equal(u.hasQuery('null', true), false, 'has truthy value check - failing null');
+
+ // falsy value
+ equal(u.hasQuery('string', false), false, 'has falsy value check - failing string');
+ equal(u.hasQuery('number', false), false, 'has falsy value check - failing number');
+ equal(u.hasQuery('list', false), false, 'has falsy value check - failing list');
+ equal(u.hasQuery('empty', false), true, 'has falsy value check - passing empty');
+ equal(u.hasQuery('null', false), true, 'has falsy value check - passing null');
+
+ // match value
+ equal(u.hasQuery('string', 'bar'), true, 'value check - passing string');
+ equal(u.hasQuery('number', 123), true, 'value check - passing number');
+ equal(u.hasQuery('number', '123'), true, 'value check - passing number as string');
+ equal(u.hasQuery('list', 'one'), false, 'value check - failing list');
+ equal(u.hasQuery('empty', ''), true, 'value check - passing empty');
+ equal(u.hasQuery('null', ''), false, 'value check - failing null');
+
+ // matching RegExp
+ equal(u.hasQuery('string', /ar$/), true, 'RegExp check - passing string');
+ equal(u.hasQuery('number', /2/), true, 'RegExp check - passing number');
+ equal(u.hasQuery('string', /nono/), false, 'RegExp check - failing string');
+ equal(u.hasQuery('number', /999/), false, 'RegExp check - failing number');
+ equal(u.hasQuery(/^nested/), true, 'RegExp name check - passing');
+ equal(u.hasQuery(/^nested/, 2), true, 'RegExp name and value - passing number');
+ equal(u.hasQuery(/^nested/, '2'), true, 'RegExp name and value - passing number as string');
+ equal(u.hasQuery(/^nested/, 'nono'), false, 'RegExp name and value - failing string');
+ equal(u.hasQuery(/^nested/, /2/), true, 'RegExp name and value - passing RegExp number');
+ equal(u.hasQuery(/^nested/, /3/), false, 'RegExp name and value exists check - failing');
+ equal(u.hasQuery(/^lis/, ['one']), false, 'RegExp name andarray check - failing incomplete list');
+ equal(u.hasQuery(/^lis/, ['one', 'two']), true, 'RegExp name and array check - passing list');
+
+ // matching array
+ equal(u.hasQuery('string', ['one']), false, 'array check - failing string');
+ equal(u.hasQuery('list', ['one']), false, 'array check - failing incomplete list');
+ equal(u.hasQuery('list', ['one', 'two']), true, 'array check - passing list');
+ equal(u.hasQuery('list', ['two', 'one']), true, 'array check - passing unsorted list');
+
+ // matching part of array
+ equal(u.hasQuery('string', ['one'], true), false, 'in array check - failing string');
+ equal(u.hasQuery('list', 'one', true), true, 'in array check - passing value');
+ equal(u.hasQuery('list', ['one'], true), true, 'in array check - passing incomplete list');
+ equal(u.hasQuery('list', ['one', 'two'], true), true, 'in array check - passing list');
+ equal(u.hasQuery('list', ['two', 'one'], true), true, 'in array check - passing unsorted list');
+ equal(u.hasQuery('list', /ne$/, true), true, 'in array check - passing RegExp');
+ equal(u.hasQuery('list', [/ne$/], true), true, 'in array check - passing RegExp list');
+
+ // comparison function
+ equal(u.hasQuery('string', function(value, name, data) {
+ equal(value, 'bar', 'Function check - param value');
+ equal(name, 'string', 'Function check - param name');
+ equal(typeof data, 'object', 'Function check - param data');
+ return true;
+ }), true, 'Function check - passing true');
+ equal(u.hasQuery('string', function() {
+ return false;
+ }), false, 'Function check - passing false');
+ });
+
+ module('normalizing');
+ test('normalize', function() {
+ var u = new URI('http://www.exämple.org:80/food/woo/.././../baz.html?&foo=bar&&baz=bam&&baz=bau&#');
+ u.normalize();
+ equal(u+'', 'http://www.xn--exmple-cua.org/baz.html?foo=bar&baz=bam&baz=bau', 'fully normalized URL');
+ });
+ test('normalizeProtocol', function() {
+ var u = new URI('hTTp://example.org/foobar.html');
+ u.normalizeProtocol();
+ equal(u+'', 'http://example.org/foobar.html', 'lowercase http');
+ });
+ test('normalizeHost', function() {
+ var u;
+
+ if (window.punycode) {
+ u = new URI('http://exämple.org/foobar.html');
+ u.normalizeHostname();
+ equal(u+'', 'http://xn--exmple-cua.org/foobar.html', 'converting IDN to punycode');
+ }
+
+ if (window.IPv6) {
+ u = new URI('http://[fe80:0000:0000:0000:0204:61ff:fe9d:f156]/foobar.html');
+ u.normalizeHostname();
+ equal(u+'', 'http://[fe80::204:61ff:fe9d:f156]/foobar.html', 'best IPv6 representation');
+ }
+
+ if (window.IPv6) {
+ u = new URI('http://[::1]/foobar.html');
+ u.normalizeHostname();
+ equal(u+'', 'http://[::1]/foobar.html', 'best IPv6 representation');
+ }
+
+ u = new URI('http://wWw.eXamplE.Org/foobar.html');
+ u.normalizeHostname();
+ equal(u+'', 'http://www.example.org/foobar.html', 'lower case hostname');
+ });
+ test('normalizePort', function() {
+ var u = new URI('http://example.org:80/foobar.html');
+ u.normalizePort();
+ equal(u+'', 'http://example.org/foobar.html', 'dropping port 80 for http');
+
+ u = new URI('ftp://example.org:80/foobar.html');
+ u.normalizePort();
+ equal(u+'', 'ftp://example.org:80/foobar.html', 'keeping port 80 for ftp');
+
+ });
+ test('normalizePath', function() {
+ // relative URL
+ var u = new URI('/food/bar/baz.html');
+
+ u.normalizePath();
+ equal(u.path(), '/food/bar/baz.html', 'absolute path without change');
+
+ u.path('food/bar/baz.html').normalizePath();
+ equal(u.path(), 'food/bar/baz.html', 'relative path without change');
+
+ u.path('/food/../bar/baz.html').normalizePath();
+ equal(u.path(), '/bar/baz.html', 'single parent');
+
+ u.path('/food/woo/../../bar/baz.html').normalizePath();
+ equal(u.path(), '/bar/baz.html', 'double parent');
+
+ u.path('/food/woo/../bar/../baz.html').normalizePath();
+ equal(u.path(), '/food/baz.html', 'split double parent');
+
+ u.path('/food/woo/.././../baz.html').normalizePath();
+ equal(u.path(), '/baz.html', 'cwd-split double parent');
+
+ u.path('food/woo/../bar/baz.html').normalizePath();
+ equal(u.path(), 'food/bar/baz.html', 'relative parent');
+
+ u.path('./food/woo/../bar/baz.html').normalizePath();
+ equal(u.path(), 'food/bar/baz.html', 'dot-relative parent');
+
+ // absolute URL
+ u = new URI('http://example.org/foo/bar/baz.html');
+ u.normalizePath();
+ equal(u.path(), '/foo/bar/baz.html', 'URL: absolute path without change');
+
+ u.path('foo/bar/baz.html').normalizePath();
+ equal(u.path(), '/foo/bar/baz.html', 'URL: relative path without change');
+
+ u.path('/foo/../bar/baz.html').normalizePath();
+ equal(u.path(), '/bar/baz.html', 'URL: single parent');
+
+ u.path('/foo/woo/../../bar/baz.html').normalizePath();
+ equal(u.path(), '/bar/baz.html', 'URL: double parent');
+
+ u.path('/foo/woo/../bar/../baz.html').normalizePath();
+ equal(u.path(), '/foo/baz.html', 'URL: split double parent');
+
+ u.path('/foo/woo/.././../baz.html').normalizePath();
+ equal(u.path(), '/baz.html', 'URL: cwd-split double parent');
+
+ u.path('foo/woo/../bar/baz.html').normalizePath();
+ equal(u.path(), '/foo/bar/baz.html', 'URL: relative parent');
+
+ u.path('./foo/woo/../bar/baz.html').normalizePath();
+ equal(u.path(), '/foo/bar/baz.html', 'URL: dot-relative parent');
+
+ u.path('/.//').normalizePath();
+ equal(u.path(), '/', 'root /.//');
+
+ // encoding
+ u._parts.path = '/~userhome/@mine;is %2F and/';
+ u.normalize();
+ equal(u.pathname(), '/~userhome/@mine;is%20%2F%20and/', 'path encoding');
+
+ // relative URL
+ u = URI('/.').normalizePath();
+ equal(u.path(), '/', 'root /.');
+
+ u = URI('/..').normalizePath();
+ equal(u.path(), '/', 'root /..');
+
+ u = URI('/foo/.').normalizePath();
+ equal(u.path(), '/foo/', 'root /foo/.');
+
+ u = URI('/foo/..').normalizePath();
+ equal(u.path(), '/', 'root /foo/..');
+
+ u = URI('/foo/.bar').normalizePath();
+ equal(u.path(), '/foo/.bar', 'root /foo/.bar');
+
+ u = URI('/foo/..bar').normalizePath();
+ equal(u.path(), '/foo/..bar', 'root /foo/..bar');
+
+ // Percent Encoding normalization has to happen before dot segment normalization
+ u = URI('/foo/%2E%2E').normalizePath();
+ equal(u.path(), '/', 'root /foo/%2E%2E');
+
+ u = URI('/foo/%2E').normalizePath();
+ equal(u.path(), '/foo/', 'root /foo/%2E');
+
+ u = URI('/foo/%2E%2E%2Fbar').normalizePath();
+ equal(u.path(), '/foo/..%2Fbar', 'root /foo/%2E%2E%2Fbar');
+
+ u = URI('../../../../../www/common/js/app/../../../../www_test/common/js/app/views/view-test.html');
+ u.normalize();
+ equal(u.path(), '../../../../../www_test/common/js/app/views/view-test.html', 'parent relative');
+
+ u = URI('/../../../../../www/common/js/app/../../../../www_test/common/js/app/views/view-test.html');
+ u.normalize();
+ equal(u.path(), '/www_test/common/js/app/views/view-test.html', 'parent absolute');
+
+ // URNs
+ u = URI('urn:people:authors:poets:Shel Silverstein');
+ u.normalize();
+ equal(u.path(), 'people:authors:poets:Shel%20Silverstein');
+
+ u = URI('urn:people:authors:philosophers:Søren Kierkegaard');
+ u.normalize();
+ equal(u.path(), 'people:authors:philosophers:S%C3%B8ren%20Kierkegaard');
+
+ // URNs path separator preserved
+ u = URI('urn:games:cards:Magic%3A the Gathering');
+ u.normalize();
+ equal(u.path(), 'games:cards:Magic%3A%20the%20Gathering');
+ });
+ test('normalizeQuery', function() {
+ var u = new URI('http://example.org/foobar.html?');
+ u.normalizeQuery();
+ equal(u+'', 'http://example.org/foobar.html', 'dropping empty query sign');
+
+ u.query('?&foo=bar&&baz=bam&').normalizeQuery();
+ equal(u.query(), 'foo=bar&baz=bam', 'bad query resolution');
+
+ u.query('?&foo=bar&&baz=bam&&baz=bau&').normalizeQuery();
+ equal(u.query(), 'foo=bar&baz=bam&baz=bau', 'bad query resolution');
+
+ u.query('?&foo=bar&foo=bar').normalizeQuery();
+ equal(u.query(), 'foo=bar', 'duplicate key=value resolution');
+ });
+ test('normalizeFragment', function() {
+ var u = new URI('http://example.org/foobar.html#');
+ u.normalizeFragment();
+ equal(u+'', 'http://example.org/foobar.html', 'dropping empty fragment sign');
+ });
+ test('readable', function() {
+ var u = new URI('http://foo:bar@www.xn--exmple-cua.org/hello%20world/ä.html?foo%5B%5D=b+är#fragment');
+ equal(u.readable(), 'http://www.exämple.org/hello world/ä.html?foo[]=b är#fragment', 'readable URL');
+ });
+
+ module('resolving URLs');
+ test('absoluteTo', function() {
+ // this being '../bar/baz.html?foo=bar'
+ // base being 'http://example.org/foo/other/file.html'
+ // return being http://example.org/foo/bar/baz.html?foo=bar'
+ var tests = [{
+ name: 'relative resolve',
+ url: 'relative/path?blubber=1#hash1',
+ base: 'http://www.example.org/path/to/file?some=query#hash',
+ result: 'http://www.example.org/path/to/relative/path?blubber=1#hash1'
+ }, {
+ name: 'absolute resolve',
+ url: '/absolute/path?blubber=1#hash1',
+ base: 'http://www.example.org/path/to/file?some=query#hash',
+ result: 'http://www.example.org/absolute/path?blubber=1#hash1'
+ }, {
+ name: 'relative resolve full URL',
+ url: 'relative/path?blubber=1#hash3',
+ base: 'http://user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ result: 'http://user:pass@www.example.org:1234/path/to/relative/path?blubber=1#hash3'
+ }, {
+ name: 'absolute resolve full URL',
+ url: '/absolute/path?blubber=1#hash3',
+ base: 'http://user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ result: 'http://user:pass@www.example.org:1234/absolute/path?blubber=1#hash3'
+ }, {
+ name: 'absolute resolve full URL without scheme',
+ url: '//user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ base: 'proto://user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ result: 'proto://user:pass@www.example.org:1234/path/to/file?some=query#hash'
+ }, {
+ name: 'path-relative resolve',
+ url: './relative/path?blubber=1#hash3',
+ base: 'http://user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ result: 'http://user:pass@www.example.org:1234/path/to/relative/path?blubber=1#hash3'
+ }, {
+ name: 'path-relative parent resolve',
+ url: '../relative/path?blubber=1#hash3',
+ base: 'http://user:pass@www.example.org:1234/path/to/file?some=query#hash',
+ result: 'http://user:pass@www.example.org:1234/path/relative/path?blubber=1#hash3'
+ }, {
+ name: 'path-relative path resolve',
+ url: './relative/path?blubber=1#hash3',
+ base: '/path/to/file?some=query#hash',
+ result: '/path/to/relative/path?blubber=1#hash3'
+ }, {
+ name: 'path-relative path resolve 2',
+ url: 'tofile',
+ base: '/path/to/file',
+ result: '/path/to/tofile'
+ }, {
+ name: 'path-relative path-root resolve',
+ url: 'tofile',
+ base: '/file',
+ result: '/tofile'
+ }, {
+ name: 'path-relative parent path resolve',
+ url: '../relative/path?blubber=1#hash3',
+ base: '/path/to/file?some=query#hash',
+ result: '/path/relative/path?blubber=1#hash3'
+ }, {
+ name: 'fragment absolute url',
+ url: '#hash3',
+ base: '/path/to/file?some=query#hash',
+ result: '/path/to/file?some=query#hash3'
+ }, {
+ name: 'fragment relative url',
+ url: '#hash3',
+ base: 'path/to/file',
+ result: 'path/to/file#hash3'
+ }, {
+ name: 'relative path - urljoin',
+ url: 'the_relative_url',
+ base: 'rel/path/',
+ result: 'rel/path/the_relative_url'
+ }, {
+ name: 'relative path file - urljoin',
+ url: 'the_relative_url',
+ base: 'rel/path/something',
+ result: 'rel/path/the_relative_url'
+ }, {
+ name: 'relative parent path file - urljoin',
+ url: '../the_relative_url',
+ base: 'rel/path/',
+ result: 'rel/the_relative_url'
+ }, {
+ name: 'relative root path file - urljoin',
+ url: '/the_relative_url',
+ base: 'rel/path/',
+ result: '/the_relative_url'
+ }, {
+ name: 'relative root file - urljoin',
+ url: '/the_relative_url',
+ base: 'http://example.com/rel/path/',
+ result: 'http://example.com/the_relative_url'
+ }, {
+ name: 'absolute passthru - urljoin',
+ url: 'http://github.com//the_relative_url',
+ base: 'http://example.com/foo/bar',
+ result: 'http://github.com//the_relative_url'
+ }, {
+ name: 'file paths - urljoin',
+ url: 'anotherFile',
+ base: 'aFile',
+ result: 'anotherFile'
+ }
+ ];
+
+ for (var i = 0, t; (t = tests[i]); i++) {
+ var u = new URI(t.url),
+ r = u.absoluteTo(t.base);
+
+ equal(r + '', t.result, t.name);
+ }
+ });
+ test('absoluteTo - RFC3986 reference resolution', function() {
+ // http://tools.ietf.org/html/rfc3986#section-5.4
+ var base = 'http://a/b/c/d;p?q';
+ var map = {
+ // normal
+ // 'g:h' : 'g:h', // identified as URN
+ 'g' : 'http://a/b/c/g',
+ './g' : 'http://a/b/c/g',
+ 'g/' : 'http://a/b/c/g/',
+ '/g' : 'http://a/g',
+ '//g' : 'http://g/', // added trailing /
+ '?y' : 'http://a/b/c/d;p?y',
+ 'g?y' : 'http://a/b/c/g?y',
+ '#s' : 'http://a/b/c/d;p?q#s',
+ 'g#s' : 'http://a/b/c/g#s',
+ 'g?y#s' : 'http://a/b/c/g?y#s',
+ ';x' : 'http://a/b/c/;x',
+ 'g;x' : 'http://a/b/c/g;x',
+ 'g;x?y#s' : 'http://a/b/c/g;x?y#s',
+ '' : 'http://a/b/c/d;p?q',
+ '.' : 'http://a/b/c/',
+ './' : 'http://a/b/c/',
+ '..' : 'http://a/b/',
+ '../' : 'http://a/b/',
+ '../g' : 'http://a/b/g',
+ '../..' : 'http://a/',
+ '../../' : 'http://a/',
+ '../../g' : 'http://a/g',
+ // abnormal
+ '../../../g' : 'http://a/g',
+ '../../../../g' : 'http://a/g'
+ };
+
+ for (var key in map) {
+ var u = new URI(key),
+ r = u.absoluteTo(base);
+
+ equal(r + '', map[key], 'resolution "' + key + '"');
+ }
+ });
+ test('relativeTo', function() {
+ var tests = [{
+ name: 'same parent',
+ url: '/relative/path?blubber=1#hash1',
+ base: '/relative/file?some=query#hash',
+ result: 'path?blubber=1#hash1'
+ }, {
+ name: 'direct parent',
+ url: '/relative/path?blubber=1#hash1',
+ base: '/relative/sub/file?some=query#hash',
+ result: '../path?blubber=1#hash1'
+ }, {
+ name: 'second parent',
+ url: '/relative/path?blubber=1#hash1',
+ base: '/relative/sub/sub/file?some=query#hash',
+ result: '../../path?blubber=1#hash1'
+ }, {
+ name: 'third parent',
+ url: '/relative/path?blubber=1#hash1',
+ base: '/relative/sub/foo/sub/file?some=query#hash',
+ result: '../../../path?blubber=1#hash1'
+ }, {
+ name: 'parent top level',
+ url: '/relative/path?blubber=1#hash1',
+ base: '/path/to/file?some=query#hash',
+ result: '../../relative/path?blubber=1#hash1'
+ }, {
+ name: 'descendant',
+ url: '/base/path/with/subdir/inner.html',
+ base: '/base/path/top.html',
+ result: 'with/subdir/inner.html'
+ }, {
+ name: 'same directory',
+ url: '/path/',
+ base: '/path/top.html',
+ result: './'
+ }, {
+ name: 'absolute /',
+ url: 'http://example.org/foo/bar/bat',
+ base: 'http://example.org/',
+ result: 'foo/bar/bat'
+ }, {
+ name: 'absolute /foo',
+ url: 'http://example.org/foo/bar/bat',
+ base: 'http://example.org/foo',
+ result: 'foo/bar/bat'
+ }, {
+ name: 'absolute /foo/',
+ url: 'http://example.org/foo/bar/bat',
+ base: 'http://example.org/foo/',
+ result: 'bar/bat'
+ }, {
+ name: 'same scheme',
+ url: 'http://example.org/foo/bar/bat',
+ base: 'http://example.com/foo/',
+ result: '//example.org/foo/bar/bat'
+ }, {
+ name: 'different scheme',
+ url: 'http://example.org/foo/bar',
+ base: 'https://example.org/foo/',
+ result: 'http://example.org/foo/bar'
+ }, {
+ name: 'base with no scheme or host',
+ url: 'http://example.org/foo/bar',
+ base: '/foo/',
+ result: 'http://example.org/foo/bar'
+ }, {
+ name: 'base with no scheme',
+ url: 'http://example.org/foo/bar',
+ base: '//example.org/foo/bar',
+ result: 'http://example.org/foo/bar'
+ }, {
+ name: 'denormalized base',
+ url: '/foo/bar/bat',
+ base: '/foo/./bar/',
+ result: 'bat'
+ }, {
+ name: 'denormalized url',
+ url: '/foo//bar/bat',
+ base: '/foo/bar/',
+ result: 'bat'
+ }, {
+ name: 'credentials',
+ url: 'http://user:pass@example.org/foo/bar',
+ base: 'http://example.org/foo/',
+ result: '//user:pass@example.org/foo/bar'
+ }, {
+ name: 'base credentials',
+ url: 'http://example.org/foo/bar',
+ base: 'http://user:pass@example.org/foo/bar',
+ result: '//example.org/foo/bar'
+ }, {
+ name: 'same credentials different host',
+ url: 'http://user:pass@example.org/foo/bar',
+ base: 'http://user:pass@example.com/foo/bar',
+ result: '//user:pass@example.org/foo/bar'
+ }, {
+ name: 'different port 1',
+ url: 'http://example.org/foo/bar',
+ base: 'http://example.org:8080/foo/bar',
+ result: '//example.org/foo/bar'
+ }, {
+ name: 'different port 2',
+ url: 'http://example.org:8081/foo/bar',
+ base: 'http://example.org:8080/foo/bar',
+ result: '//example.org:8081/foo/bar'
+ }, {
+ name: 'different port 3',
+ url: 'http://example.org:8081/foo/bar',
+ base: 'http://example.org/foo/bar',
+ result: '//example.org:8081/foo/bar'
+ }, {
+ name: 'same path - fragment',
+ url: 'http://www.example.com:8080/dir/file#abcd',
+ base: 'http://www.example.com:8080/dir/file',
+ result: '#abcd'
+ }, {
+ name: 'same path - query',
+ url: 'http://www.example.com:8080/dir/file?abcd=123',
+ base: 'http://www.example.com:8080/dir/file',
+ result: '?abcd=123'
+ }, {
+ name: 'same path - query and fragment',
+ url: 'http://www.example.com:8080/dir/file?abcd=123#alpha',
+ base: 'http://www.example.com:8080/dir/file',
+ result: '?abcd=123#alpha'
+ }, {
+ name: 'already relative',
+ url: 'foo/bar',
+ base: '/foo/',
+ 'throws': true
+ }, {
+ name: 'relative base',
+ url: '/foo/bar',
+ base: 'foo/',
+ 'throws': true
+ }
+ ];
+
+ for (var i = 0, t; (t = tests[i]); i++) {
+ var u = new URI(t.url),
+ b = new URI(t.base),
+ caught = false;
+ var r;
+
+ try {
+ r = u.relativeTo(b);
+ } catch (e) {
+ caught = true;
+ }
+ /*jshint sub:true */
+ if (t['throws']) {
+ /*jshint sub:false */
+ ok(caught, t.name + ' should throw exception');
+ } else {
+ ok(!caught, t.name + ' should not throw exception');
+ equal(r + '', t.result, t.name);
+
+ var a = r.absoluteTo(t.base);
+ var n = u.clone().normalize();
+ equal(a.toString(), n.toString(), t.name + ' reversed');
+ }
+ }
+
+ equal('b/c',
+ new URI('http://example.org/a/b/c')
+ .scheme('')
+ .authority('')
+ .relativeTo('/a/')
+ .toString(),
+ 'bug #103');
+
+ equal('b/c',
+ new URI('//example.org/a/b/c')
+ .authority('')
+ .relativeTo('/a/')
+ .toString(),
+ 'bug #103 (2)');
+ });
+
+ module('static helpers');
+ test('withinString', function() {
+ /*jshint laxbreak: true */
+ var source = 'Hello www.example.com,\n'
+ + 'http://google.com is a search engine, like http://www.bing.com\n'
+ + 'http://exämple.org/foo.html?baz=la#bumm is an IDN URL,\n'
+ + 'http://123.123.123.123/foo.html is IPv4 and http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html is IPv6.\n'
+ + 'links can also be in parens (http://example.org) or quotes »http://example.org«.';
+ var expected = 'Hello <a>www.example.com</a>,\n'
+ + '<a>http://google.com</a> is a search engine, like <a>http://www.bing.com</a>\n'
+ + '<a>http://exämple.org/foo.html?baz=la#bumm</a> is an IDN URL,\n'
+ + '<a>http://123.123.123.123/foo.html</a> is IPv4 and <a>http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html</a> is IPv6.\n'
+ + 'links can also be in parens (<a>http://example.org</a>) or quotes »<a>http://example.org</a>«.';
+ /*jshint laxbreak: false */
+ var result = URI.withinString(source, function(url) {
+ return '<a>' + url + '</a>';
+ });
+
+ equal(result, expected, 'in string URI identification');
+ });
+ test('withinString - ignore', function() {
+ var decorate = function(url) {
+ return '<a>' + url + '</a>';
+ };
+ /*jshint laxbreak: true */
+ var source = 'Hello www.example.com,\n'
+ + 'proto://example.org/foo.html?baz=la#bumm is an URL.\n';
+ var expected = 'Hello <a>www.example.com</a>,\n'
+ + 'proto://example.org/foo.html?baz=la#bumm is an URL.\n';
+ /*jshint laxbreak: false */
+ var result = URI.withinString(source, decorate, {ignore: /^proto:/i});
+
+ equal(result, expected, 'filtered in string URI identification');
+ });
+ test('withinString - ignoreHtml', function() {
+ var decorate = function(url) {
+ return '<a>' + url + '</a>';
+ };
+ /*jshint laxbreak: true */
+ var source = 'Hello www.example.com,\n'
+ + '<a href=http://example.org/foo.html?baz=la#bumm is an URL</a>.\n'
+ + '<a href="http://example.org/foo.html?baz=la#bumm> is an URL</a>.\n'
+ + '<a href=\'http://example.org/foo.html?baz=la#bumm\'> is an URL</a>.\n';
+ var expected = 'Hello <a>www.example.com</a>,\n'
+ + '<a href=http://example.org/foo.html?baz=la#bumm is an URL</a>.\n'
+ + '<a href="http://example.org/foo.html?baz=la#bumm> is an URL</a>.\n'
+ + '<a href=\'http://example.org/foo.html?baz=la#bumm\'> is an URL</a>.\n';
+ /*jshint laxbreak: false */
+ var result = URI.withinString(source, decorate, {ignoreHtml: true});
+
+ equal(result, expected, 'filtered in string URI identification');
+ });
+ test('withinString - capture only', function() {
+ /*jshint laxbreak: true */
+ var source = 'Hello www.example.com,\n'
+ + 'http://google.com is a search engine, like http://www.bing.com\n'
+ + 'http://exämple.org/foo.html?baz=la#bumm is an IDN URL,\n'
+ + 'http://123.123.123.123/foo.html is IPv4 and http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html is IPv6.\n'
+ + 'links can also be in parens (http://example.org) or quotes »http://example.org«.';
+ var expected = [
+ 'www.example.com',
+ 'http://google.com',
+ 'http://www.bing.com',
+ 'http://exämple.org/foo.html?baz=la#bumm',
+ 'http://123.123.123.123/foo.html',
+ 'http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html',
+ 'http://example.org',
+ 'http://example.org'
+ ];
+
+ /*jshint laxbreak: false */
+ var links = [];
+ var result = URI.withinString(source, function(url) {
+ links.push(url);
+ });
+
+ deepEqual(links, expected, 'urls extracted');
+ equal(result, source, 'source not modified');
+ });
+ test('noConflict', function() {
+ var actual_lib = URI; // actual library; after loading, before noConflict()
+ var unconflicted = URI.noConflict();
+
+ strictEqual( unconflicted, actual_lib, 'noConflict() returns the URI object' );
+ strictEqual( URI, URI_pre_lib, 'noConflict() restores the `URI` variable' );
+
+ // restore for other tests
+ window.URI = actual_lib;
+ });
+ test('noConflict(removeAll=true)', function() {
+ var actual = {
+ URI: URI,
+ URITemplate: URITemplate,
+ IPv6: IPv6,
+ SecondLevelDomains: SecondLevelDomains
+ };
+
+ var unconflicted = URI.noConflict(true);
+
+ deepEqual( unconflicted, actual, 'noConflict(true) returns the { URI, URITemplate, IPv6, SecondLevelDomains } object' );
+ strictEqual( URI, URI_pre_lib, 'noConflict(true) restores the `URI` variable' );
+ strictEqual( URITemplate, URITemplate_pre_lib, 'noConflict(true) restores the `URITemplate` variable' );
+ strictEqual( IPv6, IPv6_pre_lib, 'noConflict(true) restores the `IPv6` variable' );
+ strictEqual( SecondLevelDomains, SecondLevelDomains_pre_lib, 'noConflict(true) restores the `SecondLevelDomains` variable' );
+
+ // restore for other tests
+ window.URI = actual.URI;
+ window.URITemplate = actual.URITemplate;
+ window.IPv6 = actual.IPv6;
+ window.SecondLevelDomains = actual.SecondLevelDomains;
+ });
+ test('joinPaths', function() {
+ var result;
+
+ result = URI.joinPaths('/a/b', '/c', 'd', '/e').toString();
+ equal(result, '/a/b/c/d/e', 'absolute paths');
+
+ result = URI.joinPaths('a/b', 'http://example.com/c', new URI('d/'), '/e').toString();
+ equal(result, 'a/b/c/d/e', 'relative path');
+
+ result = URI.joinPaths('/a/').toString();
+ equal(result, '/a/', 'single absolute directory');
+
+ result = URI.joinPaths('/a').toString();
+ equal(result, '/a', 'single absolute segment');
+
+ result = URI.joinPaths('a').toString();
+ equal(result, 'a', 'single relative segment');
+
+ result = URI.joinPaths('').toString();
+ equal(result, '', 'empty string');
+
+ result = URI.joinPaths().toString();
+ equal(result, '', 'no argument');
+
+ result = URI.joinPaths('', 'a', '', '', 'b').toString();
+ equal(result, '/a/b', 'leading empty segment');
+
+ result = URI.joinPaths('a', '', '', 'b', '', '').toString();
+ equal(result, 'a/b/', 'trailing empty segment');
+ });
+
+ module('comparing URLs');
+ test('equals', function() {
+ var u = new URI('http://example.org/foo/bar.html?foo=bar&hello=world&hello=mars#fragment'),
+ e = [
+ 'http://example.org/foo/../foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://exAmple.org/foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://exAmple.org:80/foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://example.org/foo/bar.html?foo=bar&hello=mars&hello=world#fragment',
+ 'http://example.org/foo/bar.html?hello=mars&hello=world&foo=bar&#fragment'
+ ],
+ d = [
+ 'http://example.org/foo/../bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://example.org/foo/bar.html?foo=bar&hello=world&hello=mars#frAgment',
+ 'http://example.org/foo/bar.html?foo=bar&hello=world&hello=mArs#fragment',
+ 'http://example.org/foo/bar.hTml?foo=bar&hello=world&hello=mars#fragment',
+ 'http://example.org:8080/foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://user:pass@example.org/foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'ftp://example.org/foo/bar.html?foo=bar&hello=world&hello=mars#fragment',
+ 'http://example.org/foo/bar.html?foo=bar&hello=world&hello=mars&hello=jupiter#fragment'
+ ],
+ i, c;
+
+ for (i = 0; (c = e[i]); i++) {
+ equal(u.equals(c), true, 'equality ' + i);
+ }
+
+ for (i = 0; (c = d[i]); i++) {
+ equal(u.equals(c), false, 'different ' + i);
+ }
+ });
+
+ module('Charset');
+ test('iso8859', function() {
+ var u = new URI('/ä.html');
+ u.normalizePath();
+ equal(u.path(), '/%C3%A4.html', 'Unicode');
+
+ URI.iso8859();
+ u = new URI('/ä.html');
+ u.normalizePath();
+ equal(u.path(), '/%E4.html', 'ISO8859');
+ u.path('/ö.html');
+ equal(u.path(), '/%F6.html', 'ISO8859');
+
+ URI.unicode();
+ u = new URI('/ä.html');
+ u.normalizePath();
+ equal(u.path(), '/%C3%A4.html', 'Unicode again');
+
+ u = new URI('/ä.html');
+ u.normalizePath();
+ equal(u.path(), '/%C3%A4.html', 'convert unicode start');
+ u.iso8859();
+ equal(u.path(), '/%E4.html', 'convert iso8859');
+ u.unicode();
+ equal(u.path(), '/%C3%A4.html', 'convert unicode');
+ });
+ test('bad charset in QueryString', function() {
+ var uri = new URI('http://www.google.com.hk/search?q=pennytel%20downloads&sa=%20%CB%D1%20%CB%F7%20&forid=1&prog=aff&ie=GB2312&oe=GB2312&safe=active&source=sdo_sb_html&hl=zh-CN');
+ var data = uri.query(true);
+
+ equal(data.sa, '%20%CB%D1%20%CB%F7%20', 'undecodable value returned');
+ equal(data.forid, '1', 'decodable value returned');
+
+ uri.normalizeQuery();
+ data = uri.query(true);
+ equal(data.sa, '%20%CB%D1%20%CB%F7%20', 'undecodable value returned');
+ equal(data.forid, '1', 'decodable value returned');
+ });
+
+ module('Encoding');
+ test('decode malformed URI', function() {
+ try {
+ decodeURIComponent('%%20');
+ ok(false, 'decodeURIComponent() must throw URIError: URI malformed');
+ } catch(e) {}
+
+ try {
+ URI.decode('%%20');
+ ok(false, 'URI.decode() must throw URIError: URI malformed');
+ } catch(e) {}
+
+ equal(URI.decodeQuery('%%20'), '%%20', 'malformed URI component returned');
+ equal(URI.decodePathSegment('%%20'), '%%20', 'malformed URI component returned');
+ equal(URI.decodeUrnPathSegment('%%20'), '%%20', 'malformed URN component returned');
+ });
+ test('encodeQuery', function() {
+ var escapeQuerySpace = URI.escapeQuerySpace;
+
+ URI.escapeQuerySpace = true;
+ equal(URI.encodeQuery(' '), '+');
+ equal(URI.encode(' '), '%20');
+
+ URI.escapeQuerySpace = false;
+ equal(URI.encodeQuery(' '), '%20');
+ equal(URI.encode(' '), '%20');
+
+ URI.escapeQuerySpace = escapeQuerySpace;
+ });
+ test('decodeQuery', function() {
+ var escapeQuerySpace = URI.escapeQuerySpace;
+
+ URI.escapeQuerySpace = true;
+ equal(URI.decodeQuery('+'), ' ');
+ equal(URI.decodeQuery('%20'), ' ');
+ equal(URI.decode('%20'), ' ');
+ equal(URI.decode('+'), '+');
+
+ URI.escapeQuerySpace = false;
+ equal(URI.decodeQuery('+'), '+');
+ equal(URI.decodeQuery('%20'), ' ');
+ equal(URI.decode('%20'), ' ');
+ equal(URI.decode('+'), '+');
+
+ URI.escapeQuerySpace = escapeQuerySpace;
+ });
+ test('encodeReserved', function() {
+ equal(URI.encodeReserved('ä:/?#[]@!$&\'()*+,;='), '%C3%A4:/?#[]@!$&\'()*+,;=');
+ });
+
+})();
diff --git a/thirdparty/URI.js/test/test_fragmentQuery.js b/thirdparty/URI.js/test/test_fragmentQuery.js
new file mode 100644
index 000000000..a98406bfe
--- /dev/null
+++ b/thirdparty/URI.js/test/test_fragmentQuery.js
@@ -0,0 +1,59 @@
+(function() {
+ 'use strict';
+ /*global URI, test, equal, deepEqual */
+
+ module('URI.fragmentQuery');
+ test('storing query-data in fragment', function() {
+ var u = URI('http://example.org');
+
+ deepEqual(u.fragment(true), {}, 'empty map for missing fragment');
+
+ u = URI('http://example.org/#');
+ deepEqual(u.fragment(true), {}, 'empty map for empty fragment');
+
+ u = URI('http://example.org/#?hello=world');
+ deepEqual(u.fragment(true), {hello: 'world'}, 'reading data object');
+
+ u.fragment({bar: 'foo'});
+ deepEqual(u.fragment(true), {bar: 'foo'}, 'setting data object');
+ equal(u.toString(), 'http://example.org/#?bar=foo', 'setting data object serialized');
+
+ u.addFragment('name', 'value');
+ deepEqual(u.fragment(true), {bar: 'foo', name: 'value'}, 'adding value');
+ equal(u.toString(), 'http://example.org/#?bar=foo&name=value', 'adding value serialized');
+
+ u.removeFragment('bar');
+ deepEqual(u.fragment(true), {name: 'value'}, 'removing value bar');
+ equal(u.toString(), 'http://example.org/#?name=value', 'removing value bar serialized');
+
+ u.removeFragment('name');
+ deepEqual(u.fragment(true), {}, 'removing value name');
+ equal(u.toString(), 'http://example.org/#?', 'removing value name serialized');
+ });
+ test('fragmentPrefix', function() {
+ var u;
+
+ URI.fragmentPrefix = '!';
+ u = URI('http://example.org');
+ equal(u._parts.fragmentPrefix, '!', 'init using global property');
+
+ u.fragment('#?hello=world');
+ equal(u.fragment(), '?hello=world', 'unparsed ?');
+ deepEqual(u.fragment(true), {}, 'parsing ? prefix');
+
+ u.fragment('#!hello=world');
+ equal(u.fragment(), '!hello=world', 'unparsed !');
+ deepEqual(u.fragment(true), {hello: 'world'}, 'parsing ! prefix');
+
+ u.fragmentPrefix('§');
+ equal(u.fragment(), '!hello=world', 'unparsed §');
+ deepEqual(u.fragment(true), {}, 'parsing § prefix');
+
+ u.fragment('#§hello=world');
+ equal(u.fragment(), '§hello=world', 'unparsed §');
+ deepEqual(u.fragment(true), {hello: 'world'}, 'parsing § prefix');
+
+ URI.fragmentPrefix = '?';
+ });
+
+})(); \ No newline at end of file
diff --git a/thirdparty/URI.js/test/test_fragmentURI.js b/thirdparty/URI.js/test/test_fragmentURI.js
new file mode 100644
index 000000000..87a28b8e5
--- /dev/null
+++ b/thirdparty/URI.js/test/test_fragmentURI.js
@@ -0,0 +1,61 @@
+(function() {
+ 'use strict';
+ /*global URI, test, equal, ok */
+
+ module('URI.fragmentURI');
+ test('storing URLs in fragment', function() {
+ var u = URI('http://example.org');
+ var f;
+
+ // var uri = URI('http://example.org/#!/foo/bar/baz.html');
+ // var furi = uri.fragment(true);
+ // furi.pathname() === '/foo/bar/baz.html';
+ // furi.pathname('/hello.html');
+ // uri.toString() === 'http://example.org/#!/hello.html'
+
+ ok(u.fragment(true) instanceof URI, 'URI instance for missing fragment');
+
+ u = URI('http://example.org/#');
+ ok(u.fragment(true) instanceof URI, 'URI instance for empty fragment');
+
+ u = URI('http://example.org/#!/foo/bar/baz.html');
+ f = u.fragment(true);
+ equal(f.pathname(), '/foo/bar/baz.html', 'reading path of FragmentURI');
+ equal(f.filename(), 'baz.html', 'reading filename of FragmentURI');
+
+ f.filename('foobar.txt');
+ equal(f.pathname(), '/foo/bar/foobar.txt', 'modifying filename of FragmentURI');
+ equal(u.fragment(), '!/foo/bar/foobar.txt', 'modifying fragment() through FragmentURI on original');
+ equal(u.toString(), 'http://example.org/#!/foo/bar/foobar.txt', 'modifying filename of FragmentURI on original');
+ });
+ test('fragmentPrefix', function() {
+ var u;
+
+ URI.fragmentPrefix = '?';
+ u = URI('http://example.org');
+ equal(u._parts.fragmentPrefix, '?', 'init using global property');
+
+ u.fragment('#!/foo/bar/baz.html');
+ equal(u.fragment(), '!/foo/bar/baz.html', 'unparsed ?');
+ ok(u.fragment(true) instanceof URI, 'parsing ? prefix - is URI');
+ equal(u.fragment(true).toString(), '', 'parsing ? prefix - result');
+
+ u.fragment('#?/foo/bar/baz.html');
+ equal(u.fragment(), '?/foo/bar/baz.html', 'unparsed ?');
+ ok(u.fragment(true) instanceof URI, 'parsing ? prefix - is URI');
+ equal(u.fragment(true).toString(), '/foo/bar/baz.html', 'parsing ? prefix - result');
+
+ u.fragmentPrefix('§');
+ equal(u.fragment(), '?/foo/bar/baz.html', 'unparsed §');
+ ok(u.fragment(true) instanceof URI, 'parsing § prefix - is URI');
+ equal(u.fragment(true).toString(), '', 'parsing § prefix - result');
+
+ u.fragment('#§/foo/bar/baz.html');
+ equal(u.fragment(), '§/foo/bar/baz.html', 'unparsed §');
+ ok(u.fragment(true) instanceof URI, 'parsing § prefix - is URI');
+ equal(u.fragment(true).toString(), '/foo/bar/baz.html', 'parsing § prefix - result');
+
+ URI.fragmentPrefix = '!';
+ });
+
+})(); \ No newline at end of file
diff --git a/thirdparty/URI.js/test/test_jim.js b/thirdparty/URI.js/test/test_jim.js
new file mode 100644
index 000000000..2be6def17
--- /dev/null
+++ b/thirdparty/URI.js/test/test_jim.js
@@ -0,0 +1,146 @@
+/*
+ * What would jim do?
+ * more tests for border-edge cases
+ * Christian Harms.
+ *
+ * Note: I have no clue who or what jim is supposed to be. It might be something like the German DAU (dumbest possible user)
+ */
+(function() {
+ 'use strict';
+ /*global URI, test, equal, raises */
+
+ module('injection');
+ test('protocol', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+ raises(function() {
+ u.protocol('ftp://example.org');
+ }, TypeError, 'Failing invalid characters');
+
+ u.protocol('ftp:');
+ equal(u.protocol(), 'ftp', 'protocol() has set invalid protocoll!');
+ equal(u.hostname(), 'example.com', 'protocol() has changed the hostname');
+ });
+ test('port', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+ raises(function() {
+ u.port('99:example.org');
+ }, TypeError, 'Failing invalid characters');
+
+ u.port(':99');
+ equal(u.hostname(), 'example.com', 'port() has modified hostname');
+ equal(u.port(), 99, 'port() has set an invalid port');
+
+ u.port(false);
+ equal(u.port(), '', 'port() has set an invalid port');
+
+ // RFC 3986 says nothing about "16-bit unsigned" http://tools.ietf.org/html/rfc3986#section-3.2.3
+ // u.href(new URI("http://example.com/"))
+ // u.port(65536);
+ // notEqual(u.port(), "65536", "port() has set to an non-valid value (A port number is a 16-bit unsigned integer)");
+
+ raises(function() {
+ u.port('-99');
+ }, TypeError, 'Failing invalid characters');
+ });
+ test('domain', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+
+ raises(function() {
+ u.domain('example.org/dir0/');
+ }, TypeError, 'Failing invalid characters');
+
+ raises(function() {
+ u.domain('example.org:80');
+ }, TypeError, 'Failing invalid characters');
+
+ raises(function() {
+ u.domain('foo@example.org');
+ }, TypeError, 'Failing invalid characters');
+ });
+ test('subdomain', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+
+ raises(function() {
+ u.subdomain('example.org/dir0/');
+ }, TypeError, 'Failing invalid characters');
+
+ raises(function() {
+ u.subdomain('example.org:80');
+ }, TypeError, 'Failing invalid characters');
+
+ raises(function() {
+ u.subdomain('foo@example.org');
+ }, TypeError, 'Failing invalid characters');
+ });
+ test('tld', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+
+ raises(function() {
+ u.tld('foo/bar.html');
+ }, TypeError, 'Failing invalid characters');
+ });
+ test('path', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+ u.path('/dir3/?query3=value3#fragment');
+ equal(u.hostname(), 'example.com', 'path() has modified hostname');
+ equal(u.path(), '/dir3/%3Fquery3=value3%23fragment', 'path() has set invalid path');
+ equal(u.query(), 'query1=value1&query2=value2', 'path() has modified query');
+ equal(u.fragment(), 'hash', 'path() has modified fragment');
+ });
+ test('filename', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+
+ u.filename('name.html?query');
+ equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename');
+ equal(u.query(), 'query1=value1&query2=value2', 'filename() has modified query');
+
+ // allowed!
+ u.filename('../name.html?query');
+ equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename');
+ equal(u.directory(), '/dir1', 'filename() has not altered directory properly');
+ });
+ test('addQuery', function() {
+ var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash');
+ u.addQuery('query3', 'value3#got');
+ equal(u.query(), 'query1=value1&query2=value2&query3=value3%23got', 'addQuery() has set invalid query');
+ equal(u.fragment(), 'hash', 'addQuery() has modified fragment');
+ });
+
+ /*
+ // RFC 3986 says "…and should limit these names to no more than 255 characters in length."
+ // SHOULD is not MUST therefore not the responsibility of URI.js
+
+ module("validation");
+ test("domain", function() {
+ // this bases on the wiki page information: http://en.wikipedia.org/wiki/Domain_Name_System
+ var u = new URI("http://example.com/"), domain, i, j;
+
+ //generate a 204 character domain
+ domain = "com"
+ for (i=0; i<20; i++) {
+ domain = "0123456789." + domain;
+ }
+ u.domain(domain);
+ equals(u.hostname(), domain, "domain() has not set 204-character-domain");
+
+ //expand the domain to a 404 character domain
+ for (i=0; i<20; i++) {
+ domain = "0123456789." + domain;
+ }
+ u.domain(domain);
+ equals(u.hostname() == domain, true, "set domain() with "+domain.length+" charachters - not valid domainname");
+
+ //generate a domain with three 70-char subdomains-parts.
+ domain = "com";
+ for (j=0; j<3; j++) {
+ //start new subdomain
+ domain = "." + domain;
+ for (i=0; i<70; i++) {
+ domain = "a" + domain;
+ }
+ }
+ u.domain(domain);
+ equals(u.hostname() == domain, true, "set domain() with 70-character subdomain not valid domainname");
+ });
+ */
+})(); \ No newline at end of file
diff --git a/thirdparty/URI.js/test/test_jquery.js b/thirdparty/URI.js/test/test_jquery.js
new file mode 100644
index 000000000..b6ab8c1be
--- /dev/null
+++ b/thirdparty/URI.js/test/test_jquery.js
@@ -0,0 +1,141 @@
+(function() {
+ 'use strict';
+ /*global document, URI, $, test, equal, ok */
+
+ module('jQuery.URI', {
+ setup: function() {
+ var links = [
+ '<a href="http://example.org/">an HTTP link</a>',
+ '<a href="https://example.org/">an HTTPS link</a>',
+ '<a href="http://example.org/so)me.pdf">some pdf</a>',
+ '<a href="http://example.org/hello/world.html">hello world</a>',
+ '<a href="ftp://localhost/one/two/three/file.ext">directories</a>',
+ '<a href="ftp://localhost/one/two/file.ext">directories</a>',
+ '<a href="mailto:mail@example.org?subject=Hello+World">Mail me</a>',
+ '<a href="javascript:alert(\'ugly!\');">some javascript</a>',
+ '<a href="#anchor">jump to anchor</a>',
+ '<img src="/dontexist.jpg" alt="some jpeg">',
+ '<img src="/dontexist.svg" alt="some svg">',
+ '<form method="post" action="/some/script.php"></form>'
+ ];
+
+ $('<div id="testestest">' + links.join('') + '</div>')
+ .appendTo(document.body);
+ $('<div>foo</div>')
+ .appendTo(document.body);
+
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = '/nonexistant.js';
+ document.getElementById('testestest').appendChild(script);
+ },
+ teardown: function() {
+ var t = $('#testestest');
+ t.next().remove();
+ t.remove();
+ }
+ });
+ test('.uri()', function() {
+ var $links = $('#testestest'),
+ $first = $links.children().first(),
+ uri = $first.uri(),
+ _uri = URI('/hello.world');
+
+ ok(uri !== _uri, 'different URI instances');
+ var __uri = $first.uri(_uri);
+ ok(uri !== _uri, 'different URI instances');
+ ok(uri === __uri, 'same URI instances');
+ equal($first.attr('href'), _uri.toString(), 'equal URI');
+
+ });
+ test('filtering with :uri()', function() {
+ var $links = $('#testestest');
+
+ // find using accessor and "begins with" comparison
+ equal($('a:uri(href^=#anc)').length, 1, '$(selector) Anchor Link');
+ equal($links.find(':uri(href^=#anc)').length, 1, '.find(selector) Anchor Link');
+
+ // find using accessor and "ends with" comparison
+ equal($(':uri(href$=.css)').length, 1, ':uri(href$=.css)');
+
+ // find using accessor and "contains" comparison
+ equal($(':uri(href *= /hello/)').length, 1, ':uri(href *= /hello/)');
+
+ // find using accessor and "equals" comparison
+ equal($links.find(':uri(protocol=https)').length, 1, ':uri(protocol=https)');
+ equal($links.find(':uri(protocol=http)').length, 3, ':uri(protocol=http)');
+
+ // directory match with trailing slash
+ equal($links.find(':uri(directory *= /two/)').length, 2, ':uri(directory *= /two/)');
+
+ // find using URI.is()
+ equal($links.find(':uri(relative)').length, 5, ':uri(relative)');
+ equal($links.find(':uri(is:relative)').length, 5, ':uri(is:relative)');
+ equal($links.find(':uri(is: relative)').length, 5, ':uri(is:relative)');
+
+ // find using URI.equal()
+ // This syntax breaks Sizzle, probably because it's looking for a nested pseudo ":http"
+ //equal($links.find(':uri(equals:http://example.org/hello/foo/../world.html)').length, 1, ':uri(equals:$url$)');
+ equal($links.find(':uri(equals:"http://example.org/hello/foo/../world.html")').length, 1, ':uri(equals:$url$)');
+ equal($links.find(':uri(equals: "http://example.org/hello/foo/../world.html")').length, 1, ':uri(equals:$url$)');
+
+ // find URNs
+ equal($links.find(':uri(urn)').length, 2, ':uri(urn)');
+
+ // .is()
+ equal($links.children('script').is(':uri(suffix=js)'), true, '.is(\':uri(suffix=js)\')');
+ equal($links.children('form').is(':uri(suffix=php)'), true, '.is(\':uri(suffix=php)\')');
+
+ // .has()
+ equal($('div').has(':uri(suffix=js)').length, 1, '.has(\':uri(suffix=js)\')');
+ });
+ test('.attr("href")', function() {
+ var $links = $('#testestest'),
+ $first = $links.children().first(),
+ first = $first.get(0),
+ uri = $first.uri(),
+ href = function(elem) {
+ return elem.getAttribute('href');
+ };
+
+ if (!$.support.hrefNormalized) {
+ href = function(elem) {
+ return elem.getAttribute('href', 2);
+ };
+ }
+
+ ok(uri instanceof URI, 'instanceof URI');
+ equal(href(first), uri.toString(), 'URI equals href');
+
+ // test feedback to DOM element
+ uri.hostname('example.com');
+ ok($first.uri() === uri, 'URI persisted');
+ equal(href(first), uri.toString(), 'transparent href update');
+
+ // test feedback from DOM element
+ $first.attr('href', 'http://example.net/');
+ ok($first.uri() === uri, 'URI persisted');
+ equal(href(first), uri.toString(), 'transparent href update');
+ });
+ test('.attr("uri:accessor")', function() {
+ var $links = $('#testestest'),
+ $first = $links.children().first(),
+ uri = $first.uri(),
+ href = function(elem) {
+ return elem.getAttribute('href');
+ };
+
+ if (!$.support.hrefNormalized) {
+ href = function(elem) {
+ return elem.getAttribute('href', 2);
+ };
+ }
+
+ equal($first.attr('uri:hostname'), 'example.org', 'reading uri:hostname');
+ $first.attr('uri:hostname', 'example.com');
+ equal($first.attr('uri:hostname'), 'example.com', 'changed uri:hostname');
+ equal($first.is(':uri(hostname=example.com)'), true, ':uri() after changed uri:hostname');
+ ok($first.uri() === uri, 'URI persisted');
+ });
+
+})(); \ No newline at end of file
diff --git a/thirdparty/URI.js/test/test_template.js b/thirdparty/URI.js/test/test_template.js
new file mode 100644
index 000000000..16db0179e
--- /dev/null
+++ b/thirdparty/URI.js/test/test_template.js
@@ -0,0 +1,411 @@
+(function() {
+ 'use strict';
+ /*global window, URITemplate, URITemplate_pre_lib, test, equal, strictEqual, raises */
+ // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
+ /*jshint camelcase: false, loopfunc: true, newcap: false */
+
+ var levels = {
+ // http://tools.ietf.org/html/rfc6570#section-1.2
+ 'Level 1' : {
+ expressions : {
+ 'Simple string expansion' : {
+ '{var}' : 'value',
+ '{hello}' : 'Hello%20World%21'
+ }
+ },
+ values : {
+ 'var' : 'value',
+ 'hello' : 'Hello World!'
+ }
+ },
+ 'Level 2' : {
+ expressions : {
+ 'Reserved string expansion' : {
+ '{+var}' : 'value',
+ '{+hello}' : 'Hello%20World!',
+ '{+path}/here' : '/foo/bar/here',
+ 'here?ref={+path}' : 'here?ref=/foo/bar'
+ },
+ 'Fragment expansion, crosshatch-prefixed' : {
+ 'X{#var}' : 'X#value',
+ 'X{#hello}' : 'X#Hello%20World!'
+ }
+ },
+ values : {
+ 'var' : 'value',
+ 'hello' : 'Hello World!',
+ 'path' : '/foo/bar'
+ }
+ },
+ 'Level 3' : {
+ expressions : {
+ 'String expansion with multiple variables' : {
+ 'map?{x,y}' : 'map?1024,768',
+ '{x,hello,y}' : '1024,Hello%20World%21,768'
+ },
+ 'Reserved expansion with multiple variables' : {
+ '{+x,hello,y}' : '1024,Hello%20World!,768',
+ '{+path,x}/here' : '/foo/bar,1024/here'
+ },
+ 'Fragment expansion with multiple variables' : {
+ '{#x,hello,y}' : '#1024,Hello%20World!,768',
+ '{#path,x}/here' : '#/foo/bar,1024/here'
+ },
+ 'Label expansion, dot-prefixed' : {
+ 'X{.var}' : 'X.value',
+ 'X{.x,y}' : 'X.1024.768'
+ },
+ 'Path segments, slash-prefixed' : {
+ '{/var}' : '/value',
+ '{/var,x}/here' : '/value/1024/here'
+ },
+ 'Path-style parameters, semicolon-prefixed' : {
+ '{;x,y}' : ';x=1024;y=768',
+ '{;x,y,empty}' : ';x=1024;y=768;empty'
+ },
+ 'Form-style query, ampersand-separated' : {
+ '{?x,y}' : '?x=1024&y=768',
+ '{?x,y,empty}' : '?x=1024&y=768&empty='
+ },
+ 'Form-style query continuation' : {
+ '?fixed=yes{&x}' : '?fixed=yes&x=1024',
+ '{&x,y,empty}' : '&x=1024&y=768&empty='
+ }
+ },
+ values : {
+ 'var' : 'value',
+ 'hello' : 'Hello World!',
+ 'empty' : '',
+ 'path' : '/foo/bar',
+ 'x' : '1024',
+ 'y' : '768'
+ }
+ },
+ 'Level 4' : {
+ expressions : {
+ 'String expansion with value modifiers' : {
+ '{var:3}' : 'val',
+ '{var:30}' : 'value',
+ '{list}' : 'red,green,blue',
+ '{list*}' : 'red,green,blue',
+ '{keys}' : 'semi,%3B,dot,.,comma,%2C',
+ '{keys*}' : 'semi=%3B,dot=.,comma=%2C'
+ },
+ 'Reserved expansion with value modifiers' : {
+ '{+path:6}/here' : '/foo/b/here',
+ '{+list}' : 'red,green,blue',
+ '{+list*}' : 'red,green,blue',
+ '{+keys}' : 'semi,;,dot,.,comma,,',
+ '{+keys*}' : 'semi=;,dot=.,comma=,'
+ },
+ 'Fragment expansion with value modifiers' : {
+ '{#path:6}/here' : '#/foo/b/here',
+ '{#list}' : '#red,green,blue',
+ '{#list*}' : '#red,green,blue',
+ '{#keys}' : '#semi,;,dot,.,comma,,',
+ '{#keys*}' : '#semi=;,dot=.,comma=,'
+ },
+ 'Label expansion, dot-prefixed' : {
+ 'X{.var:3}' : 'X.val',
+ 'X{.list}' : 'X.red,green,blue',
+ 'X{.list*}' : 'X.red.green.blue',
+ 'X{.keys}' : 'X.semi,%3B,dot,.,comma,%2C',
+ 'X{.keys*}' : 'X.semi=%3B.dot=..comma=%2C'
+ },
+ 'Path segments, slash-prefixed' : {
+ '{/var:1,var}' : '/v/value',
+ '{/list}' : '/red,green,blue',
+ '{/list*}' : '/red/green/blue',
+ '{/list*,path:4}' : '/red/green/blue/%2Ffoo',
+ '{/keys}' : '/semi,%3B,dot,.,comma,%2C',
+ '{/keys*}' : '/semi=%3B/dot=./comma=%2C'
+ },
+ 'Path-style parameters, semicolon-prefixed' : {
+ '{;hello:5}' : ';hello=Hello',
+ '{;list}' : ';list=red,green,blue',
+ '{;list*}' : ';list=red;list=green;list=blue',
+ '{;keys}' : ';keys=semi,%3B,dot,.,comma,%2C',
+ '{;keys*}' : ';semi=%3B;dot=.;comma=%2C'
+ },
+
+ 'Form-style query, ampersand-separated' : {
+ '{?var:3}' : '?var=val',
+ '{?list}' : '?list=red,green,blue',
+ '{?list*}' : '?list=red&list=green&list=blue',
+ '{?keys}' : '?keys=semi,%3B,dot,.,comma,%2C',
+ '{?keys*}' : '?semi=%3B&dot=.&comma=%2C'
+ },
+ 'Form-style query continuation' : {
+ '{&var:3}' : '&var=val',
+ '{&list}' : '&list=red,green,blue',
+ '{&list*}' : '&list=red&list=green&list=blue',
+ '{&keys}' : '&keys=semi,%3B,dot,.,comma,%2C',
+ '{&keys*}' : '&semi=%3B&dot=.&comma=%2C'
+ }
+ },
+ values : {
+ 'var' : 'value',
+ 'hello' : 'Hello World!',
+ 'path' : '/foo/bar',
+ 'list' : ['red', 'green', 'blue'],
+ 'keys' : {
+ 'semi' : ';',
+ 'dot' : '.',
+ 'comma' : ','
+ }
+ }
+ },
+ // http://tools.ietf.org/html/rfc6570#section-3
+ 'Expression Expansion' : {
+ expressions : {
+ 'Variable Expansion' : {
+ '{count}' : 'one,two,three',
+ '{count*}' : 'one,two,three',
+ '{/count}' : '/one,two,three',
+ '{/count*}' : '/one/two/three',
+ '{;count}' : ';count=one,two,three',
+ '{;count*}' : ';count=one;count=two;count=three',
+ '{?count}' : '?count=one,two,three',
+ '{?count*}' : '?count=one&count=two&count=three',
+ '{&count*}' : '&count=one&count=two&count=three'
+ },
+ 'Simple String Expansion' : {
+ '{var}' : 'value',
+ '{hello}' : 'Hello%20World%21',
+ '{half}' : '50%25',
+ 'O{empty}X' : 'OX',
+ 'O{undef}X' : 'OX',
+ '{x,y}' : '1024,768',
+ '{x,hello,y}' : '1024,Hello%20World%21,768',
+ '?{x,empty}' : '?1024,',
+ '?{x,undef}' : '?1024',
+ '?{undef,y}' : '?768',
+ '{var:3}' : 'val',
+ '{var:30}' : 'value',
+ '{list}' : 'red,green,blue',
+ '{list*}' : 'red,green,blue',
+ '{keys}' : 'semi,%3B,dot,.,comma,%2C',
+ '{keys*}' : 'semi=%3B,dot=.,comma=%2C'
+ },
+ 'Reserved Expansion' : {
+ '{+var}' : 'value',
+ '{+hello}' : 'Hello%20World!',
+ '{+half}' : '50%25',
+ '{base}index' : 'http%3A%2F%2Fexample.com%2Fhome%2Findex',
+ '{+base}index' : 'http://example.com/home/index',
+ 'O{+empty}X' : 'OX',
+ 'O{+undef}X' : 'OX',
+ '{+path}/here' : '/foo/bar/here',
+ 'here?ref={+path}' : 'here?ref=/foo/bar',
+ 'up{+path}{var}/here' : 'up/foo/barvalue/here',
+ '{+x,hello,y}' : '1024,Hello%20World!,768',
+ '{+path,x}/here' : '/foo/bar,1024/here',
+ '{+path:6}/here' : '/foo/b/here',
+ '{+list}' : 'red,green,blue',
+ '{+list*}' : 'red,green,blue',
+ '{+keys}' : 'semi,;,dot,.,comma,,',
+ '{+keys*}' : 'semi=;,dot=.,comma=,'
+ },
+ 'Fragment Expansion' : {
+ '{#var}' : '#value',
+ '{#hello}' : '#Hello%20World!',
+ '{#half}' : '#50%25',
+ 'foo{#empty}' : 'foo#',
+ 'foo{#undef}' : 'foo',
+ '{#x,hello,y}' : '#1024,Hello%20World!,768',
+ '{#path,x}/here' : '#/foo/bar,1024/here',
+ '{#path:6}/here' : '#/foo/b/here',
+ '{#list}' : '#red,green,blue',
+ '{#list*}' : '#red,green,blue',
+ '{#keys}' : '#semi,;,dot,.,comma,,',
+ '{#keys*}' : '#semi=;,dot=.,comma=,'
+ },
+ 'Label Expansion with Dot-Prefix' : {
+ '{.who}' : '.fred',
+ '{.who,who}' : '.fred.fred',
+ '{.half,who}' : '.50%25.fred',
+ 'www{.dom*}' : 'www.example.com',
+ 'X{.var}' : 'X.value',
+ 'X{.empty}' : 'X.',
+ 'X{.undef}' : 'X',
+ 'X{.var:3}' : 'X.val',
+ 'X{.list}' : 'X.red,green,blue',
+ 'X{.list*}' : 'X.red.green.blue',
+ 'X{.keys}' : 'X.semi,%3B,dot,.,comma,%2C',
+ 'X{.keys*}' : 'X.semi=%3B.dot=..comma=%2C',
+ 'X{.empty_keys}' : 'X',
+ 'X{.empty_keys*}' : 'X'
+ },
+ 'Path Segment Expansion' : {
+ '{/who}' : '/fred',
+ '{/who,who}' : '/fred/fred',
+ '{/half,who}' : '/50%25/fred',
+ '{/who,dub}' : '/fred/me%2Ftoo',
+ '{/var}' : '/value',
+ '{/var,empty}' : '/value/',
+ '{/var,undef}' : '/value',
+ '{/var,x}/here' : '/value/1024/here',
+ '{/var:1,var}' : '/v/value',
+ '{/list}' : '/red,green,blue',
+ '{/list*}' : '/red/green/blue',
+ '{/list*,path:4}' : '/red/green/blue/%2Ffoo',
+ '{/keys}' : '/semi,%3B,dot,.,comma,%2C',
+ '{/keys*}' : '/semi=%3B/dot=./comma=%2C'
+ },
+ 'Path-Style Parameter Expansion' : {
+ '{;who}' : ';who=fred',
+ '{;half}' : ';half=50%25',
+ '{;empty}' : ';empty',
+ '{;v,empty,who}' : ';v=6;empty;who=fred',
+ '{;v,bar,who}' : ';v=6;who=fred',
+ '{;x,y}' : ';x=1024;y=768',
+ '{;x,y,empty}' : ';x=1024;y=768;empty',
+ '{;x,y,undef}' : ';x=1024;y=768',
+ '{;hello:5}' : ';hello=Hello',
+ '{;list}' : ';list=red,green,blue',
+ '{;list*}' : ';list=red;list=green;list=blue',
+ '{;keys}' : ';keys=semi,%3B,dot,.,comma,%2C',
+ '{;keys*}' : ';semi=%3B;dot=.;comma=%2C'
+ },
+ 'Form-Style Query Expansion' : {
+ '{?who}' : '?who=fred',
+ '{?half}' : '?half=50%25',
+ '{?x,y}' : '?x=1024&y=768',
+ '{?x,y,empty}' : '?x=1024&y=768&empty=',
+ '{?x,y,undef}' : '?x=1024&y=768',
+ '{?var:3}' : '?var=val',
+ '{?list}' : '?list=red,green,blue',
+ '{?list*}' : '?list=red&list=green&list=blue',
+ '{?keys}' : '?keys=semi,%3B,dot,.,comma,%2C',
+ '{?keys*}' : '?semi=%3B&dot=.&comma=%2C'
+ },
+ 'Form-Style Query Continuation' : {
+ '{&who}' : '&who=fred',
+ '{&half}' : '&half=50%25',
+ '?fixed=yes{&x}' : '?fixed=yes&x=1024',
+ '{&x,y,empty}' : '&x=1024&y=768&empty=',
+ '{&x,y,undef}' : '&x=1024&y=768',
+ '{&var:3}' : '&var=val',
+ '{&list}' : '&list=red,green,blue',
+ '{&list*}' : '&list=red&list=green&list=blue',
+ '{&keys}' : '&keys=semi,%3B,dot,.,comma,%2C',
+ '{&keys*}' : '&semi=%3B&dot=.&comma=%2C'
+ }
+ },
+ values : {
+ 'count' : ['one', 'two', 'three'],
+ 'dom' : ['example', 'com'],
+ 'dub' : 'me/too',
+ 'hello' : 'Hello World!',
+ 'half' : '50%',
+ 'var' : 'value',
+ 'who' : 'fred',
+ 'base' : 'http://example.com/home/',
+ 'path' : '/foo/bar',
+ 'list' : ['red', 'green', 'blue'],
+ 'keys' : {
+ 'semi' : ';',
+ 'dot' : '.',
+ 'comma' : ','
+ },
+ 'v' : '6',
+ 'x' : '1024',
+ 'y' : '768',
+ 'empty' : '',
+ 'empty_keys' : [],
+ 'undef' : null
+ }
+ }
+ };
+
+ module('URITemplate');
+ // [].forEach() no IE, lacking interest in polyfilling this...
+ for (var i in levels) {
+ (function(level, data){
+ test(level, function() {
+ var combined_expression = '',
+ combined_expansion = '',
+ template, expression, expansion;
+
+ for (var type in data.expressions) {
+ for (expression in data.expressions[type]) {
+ combined_expression += '/' + expression;
+ combined_expansion += '/' + data.expressions[type][expression];
+
+ template = new URITemplate(expression);
+ expansion = template.expand(data.values);
+ equal(expansion, data.expressions[type][expression], type + ': ' + expression);
+ }
+ }
+
+ template = new URITemplate(combined_expression);
+ expansion = template.expand(data.values);
+ equal(expansion, combined_expansion, type + ': combined');
+ });
+ })(i, levels[i]);
+ }
+ test('Data Callbacks', function() {
+ var template = new URITemplate('{var}');
+ var global = function(key) {
+ var data = {'var': 'hello world.html'};
+ return data[key];
+ };
+ var local = function() {
+ return 'hello world.html';
+ };
+
+ equal(template.expand(global), 'hello%20world.html', 'global callback');
+ equal(template.expand({'var': local}), 'hello%20world.html', 'local callback');
+ });
+
+ test('Parse errors', function() {
+ raises(function() {
+ URITemplate('AB{var$}IJ').parse();
+ }, Error, 'Failing invalid variable name');
+
+ raises(function() {
+ URITemplate('AB{$var}IJ').parse();
+ }, Error, 'Failing invalid operator');
+
+ raises(function() {
+ URITemplate('AB{var:3IJ').parse();
+ }, Error, 'Failing missing closing }');
+
+ raises(function() {
+ URITemplate('AB{var:3*}IJ').parse();
+ }, Error, 'Failing invalid modifier');
+ });
+
+ test('Expansion errors', function() {
+ raises(function() {
+ var data = {'composite_var': ['multiple', 'values']};
+ URITemplate('{composite_var:3}').expand(data);
+ }, Error, 'Failing prefix modifier after composite variable');
+ });
+
+ test('noConflict mode', function() {
+ var actual_lib = URITemplate; // actual library; after loading, before noConflict()
+ var unconflicted = URITemplate.noConflict();
+
+ strictEqual( unconflicted, actual_lib, 'noConflict() returns the URITemplate object' );
+ strictEqual( URITemplate, URITemplate_pre_lib, 'noConflict() restores the `URITemplate` variable' );
+
+ // restore for other tests
+ window.URITemplate = actual_lib;
+ });
+
+ test('Periods in varnames', function() {
+ var template = new URITemplate('{hello.world.var}');
+ var literal = 'replacement';
+ var data = {'hello.world.var': literal};
+ var expansion = template.expand(data);
+ equal(expansion, literal, 'period in varname');
+ });
+
+ test('Invalid literals', function () {
+ raises(function() {
+ URITemplate('invalid.char}acter').parse();
+ }, Error, 'Failing invalid literal');
+ });
+
+})();
diff --git a/thirdparty/URI.js/test/urls.js b/thirdparty/URI.js/test/urls.js
new file mode 100644
index 000000000..b38337ec0
--- /dev/null
+++ b/thirdparty/URI.js/test/urls.js
@@ -0,0 +1,1892 @@
+/*jshint unused:false, scripturl:true */
+var urls = [{
+ name: 'scheme and domain',
+ url: 'http://www.example.org',
+ _url: 'http://www.example.org/',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: 'www.example.org',
+ origin: 'http://www.example.org',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '', // location.hash style
+ search: '', // location.search style
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'second level domain',
+ url: 'http://www.example.co.uk',
+ _url: 'http://www.example.co.uk/',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.co.uk',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: 'www.example.co.uk',
+ origin: 'http://www.example.co.uk',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.co.uk',
+ tld: 'co.uk',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '', // location.hash style
+ search: '', // location.search style
+ host: 'www.example.co.uk',
+ hostname: 'www.example.co.uk'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: true,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ },{
+ name: 'qualified HTTP',
+ url: 'http://www.example.org/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: null,
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'www.example.org',
+ origin: 'http://www.example.org',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'funky suffix',
+ url: 'http://www.example.org/some/directory/file.html-is-awesome?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: null,
+ path: '/some/directory/file.html-is-awesome',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/some/directory/file.html-is-awesome',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html-is-awesome?query=string#fragment',
+ authority: 'www.example.org',
+ origin: 'http://www.example.org',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html-is-awesome',
+ suffix: '',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'complete URL',
+ url: 'scheme://user:pass@www.example.org:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'scheme',
+ username: 'user',
+ password: 'pass',
+ hostname: 'www.example.org',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'scheme',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@www.example.org:123',
+ origin: 'scheme://user:pass@www.example.org:123',
+ userinfo: 'user:pass',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'www.example.org:123',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'badly encoded userinfo',
+ url: 'scheme://user:pass:word@www.example.org/',
+ _url: 'scheme://user:pass%3Aword@www.example.org/',
+ parts: {
+ protocol: 'scheme',
+ username: 'user',
+ password: 'pass:word',
+ hostname: 'www.example.org',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'scheme',
+ username: 'user',
+ password: 'pass:word',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: 'user:pass%3Aword@www.example.org',
+ origin: 'scheme://user:pass%3Aword@www.example.org',
+ userinfo: 'user:pass%3Aword',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'empty username with non-empty password',
+ url: 'scheme://:password@www.example.org/',
+ _url: 'scheme://:password@www.example.org/',
+ parts: {
+ protocol: 'scheme',
+ username: null,
+ password: 'password',
+ hostname: 'www.example.org',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'scheme',
+ username: '',
+ password: 'password',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: ':password@www.example.org',
+ origin: 'scheme://:password@www.example.org',
+ userinfo: ':password',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'malformed email in userinfo',
+ url: 'scheme://john@doe.com:pass:word@www.example.org/',
+ _url: 'scheme://john%40doe.com:pass%3Aword@www.example.org/',
+ parts: {
+ protocol: 'scheme',
+ username: 'john@doe.com',
+ password: 'pass:word',
+ hostname: 'www.example.org',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'scheme',
+ username: 'john@doe.com',
+ password: 'pass:word',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: 'john%40doe.com:pass%3Aword@www.example.org',
+ origin: 'scheme://john%40doe.com:pass%3Aword@www.example.org',
+ userinfo: 'john%40doe.com:pass%3Aword',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'host-relative: URL',
+ url: '/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: '',
+ username: '',
+ password: '',
+ port: '',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'path-relative: URL',
+ url: '../some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '../some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: '',
+ username: '',
+ password: '',
+ port: '',
+ path: '../some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '../some/directory/file.html?query=string#fragment',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '../some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'missing scheme',
+ url: 'user:pass@www.example.org:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'user',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: 'pass@www.example.org:123/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'user',
+ username: '',
+ password: '',
+ port: '',
+ path: 'pass@www.example.org:123/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: 'pass@www.example.org:123/some/directory/file.html?query=string#fragment',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '',
+ filename: '',
+ suffix: '',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: true,
+ url: false,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'ignoring scheme',
+ url: '://user:pass@example.org:123/some/directory/file.html?query=string#fragment',
+ _url: '//user:pass@example.org:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: null,
+ username: 'user',
+ password: 'pass',
+ hostname: 'example.org',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: '',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@example.org:123',
+ origin: 'user:pass@example.org:123',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'example.org:123',
+ hostname: 'example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'scheme-relative URL',
+ url: '//www.example.org/',
+ parts: {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: '',
+ username: '',
+ password: '',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: 'www.example.org',
+ origin: 'www.example.org',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv4',
+ url: 'http://user:pass@123.123.123.123:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ hostname: '123.123.123.123',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@123.123.123.123:123',
+ origin: 'http://user:pass@123.123.123.123:123',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '123.123.123.123:123',
+ hostname: '123.123.123.123'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: true,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6',
+ url: 'http://user:pass@fe80:0000:0000:0000:0204:61ff:fe9d:f156/some/directory/file.html?query=string#fragment',
+ _url: 'http://user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ hostname: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156',
+ port: null,
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ port: '',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]',
+ origin: 'http://user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '[fe80:0000:0000:0000:0204:61ff:fe9d:f156]',
+ hostname: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 with port',
+ url: 'http://user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ hostname: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]:123',
+ origin: 'http://user:pass@[fe80:0000:0000:0000:0204:61ff:fe9d:f156]:123',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: '[fe80:0000:0000:0000:0204:61ff:fe9d:f156]:123',
+ hostname: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 brackets, port, file.ext',
+ url: 'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210',
+ port: '80',
+ path: '/index.html',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '80',
+ path: '/index.html',
+ query: '',
+ fragment: '',
+ resource: '/index.html',
+ authority: '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80',
+ origin: 'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: 'index.html',
+ suffix: 'html',
+ hash: '',
+ search: '',
+ host: '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80',
+ hostname: 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 brackets, file.ext',
+ url: 'http://[1080:0:0:0:8:800:200C:417A]/index.html',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '1080:0:0:0:8:800:200C:417A',
+ port: null,
+ path: '/index.html',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/index.html',
+ query: '',
+ fragment: '',
+ resource: '/index.html',
+ authority: '[1080:0:0:0:8:800:200C:417A]',
+ origin: 'http://[1080:0:0:0:8:800:200C:417A]',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: 'index.html',
+ suffix: 'html',
+ hash: '',
+ search: '',
+ host: '[1080:0:0:0:8:800:200C:417A]',
+ hostname: '1080:0:0:0:8:800:200C:417A'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 brackets ::1',
+ url: 'http://[3ffe:2a00:100:7031::1]',
+ _url: 'http://[3ffe:2a00:100:7031::1]/',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '3ffe:2a00:100:7031::1',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: '[3ffe:2a00:100:7031::1]',
+ origin: 'http://[3ffe:2a00:100:7031::1]',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '[3ffe:2a00:100:7031::1]',
+ hostname: '3ffe:2a00:100:7031::1'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 brackets, file',
+ url: 'http://[1080::8:800:200C:417A]/foo',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '1080::8:800:200C:417A',
+ port: null,
+ path: '/foo',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/foo',
+ query: '',
+ fragment: '',
+ resource: '/foo',
+ authority: '[1080::8:800:200C:417A]',
+ origin: 'http://[1080::8:800:200C:417A]',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: 'foo',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '[1080::8:800:200C:417A]',
+ hostname: '1080::8:800:200C:417A'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 IPv4 brackets, path',
+ url: 'http://[::192.9.5.5]/ipng',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '::192.9.5.5',
+ port: null,
+ path: '/ipng',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/ipng',
+ query: '',
+ fragment: '',
+ resource: '/ipng',
+ authority: '[::192.9.5.5]',
+ origin: 'http://[::192.9.5.5]',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: 'ipng',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '[::192.9.5.5]',
+ hostname: '::192.9.5.5'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 mask IPv4 brackets, port, file.ext',
+ url: 'http://[::FFFF:129.144.52.38]:80/index.html',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '::FFFF:129.144.52.38',
+ port: '80',
+ path: '/index.html',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '80',
+ path: '/index.html',
+ query: '',
+ fragment: '',
+ resource: '/index.html',
+ authority: '[::FFFF:129.144.52.38]:80',
+ origin: 'http://[::FFFF:129.144.52.38]:80',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: 'index.html',
+ suffix: 'html',
+ hash: '',
+ search: '',
+ host: '[::FFFF:129.144.52.38]:80',
+ hostname: '::FFFF:129.144.52.38'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IPv6 brackets',
+ url: 'http://[2010:836B:4179::836B:4179]',
+ _url: 'http://[2010:836B:4179::836B:4179]/',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: '2010:836B:4179::836B:4179',
+ port: null,
+ path: '/',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/',
+ query: '',
+ fragment: '',
+ resource: '/',
+ authority: '[2010:836B:4179::836B:4179]',
+ origin: 'http://[2010:836B:4179::836B:4179]',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '[2010:836B:4179::836B:4179]',
+ hostname: '2010:836B:4179::836B:4179'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: true,
+ ip4: false,
+ ip6: true,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'IDN (punycode)',
+ url: 'http://user:pass@xn--exmple-cua.org:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ hostname: 'xn--exmple-cua.org',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@xn--exmple-cua.org:123',
+ origin: 'http://user:pass@xn--exmple-cua.org:123',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: 'xn--exmple-cua.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'xn--exmple-cua.org:123',
+ hostname: 'xn--exmple-cua.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: true
+ }
+ }, {
+ name: 'IDN',
+ url: 'http://user:pass@exämple.org:123/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ hostname: 'exämple.org',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: 'user',
+ password: 'pass',
+ port: '123',
+ path: '/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/some/directory/file.html?query=string#fragment',
+ authority: 'user:pass@exämple.org:123',
+ origin: 'http://user:pass@exämple.org:123',
+ userinfo: 'user:pass',
+ subdomain: '',
+ domain: 'exämple.org',
+ tld: 'org',
+ directory: '/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'exämple.org:123',
+ hostname: 'exämple.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: true,
+ punycode: false
+ }
+ }, {
+ name: 'file://',
+ url: 'file:///foo/bar/baz.html',
+ parts: {
+ protocol: 'file',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '/foo/bar/baz.html',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'file',
+ username: '',
+ password: '',
+ port: '',
+ path: '/foo/bar/baz.html',
+ query: '',
+ fragment: '',
+ resource: '/foo/bar/baz.html',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/foo/bar',
+ filename: 'baz.html',
+ suffix: 'html',
+ hash: '',
+ search: '',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'file://example.org:123',
+ url: 'file://example.org:123/foo/bar/baz.html',
+ parts: {
+ protocol: 'file',
+ username: null,
+ password: null,
+ hostname: 'example.org',
+ port: '123',
+ path: '/foo/bar/baz.html',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'file',
+ username: '',
+ password: '',
+ port: '123',
+ path: '/foo/bar/baz.html',
+ query: '',
+ fragment: '',
+ resource: '/foo/bar/baz.html',
+ authority: 'example.org:123',
+ origin: 'file://example.org:123',
+ userinfo: '',
+ subdomain: '',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/foo/bar',
+ filename: 'baz.html',
+ suffix: 'html',
+ hash: '',
+ search: '',
+ host: 'example.org:123',
+ hostname: 'example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'file:// Windows-Drive-Letter',
+ url: 'file:///C:/WINDOWS/foo.txt',
+ parts: {
+ protocol: 'file',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '/C:/WINDOWS/foo.txt',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'file',
+ username: '',
+ password: '',
+ port: '',
+ path: '/C:/WINDOWS/foo.txt',
+ query: '',
+ fragment: '',
+ resource: '/C:/WINDOWS/foo.txt',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/C:/WINDOWS',
+ filename: 'foo.txt',
+ suffix: 'txt',
+ hash: '',
+ search: '',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'file://example.org/ Windows-Drive-Letter',
+ url: 'file://example.org/C:/WINDOWS/foo.txt',
+ parts: {
+ protocol: 'file',
+ username: null,
+ password: null,
+ hostname: 'example.org',
+ port: null,
+ path: '/C:/WINDOWS/foo.txt',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'file',
+ username: '',
+ password: '',
+ port: '',
+ path: '/C:/WINDOWS/foo.txt',
+ query: '',
+ fragment: '',
+ resource: '/C:/WINDOWS/foo.txt',
+ authority: 'example.org',
+ origin: 'file://example.org',
+ userinfo: '',
+ subdomain: '',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/C:/WINDOWS',
+ filename: 'foo.txt',
+ suffix: 'txt',
+ hash: '',
+ search: '',
+ host: 'example.org',
+ hostname: 'example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'file://localhost/ Windows-Drive-Letter with pipe',
+ url: 'file://localhost/C|/WINDOWS/foo.txt',
+ parts: {
+ protocol: 'file',
+ username: null,
+ password: null,
+ hostname: 'localhost',
+ port: null,
+ path: '/C|/WINDOWS/foo.txt',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'file',
+ username: '',
+ password: '',
+ port: '',
+ path: '/C|/WINDOWS/foo.txt',
+ query: '',
+ fragment: '',
+ resource: '/C|/WINDOWS/foo.txt',
+ authority: 'localhost',
+ origin: 'file://localhost',
+ userinfo: '',
+ subdomain: '',
+ domain: 'localhost',
+ tld: 'localhost',
+ directory: '/C|/WINDOWS',
+ filename: 'foo.txt',
+ suffix: 'txt',
+ hash: '',
+ search: '',
+ host: 'localhost',
+ hostname: 'localhost'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'Path containing @',
+ url: 'http://www.example.org/@foobar',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: null,
+ path: '/@foobar',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/@foobar',
+ query: '',
+ fragment: '',
+ resource: '/@foobar',
+ authority: 'www.example.org',
+ origin: 'http://www.example.org',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: '@foobar',
+ suffix: '',
+ hash: '', // location.hash style
+ search: '', // location.search style
+ host: 'www.example.org',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'mailto:',
+ url: 'mailto:hello@example.org?subject=hello',
+ _url: 'mailto:hello@example.org?subject=hello',
+ parts: {
+ protocol: 'mailto',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: 'hello@example.org',
+ query: 'subject=hello',
+ fragment: null
+ },
+ accessors: {
+ protocol: 'mailto',
+ username: '',
+ password: '',
+ port: '',
+ path: 'hello@example.org',
+ query: 'subject=hello',
+ fragment: '',
+ resource: 'hello@example.org?subject=hello',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '?subject=hello',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: true,
+ url: false,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'magnet:',
+ url: 'magnet:?xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ _url: 'magnet:?xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ parts: {
+ protocol: 'magnet',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '',
+ query: 'xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ fragment: null
+ },
+ accessors: {
+ protocol: 'magnet',
+ username: '',
+ password: '',
+ port: '',
+ path: '',
+ query: 'xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ fragment: '',
+ resource: '?xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '?xt=urn:btih:f8c020dac7a083defda1769a1196a13facc38ef6&dn=Linux+64x+server+11.10+Pt+Pt&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: true,
+ url: false,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'javascript:',
+ url: 'javascript:alert("hello world");',
+ _url: 'javascript:alert("hello world");',
+ parts: {
+ protocol: 'javascript',
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: 'alert("hello world");',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'javascript',
+ username: '',
+ password: '',
+ port: '',
+ path: 'alert("hello world");',
+ query: '',
+ fragment: '',
+ resource: 'alert("hello world");',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '',
+ filename: '',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: true,
+ url: false,
+ relative: false,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'colon in path',
+ url: 'http://en.wikipedia.org/wiki/Help:IPA',
+ _url: 'http://en.wikipedia.org/wiki/Help:IPA',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'en.wikipedia.org',
+ port: null,
+ path: '/wiki/Help:IPA',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/wiki/Help:IPA',
+ query: '',
+ fragment: '',
+ resource: '/wiki/Help:IPA',
+ authority: 'en.wikipedia.org',
+ origin: 'http://en.wikipedia.org',
+ userinfo: '',
+ subdomain: 'en',
+ domain: 'wikipedia.org',
+ tld: 'org',
+ directory: '/wiki',
+ filename: 'Help:IPA',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: 'en.wikipedia.org',
+ hostname: 'en.wikipedia.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'colon in path without protocol',
+ url: '/wiki/Help:IPA',
+ _url: '/wiki/Help:IPA',
+ parts: {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '/wiki/Help:IPA',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: '',
+ username: '',
+ password: '',
+ port: '',
+ path: '/wiki/Help:IPA',
+ query: '',
+ fragment: '',
+ resource: '/wiki/Help:IPA',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/wiki',
+ filename: 'Help:IPA',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'colon dash dash in path without protocol',
+ url: '/foo/xy://bar',
+ _url: '/foo/xy://bar',
+ parts: {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: null,
+ port: null,
+ path: '/foo/xy://bar',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: '',
+ username: '',
+ password: '',
+ port: '',
+ path: '/foo/xy://bar',
+ query: '',
+ fragment: '',
+ resource: '/foo/xy://bar',
+ authority: '',
+ origin: '',
+ userinfo: '',
+ subdomain: '',
+ domain: '',
+ tld: '',
+ directory: '/foo/xy:/', // sanitized empty directory!
+ filename: 'bar',
+ suffix: '',
+ hash: '',
+ search: '',
+ host: '',
+ hostname: ''
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: true,
+ name: false,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'colon in path',
+ url: 'http://www.example.org:8080/hello:world',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'www.example.org',
+ port: '8080',
+ path: '/hello:world',
+ query: null,
+ fragment: null
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '8080',
+ path: '/hello:world',
+ query: '',
+ fragment: '',
+ resource: '/hello:world',
+ authority: 'www.example.org:8080',
+ origin: 'http://www.example.org:8080',
+ userinfo: '',
+ subdomain: 'www',
+ domain: 'example.org',
+ tld: 'org',
+ directory: '/',
+ filename: 'hello:world',
+ suffix: '',
+ hash: '', // location.hash style
+ search: '', // location.search style
+ host: 'www.example.org:8080',
+ hostname: 'www.example.org'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }, {
+ name: 'backslashes',
+ url: 'http://i.xss.com\\www.example.org/some/directory/file.html?query=string#fragment',
+ _url: 'http://i.xss.com/www.example.org/some/directory/file.html?query=string#fragment',
+ parts: {
+ protocol: 'http',
+ username: null,
+ password: null,
+ hostname: 'i.xss.com',
+ port: null,
+ path: '/www.example.org/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment'
+ },
+ accessors: {
+ protocol: 'http',
+ username: '',
+ password: '',
+ port: '',
+ path: '/www.example.org/some/directory/file.html',
+ query: 'query=string',
+ fragment: 'fragment',
+ resource: '/www.example.org/some/directory/file.html?query=string#fragment',
+ authority: 'i.xss.com',
+ origin: 'http://i.xss.com',
+ userinfo: '',
+ subdomain: 'i',
+ domain: 'xss.com',
+ tld: 'com',
+ directory: '/www.example.org/some/directory',
+ filename: 'file.html',
+ suffix: 'html',
+ hash: '#fragment',
+ search: '?query=string',
+ host: 'i.xss.com',
+ hostname: 'i.xss.com'
+ },
+ is: {
+ urn: false,
+ url: true,
+ relative: false,
+ name: true,
+ sld: false,
+ ip: false,
+ ip4: false,
+ ip6: false,
+ idn: false,
+ punycode: false
+ }
+ }
+];
+