Merge "Cleanups to SqlBagOStuff"
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderTest.php
1 <?php
2
3 class ResourceLoaderTest extends ResourceLoaderTestCase {
4
5 protected function setUp() {
6 parent::setUp();
7
8 $this->setMwGlobals( [
9 'wgResourceLoaderLESSImportPaths' => [
10 dirname( dirname( __DIR__ ) ) . '/data/less/common',
11 ],
12 'wgResourceLoaderLESSVars' => [
13 'foo' => '2px',
14 'Foo' => '#eeeeee',
15 'bar' => 5,
16 ],
17 ] );
18 }
19
20 /**
21 * Ensure the ResourceLoaderRegisterModules hook is called.
22 *
23 * @covers ResourceLoader::__construct
24 */
25 public function testConstructRegistrationHook() {
26 $resourceLoaderRegisterModulesHook = false;
27
28 $this->setMwGlobals( 'wgHooks', [
29 'ResourceLoaderRegisterModules' => [
30 function ( &$resourceLoader ) use ( &$resourceLoaderRegisterModulesHook ) {
31 $resourceLoaderRegisterModulesHook = true;
32 }
33 ]
34 ] );
35
36 $unused = new ResourceLoader();
37 $this->assertTrue(
38 $resourceLoaderRegisterModulesHook,
39 'Hook ResourceLoaderRegisterModules called'
40 );
41 }
42
43 /**
44 * @covers ResourceLoader::register
45 * @covers ResourceLoader::getModule
46 */
47 public function testRegisterValid() {
48 $module = new ResourceLoaderTestModule();
49 $resourceLoader = new EmptyResourceLoader();
50 $resourceLoader->register( 'test', $module );
51 $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
52 }
53
54 /**
55 * @covers ResourceLoader::register
56 */
57 public function testRegisterInvalidName() {
58 $resourceLoader = new EmptyResourceLoader();
59 $this->setExpectedException( 'MWException', "name 'test!invalid' is invalid" );
60 $resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
61 }
62
63 /**
64 * @covers ResourceLoader::register
65 */
66 public function testRegisterInvalidType() {
67 $resourceLoader = new EmptyResourceLoader();
68 $this->setExpectedException( 'MWException', 'ResourceLoader module info type error' );
69 $resourceLoader->register( 'test', new stdClass() );
70 }
71
72 /**
73 * @covers ResourceLoader::getModuleNames
74 */
75 public function testGetModuleNames() {
76 // Use an empty one so that core and extension modules don't get in.
77 $resourceLoader = new EmptyResourceLoader();
78 $resourceLoader->register( 'test.foo', new ResourceLoaderTestModule() );
79 $resourceLoader->register( 'test.bar', new ResourceLoaderTestModule() );
80 $this->assertEquals(
81 [ 'test.foo', 'test.bar' ],
82 $resourceLoader->getModuleNames()
83 );
84 }
85
86 /**
87 * @covers ResourceLoader::isModuleRegistered
88 */
89 public function testIsModuleRegistered() {
90 $rl = new EmptyResourceLoader();
91 $rl->register( 'test', new ResourceLoaderTestModule() );
92 $this->assertTrue( $rl->isModuleRegistered( 'test' ) );
93 $this->assertFalse( $rl->isModuleRegistered( 'test.unknown' ) );
94 }
95
96 /**
97 * @covers ResourceLoader::getModule
98 */
99 public function testGetModuleUnknown() {
100 $rl = new EmptyResourceLoader();
101 $this->assertSame( null, $rl->getModule( 'test' ) );
102 }
103
104 /**
105 * @covers ResourceLoader::getModule
106 */
107 public function testGetModuleClass() {
108 $rl = new EmptyResourceLoader();
109 $rl->register( 'test', [ 'class' => ResourceLoaderTestModule::class ] );
110 $this->assertInstanceOf(
111 ResourceLoaderTestModule::class,
112 $rl->getModule( 'test' )
113 );
114 }
115
116 /**
117 * @covers ResourceLoader::getModule
118 */
119 public function testGetModuleClassDefault() {
120 $rl = new EmptyResourceLoader();
121 $rl->register( 'test', [] );
122 $this->assertInstanceOf(
123 ResourceLoaderFileModule::class,
124 $rl->getModule( 'test' ),
125 'Array-style module registrations default to FileModule'
126 );
127 }
128
129 /**
130 * @covers ResourceLoaderFileModule::compileLessFile
131 */
132 public function testLessFileCompilation() {
133 $context = $this->getResourceLoaderContext();
134 $basePath = __DIR__ . '/../../data/less/module';
135 $module = new ResourceLoaderFileModule( [
136 'localBasePath' => $basePath,
137 'styles' => [ 'styles.less' ],
138 ] );
139 $module->setName( 'test.less' );
140 $styles = $module->getStyles( $context );
141 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
142 }
143
144 public static function providePackedModules() {
145 return [
146 [
147 'Example from makePackedModulesString doc comment',
148 [ 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ],
149 'foo.bar,baz|bar.baz,quux',
150 ],
151 [
152 'Example from expandModuleNames doc comment',
153 [ 'jquery.foo', 'jquery.bar', 'jquery.ui.baz', 'jquery.ui.quux' ],
154 'jquery.foo,bar|jquery.ui.baz,quux',
155 ],
156 [
157 'Regression fixed in r88706 with dotless names',
158 [ 'foo', 'bar', 'baz' ],
159 'foo,bar,baz',
160 ],
161 [
162 'Prefixless modules after a prefixed module',
163 [ 'single.module', 'foobar', 'foobaz' ],
164 'single.module|foobar,foobaz',
165 ],
166 ];
167 }
168
169 /**
170 * @dataProvider providePackedModules
171 * @covers ResourceLoader::makePackedModulesString
172 */
173 public function testMakePackedModulesString( $desc, $modules, $packed ) {
174 $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
175 }
176
177 /**
178 * @dataProvider providePackedModules
179 * @covers ResourceLoaderContext::expandModuleNames
180 */
181 public function testexpandModuleNames( $desc, $modules, $packed ) {
182 $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
183 }
184
185 public static function provideAddSource() {
186 return [
187 [ 'foowiki', 'https://example.org/w/load.php', 'foowiki' ],
188 [ 'foowiki', [ 'loadScript' => 'https://example.org/w/load.php' ], 'foowiki' ],
189 [
190 [
191 'foowiki' => 'https://example.org/w/load.php',
192 'bazwiki' => 'https://example.com/w/load.php',
193 ],
194 null,
195 [ 'foowiki', 'bazwiki' ]
196 ]
197 ];
198 }
199
200 /**
201 * @dataProvider provideAddSource
202 * @covers ResourceLoader::addSource
203 * @covers ResourceLoader::getSources
204 */
205 public function testAddSource( $name, $info, $expected ) {
206 $rl = new ResourceLoader;
207 $rl->addSource( $name, $info );
208 if ( is_array( $expected ) ) {
209 foreach ( $expected as $source ) {
210 $this->assertArrayHasKey( $source, $rl->getSources() );
211 }
212 } else {
213 $this->assertArrayHasKey( $expected, $rl->getSources() );
214 }
215 }
216
217 /**
218 * @covers ResourceLoader::addSource
219 */
220 public function testAddSourceDupe() {
221 $rl = new ResourceLoader;
222 $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
223 $rl->addSource( 'foo', 'https://example.org/w/load.php' );
224 $rl->addSource( 'foo', 'https://example.com/w/load.php' );
225 }
226
227 /**
228 * @covers ResourceLoader::addSource
229 */
230 public function testAddSourceInvalid() {
231 $rl = new ResourceLoader;
232 $this->setExpectedException( 'MWException', 'with no "loadScript" key' );
233 $rl->addSource( 'foo', [ 'x' => 'https://example.org/w/load.php' ] );
234 }
235
236 public static function provideLoaderImplement() {
237 return [
238 [ [
239 'title' => 'Implement scripts, styles and messages',
240
241 'name' => 'test.example',
242 'scripts' => 'mw.example();',
243 'styles' => [ 'css' => [ '.mw-example {}' ] ],
244 'messages' => [ 'example' => '' ],
245 'templates' => [],
246
247 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
248 mw.example();
249 }, {
250 "css": [
251 ".mw-example {}"
252 ]
253 }, {
254 "example": ""
255 } );',
256 ] ],
257 [ [
258 'title' => 'Implement scripts',
259
260 'name' => 'test.example',
261 'scripts' => 'mw.example();',
262 'styles' => [],
263
264 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
265 mw.example();
266 } );',
267 ] ],
268 [ [
269 'title' => 'Implement styles',
270
271 'name' => 'test.example',
272 'scripts' => [],
273 'styles' => [ 'css' => [ '.mw-example {}' ] ],
274 'messages' => new XmlJsCode( '{}' ),
275
276 'expected' => 'mw.loader.implement( "test.example", [], {
277 "css": [
278 ".mw-example {}"
279 ]
280 } );',
281 ] ],
282 [ [
283 'title' => 'Implement scripts and messages',
284
285 'name' => 'test.example',
286 'scripts' => 'mw.example();',
287 'messages' => [ 'example' => '' ],
288
289 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
290 mw.example();
291 }, {}, {
292 "example": ""
293 } );',
294 ] ],
295 [ [
296 'title' => 'Implement scripts and templates',
297
298 'name' => 'test.example',
299 'scripts' => 'mw.example();',
300 'templates' => [ 'example.html' => '' ],
301
302 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
303 mw.example();
304 }, {}, {}, {
305 "example.html": ""
306 } );',
307 ] ],
308 [ [
309 'title' => 'Implement unwrapped user script',
310
311 'name' => 'user',
312 'scripts' => 'mw.example( 1 );',
313
314 'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
315 ] ],
316 [ [
317 'title' => 'Implement unwrapped user script',
318 'debug' => false,
319
320 'name' => 'user',
321 'scripts' => 'mw.example( 1 );',
322
323 'expected' => 'mw.loader.implement("user","mw.example(1);");',
324 ] ],
325 ];
326 }
327
328 /**
329 * @dataProvider provideLoaderImplement
330 * @covers ResourceLoader::makeLoaderImplementScript
331 * @covers ResourceLoader::trimArray
332 */
333 public function testMakeLoaderImplementScript( $case ) {
334 $case += [
335 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' ),
336 'debug' => true
337 ];
338 ResourceLoader::clearCache();
339 $this->setMwGlobals( 'wgResourceLoaderDebug', $case['debug'] );
340
341 $this->assertEquals(
342 $case['expected'],
343 ResourceLoader::makeLoaderImplementScript(
344 $case['name'],
345 $case['scripts'],
346 $case['styles'],
347 $case['messages'],
348 $case['templates']
349 )
350 );
351 }
352
353 /**
354 * @covers ResourceLoader::makeLoaderImplementScript
355 */
356 public function testMakeLoaderImplementScriptInvalid() {
357 $this->setExpectedException( 'MWException', 'Invalid scripts error' );
358 ResourceLoader::makeLoaderImplementScript(
359 'test', // name
360 123, // scripts
361 null, // styles
362 null, // messages
363 null // templates
364 );
365 }
366
367 /**
368 * @covers ResourceLoader::makeLoaderSourcesScript
369 */
370 public function testMakeLoaderSourcesScript() {
371 $this->assertEquals(
372 'mw.loader.addSource( "local", "/w/load.php" );',
373 ResourceLoader::makeLoaderSourcesScript( 'local', '/w/load.php' )
374 );
375 $this->assertEquals(
376 'mw.loader.addSource( {
377 "local": "/w/load.php"
378 } );',
379 ResourceLoader::makeLoaderSourcesScript( [ 'local' => '/w/load.php' ] )
380 );
381 $this->assertEquals(
382 'mw.loader.addSource( {
383 "local": "/w/load.php",
384 "example": "https://example.org/w/load.php"
385 } );',
386 ResourceLoader::makeLoaderSourcesScript( [
387 'local' => '/w/load.php',
388 'example' => 'https://example.org/w/load.php'
389 ] )
390 );
391 $this->assertEquals(
392 'mw.loader.addSource( [] );',
393 ResourceLoader::makeLoaderSourcesScript( [] )
394 );
395 }
396
397 private static function fakeSources() {
398 return [
399 'examplewiki' => [
400 'loadScript' => '//example.org/w/load.php',
401 'apiScript' => '//example.org/w/api.php',
402 ],
403 'example2wiki' => [
404 'loadScript' => '//example.com/w/load.php',
405 'apiScript' => '//example.com/w/api.php',
406 ],
407 ];
408 }
409
410 /**
411 * @covers ResourceLoader::getLoadScript
412 */
413 public function testGetLoadScript() {
414 $this->setMwGlobals( 'wgResourceLoaderSources', [] );
415 $rl = new ResourceLoader();
416 $sources = self::fakeSources();
417 $rl->addSource( $sources );
418 foreach ( [ 'examplewiki', 'example2wiki' ] as $name ) {
419 $this->assertEquals( $rl->getLoadScript( $name ), $sources[$name]['loadScript'] );
420 }
421
422 try {
423 $rl->getLoadScript( 'thiswasneverreigstered' );
424 $this->assertTrue( false, 'ResourceLoader::getLoadScript should have thrown an exception' );
425 } catch ( MWException $e ) {
426 $this->assertTrue( true );
427 }
428 }
429 }