Merge "Fix StatusValue::setResult phpdoc typo"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki / mediawiki.loader.test.js
1 ( function ( mw, $ ) {
2 QUnit.module( 'mediawiki (mw.loader)', QUnit.newMwEnvironment( {
3 setup: function () {
4 mw.loader.store.enabled = false;
5 },
6 teardown: function () {
7 mw.loader.store.enabled = false;
8 }
9 } ) );
10
11 mw.loader.addSource(
12 'testloader',
13 QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
14 );
15
16 /**
17 * The sync style load test (for @import). This is, in a way, also an open bug for
18 * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
19 * way to get a callback from when a stylesheet is loaded (that is, including any
20 * `@import` rules inside). To work around this, we'll have a little time loop to check
21 * if the styles apply.
22 *
23 * Note: This test originally used new Image() and onerror to get a callback
24 * when the url is loaded, but that is fragile since it doesn't monitor the
25 * same request as the css @import, and Safari 4 has issues with
26 * onerror/onload not being fired at all in weird cases like this.
27 */
28 function assertStyleAsync( assert, $element, prop, val, fn ) {
29 var styleTestStart,
30 el = $element.get( 0 ),
31 styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200;
32
33 function isCssImportApplied() {
34 // Trigger reflow, repaint, redraw, whatever (cross-browser)
35 $element.css( 'height' );
36 el.innerHTML;
37 el.className = el.className;
38 document.documentElement.clientHeight;
39
40 return $element.css( prop ) === val;
41 }
42
43 function styleTestLoop() {
44 var styleTestSince = new Date().getTime() - styleTestStart;
45 // If it is passing or if we timed out, run the real test and stop the loop
46 if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
47 assert.equal( $element.css( prop ), val,
48 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
49 );
50
51 if ( fn ) {
52 fn();
53 }
54
55 return;
56 }
57 // Otherwise, keep polling
58 setTimeout( styleTestLoop );
59 }
60
61 // Start the loop
62 styleTestStart = new Date().getTime();
63 styleTestLoop();
64 }
65
66 function urlStyleTest( selector, prop, val ) {
67 return QUnit.fixurl(
68 mw.config.get( 'wgScriptPath' ) +
69 '/tests/qunit/data/styleTest.css.php?' +
70 $.param( {
71 selector: selector,
72 prop: prop,
73 val: val
74 } )
75 );
76 }
77
78 QUnit.test( 'Basic', function ( assert ) {
79 var isAwesomeDone;
80
81 mw.loader.testCallback = function () {
82 assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
83 isAwesomeDone = true;
84 };
85
86 mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
87
88 return mw.loader.using( 'test.callback', function () {
89 assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
90 delete mw.loader.testCallback;
91
92 }, function () {
93 assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
94 } );
95 } );
96
97 QUnit.test( 'Object method as module name', function ( assert ) {
98 var isAwesomeDone;
99
100 mw.loader.testCallback = function () {
101 assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
102 isAwesomeDone = true;
103 };
104
105 mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
106
107 return mw.loader.using( 'hasOwnProperty', function () {
108 assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
109 delete mw.loader.testCallback;
110
111 }, function () {
112 assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
113 } );
114 } );
115
116 QUnit.test( '.using( .. ) Promise', function ( assert ) {
117 var isAwesomeDone;
118
119 mw.loader.testCallback = function () {
120 assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
121 isAwesomeDone = true;
122 };
123
124 mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
125
126 return mw.loader.using( 'test.promise' )
127 .done( function () {
128 assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
129 delete mw.loader.testCallback;
130 } )
131 .fail( function () {
132 assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
133 } );
134 } );
135
136 QUnit.test( '.using() Error: Circular dependency', function ( assert ) {
137 var done = assert.async();
138
139 mw.loader.register( [
140 [ 'test.circle1', '0', [ 'test.circle2' ] ],
141 [ 'test.circle2', '0', [ 'test.circle3' ] ],
142 [ 'test.circle3', '0', [ 'test.circle1' ] ]
143 ] );
144 mw.loader.using( 'test.circle3' ).then(
145 function done() {
146 assert.ok( false, 'Unexpected resolution, expected error.' );
147 },
148 function fail( e ) {
149 assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' );
150 }
151 )
152 .always( done );
153 } );
154
155 QUnit.test( '.load() - Error: Circular dependency', function ( assert ) {
156 mw.loader.register( [
157 [ 'test.circleA', '0', [ 'test.circleB' ] ],
158 [ 'test.circleB', '0', [ 'test.circleC' ] ],
159 [ 'test.circleC', '0', [ 'test.circleA' ] ]
160 ] );
161 assert.throws( function () {
162 mw.loader.load( 'test.circleC' );
163 }, /Circular/, 'Detect circular dependency' );
164 } );
165
166 QUnit.test( '.using() - Error: Unregistered', function ( assert ) {
167 var done = assert.async();
168
169 mw.loader.using( 'test.using.unreg' ).then(
170 function done() {
171 assert.ok( false, 'Unexpected resolution, expected error.' );
172 },
173 function fail( e ) {
174 assert.ok( /Unknown/.test( String( e ) ), 'Detect unknown dependency' );
175 }
176 ).always( done );
177 } );
178
179 QUnit.test( '.load() - Error: Unregistered (ignored)', function ( assert ) {
180 assert.expect( 0 );
181 mw.loader.load( 'test.using.unreg2' );
182 } );
183
184 QUnit.test( '.implement( styles={ "css": [text, ..] } )', function ( assert ) {
185 var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
186
187 assert.notEqual(
188 $element.css( 'float' ),
189 'right',
190 'style is clear'
191 );
192
193 mw.loader.implement(
194 'test.implement.a',
195 function () {
196 assert.equal(
197 $element.css( 'float' ),
198 'right',
199 'style is applied'
200 );
201 },
202 {
203 all: '.mw-test-implement-a { float: right; }'
204 }
205 );
206
207 return mw.loader.using( 'test.implement.a' );
208 } );
209
210 QUnit.test( '.implement( styles={ "url": { <media>: [url, ..] } } )', function ( assert ) {
211 var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
212 $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
213 $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
214 done = assert.async();
215
216 assert.notEqual(
217 $element1.css( 'text-align' ),
218 'center',
219 'style is clear'
220 );
221 assert.notEqual(
222 $element2.css( 'float' ),
223 'left',
224 'style is clear'
225 );
226 assert.notEqual(
227 $element3.css( 'text-align' ),
228 'right',
229 'style is clear'
230 );
231
232 mw.loader.implement(
233 'test.implement.b',
234 function () {
235 // Note: done() must only be called when the entire test is
236 // complete. So, make sure that we don't start until *both*
237 // assertStyleAsync calls have completed.
238 var pending = 2;
239 assertStyleAsync( assert, $element2, 'float', 'left', function () {
240 assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
241
242 pending--;
243 if ( pending === 0 ) {
244 done();
245 }
246 } );
247 assertStyleAsync( assert, $element3, 'float', 'right', function () {
248 assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
249
250 pending--;
251 if ( pending === 0 ) {
252 done();
253 }
254 } );
255 },
256 {
257 url: {
258 print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
259 screen: [
260 // T42834: Make sure it actually works with more than 1 stylesheet reference
261 urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
262 urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
263 ]
264 }
265 }
266 );
267
268 mw.loader.load( 'test.implement.b' );
269 } );
270
271 // Backwards compatibility
272 QUnit.test( '.implement( styles={ <media>: text } ) (back-compat)', function ( assert ) {
273 var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
274
275 assert.notEqual(
276 $element.css( 'float' ),
277 'right',
278 'style is clear'
279 );
280
281 mw.loader.implement(
282 'test.implement.c',
283 function () {
284 assert.equal(
285 $element.css( 'float' ),
286 'right',
287 'style is applied'
288 );
289 },
290 {
291 all: '.mw-test-implement-c { float: right; }'
292 }
293 );
294
295 return mw.loader.using( 'test.implement.c' );
296 } );
297
298 // Backwards compatibility
299 QUnit.test( '.implement( styles={ <media>: [url, ..] } ) (back-compat)', function ( assert ) {
300 var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
301 $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
302 done = assert.async();
303
304 assert.notEqual(
305 $element.css( 'float' ),
306 'right',
307 'style is clear'
308 );
309 assert.notEqual(
310 $element2.css( 'text-align' ),
311 'center',
312 'style is clear'
313 );
314
315 mw.loader.implement(
316 'test.implement.d',
317 function () {
318 assertStyleAsync( assert, $element, 'float', 'right', function () {
319 assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (T42500)' );
320 done();
321 } );
322 },
323 {
324 all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
325 print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
326 }
327 );
328
329 mw.loader.load( 'test.implement.d' );
330 } );
331
332 // @import (T33676)
333 QUnit.test( '.implement( styles has @import )', function ( assert ) {
334 var isJsExecuted, $element,
335 done = assert.async();
336
337 mw.loader.implement(
338 'test.implement.import',
339 function () {
340 assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
341 isJsExecuted = true;
342
343 assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
344
345 $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
346
347 assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
348
349 assertStyleAsync( assert, $element, 'float', 'right', function () {
350 assert.equal( $element.css( 'text-align' ), 'center',
351 'CSS styles after the @import rule are working'
352 );
353
354 done();
355 } );
356 },
357 {
358 css: [
359 '@import url(\''
360 + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
361 + '\');\n'
362 + '.mw-test-implement-import { text-align: center; }'
363 ]
364 },
365 {
366 'test-foobar': 'Hello Foobar, $1!'
367 }
368 );
369
370 mw.loader.using( 'test.implement.import' ).always( function () {
371 assert.strictEqual( isJsExecuted, true, 'script executed' );
372 assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
373 } );
374 } );
375
376 QUnit.test( '.implement( dependency with styles )', function ( assert ) {
377 var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
378 $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
379
380 assert.notEqual(
381 $element.css( 'float' ),
382 'right',
383 'style is clear'
384 );
385 assert.notEqual(
386 $element2.css( 'float' ),
387 'left',
388 'style is clear'
389 );
390
391 mw.loader.register( [
392 [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
393 [ 'test.implement.e2', '0' ]
394 ] );
395
396 mw.loader.implement(
397 'test.implement.e',
398 function () {
399 assert.equal(
400 $element.css( 'float' ),
401 'right',
402 'Depending module\'s style is applied'
403 );
404 },
405 {
406 all: '.mw-test-implement-e { float: right; }'
407 }
408 );
409
410 mw.loader.implement(
411 'test.implement.e2',
412 function () {
413 assert.equal(
414 $element2.css( 'float' ),
415 'left',
416 'Dependency\'s style is applied'
417 );
418 },
419 {
420 all: '.mw-test-implement-e2 { float: left; }'
421 }
422 );
423
424 return mw.loader.using( 'test.implement.e' );
425 } );
426
427 QUnit.test( '.implement( only scripts )', function ( assert ) {
428 mw.loader.implement( 'test.onlyscripts', function () {} );
429 assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
430 } );
431
432 QUnit.test( '.implement( only messages )', function ( assert ) {
433 assert.assertFalse( mw.messages.exists( 'T31107' ), 'Verify that the test message doesn\'t exist yet' );
434
435 mw.loader.implement( 'test.implement.msgs', [], {}, { T31107: 'loaded' } );
436
437 return mw.loader.using( 'test.implement.msgs', function () {
438 assert.ok( mw.messages.exists( 'T31107' ), 'T31107: messages-only module should implement ok' );
439 }, function () {
440 assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
441 } );
442 } );
443
444 QUnit.test( '.implement( empty )', function ( assert ) {
445 mw.loader.implement( 'test.empty' );
446 assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
447 } );
448
449 QUnit.test( 'Broken indirect dependency', function ( assert ) {
450 // don't emit an error event
451 this.sandbox.stub( mw, 'track' );
452
453 mw.loader.register( [
454 [ 'test.module1', '0' ],
455 [ 'test.module2', '0', [ 'test.module1' ] ],
456 [ 'test.module3', '0', [ 'test.module2' ] ]
457 ] );
458 mw.loader.implement( 'test.module1', function () {
459 throw new Error( 'expected' );
460 }, {}, {} );
461 assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
462 assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
463 assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
464
465 assert.strictEqual( mw.track.callCount, 1 );
466 } );
467
468 QUnit.test( 'Out-of-order implementation', function ( assert ) {
469 mw.loader.register( [
470 [ 'test.module4', '0' ],
471 [ 'test.module5', '0', [ 'test.module4' ] ],
472 [ 'test.module6', '0', [ 'test.module5' ] ]
473 ] );
474 mw.loader.implement( 'test.module4', function () {} );
475 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
476 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
477 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
478 mw.loader.implement( 'test.module6', function () {} );
479 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
480 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
481 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
482 mw.loader.implement( 'test.module5', function () {} );
483 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
484 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
485 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
486 } );
487
488 QUnit.test( 'Missing dependency', function ( assert ) {
489 mw.loader.register( [
490 [ 'test.module7', '0' ],
491 [ 'test.module8', '0', [ 'test.module7' ] ],
492 [ 'test.module9', '0', [ 'test.module8' ] ]
493 ] );
494 mw.loader.implement( 'test.module8', function () {} );
495 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
496 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
497 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
498 mw.loader.state( 'test.module7', 'missing' );
499 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
500 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
501 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
502 mw.loader.implement( 'test.module9', function () {} );
503 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
504 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
505 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
506 mw.loader.using(
507 [ 'test.module7' ],
508 function () {
509 assert.ok( false, 'Success fired despite missing dependency' );
510 assert.ok( true, 'QUnit expected() count dummy' );
511 },
512 function ( e, dependencies ) {
513 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
514 assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
515 }
516 );
517 mw.loader.using(
518 [ 'test.module9' ],
519 function () {
520 assert.ok( false, 'Success fired despite missing dependency' );
521 assert.ok( true, 'QUnit expected() count dummy' );
522 },
523 function ( e, dependencies ) {
524 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
525 dependencies.sort();
526 assert.deepEqual(
527 dependencies,
528 [ 'test.module7', 'test.module8', 'test.module9' ],
529 'Error callback called with all three modules as dependencies'
530 );
531 }
532 );
533 } );
534
535 QUnit.test( 'Dependency handling', function ( assert ) {
536 var done = assert.async();
537 mw.loader.register( [
538 // [module, version, dependencies, group, source]
539 [ 'testMissing', '1', [], null, 'testloader' ],
540 [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
541 [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
542 ] );
543
544 function verifyModuleStates() {
545 assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
546 assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
547 assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
548 }
549
550 mw.loader.using( [ 'testUsesNestedMissing' ],
551 function () {
552 assert.ok( false, 'Error handler should be invoked.' );
553 assert.ok( true ); // Dummy to reach QUnit expect()
554
555 verifyModuleStates();
556
557 done();
558 },
559 function ( e, badmodules ) {
560 assert.ok( true, 'Error handler should be invoked.' );
561 // As soon as server spits out state('testMissing', 'missing');
562 // it will bubble up and trigger the error callback.
563 // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
564 assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
565
566 verifyModuleStates();
567
568 done();
569 }
570 );
571 } );
572
573 QUnit.test( 'Skip-function handling', function ( assert ) {
574 mw.loader.register( [
575 // [module, version, dependencies, group, source, skip]
576 [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
577 [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
578 [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
579 ] );
580
581 function verifyModuleStates() {
582 assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
583 assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
584 assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
585 }
586
587 return mw.loader.using( [ 'testUsesSkippable' ],
588 function () {
589 assert.ok( true, 'Success handler should be invoked.' );
590 assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
591
592 verifyModuleStates();
593 },
594 function ( e, badmodules ) {
595 assert.ok( false, 'Error handler should not be invoked.' );
596 assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
597
598 verifyModuleStates();
599 }
600 );
601 } );
602
603 QUnit.asyncTest( '.load( "//protocol-relative" ) - T32825', function ( assert ) {
604 // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
605 // Test is for regressions!
606
607 // Forge a URL to the test callback script
608 var target = QUnit.fixurl(
609 mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
610 );
611
612 // Confirm that mw.loader.load() works with protocol-relative URLs
613 target = target.replace( /https?:/, '' );
614
615 assert.equal( target.slice( 0, 2 ), '//',
616 'URL must be relative to test relative URLs!'
617 );
618
619 // Async!
620 // The target calls QUnit.start
621 mw.loader.load( target );
622 } );
623
624 QUnit.asyncTest( '.load( "/absolute-path" )', function ( assert ) {
625 // Forge a URL to the test callback script
626 var target = QUnit.fixurl(
627 mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
628 );
629
630 // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
631 assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
632
633 // Async!
634 // The target calls QUnit.start
635 mw.loader.load( target );
636 } );
637
638 QUnit.test( 'Empty string module name - T28804', function ( assert ) {
639 var done = false;
640
641 assert.strictEqual( mw.loader.getState( '' ), null, 'State (unregistered)' );
642
643 mw.loader.register( '', 'v1' );
644 assert.strictEqual( mw.loader.getState( '' ), 'registered', 'State (registered)' );
645 assert.strictEqual( mw.loader.getVersion( '' ), 'v1', 'Version' );
646
647 mw.loader.implement( '', function () {
648 done = true;
649 } );
650
651 return mw.loader.using( '', function () {
652 assert.strictEqual( done, true, 'script ran' );
653 assert.strictEqual( mw.loader.getState( '' ), 'ready', 'State (ready)' );
654 } );
655 } );
656
657 QUnit.test( 'Executing race - T112232', function ( assert ) {
658 var done = false;
659
660 // The red herring schedules its CSS buffer first. In T112232, a bug in the
661 // state machine would cause the job for testRaceLoadMe to run with an earlier job.
662 mw.loader.implement(
663 'testRaceRedHerring',
664 function () {},
665 { css: [ '.mw-testRaceRedHerring {}' ] }
666 );
667 mw.loader.implement(
668 'testRaceLoadMe',
669 function () {
670 done = true;
671 },
672 { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
673 );
674
675 mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
676 return mw.loader.using( 'testRaceLoadMe', function () {
677 assert.strictEqual( done, true, 'script ran' );
678 assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
679 } );
680 } );
681
682 QUnit.test( 'Stale response caching - T117587', function ( assert ) {
683 var count = 0;
684 mw.loader.store.enabled = true;
685 mw.loader.register( 'test.stale', 'v2' );
686 assert.strictEqual( mw.loader.store.get( 'test.stale' ), false, 'Not in store' );
687
688 mw.loader.implement( 'test.stale@v1', function () {
689 count++;
690 } );
691
692 return mw.loader.using( 'test.stale' )
693 .then( function () {
694 assert.strictEqual( count, 1 );
695 // After implementing, registry contains version as implemented by the response.
696 assert.strictEqual( mw.loader.getVersion( 'test.stale' ), 'v1', 'Override version' );
697 assert.strictEqual( mw.loader.getState( 'test.stale' ), 'ready' );
698 assert.ok( mw.loader.store.get( 'test.stale' ), 'In store' );
699 } )
700 .then( function () {
701 // Reset run time, but keep mw.loader.store
702 mw.loader.moduleRegistry[ 'test.stale' ].script = undefined;
703 mw.loader.moduleRegistry[ 'test.stale' ].state = 'registered';
704 mw.loader.moduleRegistry[ 'test.stale' ].version = 'v2';
705
706 // Module was stored correctly as v1
707 // On future navigations, it will be ignored until evicted
708 assert.strictEqual( mw.loader.store.get( 'test.stale' ), false, 'Not in store' );
709 } );
710 } );
711
712 QUnit.test( 'Stale response caching - backcompat', function ( assert ) {
713 var count = 0;
714 mw.loader.store.enabled = true;
715 mw.loader.register( 'test.stalebc', 'v2' );
716 assert.strictEqual( mw.loader.store.get( 'test.stalebc' ), false, 'Not in store' );
717
718 mw.loader.implement( 'test.stalebc', function () {
719 count++;
720 } );
721
722 return mw.loader.using( 'test.stalebc' )
723 .then( function () {
724 assert.strictEqual( count, 1 );
725 assert.strictEqual( mw.loader.getState( 'test.stalebc' ), 'ready' );
726 assert.ok( mw.loader.store.get( 'test.stalebc' ), 'In store' );
727 } )
728 .then( function () {
729 // Reset run time, but keep mw.loader.store
730 mw.loader.moduleRegistry[ 'test.stalebc' ].script = undefined;
731 mw.loader.moduleRegistry[ 'test.stalebc' ].state = 'registered';
732 mw.loader.moduleRegistry[ 'test.stalebc' ].version = 'v2';
733
734 // Legacy behaviour is storing under the expected version,
735 // which woudl lead to whitewashing and stale values (T117587).
736 assert.ok( mw.loader.store.get( 'test.stalebc' ), 'In store' );
737 } );
738 } );
739
740 QUnit.test( 'require()', function ( assert ) {
741 mw.loader.register( [
742 [ 'test.require1', '0' ],
743 [ 'test.require2', '0' ],
744 [ 'test.require3', '0' ],
745 [ 'test.require4', '0', [ 'test.require3' ] ]
746 ] );
747 mw.loader.implement( 'test.require1', function () {} );
748 mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
749 module.exports = 1;
750 } );
751 mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
752 module.exports = function () {
753 return 'hello world';
754 };
755 } );
756 mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
757 var other = require( 'test.require3' );
758 module.exports = {
759 pizza: function () {
760 return other();
761 }
762 };
763 } );
764 return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
765 .then( function ( require ) {
766 var module1, module2, module3, module4;
767
768 module1 = require( 'test.require1' );
769 module2 = require( 'test.require2' );
770 module3 = require( 'test.require3' );
771 module4 = require( 'test.require4' );
772
773 assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
774 assert.strictEqual( module2, 1, 'export a number' );
775 assert.strictEqual( module3(), 'hello world', 'export a function' );
776 assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
777 assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
778
779 assert.throws( function () {
780 require( '_badmodule' );
781 }, /is not loaded/, 'Requesting non-existent modules throws error.' );
782 } );
783 } );
784
785 QUnit.test( 'require() in debug mode', function ( assert ) {
786 var path = mw.config.get( 'wgScriptPath' );
787 mw.loader.register( [
788 [ 'test.require.define', '0' ],
789 [ 'test.require.callback', '0', [ 'test.require.define' ] ]
790 ] );
791 mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
792 mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
793
794 return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
795 var cb = require( 'test.require.callback' );
796 assert.strictEqual( cb.immediate, 'Defined.', 'module.exports and require work in debug mode' );
797 // Must use try-catch because cb.later() will throw if require is undefined,
798 // which doesn't work well inside Deferred.then() when using jQuery 1.x with QUnit
799 try {
800 assert.strictEqual( cb.later(), 'Defined.', 'require works asynchrously in debug mode' );
801 } catch ( e ) {
802 assert.equal( null, String( e ), 'require works asynchrously in debug mode' );
803 }
804 }, function () {
805 assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
806 } );
807 } );
808
809 }( mediaWiki, jQuery ) );