Merge "profiler: Centralise output responsibility from ProfilerOutputText to Profiler"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki / mediawiki.Uri.test.js
1 ( function () {
2 QUnit.module( 'mediawiki.Uri', QUnit.newMwEnvironment( {
3 setup: function () {
4 this.mwUriOrg = mw.Uri;
5 mw.Uri = mw.UriRelative( 'http://example.org/w/index.php' );
6 },
7 teardown: function () {
8 mw.Uri = this.mwUriOrg;
9 delete this.mwUriOrg;
10 }
11 } ) );
12
13 [ true, false ].forEach( function ( strictMode ) {
14 QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', function ( assert ) {
15 var uriString, uri;
16 uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
17 uri = new mw.Uri( uriString, {
18 strictMode: strictMode
19 } );
20
21 assert.deepEqual(
22 {
23 protocol: uri.protocol,
24 host: uri.host,
25 port: uri.port,
26 path: uri.path,
27 query: uri.query,
28 fragment: uri.fragment
29 }, {
30 protocol: 'http',
31 host: 'www.ietf.org',
32 port: undefined,
33 path: '/rfc/rfc2396.txt',
34 query: {},
35 fragment: undefined
36 },
37 'basic object properties'
38 );
39
40 assert.deepEqual(
41 {
42 userInfo: uri.getUserInfo(),
43 authority: uri.getAuthority(),
44 hostPort: uri.getHostPort(),
45 queryString: uri.getQueryString(),
46 relativePath: uri.getRelativePath(),
47 toString: uri.toString()
48 },
49 {
50 userInfo: '',
51 authority: 'www.ietf.org',
52 hostPort: 'www.ietf.org',
53 queryString: '',
54 relativePath: '/rfc/rfc2396.txt',
55 toString: uriString
56 },
57 'construct composite components of URI on request'
58 );
59 } );
60 } );
61
62 QUnit.test( 'Constructor( String[, Object ] )', function ( assert ) {
63 var uri;
64
65 uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
66 overrideKeys: true
67 } );
68
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' );
72
73 uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
74 overrideKeys: false
75 } );
76
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' );
81
82 uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' );
83
84 assert.deepEqual(
85 {
86 protocol: uri.protocol,
87 user: uri.user,
88 password: uri.password,
89 host: uri.host,
90 port: uri.port,
91 path: uri.path,
92 query: uri.query,
93 fragment: uri.fragment
94 },
95 {
96 protocol: 'ftp',
97 user: 'usr',
98 password: 'pwd',
99 host: '192.0.2.16',
100 port: undefined,
101 path: '/',
102 query: {},
103 fragment: undefined
104 },
105 'Parse an ftp URI correctly with user and password'
106 );
107
108 uri = new mw.Uri( 'http://example.com/?foo[1]=b&foo[0]=a&foo[]=c' );
109
110 assert.deepEqual(
111 uri.query,
112 {
113 'foo[1]': 'b',
114 'foo[0]': 'a',
115 'foo[]': 'c'
116 },
117 'Array query parameters parsed as normal with arrayParams:false'
118 );
119
120 assert.throws(
121 function () {
122 return new mw.Uri( 'glaswegian penguins' );
123 },
124 function ( e ) {
125 return e.message === 'Bad constructor arguments';
126 },
127 'throw error on non-URI as argument to constructor'
128 );
129
130 assert.throws(
131 function () {
132 return new mw.Uri( 'example.com/bar/baz', {
133 strictMode: true
134 } );
135 },
136 function ( e ) {
137 return e.message === 'Bad constructor arguments';
138 },
139 'throw error on URI without protocol or // or leading / in strict mode'
140 );
141
142 uri = new mw.Uri( 'example.com/bar/baz', {
143 strictMode: false
144 } );
145 assert.strictEqual( uri.toString(), 'http://example.com/bar/baz', 'normalize URI without protocol or // in loose mode' );
146
147 uri = new mw.Uri( 'http://example.com/index.php?key=key&hasOwnProperty=hasOwnProperty&constructor=constructor&watch=watch' );
148 assert.deepEqual(
149 uri.query,
150 {
151 key: 'key',
152 constructor: 'constructor',
153 hasOwnProperty: 'hasOwnProperty',
154 watch: 'watch'
155 },
156 'Keys in query strings support names of Object prototypes (bug T114344)'
157 );
158 } );
159
160 QUnit.test( 'Constructor( Object )', function ( assert ) {
161 var uri = new mw.Uri( {
162 protocol: 'http',
163 host: 'www.foo.local',
164 path: '/this'
165 } );
166 assert.strictEqual( uri.toString(), 'http://www.foo.local/this', 'Basic properties' );
167
168 uri = new mw.Uri( {
169 protocol: 'http',
170 host: 'www.foo.local',
171 path: '/this',
172 query: { hi: 'there' },
173 fragment: 'blah'
174 } );
175 assert.strictEqual( uri.toString(), 'http://www.foo.local/this?hi=there#blah', 'More complex properties' );
176
177 assert.throws(
178 function () {
179 return new mw.Uri( {
180 protocol: 'http',
181 host: 'www.foo.local'
182 } );
183 },
184 function ( e ) {
185 return e.message === 'Bad constructor arguments';
186 },
187 'Construction failed when missing required properties'
188 );
189 } );
190
191 QUnit.test( 'Constructor( empty[, Object ] )', function ( assert ) {
192 var testuri, MyUri, uri;
193
194 testuri = 'http://example.org/w/index.php?a=1&a=2';
195 MyUri = mw.UriRelative( testuri );
196
197 uri = new MyUri();
198 assert.strictEqual( uri.toString(), testuri, 'no arguments' );
199
200 uri = new MyUri( undefined );
201 assert.strictEqual( uri.toString(), testuri, 'undefined' );
202
203 uri = new MyUri( null );
204 assert.strictEqual( uri.toString(), testuri, 'null' );
205
206 uri = new MyUri( '' );
207 assert.strictEqual( uri.toString(), testuri, 'empty string' );
208
209 uri = new MyUri( null, { overrideKeys: true } );
210 assert.deepEqual( uri.query, { a: '2' }, 'null, with options' );
211 } );
212
213 QUnit.test( 'Properties', function ( assert ) {
214 var uriBase, uri;
215
216 uriBase = new mw.Uri( 'http://en.wiki.local/w/api.php' );
217
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' );
223
224 uri = uriBase.clone();
225 uri.host = 'fr.wiki.local';
226 uri.port = '8080';
227 assert.strictEqual( uri.toString(), 'http://fr.wiki.local:8080/w/api.php', 'change host and port' );
228
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' );
232
233 delete uri.query.foo;
234 assert.strictEqual( uri.toString(), 'http://en.wiki.local/w/api.php', 'delete query arguments' );
235
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' );
239 uri.extend( {
240 foo: 'quux',
241 pif: 'paf'
242 } );
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' );
246 } );
247
248 QUnit.test( '.getQueryString()', function ( assert ) {
249 var uri = new mw.Uri( 'http://search.example.com/?q=uri' );
250
251 assert.deepEqual(
252 {
253 protocol: uri.protocol,
254 host: uri.host,
255 port: uri.port,
256 path: uri.path,
257 query: uri.query,
258 fragment: uri.fragment,
259 queryString: uri.getQueryString()
260 },
261 {
262 protocol: 'http',
263 host: 'search.example.com',
264 port: undefined,
265 path: '/',
266 query: { q: 'uri' },
267 fragment: undefined,
268 queryString: 'q=uri'
269 },
270 'basic object properties'
271 );
272
273 uri = new mw.Uri( 'https://example.com/mw/index.php?title=Sandbox/7&other=Sandbox/7&foo' );
274 assert.strictEqual(
275 uri.getQueryString(),
276 'title=Sandbox/7&other=Sandbox%2F7&foo',
277 'title parameter is escaped the wiki-way'
278 );
279
280 } );
281
282 QUnit.test( 'arrayParams', function ( assert ) {
283 var uri1, uri2, uri3, expectedQ, expectedS,
284 uriMissing, expectedMissingQ, expectedMissingS,
285 uriWeird, expectedWeirdQ, expectedWeirdS;
286
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';
292
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)' );
305
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';
310
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)' );
315
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';
319
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)' );
324 } );
325
326 QUnit.test( '.clone()', function ( assert ) {
327 var original, clone;
328
329 original = new mw.Uri( 'http://foo.example.org/index.php?one=1&two=2' );
330 clone = original.clone();
331
332 assert.deepEqual( clone, original, 'clone has equivalent properties' );
333 assert.strictEqual( original.toString(), clone.toString(), 'toString matches original' );
334
335 assert.notStrictEqual( clone, original, 'clone is a different object when compared by reference' );
336
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' );
340
341 clone.query.three = 3;
342
343 assert.deepEqual(
344 original.query,
345 { one: '1', two: '2' },
346 'Properties is deep cloned (T39708)'
347 );
348 } );
349
350 QUnit.test( '.toString() after query manipulation', function ( assert ) {
351 var uri;
352
353 uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
354 overrideKeys: true
355 } );
356
357 uri.query.n = [ 'x', 'y', 'z' ];
358
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' );
364
365 uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', {
366 overrideKeys: false
367 } );
368
369 // Change query values
370 uri.query.n = [ 'x', 'y', 'z' ];
371
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' );
377
378 // Remove query values
379 uri.query.m.splice( 0, 1 );
380 delete uri.query.n;
381
382 assert.strictEqual( uri.toString(), 'http://www.example.com/dir/?m=bar', 'deletion properties' );
383
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' );
387 } );
388
389 QUnit.test( 'Variable defaultUri', function ( assert ) {
390 var uri,
391 href = 'http://example.org/w/index.php#here',
392 UriClass = mw.UriRelative( function () {
393 return href;
394 } );
395
396 uri = new UriClass();
397 assert.deepEqual(
398 {
399 protocol: uri.protocol,
400 user: uri.user,
401 password: uri.password,
402 host: uri.host,
403 port: uri.port,
404 path: uri.path,
405 query: uri.query,
406 fragment: uri.fragment
407 },
408 {
409 protocol: 'http',
410 user: undefined,
411 password: undefined,
412 host: 'example.org',
413 port: undefined,
414 path: '/w/index.php',
415 query: {},
416 fragment: 'here'
417 },
418 'basic object properties'
419 );
420
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();
424 assert.deepEqual(
425 {
426 protocol: uri.protocol,
427 user: uri.user,
428 password: uri.password,
429 host: uri.host,
430 port: uri.port,
431 path: uri.path,
432 query: uri.query,
433 fragment: uri.fragment
434 },
435 {
436 protocol: 'https',
437 user: undefined,
438 password: undefined,
439 host: 'example.com',
440 port: undefined,
441 path: '/wiki/Foo',
442 query: { v: '2' },
443 fragment: undefined
444 },
445 'basic object properties'
446 );
447 } );
448
449 QUnit.test( 'Advanced URL', function ( assert ) {
450 var uri, queryString, relativePath;
451
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' );
453
454 assert.deepEqual(
455 {
456 protocol: uri.protocol,
457 user: uri.user,
458 password: uri.password,
459 host: uri.host,
460 port: uri.port,
461 path: uri.path,
462 query: uri.query,
463 fragment: uri.fragment
464 },
465 {
466 protocol: 'http',
467 user: 'auth',
468 password: undefined,
469 host: 'www.example.com',
470 port: '81',
471 path: '/dir/dir.2/index.htm',
472 query: { q1: '0', test1: null, test2: 'value (escaped)' },
473 fragment: 'café'
474 },
475 'basic object properties'
476 );
477
478 assert.strictEqual( uri.getUserInfo(), 'auth', 'user info' );
479
480 assert.strictEqual( uri.getAuthority(), 'auth@www.example.com:81', 'authority equal to auth@hostport' );
481
482 assert.strictEqual( uri.getHostPort(), 'www.example.com:81', 'hostport equal to host:port' );
483
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' );
489
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' );
494 } );
495
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@=@' );
498
499 assert.deepEqual(
500 {
501 protocol: uri.protocol,
502 user: uri.user,
503 password: uri.password,
504 host: uri.host,
505 port: uri.port,
506 path: uri.path,
507 query: uri.query,
508 fragment: uri.fragment,
509 queryString: uri.getQueryString()
510 },
511 {
512 protocol: 'http',
513 user: undefined,
514 password: undefined,
515 host: 'www.example.com',
516 port: undefined,
517 path: '/test@test',
518 query: { x: '@uri', 'y@': 'uri', 'z@': '@' },
519 fragment: undefined,
520 queryString: 'x=%40uri&y%40=uri&z%40=%40'
521 },
522 'basic object properties'
523 );
524 } );
525
526 QUnit.test( 'Handle protocol-relative URLs', function ( assert ) {
527 var UriRel, uri;
528
529 UriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' );
530
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' );
533
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' );
536
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' );
539
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' );
542
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' );
545 } );
546
547 QUnit.test( 'T37658', function ( assert ) {
548 var testProtocol, testServer, testPort, testPath, UriClass, uri, href;
549
550 testProtocol = 'https://';
551 testServer = 'foo.example.org';
552 testPort = '3004';
553 testPath = '/!1qy';
554
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' );
559
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' );
564 } );
565 }() );