diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-10-10 03:47:49 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-10-10 03:47:49 +0200 |
commit | 21e6b15991212e21a0bd9928890e8e8518f367e8 (patch) | |
tree | 3859320d916ff6c2e2fbe2f022375827fa28997c /thirdparty/URI.js/test | |
parent | 02fa518542328183f35ae04d389b61226f4c4e30 (diff) | |
parent | d5194154335d6cb30edca9b648083069faf9778c (diff) | |
download | wallet-core-21e6b15991212e21a0bd9928890e8e8518f367e8.tar.xz |
Merge commit 'd5194154335d6cb30edca9b648083069faf9778c' as 'thirdparty/URI.js'
Diffstat (limited to 'thirdparty/URI.js/test')
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/> "; + } + }, + + // 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 "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); +} + +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 ? " " : " "; + }, + // 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, " " ); + } + 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 ? "<" : "<", + close = QUnit.jsDump.HTML ? ">" : ">", + 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 + } + } +]; + |