2 QUnit
.module( 'mediawiki.Uri', QUnit
.newMwEnvironment( {
4 this.mwUriOrg
= mw
.Uri
;
5 mw
.Uri
= mw
.UriRelative( 'http://example.org/w/index.php' );
7 teardown: function () {
8 mw
.Uri
= this.mwUriOrg
;
13 [ true, false ].forEach( function ( strictMode
) {
14 QUnit
.test( 'Basic construction and properties (' + ( strictMode
? '' : 'non-' ) + 'strict mode)', function ( assert
) {
16 uriString
= 'http://www.ietf.org/rfc/rfc2396.txt';
17 uri
= new mw
.Uri( uriString
, {
18 strictMode
: strictMode
23 protocol
: uri
.protocol
,
28 fragment
: uri
.fragment
33 path
: '/rfc/rfc2396.txt',
37 'basic object properties'
42 userInfo
: uri
.getUserInfo(),
43 authority
: uri
.getAuthority(),
44 hostPort
: uri
.getHostPort(),
45 queryString
: uri
.getQueryString(),
46 relativePath
: uri
.getRelativePath(),
47 toString
: uri
.toString()
51 authority
: 'www.ietf.org',
52 hostPort
: 'www.ietf.org',
54 relativePath
: '/rfc/rfc2396.txt',
57 'construct composite components of URI on request'
62 QUnit
.test( 'Constructor( String[, Object ] )', function ( assert
) {
65 uri
= new mw
.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
69 // Strict comparison to assert that numerical values stay strings
70 assert
.strictEqual( uri
.query
.n
, '1', 'Simple parameter with overrideKeys:true' );
71 assert
.strictEqual( uri
.query
.m
, 'bar', 'Last key overrides earlier keys with overrideKeys:true' );
73 uri
= new mw
.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
77 assert
.strictEqual( uri
.query
.n
, '1', 'Simple parameter with overrideKeys:false' );
78 assert
.strictEqual( uri
.query
.m
[ 0 ], 'foo', 'Order of multi-value parameters with overrideKeys:true' );
79 assert
.strictEqual( uri
.query
.m
[ 1 ], 'bar', 'Order of multi-value parameters with overrideKeys:true' );
80 assert
.strictEqual( uri
.query
.m
.length
, 2, 'Number of mult-value field is correct' );
82 uri
= new mw
.Uri( 'ftp://usr:pwd@192.0.2.16/' );
86 protocol
: uri
.protocol
,
88 password
: uri
.password
,
93 fragment
: uri
.fragment
105 'Parse an ftp URI correctly with user and password'
108 uri
= new mw
.Uri( 'http://example.com/?foo[1]=b&foo[0]=a&foo[]=c' );
117 'Array query parameters parsed as normal with arrayParams:false'
122 return new mw
.Uri( 'glaswegian penguins' );
125 return e
.message
=== 'Bad constructor arguments';
127 'throw error on non-URI as argument to constructor'
132 return new mw
.Uri( 'example.com/bar/baz', {
137 return e
.message
=== 'Bad constructor arguments';
139 'throw error on URI without protocol or // or leading / in strict mode'
142 uri
= new mw
.Uri( 'example.com/bar/baz', {
145 assert
.strictEqual( uri
.toString(), 'http://example.com/bar/baz', 'normalize URI without protocol or // in loose mode' );
147 uri
= new mw
.Uri( 'http://example.com/index.php?key=key&hasOwnProperty=hasOwnProperty&constructor=constructor&watch=watch' );
152 constructor: 'constructor',
153 hasOwnProperty
: 'hasOwnProperty',
156 'Keys in query strings support names of Object prototypes (bug T114344)'
160 QUnit
.test( 'Constructor( Object )', function ( assert
) {
161 var uri
= new mw
.Uri( {
163 host
: 'www.foo.local',
166 assert
.strictEqual( uri
.toString(), 'http://www.foo.local/this', 'Basic properties' );
170 host
: 'www.foo.local',
172 query
: { hi
: 'there' },
175 assert
.strictEqual( uri
.toString(), 'http://www.foo.local/this?hi=there#blah', 'More complex properties' );
181 host
: 'www.foo.local'
185 return e
.message
=== 'Bad constructor arguments';
187 'Construction failed when missing required properties'
191 QUnit
.test( 'Constructor( empty[, Object ] )', function ( assert
) {
192 var testuri
, MyUri
, uri
;
194 testuri
= 'http://example.org/w/index.php?a=1&a=2';
195 MyUri
= mw
.UriRelative( testuri
);
198 assert
.strictEqual( uri
.toString(), testuri
, 'no arguments' );
200 uri
= new MyUri( undefined );
201 assert
.strictEqual( uri
.toString(), testuri
, 'undefined' );
203 uri
= new MyUri( null );
204 assert
.strictEqual( uri
.toString(), testuri
, 'null' );
206 uri
= new MyUri( '' );
207 assert
.strictEqual( uri
.toString(), testuri
, 'empty string' );
209 uri
= new MyUri( null, { overrideKeys
: true } );
210 assert
.deepEqual( uri
.query
, { a
: '2' }, 'null, with options' );
213 QUnit
.test( 'Properties', function ( assert
) {
216 uriBase
= new mw
.Uri( 'http://en.wiki.local/w/api.php' );
218 uri
= uriBase
.clone();
219 uri
.fragment
= 'frag';
220 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/w/api.php#frag', 'add a fragment' );
221 uri
.fragment
= 'café';
222 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/w/api.php#caf%C3%A9', 'fragment is url-encoded' );
224 uri
= uriBase
.clone();
225 uri
.host
= 'fr.wiki.local';
227 assert
.strictEqual( uri
.toString(), 'http://fr.wiki.local:8080/w/api.php', 'change host and port' );
229 uri
= uriBase
.clone();
230 uri
.query
.foo
= 'bar';
231 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'add query arguments' );
233 delete uri
.query
.foo
;
234 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/w/api.php', 'delete query arguments' );
236 uri
= uriBase
.clone();
237 uri
.query
.foo
= 'bar';
238 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'extend query arguments' );
243 assert
.strictEqual( uri
.toString().indexOf( 'foo=quux' ) !== -1, true, 'extend query arguments' );
244 assert
.strictEqual( uri
.toString().indexOf( 'foo=bar' ) !== -1, false, 'extend query arguments' );
245 assert
.strictEqual( uri
.toString().indexOf( 'pif=paf' ) !== -1, true, 'extend query arguments' );
248 QUnit
.test( '.getQueryString()', function ( assert
) {
249 var uri
= new mw
.Uri( 'http://search.example.com/?q=uri' );
253 protocol
: uri
.protocol
,
258 fragment
: uri
.fragment
,
259 queryString
: uri
.getQueryString()
263 host
: 'search.example.com',
270 'basic object properties'
273 uri
= new mw
.Uri( 'https://example.com/mw/index.php?title=Sandbox/7&other=Sandbox/7&foo' );
275 uri
.getQueryString(),
276 'title=Sandbox/7&other=Sandbox%2F7&foo',
277 'title parameter is escaped the wiki-way'
282 QUnit
.test( 'arrayParams', function ( assert
) {
283 var uri1
, uri2
, uri3
, expectedQ
, expectedS
,
284 uriMissing
, expectedMissingQ
, expectedMissingS
,
285 uriWeird
, expectedWeirdQ
, expectedWeirdS
;
287 uri1
= new mw
.Uri( 'http://example.com/?foo[]=a&foo[]=b&foo[]=c', { arrayParams
: true } );
288 uri2
= new mw
.Uri( 'http://example.com/?foo[0]=a&foo[1]=b&foo[2]=c', { arrayParams
: true } );
289 uri3
= new mw
.Uri( 'http://example.com/?foo[1]=b&foo[0]=a&foo[]=c', { arrayParams
: true } );
290 expectedQ
= { foo
: [ 'a', 'b', 'c' ] };
291 expectedS
= 'foo%5B0%5D=a&foo%5B1%5D=b&foo%5B2%5D=c';
293 assert
.deepEqual( uri1
.query
, expectedQ
,
294 'array query parameters are parsed (implicit indexes)' );
295 assert
.deepEqual( uri1
.getQueryString(), expectedS
,
296 'array query parameters are encoded (always with explicit indexes)' );
297 assert
.deepEqual( uri2
.query
, expectedQ
,
298 'array query parameters are parsed (explicit indexes)' );
299 assert
.deepEqual( uri2
.getQueryString(), expectedS
,
300 'array query parameters are encoded (always with explicit indexes)' );
301 assert
.deepEqual( uri3
.query
, expectedQ
,
302 'array query parameters are parsed (mixed indexes, out of order)' );
303 assert
.deepEqual( uri3
.getQueryString(), expectedS
,
304 'array query parameters are encoded (always with explicit indexes)' );
306 uriMissing
= new mw
.Uri( 'http://example.com/?foo[0]=a&foo[2]=c', { arrayParams
: true } );
307 // eslint-disable-next-line no-sparse-arrays
308 expectedMissingQ
= { foo
: [ 'a', , 'c' ] };
309 expectedMissingS
= 'foo%5B0%5D=a&foo%5B2%5D=c';
311 assert
.deepEqual( uriMissing
.query
, expectedMissingQ
,
312 'array query parameters are parsed (missing array item)' );
313 assert
.deepEqual( uriMissing
.getQueryString(), expectedMissingS
,
314 'array query parameters are encoded (missing array item)' );
316 uriWeird
= new mw
.Uri( 'http://example.com/?foo[0]=a&foo[1][1]=b&foo[x]=c', { arrayParams
: true } );
317 expectedWeirdQ
= { foo
: [ 'a' ], 'foo[1][1]': 'b', 'foo[x]': 'c' };
318 expectedWeirdS
= 'foo%5B0%5D=a&foo%5B1%5D%5B1%5D=b&foo%5Bx%5D=c';
320 assert
.deepEqual( uriWeird
.query
, expectedWeirdQ
,
321 'array query parameters are parsed (multi-dimensional or associative arrays are ignored)' );
322 assert
.deepEqual( uriWeird
.getQueryString(), expectedWeirdS
,
323 'array query parameters are encoded (multi-dimensional or associative arrays are ignored)' );
326 QUnit
.test( '.clone()', function ( assert
) {
329 original
= new mw
.Uri( 'http://foo.example.org/index.php?one=1&two=2' );
330 clone
= original
.clone();
332 assert
.deepEqual( clone
, original
, 'clone has equivalent properties' );
333 assert
.strictEqual( original
.toString(), clone
.toString(), 'toString matches original' );
335 assert
.notStrictEqual( clone
, original
, 'clone is a different object when compared by reference' );
337 clone
.host
= 'bar.example.org';
338 assert
.notEqual( original
.host
, clone
.host
, 'manipulating clone did not effect original' );
339 assert
.notEqual( original
.toString(), clone
.toString(), 'Stringified url no longer matches original' );
341 clone
.query
.three
= 3;
345 { one
: '1', two
: '2' },
346 'Properties is deep cloned (T39708)'
350 QUnit
.test( '.toString() after query manipulation', function ( assert
) {
353 uri
= new mw
.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
357 uri
.query
.n
= [ 'x', 'y', 'z' ];
359 // Verify parts and total length instead of entire string because order
360 // of iteration can vary.
361 assert
.strictEqual( uri
.toString().indexOf( 'm=bar' ) !== -1, true, 'toString preserves other values' );
362 assert
.strictEqual( uri
.toString().indexOf( 'n=x&n=y&n=z' ) !== -1, true, 'toString parameter includes all values of an array query parameter' );
363 assert
.strictEqual( uri
.toString().length
, 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length
, 'toString matches expected string' );
365 uri
= new mw
.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
369 // Change query values
370 uri
.query
.n
= [ 'x', 'y', 'z' ];
372 // Verify parts and total length instead of entire string because order
373 // of iteration can vary.
374 assert
.strictEqual( uri
.toString().indexOf( 'm=foo&m=bar' ) !== -1, true, 'toString preserves other values' );
375 assert
.strictEqual( uri
.toString().indexOf( 'n=x&n=y&n=z' ) !== -1, true, 'toString parameter includes all values of an array query parameter' );
376 assert
.strictEqual( uri
.toString().length
, 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length
, 'toString matches expected string' );
378 // Remove query values
379 uri
.query
.m
.splice( 0, 1 );
382 assert
.strictEqual( uri
.toString(), 'http://www.example.com/dir/?m=bar', 'deletion properties' );
384 // Remove more query values, leaving an empty array
385 uri
.query
.m
.splice( 0, 1 );
386 assert
.strictEqual( uri
.toString(), 'http://www.example.com/dir/', 'empty array value is ommitted' );
389 QUnit
.test( 'Variable defaultUri', function ( assert
) {
391 href
= 'http://example.org/w/index.php#here',
392 UriClass
= mw
.UriRelative( function () {
396 uri
= new UriClass();
399 protocol
: uri
.protocol
,
401 password
: uri
.password
,
406 fragment
: uri
.fragment
414 path
: '/w/index.php',
418 'basic object properties'
421 // Default URI may change, e.g. via history.replaceState, pushState or location.hash (T74334)
422 href
= 'https://example.com/wiki/Foo?v=2';
423 uri
= new UriClass();
426 protocol
: uri
.protocol
,
428 password
: uri
.password
,
433 fragment
: uri
.fragment
445 'basic object properties'
449 QUnit
.test( 'Advanced URL', function ( assert
) {
450 var uri
, queryString
, relativePath
;
452 uri
= new mw
.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#caf%C3%A9' );
456 protocol
: uri
.protocol
,
458 password
: uri
.password
,
463 fragment
: uri
.fragment
469 host
: 'www.example.com',
471 path
: '/dir/dir.2/index.htm',
472 query
: { q1
: '0', test1
: null, test2
: 'value (escaped)' },
475 'basic object properties'
478 assert
.strictEqual( uri
.getUserInfo(), 'auth', 'user info' );
480 assert
.strictEqual( uri
.getAuthority(), 'auth@www.example.com:81', 'authority equal to auth@hostport' );
482 assert
.strictEqual( uri
.getHostPort(), 'www.example.com:81', 'hostport equal to host:port' );
484 queryString
= uri
.getQueryString();
485 assert
.strictEqual( queryString
.indexOf( 'q1=0' ) !== -1, true, 'query param with numbers' );
486 assert
.strictEqual( queryString
.indexOf( 'test1' ) !== -1, true, 'query param with null value is included' );
487 assert
.strictEqual( queryString
.indexOf( 'test1=' ) !== -1, false, 'query param with null value does not generate equals sign' );
488 assert
.strictEqual( queryString
.indexOf( 'test2=value+%28escaped%29' ) !== -1, true, 'query param is url escaped' );
490 relativePath
= uri
.getRelativePath();
491 assert
.ok( relativePath
.indexOf( uri
.path
) >= 0, 'path in relative path' );
492 assert
.ok( relativePath
.indexOf( uri
.getQueryString() ) >= 0, 'query string in relative path' );
493 assert
.ok( relativePath
.indexOf( mw
.Uri
.encode( uri
.fragment
) ) >= 0, 'escaped fragment in relative path' );
496 QUnit
.test( 'Parse a uri with an @ symbol in the path and query', function ( assert
) {
497 var uri
= new mw
.Uri( 'http://www.example.com/test@test?x=@uri&y@=uri&z@=@' );
501 protocol
: uri
.protocol
,
503 password
: uri
.password
,
508 fragment
: uri
.fragment
,
509 queryString
: uri
.getQueryString()
515 host
: 'www.example.com',
518 query
: { x
: '@uri', 'y@': 'uri', 'z@': '@' },
520 queryString
: 'x=%40uri&y%40=uri&z%40=%40'
522 'basic object properties'
526 QUnit
.test( 'Handle protocol-relative URLs', function ( assert
) {
529 UriRel
= mw
.UriRelative( 'glork://en.wiki.local/foo.php' );
531 uri
= new UriRel( '//en.wiki.local/w/api.php' );
532 assert
.strictEqual( uri
.protocol
, 'glork', 'create protocol-relative URLs with same protocol as document' );
534 uri
= new UriRel( '/foo.com' );
535 assert
.strictEqual( uri
.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in loose mode' );
537 uri
= new UriRel( 'http:/foo.com' );
538 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in loose mode' );
540 uri
= new UriRel( '/foo.com', true );
541 assert
.strictEqual( uri
.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in strict mode' );
543 uri
= new UriRel( 'http:/foo.com', true );
544 assert
.strictEqual( uri
.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in strict mode' );
547 QUnit
.test( 'T37658', function ( assert
) {
548 var testProtocol
, testServer
, testPort
, testPath
, UriClass
, uri
, href
;
550 testProtocol
= 'https://';
551 testServer
= 'foo.example.org';
555 UriClass
= mw
.UriRelative( testProtocol
+ testServer
+ '/some/path/index.html' );
556 uri
= new UriClass( testPath
);
557 href
= uri
.toString();
558 assert
.strictEqual( href
, testProtocol
+ testServer
+ testPath
, 'Root-relative URL gets host & protocol supplied' );
560 UriClass
= mw
.UriRelative( testProtocol
+ testServer
+ ':' + testPort
+ '/some/path.php' );
561 uri
= new UriClass( testPath
);
562 href
= uri
.toString();
563 assert
.strictEqual( href
, testProtocol
+ testServer
+ ':' + testPort
+ testPath
, 'Root-relative URL gets host, protocol, and port supplied' );