Merge "Use and forward returntoquery parameter in Special:ChangePassword"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki / mediawiki.test.js
1 ( function ( mw, $ ) {
2
3 var specialCharactersPageName;
4
5
6 // Since QUnitTestResources.php loads both mediawiki and mediawiki.jqueryMsg as
7 // dependencies, this only tests the monkey-patched behavior with the two of them combined.
8
9 // See mediawiki.jqueryMsg.test.js for unit tests for jqueryMsg-specific functionality.
10
11 QUnit.module( 'mediawiki', QUnit.newMwEnvironment( {
12 setup: function () {
13 // Messages used in multiple tests
14 mw.messages.set( {
15 'other-message': 'Other Message',
16 'mediawiki-test-pagetriage-del-talk-page-notify-summary': 'Notifying author of deletion nomination for [[$1]]',
17 'gender-plural-msg': '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome',
18 'grammar-msg': 'Przeszukaj {{GRAMMAR:grammar_case_foo|{{SITENAME}}}}',
19 'formatnum-msg': '{{formatnum:$1}}',
20 'int-msg': 'Some {{int:other-message}}'
21 } );
22
23 // For formatnum tests
24 mw.config.set( 'wgUserLanguage', 'en' );
25
26 specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?';
27 }
28 } ) );
29
30 QUnit.test( 'Initial check', 8, function ( assert ) {
31 assert.ok( window.jQuery, 'jQuery defined' );
32 assert.ok( window.$, '$j defined' );
33 assert.ok( window.$j, '$j defined' );
34 assert.strictEqual( window.$, window.jQuery, '$ alias to jQuery' );
35 assert.strictEqual( window.$j, window.jQuery, '$j alias to jQuery' );
36
37 assert.ok( window.mediaWiki, 'mediaWiki defined' );
38 assert.ok( window.mw, 'mw defined' );
39 assert.strictEqual( window.mw, window.mediaWiki, 'mw alias to mediaWiki' );
40 });
41
42 QUnit.test( 'mw.Map', 17, function ( assert ) {
43 var arry, conf, funky, globalConf, nummy, someValues;
44
45 assert.ok( mw.Map, 'mw.Map defined' );
46
47 conf = new mw.Map();
48 // Dummy variables
49 funky = function () {};
50 arry = [];
51 nummy = 7;
52
53 // Tests for input validation
54 assert.strictEqual( conf.get( 'inexistantKey' ), null, 'Map.get returns null if selection was a string and the key was not found' );
55 assert.strictEqual( conf.set( 'myKey', 'myValue' ), true, 'Map.set returns boolean true if a value was set for a valid key string' );
56 assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' );
57 assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' );
58 assert.strictEqual( conf.set( nummy, 'Nummy' ), false, 'Map.set returns boolean false if key was invalid (Number)' );
59 assert.equal( conf.get( 'myKey' ), 'myValue', 'Map.get returns a single value value correctly' );
60 assert.strictEqual( conf.get( nummy ), null, 'Map.get ruturns null if selection was invalid (Number)' );
61 assert.strictEqual( conf.get( funky ), null, 'Map.get ruturns null if selection was invalid (Function)' );
62
63 // Multiple values at once
64 someValues = {
65 'foo': 'bar',
66 'lorem': 'ipsum',
67 'MediaWiki': true
68 };
69 assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' );
70 assert.deepEqual( conf.get( ['foo', 'lorem'] ), {
71 'foo': 'bar',
72 'lorem': 'ipsum'
73 }, 'Map.get returns multiple values correctly as an object' );
74
75 assert.deepEqual( conf.get( ['foo', 'notExist'] ), {
76 'foo': 'bar',
77 'notExist': null
78 }, 'Map.get return includes keys that were not found as null values' );
79
80 assert.strictEqual( conf.exists( 'foo' ), true, 'Map.exists returns boolean true if a key exists' );
81 assert.strictEqual( conf.exists( 'notExist' ), false, 'Map.exists returns boolean false if a key does not exists' );
82
83 // Interacting with globals and accessing the values object
84 assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' );
85
86 conf.set( 'globalMapChecker', 'Hi' );
87
88 assert.ok( false === 'globalMapChecker' in window, 'new mw.Map did not store its values in the global window object by default' );
89
90 globalConf = new mw.Map( true );
91 globalConf.set( 'anotherGlobalMapChecker', 'Hello' );
92
93 assert.ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' );
94
95 // Whitelist this global variable for QUnit's 'noglobal' mode
96 if ( QUnit.config.noglobals ) {
97 QUnit.config.pollution.push( 'anotherGlobalMapChecker' );
98 }
99 });
100
101 QUnit.test( 'mw.config', 1, function ( assert ) {
102 assert.ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
103 });
104
105 QUnit.test( 'mw.message & mw.messages', 54, function ( assert ) {
106 var goodbye, hello;
107
108 // Convenience method for asserting the same result for multiple formats
109 function assertMultipleFormats( messageArguments, formats, expectedResult, assertMessage) {
110 var len = formats.length, format, i;
111 for ( i = 0; i < len; i++ ) {
112 format = formats[i];
113 assert.equal( mw.message.apply( null, messageArguments )[format](), expectedResult, assertMessage + ' when format is ' + format);
114 }
115 }
116
117 assert.ok( mw.messages, 'messages defined' );
118 assert.ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' );
119 assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
120
121 hello = mw.message( 'hello' );
122
123 // https://bugzilla.wikimedia.org/show_bug.cgi?id=44459
124 assert.equal( hello.format, 'text', 'Message property "format" defaults to "text"' );
125
126 assert.strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' );
127 assert.equal( hello.key, 'hello', 'Message property "key" (currect key)' );
128 assert.deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' );
129
130 // Todo
131 assert.ok( hello.params, 'Message prototype "params"' );
132
133 hello.format = 'plain';
134 assert.equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString returns the message as a string with the current "format"' );
135
136 assert.equal( hello.escaped(), 'Hello &lt;b&gt;awesome&lt;/b&gt; world', 'Message.escaped returns the escaped message' );
137 assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' );
138
139 assert.ok( mw.messages.set( 'escaped-with-curly-brace', '"{{SITENAME}}" is the home of {{int:other-message}}' ) );
140 assert.equal( mw.message( 'escaped-with-curly-brace' ).escaped(), mw.html.escape( '"' + mw.config.get( 'wgSiteName') + '" is the home of Other Message' ), 'Escaped format works correctly for curly brace message' );
141
142 assert.ok( mw.messages.set( 'escaped-with-square-brackets', 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]' ) );
143 assert.equal( mw.message( 'escaped-with-square-brackets' ).escaped(), 'Visit the [[Project:Community portal|community portal]] &amp; [[Project:Help desk|help desk]]', 'Escaped format works correctly for square bracket message' );
144
145 hello.parse();
146 assert.equal( hello.format, 'parse', 'Message.parse correctly updated the "format" property' );
147
148 hello.plain();
149 assert.equal( hello.format, 'plain', 'Message.plain correctly updated the "format" property' );
150
151 hello.text();
152 assert.equal( hello.format, 'text', 'Message.text correctly updated the "format" property' );
153
154 assert.strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' );
155
156 goodbye = mw.message( 'goodbye' );
157 assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' );
158
159 assertMultipleFormats( ['goodbye'], ['plain', 'text'], '<goodbye>', 'Message.toString returns <key> if key does not exist' );
160 // bug 30684
161 assertMultipleFormats( ['goodbye'], ['parse', 'escaped'], '&lt;goodbye&gt;', 'Message.toString returns properly escaped &lt;key&gt; if key does not exist' );
162
163 assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' );
164 assertMultipleFormats( ['plural-test-msg', 6], ['text', 'parse', 'escaped'], 'There are 6 results', 'plural get resolved' );
165 assert.equal( mw.message( 'plural-test-msg', 6 ).plain(), 'There {{PLURAL:6|is|are}} 6 {{PLURAL:6|result|results}}', 'Parameter is substituted but plural is not resolved in plain' );
166
167 assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' );
168
169 assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' );
170
171 assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' );
172
173
174 assert.ok( mw.messages.set( 'mediawiki-test-categorytree-collapse-bullet', '[<b>−</b>]' ), 'mw.messages.set: Register' );
175 assert.equal( mw.message( 'mediawiki-test-categorytree-collapse-bullet' ).plain(), mw.messages.get( 'mediawiki-test-categorytree-collapse-bullet' ), 'Single square brackets unchanged in plain mode' );
176
177 assert.ok( mw.messages.set( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result', '<a href=\'#\' title=\'{{#special:mypage}}\'>Username</a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk</a>)' ) );
178 assert.equal( mw.message( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ).plain(), mw.messages.get( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ), 'HTML message with curly braces is not changed in plain mode' );
179
180 assertMultipleFormats( ['gender-plural-msg', 'male', 1], ['text', 'parse', 'escaped'], 'he is awesome', 'Gender and plural are resolved' );
181 assert.equal( mw.message( 'gender-plural-msg', 'male', 1 ).plain(), '{{GENDER:male|he|she|they}} {{PLURAL:1|is|are}} awesome', 'Parameters are substituted, but gender and plural are not resolved in plain mode' );
182
183 assert.equal( mw.message( 'grammar-msg' ).plain(), mw.messages.get( 'grammar-msg' ), 'Grammar is not resolved in plain mode' );
184 assertMultipleFormats( ['grammar-msg'], ['text', 'parse'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar is resolved' );
185 assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + mw.html.escape( mw.config.get( 'wgSiteName' ) ), 'Grammar is resolved in escaped mode' );
186
187 assertMultipleFormats( ['formatnum-msg', '987654321.654321'], ['text', 'parse', 'escaped'], '987654321.654321', 'formatnum is resolved' );
188 assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' );
189
190 assertMultipleFormats( ['int-msg'], ['text', 'parse', 'escaped'], 'Some Other Message', 'int is resolved' );
191 assert.equal( mw.message( 'int-msg' ).plain(), mw.messages.get( 'int-msg' ), 'int is not resolved in plain mode' );
192 });
193
194 QUnit.test( 'mw.msg', 14, function ( assert ) {
195 assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
196 assert.equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' );
197 assert.equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' );
198
199 assert.ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ) );
200 assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' );
201 assert.equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' );
202 assert.equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' );
203
204 assert.equal( mw.msg( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ), 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets in mw.msg one parameter' );
205
206 assert.equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' );
207 assert.equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' );
208 assert.equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' );
209
210 assert.equal( mw.msg( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName'), 'Grammar is resolved' );
211
212 assert.equal( mw.msg( 'formatnum-msg', '987654321.654321' ), '987654321.654321', 'formatnum is resolved' );
213
214 assert.equal( mw.msg( 'int-msg' ), 'Some Other Message', 'int is resolved' );
215 });
216
217 /**
218 * The sync style load test (for @import). This is, in a way, also an open bug for
219 * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
220 * way to get a callback from when a stylesheet is loaded (that is, including any
221 * @import rules inside). To work around this, we'll have a little time loop to check
222 * if the styles apply.
223 * Note: This test originally used new Image() and onerror to get a callback
224 * when the url is loaded, but that is fragile since it doesn't monitor the
225 * same request as the css @import, and Safari 4 has issues with
226 * onerror/onload not being fired at all in weird cases like this.
227 */
228 function assertStyleAsync( assert, $element, prop, val, fn ) {
229 var styleTestStart,
230 el = $element.get( 0 ),
231 styleTestTimeout = ( QUnit.config.testTimeout - 200 ) || 5000;
232
233 function isCssImportApplied() {
234 // Trigger reflow, repaint, redraw, whatever (cross-browser)
235 var x = $element.css( 'height' );
236 x = el.innerHTML;
237 el.className = el.className;
238 x = document.documentElement.clientHeight;
239
240 return $element.css( prop ) === val;
241 }
242
243 function styleTestLoop() {
244 var styleTestSince = new Date().getTime() - styleTestStart;
245 // If it is passing or if we timed out, run the real test and stop the loop
246 if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
247 assert.equal( $element.css( prop ), val,
248 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
249 );
250
251 if ( fn ) {
252 fn();
253 }
254
255 return;
256 }
257 // Otherwise, keep polling
258 setTimeout( styleTestLoop, 150 );
259 }
260
261 // Start the loop
262 styleTestStart = new Date().getTime();
263 styleTestLoop();
264 }
265
266 function urlStyleTest( selector, prop, val ) {
267 return QUnit.fixurl(
268 mw.config.get( 'wgScriptPath' ) +
269 '/tests/qunit/data/styleTest.css.php?' +
270 $.param( {
271 selector: selector,
272 prop: prop,
273 val: val
274 } )
275 );
276 }
277
278 QUnit.asyncTest( 'mw.loader', 2, function ( assert ) {
279 var isAwesomeDone;
280
281 mw.loader.testCallback = function () {
282 QUnit.start();
283 assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined');
284 isAwesomeDone = true;
285 };
286
287 mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} );
288
289 mw.loader.using( 'test.callback', function () {
290
291 // /sample/awesome.js declares the "mw.loader.testCallback" function
292 // which contains a call to start() and ok()
293 assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
294 delete mw.loader.testCallback;
295
296 }, function () {
297 QUnit.start();
298 assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
299 });
300 });
301
302 QUnit.test( 'mw.loader.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
303 var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
304
305 assert.notEqual(
306 $element.css( 'float' ),
307 'right',
308 'style is clear'
309 );
310
311 mw.loader.implement(
312 'test.implement.a',
313 function () {
314 assert.equal(
315 $element.css( 'float' ),
316 'right',
317 'style is applied'
318 );
319 },
320 {
321 'all': '.mw-test-implement-a { float: right; }'
322 },
323 {}
324 );
325
326 mw.loader.load([
327 'test.implement.a'
328 ]);
329 } );
330
331 QUnit.asyncTest( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
332 var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
333 $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
334 $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' );
335
336 assert.notEqual(
337 $element1.css( 'text-align' ),
338 'center',
339 'style is clear'
340 );
341 assert.notEqual(
342 $element2.css( 'float' ),
343 'left',
344 'style is clear'
345 );
346 assert.notEqual(
347 $element3.css( 'text-align' ),
348 'right',
349 'style is clear'
350 );
351
352 mw.loader.implement(
353 'test.implement.b',
354 function () {
355 // Note: QUnit.start() must only be called when the entire test is
356 // complete. So, make sure that we don't start until *both*
357 // assertStyleAsync calls have completed.
358 var pending = 2;
359 assertStyleAsync( assert, $element2, 'float', 'left', function () {
360 assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
361
362 pending--;
363 if ( pending === 0 ) {
364 QUnit.start();
365 }
366 } );
367 assertStyleAsync( assert, $element3, 'float', 'right', function () {
368 assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
369
370 pending--;
371 if ( pending === 0 ) {
372 QUnit.start();
373 }
374 } );
375 },
376 {
377 'url': {
378 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )],
379 'screen': [
380 // bug 40834: Make sure it actually works with more than 1 stylesheet reference
381 urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
382 urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
383 ]
384 }
385 },
386 {}
387 );
388
389 mw.loader.load([
390 'test.implement.b'
391 ]);
392 } );
393
394 // Backwards compatibility
395 QUnit.test( 'mw.loader.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
396 var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
397
398 assert.notEqual(
399 $element.css( 'float' ),
400 'right',
401 'style is clear'
402 );
403
404 mw.loader.implement(
405 'test.implement.c',
406 function () {
407 assert.equal(
408 $element.css( 'float' ),
409 'right',
410 'style is applied'
411 );
412 },
413 {
414 'all': '.mw-test-implement-c { float: right; }'
415 },
416 {}
417 );
418
419 mw.loader.load([
420 'test.implement.c'
421 ]);
422 } );
423
424 // Backwards compatibility
425 QUnit.asyncTest( 'mw.loader.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
426 var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
427 $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' );
428
429 assert.notEqual(
430 $element.css( 'float' ),
431 'right',
432 'style is clear'
433 );
434 assert.notEqual(
435 $element2.css( 'text-align' ),
436 'center',
437 'style is clear'
438 );
439
440 mw.loader.implement(
441 'test.implement.d',
442 function () {
443 assertStyleAsync( assert, $element, 'float', 'right', function () {
444
445 assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
446
447 QUnit.start();
448 } );
449 },
450 {
451 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )],
452 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )]
453 },
454 {}
455 );
456
457 mw.loader.load([
458 'test.implement.d'
459 ]);
460 } );
461
462 // @import (bug 31676)
463 QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) {
464 var isJsExecuted, $element;
465
466 mw.loader.implement(
467 'test.implement.import',
468 function () {
469 assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' );
470 isJsExecuted = true;
471
472 assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' );
473
474 $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
475
476 assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' );
477
478 assertStyleAsync( assert, $element, 'float', 'right', function () {
479 assert.equal( $element.css( 'text-align' ),'center',
480 'CSS styles after the @import rule are working'
481 );
482
483 QUnit.start();
484 } );
485 },
486 {
487 'css': [
488 '@import url(\''
489 + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
490 + '\');\n'
491 + '.mw-test-implement-import { text-align: center; }'
492 ]
493 },
494 {
495 'test-foobar': 'Hello Foobar, $1!'
496 }
497 );
498
499 mw.loader.load( 'test.implement' );
500
501 });
502
503 QUnit.asyncTest( 'mw.loader.implement( only messages )' , 2, function ( assert ) {
504 assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
505
506 mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } );
507 mw.loader.using( 'test.implement.msgs', function() {
508 QUnit.start();
509 assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
510 }, function() {
511 QUnit.start();
512 assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
513 });
514 });
515
516 QUnit.test( 'mw.loader erroneous indirect dependency', 3, function ( assert ) {
517 mw.loader.register( [
518 ['test.module1', '0'],
519 ['test.module2', '0', ['test.module1']],
520 ['test.module3', '0', ['test.module2']]
521 ] );
522 mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); }, {}, {} );
523 assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
524 assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
525 assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
526 } );
527
528 QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) {
529 mw.loader.register( [
530 ['test.module4', '0'],
531 ['test.module5', '0', ['test.module4']],
532 ['test.module6', '0', ['test.module5']]
533 ] );
534 mw.loader.implement( 'test.module4', function () {}, {}, {} );
535 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
536 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
537 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
538 mw.loader.implement( 'test.module6', function () {}, {}, {} );
539 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
540 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
541 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
542 mw.loader.implement( 'test.module5', function() {}, {}, {} );
543 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
544 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
545 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
546 } );
547
548 QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) {
549 mw.loader.register( [
550 ['test.module7', '0'],
551 ['test.module8', '0', ['test.module7']],
552 ['test.module9', '0', ['test.module8']]
553 ] );
554 mw.loader.implement( 'test.module8', function () {}, {}, {} );
555 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
556 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
557 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
558 mw.loader.state( 'test.module7', 'missing' );
559 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
560 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
561 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
562 mw.loader.implement( 'test.module9', function () {}, {}, {} );
563 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
564 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
565 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
566 mw.loader.using(
567 ['test.module7'],
568 function () {
569 assert.ok( false, 'Success fired despite missing dependency' );
570 assert.ok( true , 'QUnit expected() count dummy' );
571 },
572 function ( e, dependencies ) {
573 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
574 assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' );
575 }
576 );
577 mw.loader.using(
578 ['test.module9'],
579 function () {
580 assert.ok( false, 'Success fired despite missing dependency' );
581 assert.ok( true , 'QUnit expected() count dummy' );
582 },
583 function ( e, dependencies ) {
584 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
585 dependencies.sort();
586 assert.deepEqual(
587 dependencies,
588 ['test.module7', 'test.module8', 'test.module9'],
589 'Error callback called with all three modules as dependencies'
590 );
591 }
592 );
593 } );
594
595 QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) {
596 mw.loader.addSource(
597 'testloader',
598 {
599 loadScript: QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
600 }
601 );
602
603 mw.loader.register( [
604 // [module, version, dependencies, group, source]
605 ['testMissing', '1', [], null, 'testloader'],
606 ['testUsesMissing', '1', ['testMissing'], null, 'testloader'],
607 ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader']
608 ] );
609
610 function verifyModuleStates() {
611 assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
612 assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
613 assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
614 }
615
616 mw.loader.using( ['testUsesNestedMissing'],
617 function () {
618 assert.ok( false, 'Error handler should be invoked.' );
619 assert.ok( true ); // Dummy to reach QUnit expect()
620
621 verifyModuleStates();
622
623 QUnit.start();
624 },
625 function ( e, badmodules ) {
626 assert.ok( true, 'Error handler should be invoked.' );
627 // As soon as server spits out state('testMissing', 'missing');
628 // it will bubble up and trigger the error callback.
629 // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
630 assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' );
631
632 verifyModuleStates();
633
634 QUnit.start();
635 }
636 );
637 } );
638
639 QUnit.asyncTest( 'mw.loader( "//protocol-relative" ) (bug 30825)', 2, function ( assert ) {
640 // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
641 // Test is for regressions!
642
643 // Forge an URL to the test callback script
644 var target = QUnit.fixurl(
645 mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
646 );
647
648 // Confirm that mw.loader.load() works with protocol-relative URLs
649 target = target.replace( /https?:/, '' );
650
651 assert.equal( target.substr( 0, 2 ), '//',
652 'URL must be relative to test relative URLs!'
653 );
654
655 // Async!
656 // The target calls QUnit.start
657 mw.loader.load( target );
658 });
659
660 QUnit.test( 'mw.html', 13, function ( assert ) {
661 assert.throws( function () {
662 mw.html.escape();
663 }, TypeError, 'html.escape throws a TypeError if argument given is not a string' );
664
665 assert.equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ),
666 '&lt;mw awesome=&quot;awesome&quot; value=&#039;test&#039; /&gt;', 'escape() escapes special characters to html entities' );
667
668 assert.equal( mw.html.element(),
669 '<undefined/>', 'element() always returns a valid html string (even without arguments)' );
670
671 assert.equal( mw.html.element( 'div' ), '<div/>', 'element() Plain DIV (simple)' );
672
673 assert.equal( mw.html.element( 'div', {}, '' ), '<div></div>', 'element() Basic DIV (simple)' );
674
675 assert.equal(
676 mw.html.element(
677 'div', {
678 id: 'foobar'
679 }
680 ),
681 '<div id="foobar"/>',
682 'html.element DIV (attribs)' );
683
684 assert.equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' );
685
686 assert.equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' );
687
688 // Example from https://www.mediawiki.org/wiki/ResourceLoader/Default_modules#mediaWiki.html
689 assert.equal(
690 mw.html.element(
691 'div',
692 {},
693 new mw.html.Raw(
694 mw.html.element( 'img', { src: '<' } )
695 )
696 ),
697 '<div><img src="&lt;"/></div>',
698 'Raw inclusion of another element'
699 );
700
701 assert.equal(
702 mw.html.element(
703 'option', {
704 selected: true
705 }, 'Foo'
706 ),
707 '<option selected="selected">Foo</option>',
708 'Attributes may have boolean values. True copies the attribute name to the value.'
709 );
710
711 assert.equal(
712 mw.html.element(
713 'option', {
714 value: 'foo',
715 selected: false
716 }, 'Foo'
717 ),
718 '<option value="foo">Foo</option>',
719 'Attributes may have boolean values. False keeps the attribute from output.'
720 );
721
722 assert.equal( mw.html.element( 'div',
723 null, 'a' ),
724 '<div>a</div>',
725 'html.element DIV (content)' );
726
727 assert.equal( mw.html.element( 'a',
728 { href: 'http://mediawiki.org/w/index.php?title=RL&action=history' }, 'a' ),
729 '<a href="http://mediawiki.org/w/index.php?title=RL&amp;action=history">a</a>',
730 'html.element DIV (attribs + content)' );
731
732 });
733
734 }( mediaWiki, jQuery ) );