Make MessageCache::load() require a language code
[mediawiki.git] / tests / qunit / suites / resources / mediawiki / mediawiki.loader.test.js
blobbfac513cf309112aea3338bb4b58fab85286767a
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         } ) );
11         mw.loader.addSource(
12                 'testloader',
13                 QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
14         );
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;
33                 function isCssImportApplied() {
34                         // Trigger reflow, repaint, redraw, whatever (cross-browser)
35                         var x = $element.css( 'height' );
36                         x = el.innerHTML;
37                         el.className = el.className;
38                         x = document.documentElement.clientHeight;
40                         return $element.css( prop ) === val;
41                 }
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                                 );
51                                 if ( fn ) {
52                                         fn();
53                                 }
55                                 return;
56                         }
57                         // Otherwise, keep polling
58                         setTimeout( styleTestLoop );
59                 }
61                 // Start the loop
62                 styleTestStart = new Date().getTime();
63                 styleTestLoop();
64         }
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         }
78         QUnit.test( 'Basic', 2, function ( assert ) {
79                 var isAwesomeDone;
81                 mw.loader.testCallback = function () {
82                         assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
83                         isAwesomeDone = true;
84                 };
86                 mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
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;
92                 }, function () {
93                         assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
94                 } );
95         } );
97         QUnit.test( 'Object method as module name', 2, function ( assert ) {
98                 var isAwesomeDone;
100                 mw.loader.testCallback = function () {
101                         assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
102                         isAwesomeDone = true;
103                 };
105                 mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
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;
111                 }, function () {
112                         assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
113                 } );
114         } );
116         QUnit.test( '.using( .. ) Promise', 2, function ( assert ) {
117                 var isAwesomeDone;
119                 mw.loader.testCallback = function () {
120                         assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
121                         isAwesomeDone = true;
122                 };
124                 mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
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;
131                 } )
132                 .fail( function () {
133                         assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
134                 } );
135         } );
137         QUnit.test( '.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
138                 var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
140                 assert.notEqual(
141                         $element.css( 'float' ),
142                         'right',
143                         'style is clear'
144                 );
146                 mw.loader.implement(
147                         'test.implement.a',
148                         function () {
149                                 assert.equal(
150                                         $element.css( 'float' ),
151                                         'right',
152                                         'style is applied'
153                                 );
154                         },
155                         {
156                                 all: '.mw-test-implement-a { float: right; }'
157                         }
158                 );
160                 return mw.loader.using( 'test.implement.a' );
161         } );
163         QUnit.test( '.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
164                 var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
165                         $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
166                         $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
167                         done = assert.async();
169                 assert.notEqual(
170                         $element1.css( 'text-align' ),
171                         'center',
172                         'style is clear'
173                 );
174                 assert.notEqual(
175                         $element2.css( 'float' ),
176                         'left',
177                         'style is clear'
178                 );
179                 assert.notEqual(
180                         $element3.css( 'text-align' ),
181                         'right',
182                         'style is clear'
183                 );
185                 mw.loader.implement(
186                         'test.implement.b',
187                         function () {
188                                 // Note: done() must only be called when the entire test is
189                                 // complete. So, make sure that we don't start until *both*
190                                 // assertStyleAsync calls have completed.
191                                 var pending = 2;
192                                 assertStyleAsync( assert, $element2, 'float', 'left', function () {
193                                         assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
195                                         pending--;
196                                         if ( pending === 0 ) {
197                                                 done();
198                                         }
199                                 } );
200                                 assertStyleAsync( assert, $element3, 'float', 'right', function () {
201                                         assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
203                                         pending--;
204                                         if ( pending === 0 ) {
205                                                 done();
206                                         }
207                                 } );
208                         },
209                         {
210                                 url: {
211                                         print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
212                                         screen: [
213                                                 // bug 40834: Make sure it actually works with more than 1 stylesheet reference
214                                                 urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
215                                                 urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
216                                         ]
217                                 }
218                         }
219                 );
221                 mw.loader.load( 'test.implement.b' );
222         } );
224         // Backwards compatibility
225         QUnit.test( '.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
226                 var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
228                 assert.notEqual(
229                         $element.css( 'float' ),
230                         'right',
231                         'style is clear'
232                 );
234                 mw.loader.implement(
235                         'test.implement.c',
236                         function () {
237                                 assert.equal(
238                                         $element.css( 'float' ),
239                                         'right',
240                                         'style is applied'
241                                 );
242                         },
243                         {
244                                 all: '.mw-test-implement-c { float: right; }'
245                         }
246                 );
248                 return mw.loader.using( 'test.implement.c' );
249         } );
251         // Backwards compatibility
252         QUnit.test( '.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
253                 var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
254                         $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
255                         done = assert.async();
257                 assert.notEqual(
258                         $element.css( 'float' ),
259                         'right',
260                         'style is clear'
261                 );
262                 assert.notEqual(
263                         $element2.css( 'text-align' ),
264                         'center',
265                         'style is clear'
266                 );
268                 mw.loader.implement(
269                         'test.implement.d',
270                         function () {
271                                 assertStyleAsync( assert, $element, 'float', 'right', function () {
272                                         assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
273                                         done();
274                                 } );
275                         },
276                         {
277                                 all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
278                                 print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
279                         }
280                 );
282                 mw.loader.load( 'test.implement.d' );
283         } );
285         // @import (bug 31676)
286         QUnit.test( '.implement( styles has @import )', 7, function ( assert ) {
287                 var isJsExecuted, $element,
288                         done = assert.async();
290                 mw.loader.implement(
291                         'test.implement.import',
292                         function () {
293                                 assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
294                                 isJsExecuted = true;
296                                 assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
298                                 $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
300                                 assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
302                                 assertStyleAsync( assert, $element, 'float', 'right', function () {
303                                         assert.equal( $element.css( 'text-align' ), 'center',
304                                                 'CSS styles after the @import rule are working'
305                                         );
307                                         done();
308                                 } );
309                         },
310                         {
311                                 css: [
312                                         '@import url(\''
313                                                 + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
314                                                 + '\');\n'
315                                                 + '.mw-test-implement-import { text-align: center; }'
316                                 ]
317                         },
318                         {
319                                 'test-foobar': 'Hello Foobar, $1!'
320                         }
321                 );
323                 mw.loader.using( 'test.implement.import' ).always( function () {
324                         assert.strictEqual( isJsExecuted, true, 'script executed' );
325                         assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
326                 } );
327         } );
329         QUnit.test( '.implement( dependency with styles )', 4, function ( assert ) {
330                 var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
331                         $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
333                 assert.notEqual(
334                         $element.css( 'float' ),
335                         'right',
336                         'style is clear'
337                 );
338                 assert.notEqual(
339                         $element2.css( 'float' ),
340                         'left',
341                         'style is clear'
342                 );
344                 mw.loader.register( [
345                         [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
346                         [ 'test.implement.e2', '0' ]
347                 ] );
349                 mw.loader.implement(
350                         'test.implement.e',
351                         function () {
352                                 assert.equal(
353                                         $element.css( 'float' ),
354                                         'right',
355                                         'Depending module\'s style is applied'
356                                 );
357                         },
358                         {
359                                 all: '.mw-test-implement-e { float: right; }'
360                         }
361                 );
363                 mw.loader.implement(
364                         'test.implement.e2',
365                         function () {
366                                 assert.equal(
367                                         $element2.css( 'float' ),
368                                         'left',
369                                         'Dependency\'s style is applied'
370                                 );
371                         },
372                         {
373                                 all: '.mw-test-implement-e2 { float: left; }'
374                         }
375                 );
377                 return mw.loader.using( 'test.implement.e' );
378         } );
380         QUnit.test( '.implement( only scripts )', 1, function ( assert ) {
381                 mw.loader.implement( 'test.onlyscripts', function () {} );
382                 assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
383         } );
385         QUnit.test( '.implement( only messages )', 2, function ( assert ) {
386                 assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
388                 // jscs: disable requireCamelCaseOrUpperCaseIdentifiers
389                 mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } );
390                 // jscs: enable requireCamelCaseOrUpperCaseIdentifiers
392                 return mw.loader.using( 'test.implement.msgs', function () {
393                         assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
394                 }, function () {
395                         assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
396                 } );
397         } );
399         QUnit.test( '.implement( empty )', 1, function ( assert ) {
400                 mw.loader.implement( 'test.empty' );
401                 assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
402         } );
404         QUnit.test( 'Broken indirect dependency', 4, function ( assert ) {
405                 // don't emit an error event
406                 this.sandbox.stub( mw, 'track' );
408                 mw.loader.register( [
409                         [ 'test.module1', '0' ],
410                         [ 'test.module2', '0', [ 'test.module1' ] ],
411                         [ 'test.module3', '0', [ 'test.module2' ] ]
412                 ] );
413                 mw.loader.implement( 'test.module1', function () {
414                         throw new Error( 'expected' );
415                 }, {}, {} );
416                 assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
417                 assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
418                 assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
420                 assert.strictEqual( mw.track.callCount, 1 );
421         } );
423         QUnit.test( 'Circular dependency', 1, function ( assert ) {
424                 mw.loader.register( [
425                         [ 'test.circle1', '0', [ 'test.circle2' ] ],
426                         [ 'test.circle2', '0', [ 'test.circle3' ] ],
427                         [ 'test.circle3', '0', [ 'test.circle1' ] ]
428                 ] );
429                 assert.throws( function () {
430                         mw.loader.using( 'test.circle3' );
431                 }, /Circular/, 'Detect circular dependency' );
432         } );
434         QUnit.test( 'Out-of-order implementation', 9, function ( assert ) {
435                 mw.loader.register( [
436                         [ 'test.module4', '0' ],
437                         [ 'test.module5', '0', [ 'test.module4' ] ],
438                         [ 'test.module6', '0', [ 'test.module5' ] ]
439                 ] );
440                 mw.loader.implement( 'test.module4', function () {} );
441                 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
442                 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
443                 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
444                 mw.loader.implement( 'test.module6', function () {} );
445                 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
446                 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
447                 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
448                 mw.loader.implement( 'test.module5', function () {} );
449                 assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
450                 assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
451                 assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
452         } );
454         QUnit.test( 'Missing dependency', 13, function ( assert ) {
455                 mw.loader.register( [
456                         [ 'test.module7', '0' ],
457                         [ 'test.module8', '0', [ 'test.module7' ] ],
458                         [ 'test.module9', '0', [ 'test.module8' ] ]
459                 ] );
460                 mw.loader.implement( 'test.module8', function () {} );
461                 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
462                 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
463                 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
464                 mw.loader.state( 'test.module7', 'missing' );
465                 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
466                 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
467                 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
468                 mw.loader.implement( 'test.module9', function () {} );
469                 assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
470                 assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
471                 assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
472                 mw.loader.using(
473                         [ 'test.module7' ],
474                         function () {
475                                 assert.ok( false, 'Success fired despite missing dependency' );
476                                 assert.ok( true, 'QUnit expected() count dummy' );
477                         },
478                         function ( e, dependencies ) {
479                                 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
480                                 assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
481                         }
482                 );
483                 mw.loader.using(
484                         [ 'test.module9' ],
485                         function () {
486                                 assert.ok( false, 'Success fired despite missing dependency' );
487                                 assert.ok( true, 'QUnit expected() count dummy' );
488                         },
489                         function ( e, dependencies ) {
490                                 assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
491                                 dependencies.sort();
492                                 assert.deepEqual(
493                                         dependencies,
494                                         [ 'test.module7', 'test.module8', 'test.module9' ],
495                                         'Error callback called with all three modules as dependencies'
496                                 );
497                         }
498                 );
499         } );
501         QUnit.test( 'Dependency handling', 5, function ( assert ) {
502                 var done = assert.async();
503                 mw.loader.register( [
504                         // [module, version, dependencies, group, source]
505                         [ 'testMissing', '1', [], null, 'testloader' ],
506                         [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
507                         [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
508                 ] );
510                 function verifyModuleStates() {
511                         assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
512                         assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
513                         assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
514                 }
516                 mw.loader.using( [ 'testUsesNestedMissing' ],
517                         function () {
518                                 assert.ok( false, 'Error handler should be invoked.' );
519                                 assert.ok( true ); // Dummy to reach QUnit expect()
521                                 verifyModuleStates();
523                                 done();
524                         },
525                         function ( e, badmodules ) {
526                                 assert.ok( true, 'Error handler should be invoked.' );
527                                 // As soon as server spits out state('testMissing', 'missing');
528                                 // it will bubble up and trigger the error callback.
529                                 // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
530                                 assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
532                                 verifyModuleStates();
534                                 done();
535                         }
536                 );
537         } );
539         QUnit.test( 'Skip-function handling', 5, function ( assert ) {
540                 mw.loader.register( [
541                         // [module, version, dependencies, group, source, skip]
542                         [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
543                         [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
544                         [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
545                 ] );
547                 function verifyModuleStates() {
548                         assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
549                         assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
550                         assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
551                 }
553                 return mw.loader.using( [ 'testUsesSkippable' ],
554                         function () {
555                                 assert.ok( true, 'Success handler should be invoked.' );
556                                 assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
558                                 verifyModuleStates();
559                         },
560                         function ( e, badmodules ) {
561                                 assert.ok( false, 'Error handler should not be invoked.' );
562                                 assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
564                                 verifyModuleStates();
565                         }
566                 );
567         } );
569         QUnit.asyncTest( '.load( "//protocol-relative" ) - T32825', 2, function ( assert ) {
570                 // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
571                 // Test is for regressions!
573                 // Forge a URL to the test callback script
574                 var target = QUnit.fixurl(
575                         mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
576                 );
578                 // Confirm that mw.loader.load() works with protocol-relative URLs
579                 target = target.replace( /https?:/, '' );
581                 assert.equal( target.slice( 0, 2 ), '//',
582                         'URL must be relative to test relative URLs!'
583                 );
585                 // Async!
586                 // The target calls QUnit.start
587                 mw.loader.load( target );
588         } );
590         QUnit.asyncTest( '.load( "/absolute-path" )', 2, function ( assert ) {
591                 // Forge a URL to the test callback script
592                 var target = QUnit.fixurl(
593                         mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
594                 );
596                 // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
597                 assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
599                 // Async!
600                 // The target calls QUnit.start
601                 mw.loader.load( target );
602         } );
604         QUnit.test( 'Executing race - T112232', 2, function ( assert ) {
605                 var done = false;
607                 // The red herring schedules its CSS buffer first. In T112232, a bug in the
608                 // state machine would cause the job for testRaceLoadMe to run with an earlier job.
609                 mw.loader.implement(
610                         'testRaceRedHerring',
611                         function () {},
612                         { css: [ '.mw-testRaceRedHerring {}' ] }
613                 );
614                 mw.loader.implement(
615                         'testRaceLoadMe',
616                         function () {
617                                 done = true;
618                         },
619                         { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
620                 );
622                 mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
623                 return mw.loader.using( 'testRaceLoadMe', function () {
624                         assert.strictEqual( done, true, 'script ran' );
625                         assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
626                 } );
627         } );
629         QUnit.test( 'Stale response caching - T117587', function ( assert ) {
630                 var count = 0;
631                 mw.loader.store.enabled = true;
632                 mw.loader.register( 'test.stale', 'v2' );
633                 assert.strictEqual( mw.loader.store.get( 'test.stale' ), false, 'Not in store' );
635                 mw.loader.implement( 'test.stale@v1', function () {
636                         count++;
637                 } );
639                 return mw.loader.using( 'test.stale' )
640                         .then( function () {
641                                 assert.strictEqual( count, 1 );
642                                 assert.strictEqual( mw.loader.getState( 'test.stale' ), 'ready' );
643                                 assert.ok( mw.loader.store.get( 'test.stale' ), 'In store' );
644                         } )
645                         .then( function () {
646                                 // Reset run time, but keep mw.loader.store
647                                 mw.loader.moduleRegistry[ 'test.stale' ].script = undefined;
648                                 mw.loader.moduleRegistry[ 'test.stale' ].state = 'registered';
649                                 mw.loader.moduleRegistry[ 'test.stale' ].version = 'v2';
651                                 // Module was stored correctly as v1
652                                 // On future navigations, it will be ignored until evicted
653                                 assert.strictEqual( mw.loader.store.get( 'test.stale' ), false, 'Not in store' );
654                         } );
655         } );
657         QUnit.test( 'Stale response caching - backcompat', function ( assert ) {
658                 var count = 0;
659                 mw.loader.store.enabled = true;
660                 mw.loader.register( 'test.stalebc', 'v2' );
661                 assert.strictEqual( mw.loader.store.get( 'test.stalebc' ), false, 'Not in store' );
663                 mw.loader.implement( 'test.stalebc', function () {
664                         count++;
665                 } );
667                 return mw.loader.using( 'test.stalebc' )
668                         .then( function () {
669                                 assert.strictEqual( count, 1 );
670                                 assert.strictEqual( mw.loader.getState( 'test.stalebc' ), 'ready' );
671                                 assert.ok( mw.loader.store.get( 'test.stalebc' ), 'In store' );
672                         } )
673                         .then( function () {
674                                 // Reset run time, but keep mw.loader.store
675                                 mw.loader.moduleRegistry[ 'test.stalebc' ].script = undefined;
676                                 mw.loader.moduleRegistry[ 'test.stalebc' ].state = 'registered';
677                                 mw.loader.moduleRegistry[ 'test.stalebc' ].version = 'v2';
679                                 // Legacy behaviour is storing under the expected version,
680                                 // which woudl lead to whitewashing and stale values (T117587).
681                                 assert.ok( mw.loader.store.get( 'test.stalebc' ), 'In store' );
682                         } );
683         } );
685         QUnit.test( 'require()', 6, function ( assert ) {
686                 mw.loader.register( [
687                         [ 'test.require1', '0' ],
688                         [ 'test.require2', '0' ],
689                         [ 'test.require3', '0' ],
690                         [ 'test.require4', '0', [ 'test.require3' ] ]
691                 ] );
692                 mw.loader.implement( 'test.require1', function () {} );
693                 mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
694                         module.exports = 1;
695                 } );
696                 mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
697                         module.exports = function () {
698                                 return 'hello world';
699                         };
700                 } );
701                 mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
702                         var other = require( 'test.require3' );
703                         module.exports = {
704                                 pizza: function () {
705                                         return other();
706                                 }
707                         };
708                 } );
709                 return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
710                 .then( function ( require ) {
711                         var module1, module2, module3, module4;
713                         module1 = require( 'test.require1' );
714                         module2 = require( 'test.require2' );
715                         module3 = require( 'test.require3' );
716                         module4 = require( 'test.require4' );
718                         assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
719                         assert.strictEqual( module2, 1, 'export a number' );
720                         assert.strictEqual( module3(), 'hello world', 'export a function' );
721                         assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
722                         assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
724                         assert.throws( function () {
725                                 require( '_badmodule' );
726                         }, /is not loaded/, 'Requesting non-existent modules throws error.' );
727                 } );
728         } );
730         QUnit.test( 'require() in debug mode', function ( assert ) {
731                 var path = mw.config.get( 'wgScriptPath' );
732                 mw.loader.register( [
733                         [ 'test.require.define', '0' ],
734                         [ 'test.require.callback', '0', [ 'test.require.define' ] ]
735                 ] );
736                 mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
737                 mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
739                 return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
740                         var cb = require( 'test.require.callback' );
741                         assert.strictEqual( cb.immediate, 'Defined.', 'module.exports and require work in debug mode' );
742                         // Must use try-catch because cb.later() will throw if require is undefined,
743                         // which doesn't work well inside Deferred.then() when using jQuery 1.x with QUnit
744                         try {
745                                 assert.strictEqual( cb.later(), 'Defined.', 'require works asynchrously in debug mode' );
746                         } catch ( e ) {
747                                 assert.equal( null, String( e ), 'require works asynchrously in debug mode' );
748                         }
749                 }, function () {
750                         assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
751                 } );
752         } );
754 }( mediaWiki, jQuery ) );