2 QUnit
.module( 'mediawiki.loader', QUnit
.newMwEnvironment( {
3 setup: function ( assert
) {
4 mw
.loader
.store
.enabled
= false;
6 // Expose for load.mock.php
7 mw
.loader
.testFail = function ( reason
) {
8 assert
.ok( false, reason
);
11 teardown: function () {
12 mw
.loader
.store
.enabled
= false;
13 // Teardown for StringSet shim test
14 if ( this.nativeSet
) {
15 window
.Set
= this.nativeSet
;
16 mw
.redefineFallbacksForTest();
18 // Remove any remaining temporary statics
19 // exposed for cross-file mocks.
20 delete mw
.loader
.testCallback
;
21 delete mw
.loader
.testFail
;
27 QUnit
.fixurl( mw
.config
.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
31 * The sync style load test (for @import). This is, in a way, also an open bug for
32 * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
33 * way to get a callback from when a stylesheet is loaded (that is, including any
34 * `@import` rules inside). To work around this, we'll have a little time loop to check
35 * if the styles apply.
37 * Note: This test originally used new Image() and onerror to get a callback
38 * when the url is loaded, but that is fragile since it doesn't monitor the
39 * same request as the css @import, and Safari 4 has issues with
40 * onerror/onload not being fired at all in weird cases like this.
42 function assertStyleAsync( assert
, $element
, prop
, val
, fn
) {
44 el
= $element
.get( 0 ),
45 styleTestTimeout
= ( QUnit
.config
.testTimeout
|| 5000 ) - 200;
47 function isCssImportApplied() {
48 // Trigger reflow, repaint, redraw, whatever (cross-browser)
49 $element
.css( 'height' );
50 // eslint-disable-next-line no-unused-expressions
52 el
.className
= el
.className
;
53 // eslint-disable-next-line no-unused-expressions
54 document
.documentElement
.clientHeight
;
56 return $element
.css( prop
) === val
;
59 function styleTestLoop() {
60 var styleTestSince
= new Date().getTime() - styleTestStart
;
61 // If it is passing or if we timed out, run the real test and stop the loop
62 if ( isCssImportApplied() || styleTestSince
> styleTestTimeout
) {
63 assert
.equal( $element
.css( prop
), val
,
64 'style "' + prop
+ ': ' + val
+ '" from url is applied (after ' + styleTestSince
+ 'ms)'
73 // Otherwise, keep polling
74 setTimeout( styleTestLoop
);
78 styleTestStart
= new Date().getTime();
82 function urlStyleTest( selector
, prop
, val
) {
84 mw
.config
.get( 'wgScriptPath' ) +
85 '/tests/qunit/data/styleTest.css.php?' +
94 QUnit
.test( '.using( .., Function callback ) Promise', function ( assert
) {
95 var script
= 0, callback
= 0;
96 mw
.loader
.testCallback = function () {
99 mw
.loader
.implement( 'test.promise', [ QUnit
.fixurl( mw
.config
.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
101 return mw
.loader
.using( 'test.promise', function () {
103 } ).then( function () {
104 assert
.strictEqual( script
, 1, 'module script ran' );
105 assert
.strictEqual( callback
, 1, 'using() callback ran' );
109 QUnit
.test( 'Prototype method as module name', function ( assert
) {
111 mw
.loader
.testCallback = function () {
114 mw
.loader
.implement( 'hasOwnProperty', [ QUnit
.fixurl( mw
.config
.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ], {}, {} );
116 return mw
.loader
.using( 'hasOwnProperty', function () {
117 assert
.strictEqual( call
, 1, 'module script ran' );
121 // Covers mw.loader#sortDependencies (with native Set if available)
122 QUnit
.test( '.using() - Error: Circular dependency [StringSet default]', function ( assert
) {
123 var done
= assert
.async();
125 mw
.loader
.register( [
126 [ 'test.circle1', '0', [ 'test.circle2' ] ],
127 [ 'test.circle2', '0', [ 'test.circle3' ] ],
128 [ 'test.circle3', '0', [ 'test.circle1' ] ]
130 mw
.loader
.using( 'test.circle3' ).then(
132 assert
.ok( false, 'Unexpected resolution, expected error.' );
135 assert
.ok( /Circular/.test( String( e
) ), 'Detect circular dependency' );
141 // @covers mw.loader#sortDependencies (with fallback shim)
142 QUnit
.test( '.using() - Error: Circular dependency [StringSet shim]', function ( assert
) {
143 var done
= assert
.async();
151 this.nativeSet
= window
.Set
;
152 window
.Set
= undefined;
153 mw
.redefineFallbacksForTest();
155 mw
.loader
.register( [
156 [ 'test.shim.circle1', '0', [ 'test.shim.circle2' ] ],
157 [ 'test.shim.circle2', '0', [ 'test.shim.circle3' ] ],
158 [ 'test.shim.circle3', '0', [ 'test.shim.circle1' ] ]
160 mw
.loader
.using( 'test.shim.circle3' ).then(
162 assert
.ok( false, 'Unexpected resolution, expected error.' );
165 assert
.ok( /Circular/.test( String( e
) ), 'Detect circular dependency' );
171 QUnit
.test( '.load() - Error: Circular dependency', function ( assert
) {
173 mw
.loader
.register( [
174 [ 'test.circleA', '0', [ 'test.circleB' ] ],
175 [ 'test.circleB', '0', [ 'test.circleC' ] ],
176 [ 'test.circleC', '0', [ 'test.circleA' ] ]
178 this.sandbox
.stub( mw
, 'track', function ( topic
, data
) {
181 error
: data
.exception
&& data
.exception
.message
,
186 mw
.loader
.load( 'test.circleC' );
189 topic
: 'resourceloader.exception',
190 error
: 'Circular reference detected: test.circleB -> test.circleC',
194 'Detect circular dependency'
198 QUnit
.test( '.using() - Error: Unregistered', function ( assert
) {
199 var done
= assert
.async();
201 mw
.loader
.using( 'test.using.unreg' ).then(
203 assert
.ok( false, 'Unexpected resolution, expected error.' );
206 assert
.ok( /Unknown/.test( String( e
) ), 'Detect unknown dependency' );
211 QUnit
.test( '.load() - Error: Unregistered', function ( assert
) {
213 this.sandbox
.stub( mw
, 'track', function ( topic
, data
) {
216 error
: data
.exception
&& data
.exception
.message
,
221 mw
.loader
.load( 'test.load.unreg' );
224 topic
: 'resourceloader.exception',
225 error
: 'Unknown dependency: test.load.unreg',
232 // Regression test for T36853
233 QUnit
.test( '.load() - Error: Missing dependency', function ( assert
) {
235 this.sandbox
.stub( mw
, 'track', function ( topic
, data
) {
238 error
: data
.exception
&& data
.exception
.message
,
243 mw
.loader
.register( [
244 [ 'test.load.missingdep1', '0', [ 'test.load.missingdep2' ] ],
245 [ 'test.load.missingdep', '0', [ 'test.load.missingdep1' ] ]
247 mw
.loader
.load( 'test.load.missingdep' );
250 topic
: 'resourceloader.exception',
251 error
: 'Unknown dependency: test.load.missingdep2',
258 QUnit
.test( '.implement( styles={ "css": [text, ..] } )', function ( assert
) {
259 var $element
= $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
262 $element
.css( 'float' ),
271 $element
.css( 'float' ),
277 all
: '.mw-test-implement-a { float: right; }'
281 return mw
.loader
.using( 'test.implement.a' );
284 QUnit
.test( '.implement( styles={ "url": { <media>: [url, ..] } } )', function ( assert
) {
285 var $element1
= $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
286 $element2
= $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
287 $element3
= $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
288 done
= assert
.async();
291 $element1
.css( 'text-align' ),
296 $element2
.css( 'float' ),
301 $element3
.css( 'text-align' ),
309 // Note: done() must only be called when the entire test is
310 // complete. So, make sure that we don't start until *both*
311 // assertStyleAsync calls have completed.
313 assertStyleAsync( assert
, $element2
, 'float', 'left', function () {
314 assert
.notEqual( $element1
.css( 'text-align' ), 'center', 'print style is not applied' );
317 if ( pending
=== 0 ) {
321 assertStyleAsync( assert
, $element3
, 'float', 'right', function () {
322 assert
.notEqual( $element1
.css( 'text-align' ), 'center', 'print style is not applied' );
325 if ( pending
=== 0 ) {
332 print
: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
334 // T42834: Make sure it actually works with more than 1 stylesheet reference
335 urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
336 urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
342 mw
.loader
.load( 'test.implement.b' );
345 // Backwards compatibility
346 QUnit
.test( '.implement( styles={ <media>: text } ) (back-compat)', function ( assert
) {
347 var $element
= $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
350 $element
.css( 'float' ),
359 $element
.css( 'float' ),
365 all
: '.mw-test-implement-c { float: right; }'
369 return mw
.loader
.using( 'test.implement.c' );
372 // Backwards compatibility
373 QUnit
.test( '.implement( styles={ <media>: [url, ..] } ) (back-compat)', function ( assert
) {
374 var $element
= $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
375 $element2
= $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
376 done
= assert
.async();
379 $element
.css( 'float' ),
384 $element2
.css( 'text-align' ),
392 assertStyleAsync( assert
, $element
, 'float', 'right', function () {
393 assert
.notEqual( $element2
.css( 'text-align' ), 'center', 'print style is not applied (T42500)' );
398 all
: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
399 print
: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
403 mw
.loader
.load( 'test.implement.d' );
406 QUnit
.test( '.implement( messages before script )', function ( assert
) {
408 'test.implement.order',
410 assert
.equal( mw
.loader
.getState( 'test.implement.order' ), 'executing', 'state during script execution' );
411 assert
.equal( mw
.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
415 'test-foobar': 'Hello Foobar, $1!'
419 return mw
.loader
.using( 'test.implement.order' ).then( function () {
420 assert
.equal( mw
.loader
.getState( 'test.implement.order' ), 'ready', 'final success state' );
425 QUnit
.test( '.implement( styles with @import )', function ( assert
) {
427 done
= assert
.async();
430 'test.implement.import',
432 $element
= $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
434 assertStyleAsync( assert
, $element
, 'float', 'right', function () {
435 assert
.equal( $element
.css( 'text-align' ), 'center',
436 'CSS styles after the @import rule are working'
445 + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
447 + '.mw-test-implement-import { text-align: center; }'
452 return mw
.loader
.using( 'test.implement.import' );
455 QUnit
.test( '.implement( dependency with styles )', function ( assert
) {
456 var $element
= $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
457 $element2
= $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
460 $element
.css( 'float' ),
465 $element2
.css( 'float' ),
470 mw
.loader
.register( [
471 [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
472 [ 'test.implement.e2', '0' ]
479 $element
.css( 'float' ),
481 'Depending module\'s style is applied'
485 all
: '.mw-test-implement-e { float: right; }'
493 $element2
.css( 'float' ),
495 'Dependency\'s style is applied'
499 all
: '.mw-test-implement-e2 { float: left; }'
503 return mw
.loader
.using( 'test.implement.e' );
506 QUnit
.test( '.implement( only scripts )', function ( assert
) {
507 mw
.loader
.implement( 'test.onlyscripts', function () {} );
508 assert
.strictEqual( mw
.loader
.getState( 'test.onlyscripts' ), 'ready' );
511 QUnit
.test( '.implement( only messages )', function ( assert
) {
512 assert
.assertFalse( mw
.messages
.exists( 'T31107' ), 'Verify that the test message doesn\'t exist yet' );
514 mw
.loader
.implement( 'test.implement.msgs', [], {}, { T31107
: 'loaded' } );
516 return mw
.loader
.using( 'test.implement.msgs', function () {
517 assert
.ok( mw
.messages
.exists( 'T31107' ), 'T31107: messages-only module should implement ok' );
521 QUnit
.test( '.implement( empty )', function ( assert
) {
522 mw
.loader
.implement( 'test.empty' );
523 assert
.strictEqual( mw
.loader
.getState( 'test.empty' ), 'ready' );
526 // @covers mw.loader#batchRequest
527 // This is a regression test because in the past we called getCombinedVersion()
528 // for all requested modules, before url splitting took place.
529 // Discovered as part of T188076, but not directly related.
530 QUnit
.test( 'Url composition (modules considered for version)', function ( assert
) {
531 mw
.loader
.register( [
532 // [module, version, dependencies, group, source]
533 [ 'testUrlInc', 'url', [], null, 'testloader' ],
534 [ 'testUrlIncDump', 'dump', [], null, 'testloader' ]
537 mw
.config
.set( 'wgResourceLoaderMaxQueryLength', 10 );
539 return mw
.loader
.using( [ 'testUrlIncDump', 'testUrlInc' ] ).then( function ( require
) {
541 require( 'testUrlIncDump' ).query
,
543 modules
: 'testUrlIncDump',
544 // Expected: Wrapped hash just for this one module
545 // $hash = hash( 'fnv132', 'dump');
546 // base_convert( $hash, 16, 36 ); // "13e9zzn"
547 // Previously: Wrapped hash for both modules, despite being in separate requests
548 // $hash = hash( 'fnv132', 'urldump' );
549 // base_convert( $hash, 16, 36 ); // "18kz9ca"
555 assert
.strictEqual( mw
.loader
.getState( 'testUrlInc' ), 'ready', 'testUrlInc also loaded' );
559 // @covers mw.loader#batchRequest
560 // @covers mw.loader#buildModulesString
561 QUnit
.test( 'Url composition (order of modules for version) – T188076', function ( assert
) {
562 mw
.loader
.register( [
563 // [module, version, dependencies, group, source]
564 [ 'testUrlOrder', 'url', [], null, 'testloader' ],
565 [ 'testUrlOrder.a', '1', [], null, 'testloader' ],
566 [ 'testUrlOrder.b', '2', [], null, 'testloader' ],
567 [ 'testUrlOrderDump', 'dump', [], null, 'testloader' ]
570 return mw
.loader
.using( [
575 ] ).then( function ( require
) {
577 require( 'testUrlOrderDump' ).query
,
579 modules
: 'testUrlOrder,testUrlOrderDump|testUrlOrder.a,b',
580 // Expected: Combined in order after string packing
581 // $hash = hash( 'fnv132', 'urldump12' );
582 // base_convert( $hash, 16, 36 ); // "1knqzan"
583 // Previously: Combined in order of before string packing
584 // $hash = hash( 'fnv132', 'url12dump' );
585 // base_convert( $hash, 16, 36 ); // "11eo3in"
593 QUnit
.test( 'Broken indirect dependency', function ( assert
) {
594 // don't emit an error event
595 this.sandbox
.stub( mw
, 'track' );
597 mw
.loader
.register( [
598 [ 'test.module1', '0' ],
599 [ 'test.module2', '0', [ 'test.module1' ] ],
600 [ 'test.module3', '0', [ 'test.module2' ] ]
602 mw
.loader
.implement( 'test.module1', function () {
603 throw new Error( 'expected' );
605 assert
.strictEqual( mw
.loader
.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
606 assert
.strictEqual( mw
.loader
.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
607 assert
.strictEqual( mw
.loader
.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
609 assert
.strictEqual( mw
.track
.callCount
, 1 );
612 QUnit
.test( 'Out-of-order implementation', function ( assert
) {
613 mw
.loader
.register( [
614 [ 'test.module4', '0' ],
615 [ 'test.module5', '0', [ 'test.module4' ] ],
616 [ 'test.module6', '0', [ 'test.module5' ] ]
618 mw
.loader
.implement( 'test.module4', function () {} );
619 assert
.strictEqual( mw
.loader
.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
620 assert
.strictEqual( mw
.loader
.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
621 assert
.strictEqual( mw
.loader
.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
622 mw
.loader
.implement( 'test.module6', function () {} );
623 assert
.strictEqual( mw
.loader
.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
624 assert
.strictEqual( mw
.loader
.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
625 assert
.strictEqual( mw
.loader
.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
626 mw
.loader
.implement( 'test.module5', function () {} );
627 assert
.strictEqual( mw
.loader
.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
628 assert
.strictEqual( mw
.loader
.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
629 assert
.strictEqual( mw
.loader
.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
632 QUnit
.test( 'Missing dependency', function ( assert
) {
633 mw
.loader
.register( [
634 [ 'test.module7', '0' ],
635 [ 'test.module8', '0', [ 'test.module7' ] ],
636 [ 'test.module9', '0', [ 'test.module8' ] ]
638 mw
.loader
.implement( 'test.module8', function () {} );
639 assert
.strictEqual( mw
.loader
.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
640 assert
.strictEqual( mw
.loader
.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
641 assert
.strictEqual( mw
.loader
.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
642 mw
.loader
.state( 'test.module7', 'missing' );
643 assert
.strictEqual( mw
.loader
.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
644 assert
.strictEqual( mw
.loader
.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
645 assert
.strictEqual( mw
.loader
.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
646 mw
.loader
.implement( 'test.module9', function () {} );
647 assert
.strictEqual( mw
.loader
.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
648 assert
.strictEqual( mw
.loader
.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
649 assert
.strictEqual( mw
.loader
.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
653 assert
.ok( false, 'Success fired despite missing dependency' );
654 assert
.ok( true, 'QUnit expected() count dummy' );
656 function ( e
, dependencies
) {
657 assert
.strictEqual( Array
.isArray( dependencies
), true, 'Expected array of dependencies' );
658 assert
.deepEqual( dependencies
, [ 'test.module7' ], 'Error callback called with module test.module7' );
664 assert
.ok( false, 'Success fired despite missing dependency' );
665 assert
.ok( true, 'QUnit expected() count dummy' );
667 function ( e
, dependencies
) {
668 assert
.strictEqual( Array
.isArray( dependencies
), true, 'Expected array of dependencies' );
672 [ 'test.module7', 'test.module8', 'test.module9' ],
673 'Error callback called with all three modules as dependencies'
679 QUnit
.test( 'Dependency handling', function ( assert
) {
680 var done
= assert
.async();
681 mw
.loader
.register( [
682 // [module, version, dependencies, group, source]
683 [ 'testMissing', '1', [], null, 'testloader' ],
684 [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
685 [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
688 function verifyModuleStates() {
689 assert
.equal( mw
.loader
.getState( 'testMissing' ), 'missing', 'Module "testMissing" state' );
690 assert
.equal( mw
.loader
.getState( 'testUsesMissing' ), 'error', 'Module "testUsesMissing" state' );
691 assert
.equal( mw
.loader
.getState( 'testUsesNestedMissing' ), 'error', 'Module "testUsesNestedMissing" state' );
694 mw
.loader
.using( [ 'testUsesNestedMissing' ],
696 assert
.ok( false, 'Error handler should be invoked.' );
697 assert
.ok( true ); // Dummy to reach QUnit expect()
699 verifyModuleStates();
703 function ( e
, badmodules
) {
704 assert
.ok( true, 'Error handler should be invoked.' );
705 // As soon as server spits out state('testMissing', 'missing');
706 // it will bubble up and trigger the error callback.
707 // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
708 assert
.deepEqual( badmodules
, [ 'testMissing' ], 'Bad modules as expected.' );
710 verifyModuleStates();
717 QUnit
.test( 'Skip-function handling', function ( assert
) {
718 mw
.loader
.register( [
719 // [module, version, dependencies, group, source, skip]
720 [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
721 [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
722 [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
725 return mw
.loader
.using( [ 'testUsesSkippable' ] ).then(
727 assert
.equal( mw
.loader
.getState( 'testSkipped' ), 'ready', 'Skipped module' );
728 assert
.equal( mw
.loader
.getState( 'testNotSkipped' ), 'ready', 'Regular module' );
729 assert
.equal( mw
.loader
.getState( 'testUsesSkippable' ), 'ready', 'Regular module with skippable dependency' );
731 function ( e
, badmodules
) {
732 // Should not fail and QUnit would already catch this,
733 // but add a handler anyway to report details from 'badmodules
734 assert
.deepEqual( badmodules
, [], 'Bad modules' );
739 // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
740 QUnit
.test( '.load( "//protocol-relative" ) - T32825', function ( assert
) {
742 done
= assert
.async();
744 // URL to the callback script
745 target
= QUnit
.fixurl(
746 mw
.config
.get( 'wgServer' ) + mw
.config
.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js'
748 // Ensure a protocol-relative URL for this test
749 target
= target
.replace( /https?:/, '' );
750 assert
.equal( target
.slice( 0, 2 ), '//', 'URL is protocol-relative' );
752 mw
.loader
.testCallback = function () {
753 // Ensure once, delete now
754 delete mw
.loader
.testCallback
;
755 assert
.ok( true, 'callback' );
760 mw
.loader
.load( target
);
763 QUnit
.test( '.load( "/absolute-path" )', function ( assert
) {
765 done
= assert
.async();
767 // URL to the callback script
768 target
= QUnit
.fixurl( mw
.config
.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' );
769 assert
.equal( target
.slice( 0, 1 ), '/', 'URL is relative to document root' );
771 mw
.loader
.testCallback = function () {
772 // Ensure once, delete now
773 delete mw
.loader
.testCallback
;
774 assert
.ok( true, 'callback' );
779 mw
.loader
.load( target
);
782 QUnit
.test( 'Empty string module name - T28804', function ( assert
) {
785 assert
.strictEqual( mw
.loader
.getState( '' ), null, 'State (unregistered)' );
787 mw
.loader
.register( '', 'v1' );
788 assert
.strictEqual( mw
.loader
.getState( '' ), 'registered', 'State (registered)' );
789 assert
.strictEqual( mw
.loader
.getVersion( '' ), 'v1', 'Version' );
791 mw
.loader
.implement( '', function () {
795 return mw
.loader
.using( '', function () {
796 assert
.strictEqual( done
, true, 'script ran' );
797 assert
.strictEqual( mw
.loader
.getState( '' ), 'ready', 'State (ready)' );
801 QUnit
.test( 'Executing race - T112232', function ( assert
) {
804 // The red herring schedules its CSS buffer first. In T112232, a bug in the
805 // state machine would cause the job for testRaceLoadMe to run with an earlier job.
807 'testRaceRedHerring',
809 { css
: [ '.mw-testRaceRedHerring {}' ] }
816 { css
: [ '.mw-testRaceLoadMe { float: left; }' ] }
819 mw
.loader
.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
820 return mw
.loader
.using( 'testRaceLoadMe', function () {
821 assert
.strictEqual( done
, true, 'script ran' );
822 assert
.strictEqual( mw
.loader
.getState( 'testRaceLoadMe' ), 'ready', 'state' );
826 QUnit
.test( 'Stale response caching - T117587', function ( assert
) {
828 mw
.loader
.store
.enabled
= true;
829 mw
.loader
.register( 'test.stale', 'v2' );
830 assert
.strictEqual( mw
.loader
.store
.get( 'test.stale' ), false, 'Not in store' );
832 mw
.loader
.implement( 'test.stale@v1', function () {
836 return mw
.loader
.using( 'test.stale' )
838 assert
.strictEqual( count
, 1 );
839 // After implementing, registry contains version as implemented by the response.
840 assert
.strictEqual( mw
.loader
.getVersion( 'test.stale' ), 'v1', 'Override version' );
841 assert
.strictEqual( mw
.loader
.getState( 'test.stale' ), 'ready' );
842 assert
.ok( mw
.loader
.store
.get( 'test.stale' ), 'In store' );
845 // Reset run time, but keep mw.loader.store
846 mw
.loader
.moduleRegistry
[ 'test.stale' ].script
= undefined;
847 mw
.loader
.moduleRegistry
[ 'test.stale' ].state
= 'registered';
848 mw
.loader
.moduleRegistry
[ 'test.stale' ].version
= 'v2';
850 // Module was stored correctly as v1
851 // On future navigations, it will be ignored until evicted
852 assert
.strictEqual( mw
.loader
.store
.get( 'test.stale' ), false, 'Not in store' );
856 QUnit
.test( 'Stale response caching - backcompat', function ( assert
) {
858 mw
.loader
.store
.enabled
= true;
859 mw
.loader
.register( 'test.stalebc', 'v2' );
860 assert
.strictEqual( mw
.loader
.store
.get( 'test.stalebc' ), false, 'Not in store' );
862 mw
.loader
.implement( 'test.stalebc', function () {
866 return mw
.loader
.using( 'test.stalebc' )
868 assert
.strictEqual( script
, 1, 'module script ran' );
869 assert
.strictEqual( mw
.loader
.getState( 'test.stalebc' ), 'ready' );
870 assert
.ok( mw
.loader
.store
.get( 'test.stalebc' ), 'In store' );
873 // Reset run time, but keep mw.loader.store
874 mw
.loader
.moduleRegistry
[ 'test.stalebc' ].script
= undefined;
875 mw
.loader
.moduleRegistry
[ 'test.stalebc' ].state
= 'registered';
876 mw
.loader
.moduleRegistry
[ 'test.stalebc' ].version
= 'v2';
878 // Legacy behaviour is storing under the expected version,
879 // which woudl lead to whitewashing and stale values (T117587).
880 assert
.ok( mw
.loader
.store
.get( 'test.stalebc' ), 'In store' );
884 QUnit
.test( 'require()', function ( assert
) {
885 mw
.loader
.register( [
886 [ 'test.require1', '0' ],
887 [ 'test.require2', '0' ],
888 [ 'test.require3', '0' ],
889 [ 'test.require4', '0', [ 'test.require3' ] ]
891 mw
.loader
.implement( 'test.require1', function () {} );
892 mw
.loader
.implement( 'test.require2', function ( $, jQuery
, require
, module
) {
895 mw
.loader
.implement( 'test.require3', function ( $, jQuery
, require
, module
) {
896 module
.exports = function () {
897 return 'hello world';
900 mw
.loader
.implement( 'test.require4', function ( $, jQuery
, require
, module
) {
901 var other
= require( 'test.require3' );
908 return mw
.loader
.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] ).then( function ( require
) {
909 var module1
, module2
, module3
, module4
;
911 module1
= require( 'test.require1' );
912 module2
= require( 'test.require2' );
913 module3
= require( 'test.require3' );
914 module4
= require( 'test.require4' );
916 assert
.strictEqual( typeof module1
, 'object', 'export of module with no export' );
917 assert
.strictEqual( module2
, 1, 'export a number' );
918 assert
.strictEqual( module3(), 'hello world', 'export a function' );
919 assert
.strictEqual( typeof module4
.pizza
, 'function', 'export an object' );
920 assert
.strictEqual( module4
.pizza(), 'hello world', 'module can require other modules' );
922 assert
.throws( function () {
923 require( '_badmodule' );
924 }, /is not loaded/, 'Requesting non-existent modules throws error.' );
928 QUnit
.test( 'require() in debug mode', function ( assert
) {
929 var path
= mw
.config
.get( 'wgScriptPath' );
930 mw
.loader
.register( [
931 [ 'test.require.define', '0' ],
932 [ 'test.require.callback', '0', [ 'test.require.define' ] ]
934 mw
.loader
.implement( 'test.require.callback', [ QUnit
.fixurl( path
+ '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
935 mw
.loader
.implement( 'test.require.define', [ QUnit
.fixurl( path
+ '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
937 return mw
.loader
.using( 'test.require.callback' ).then( function ( require
) {
938 var cb
= require( 'test.require.callback' );
939 assert
.strictEqual( cb
.immediate
, 'Defined.', 'module.exports and require work in debug mode' );
940 // Must use try-catch because cb.later() will throw if require is undefined,
941 // which doesn't work well inside Deferred.then() when using jQuery 1.x with QUnit
943 assert
.strictEqual( cb
.later(), 'Defined.', 'require works asynchrously in debug mode' );
945 assert
.equal( null, String( e
), 'require works asynchrously in debug mode' );
950 QUnit
.test( 'Implicit dependencies', function ( assert
) {
969 return mw
.loader
.using( 'user', function () {
970 assert
.strictEqual( site
, 1, 'site module' );
971 assert
.strictEqual( user
, 1, 'user module' );
972 assert
.strictEqual( siteFromUser
, 1, 'site ran before user' );
973 } ).always( function () {
975 mw
.loader
.moduleRegistry
[ 'site' ].state
= 'registered';
976 mw
.loader
.moduleRegistry
[ 'user' ].state
= 'registered';
980 }( mediaWiki
, jQuery
) );