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