58e56adf4b1d7927fcf3dc00af6cf4a8c9209a7c
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki / mediawiki.test.js
1 QUnit.module( 'mediawiki', QUnit.newMwEnvironment() );
2
3 QUnit.test( '-- Initial check', 8, function ( assert ) {
4 assert.ok( window.jQuery, 'jQuery defined' );
5 assert.ok( window.$, '$j defined' );
6 assert.ok( window.$j, '$j defined' );
7 assert.strictEqual( window.$, window.jQuery, '$ alias to jQuery' );
8 assert.strictEqual( window.$j, window.jQuery, '$j alias to jQuery' );
9
10 assert.ok( window.mediaWiki, 'mediaWiki defined' );
11 assert.ok( window.mw, 'mw defined' );
12 assert.strictEqual( window.mw, window.mediaWiki, 'mw alias to mediaWiki' );
13 });
14
15 QUnit.test( 'mw.Map', 17, function ( assert ) {
16 var arry, conf, funky, globalConf, nummy, someValues;
17
18 assert.ok( mw.Map, 'mw.Map defined' );
19
20 conf = new mw.Map();
21 // Dummy variables
22 funky = function () {};
23 arry = [];
24 nummy = 7;
25
26 // Tests for input validation
27 assert.strictEqual( conf.get( 'inexistantKey' ), null, 'Map.get returns null if selection was a string and the key was not found' );
28 assert.strictEqual( conf.set( 'myKey', 'myValue' ), true, 'Map.set returns boolean true if a value was set for a valid key string' );
29 assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' );
30 assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' );
31 assert.strictEqual( conf.set( nummy, 'Nummy' ), false, 'Map.set returns boolean false if key was invalid (Number)' );
32 assert.equal( conf.get( 'myKey' ), 'myValue', 'Map.get returns a single value value correctly' );
33 assert.strictEqual( conf.get( nummy ), null, 'Map.get ruturns null if selection was invalid (Number)' );
34 assert.strictEqual( conf.get( funky ), null, 'Map.get ruturns null if selection was invalid (Function)' );
35
36 // Multiple values at once
37 someValues = {
38 'foo': 'bar',
39 'lorem': 'ipsum',
40 'MediaWiki': true
41 };
42 assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' );
43 assert.deepEqual( conf.get( ['foo', 'lorem'] ), {
44 'foo': 'bar',
45 'lorem': 'ipsum'
46 }, 'Map.get returns multiple values correctly as an object' );
47
48 assert.deepEqual( conf.get( ['foo', 'notExist'] ), {
49 'foo': 'bar',
50 'notExist': null
51 }, 'Map.get return includes keys that were not found as null values' );
52
53 assert.strictEqual( conf.exists( 'foo' ), true, 'Map.exists returns boolean true if a key exists' );
54 assert.strictEqual( conf.exists( 'notExist' ), false, 'Map.exists returns boolean false if a key does not exists' );
55
56 // Interacting with globals and accessing the values object
57 assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' );
58
59 conf.set( 'globalMapChecker', 'Hi' );
60
61 assert.ok( false === 'globalMapChecker' in window, 'new mw.Map did not store its values in the global window object by default' );
62
63 globalConf = new mw.Map( true );
64 globalConf.set( 'anotherGlobalMapChecker', 'Hello' );
65
66 assert.ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' );
67
68 // Whitelist this global variable for QUnit's 'noglobal' mode
69 if ( QUnit.config.noglobals ) {
70 QUnit.config.pollution.push( 'anotherGlobalMapChecker' );
71 }
72 });
73
74 QUnit.test( 'mw.config', 1, function ( assert ) {
75 assert.ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
76 });
77
78 QUnit.test( 'mw.message & mw.messages', 20, function ( assert ) {
79 var goodbye, hello, pluralMessage;
80
81 assert.ok( mw.messages, 'messages defined' );
82 assert.ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' );
83 assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
84
85 hello = mw.message( 'hello' );
86
87 assert.equal( hello.format, 'plain', 'Message property "format" defaults to "plain"' );
88 assert.strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' );
89 assert.equal( hello.key, 'hello', 'Message property "key" (currect key)' );
90 assert.deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' );
91
92 // Todo
93 assert.ok( hello.params, 'Message prototype "params"' );
94
95 hello.format = 'plain';
96 assert.equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString returns the message as a string with the current "format"' );
97
98 assert.equal( hello.escaped(), 'Hello &lt;b&gt;awesome&lt;/b&gt; world', 'Message.escaped returns the escaped message' );
99 assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' );
100
101 hello.parse();
102 assert.equal( hello.format, 'parse', 'Message.parse correctly updated the "format" property' );
103
104 hello.plain();
105 assert.equal( hello.format, 'plain', 'Message.plain correctly updated the "format" property' );
106
107 assert.strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' );
108
109 goodbye = mw.message( 'goodbye' );
110 assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' );
111
112 assert.equal( goodbye.plain(), '<goodbye>', 'Message.toString returns plain <key> if format is "plain" and key does not exist' );
113 // bug 30684
114 assert.equal( goodbye.escaped(), '&lt;goodbye&gt;', 'Message.toString returns properly escaped &lt;key&gt; if format is "escaped" and key does not exist' );
115
116 assert.ok( mw.messages.set( 'pluraltestmsg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' );
117 pluralMessage = mw.message( 'pluraltestmsg' , 6 );
118 assert.equal( pluralMessage.plain(), 'There are 6 results', 'plural get resolved when format is plain' );
119 assert.equal( pluralMessage.parse(), 'There are 6 results', 'plural get resolved when format is parse' );
120
121 });
122
123 QUnit.test( 'mw.msg', 11, function ( assert ) {
124 assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
125 assert.equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' );
126 assert.equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' );
127
128 assert.ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ) );
129 assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' );
130 assert.equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' );
131 assert.equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' );
132
133 assert.ok( mw.messages.set('gender-plural-msg' , '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome' ) );
134 assert.equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' );
135 assert.equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' );
136 assert.equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' );
137
138 });
139
140 QUnit.asyncTest( 'mw.loader', 2, function ( assert ) {
141 var isAwesomeDone;
142
143 mw.loader.testCallback = function () {
144 QUnit.start();
145 assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined');
146 isAwesomeDone = true;
147 };
148
149 mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} );
150
151 mw.loader.using( 'test.callback', function () {
152
153 // /sample/awesome.js declares the "mw.loader.testCallback" function
154 // which contains a call to start() and ok()
155 assert.strictEqual( isAwesomeDone, true, "test.callback module should've caused isAwesomeDone to be true" );
156 delete mw.loader.testCallback;
157
158 }, function () {
159 QUnit.start();
160 assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
161 });
162 });
163
164 QUnit.asyncTest( 'mw.loader.implement', 5, function ( assert ) {
165 var isJsExecuted, $element, styleTestUrl;
166
167 styleTestUrl = QUnit.fixurl(
168 mw.config.get( 'wgScriptPath' )
169 + '/tests/qunit/data/styleTest.css.php?'
170 + $.param({
171 selector: '.mw-test-loaderimplement',
172 prop: 'float',
173 val: 'right'
174 })
175 );
176
177 mw.loader.implement(
178 'test.implement',
179 function () {
180 var styleTestTimeout, styleTestStart, styleTestSince;
181
182 assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' );
183 isJsExecuted = true;
184
185 assert.equal( mw.loader.getState( 'test.implement' ), 'ready', 'module state is "ready" while implement() is executing javascript' );
186
187 $element = $( '<div class="mw-test-loaderimplement">Foo bar</div>' ).appendTo( '#qunit-fixture' );
188
189 assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' );
190
191 // The @import test. This is, in a way, also an open bug for ResourceLoader
192 // ("execute js after styles are loaded"), but browsers don't offer a way to
193 // get a callback from when a stylesheet is loaded (that is, including any
194 // @import rules inside).
195 // To work around this, we'll have a little time loop to check if the styles
196 // apply.
197 // Note: This test originally used new Image() and onerror to get a callback
198 // when the url is loaded, but that is fragile since it doesn't monitor the
199 // same request as the css @import, and Safari 4 has issues with
200 // onerror/onload not being fired at all in weird cases like this.
201
202 styleTestTimeout = QUnit.config.testTimeout || 5000; // milliseconds
203
204 function isCssImportApplied() {
205 return $element.css( 'float' ) === 'right';
206 }
207
208 function styleTestLoop() {
209 styleTestSince = new Date().getTime() - styleTestStart;
210 // If it is passing or if we timed out, run the real test and stop the loop
211 if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
212 assert.equal( $element.css( 'float' ), 'right',
213 'CSS stylesheet via @import was applied (after ' + styleTestSince + 'ms) (bug 34669). ("float: right")'
214 );
215
216 assert.equal( $element.css( 'text-align' ),'center',
217 'CSS styles after the @import are working ("text-align: center")'
218 );
219
220 // Async done
221 QUnit.start();
222
223 return;
224 }
225 // Otherwise, keep polling
226 setTimeout( styleTestLoop, 100 );
227 }
228
229 // Start the loop
230 styleTestStart = new Date().getTime();
231 styleTestLoop();
232 },
233 {
234 "all": "@import url('"
235 + styleTestUrl
236 + "');\n"
237 + '.mw-test-loaderimplement { text-align: center; }'
238 },
239 {
240 "test-foobar": "Hello Foobar, $1!"
241 }
242 );
243
244 mw.loader.load( 'test.implement' );
245
246 });
247
248 QUnit.test( 'mw.loader erroneous indirect dependency', 3, function ( assert ) {
249 mw.loader.register( [
250 ['test.module1', '0'],
251 ['test.module2', '0', ['test.module1']],
252 ['test.module3', '0', ['test.module2']]
253 ] );
254 mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); }, {}, {} );
255 assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
256 assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
257 assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
258 } );
259
260 QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) {
261 mw.loader.register( [
262 ['test.module4', '0'],
263 ['test.module5', '0', ['test.module4']],
264 ['test.module6', '0', ['test.module5']]
265 ] );
266 mw.loader.implement( 'test.module4', function () {}, {}, {} );
267 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
268 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
269 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
270 mw.loader.implement( 'test.module6', function () {}, {}, {} );
271 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
272 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
273 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
274 mw.loader.implement( 'test.module5', function() {}, {}, {} );
275 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
276 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
277 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
278 } );
279
280 QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) {
281 mw.loader.register( [
282 ['test.module7', '0'],
283 ['test.module8', '0', ['test.module7']],
284 ['test.module9', '0', ['test.module8']]
285 ] );
286 mw.loader.implement( 'test.module8', function () {}, {}, {} );
287 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
288 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
289 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
290 mw.loader.state( 'test.module7', 'missing' );
291 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
292 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
293 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
294 mw.loader.implement( 'test.module9', function () {}, {}, {} );
295 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
296 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
297 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
298 mw.loader.using(
299 ['test.module7'],
300 function () {
301 assert.ok( false, "Success fired despite missing dependency" );
302 assert.ok( true , "QUnit expected() count dummy" );
303 },
304 function ( e, dependencies ) {
305 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
306 assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' );
307 }
308 );
309 mw.loader.using(
310 ['test.module9'],
311 function () {
312 assert.ok( false, "Success fired despite missing dependency" );
313 assert.ok( true , "QUnit expected() count dummy" );
314 },
315 function ( e, dependencies ) {
316 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
317 dependencies.sort();
318 assert.deepEqual(
319 dependencies,
320 ['test.module7', 'test.module8', 'test.module9'],
321 'Error callback called with all three modules as dependencies'
322 );
323 }
324 );
325 } );
326
327 QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) {
328 mw.loader.addSource(
329 'testloader',
330 {
331 loadScript: QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
332 }
333 );
334
335 mw.loader.register( [
336 // [module, version, dependencies, group, source]
337 ['testMissing', '1', [], null, 'testloader'],
338 ['testUsesMissing', '1', ['testMissing'], null, 'testloader'],
339 ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader']
340 ] );
341
342 function verifyModuleStates() {
343 assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
344 assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
345 assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
346 }
347
348 mw.loader.using( ['testUsesNestedMissing'],
349 function () {
350 assert.ok( false, 'Error handler should be invoked.' );
351 assert.ok( true ); // Dummy to reach QUnit expect()
352
353 verifyModuleStates();
354
355 QUnit.start();
356 },
357 function ( e, badmodules ) {
358 assert.ok( true, 'Error handler should be invoked.' );
359 // As soon as server spits out state('testMissing', 'missing');
360 // it will bubble up and trigger the error callback.
361 // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
362 assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' );
363
364 verifyModuleStates();
365
366 QUnit.start();
367 }
368 );
369 } );
370
371 QUnit.asyncTest( 'mw.loader bug29107' , 2, function ( assert ) {
372 // Message doesn't exist already
373 assert.ok( !mw.messages.exists( 'bug29107' ) );
374
375 mw.loader.implement( 'bug29107.messages-only', [], {}, {'bug29107': 'loaded'} );
376 mw.loader.using( 'bug29107.messages-only', function() {
377 QUnit.start();
378 assert.ok( mw.messages.exists( 'bug29107' ), 'Bug 29107: messages-only module should implement ok' );
379 }, function() {
380 QUnit.start();
381 assert.ok( false, 'Error callback fired while implementing "bug29107.messages-only" module' );
382 });
383 });
384
385 QUnit.asyncTest( 'mw.loader.bug30825', 2, function ( assert ) {
386 // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
387 // Test is for regressions!
388
389 // Forge an URL to the test callback script
390 var target = QUnit.fixurl(
391 mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
392 );
393
394 // Confirm that mw.loader.load() works with protocol-relative URLs
395 target = target.replace( /https?:/, '' );
396
397 assert.equal( target.substr( 0, 2 ), '//',
398 'URL must be relative to test relative URLs!'
399 );
400
401 // Async!
402 // The target calls QUnit.start
403 mw.loader.load( target );
404 });
405
406 QUnit.test( 'mw.html', 13, function ( assert ) {
407 assert.throws( function () {
408 mw.html.escape();
409 }, TypeError, 'html.escape throws a TypeError if argument given is not a string' );
410
411 assert.equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ),
412 '&lt;mw awesome=&quot;awesome&quot; value=&#039;test&#039; /&gt;', 'escape() escapes special characters to html entities' );
413
414 assert.equal( mw.html.element(),
415 '<undefined/>', 'element() always returns a valid html string (even without arguments)' );
416
417 assert.equal( mw.html.element( 'div' ), '<div/>', 'element() Plain DIV (simple)' );
418
419 assert.equal( mw.html.element( 'div', {}, '' ), '<div></div>', 'element() Basic DIV (simple)' );
420
421 assert.equal(
422 mw.html.element(
423 'div', {
424 id: 'foobar'
425 }
426 ),
427 '<div id="foobar"/>',
428 'html.element DIV (attribs)' );
429
430 assert.equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' );
431
432 assert.equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' );
433
434 // Example from https://www.mediawiki.org/wiki/ResourceLoader/Default_modules#mediaWiki.html
435 assert.equal(
436 mw.html.element(
437 'div',
438 {},
439 new mw.html.Raw(
440 mw.html.element( 'img', { src: '<' } )
441 )
442 ),
443 '<div><img src="&lt;"/></div>',
444 'Raw inclusion of another element'
445 );
446
447 assert.equal(
448 mw.html.element(
449 'option', {
450 selected: true
451 }, 'Foo'
452 ),
453 '<option selected="selected">Foo</option>',
454 'Attributes may have boolean values. True copies the attribute name to the value.'
455 );
456
457 assert.equal(
458 mw.html.element(
459 'option', {
460 value: 'foo',
461 selected: false
462 }, 'Foo'
463 ),
464 '<option value="foo">Foo</option>',
465 'Attributes may have boolean values. False keeps the attribute from output.'
466 );
467
468 assert.equal( mw.html.element( 'div',
469 null, 'a' ),
470 '<div>a</div>',
471 'html.element DIV (content)' );
472
473 assert.equal( mw.html.element( 'a',
474 { href: 'http://mediawiki.org/w/index.php?title=RL&action=history' }, 'a' ),
475 '<a href="http://mediawiki.org/w/index.php?title=RL&amp;action=history">a</a>',
476 'html.element DIV (attribs + content)' );
477
478 });