Merge "Override momentjs's digit transform logic with MW's"
[lhc/web/wiklou.git] / tests / phpunit / includes / registration / ExtensionProcessorTest.php
1 <?php
2
3 class ExtensionProcessorTest extends MediaWikiTestCase {
4
5 private $dir;
6
7 public function setUp() {
8 parent::setUp();
9 $this->dir = __DIR__ . '/FooBar/extension.json';
10 }
11
12 /**
13 * 'name' is absolutely required
14 *
15 * @var array
16 */
17 public static $default = [
18 'name' => 'FooBar',
19 ];
20
21 /**
22 * @covers ExtensionProcessor::extractInfo
23 */
24 public function testExtractInfo() {
25 // Test that attributes that begin with @ are ignored
26 $processor = new ExtensionProcessor();
27 $processor->extractInfo( $this->dir, self::$default + [
28 '@metadata' => [ 'foobarbaz' ],
29 'AnAttribute' => [ 'omg' ],
30 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
31 ], 1 );
32
33 $extracted = $processor->getExtractedInfo();
34 $attributes = $extracted['attributes'];
35 $this->assertArrayHasKey( 'AnAttribute', $attributes );
36 $this->assertArrayNotHasKey( '@metadata', $attributes );
37 $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
38 }
39
40 public static function provideRegisterHooks() {
41 $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
42 // Format:
43 // Current $wgHooks
44 // Content in extension.json
45 // Expected value of $wgHooks
46 return [
47 // No hooks
48 [
49 [],
50 self::$default,
51 $merge,
52 ],
53 // No current hooks, adding one for "FooBaz" in string format
54 [
55 [],
56 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
57 [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
58 ],
59 // Hook for "FooBaz", adding another one
60 [
61 [ 'FooBaz' => [ 'PriorCallback' ] ],
62 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
63 [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
64 ],
65 // No current hooks, adding one for "FooBaz" in verbose array format
66 [
67 [],
68 [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
69 [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
70 ],
71 // Hook for "BarBaz", adding one for "FooBaz"
72 [
73 [ 'BarBaz' => [ 'BarBazCallback' ] ],
74 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
75 [
76 'BarBaz' => [ 'BarBazCallback' ],
77 'FooBaz' => [ 'FooBazCallback' ],
78 ] + $merge,
79 ],
80 // Callbacks for FooBaz wrapped in an array
81 [
82 [],
83 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
84 [
85 'FooBaz' => [ 'Callback1' ],
86 ] + $merge,
87 ],
88 // Multiple callbacks for FooBaz hook
89 [
90 [],
91 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
92 [
93 'FooBaz' => [ 'Callback1', 'Callback2' ],
94 ] + $merge,
95 ],
96 ];
97 }
98
99 /**
100 * @covers ExtensionProcessor::extractHooks
101 * @dataProvider provideRegisterHooks
102 */
103 public function testRegisterHooks( $pre, $info, $expected ) {
104 $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
105 $processor->extractInfo( $this->dir, $info, 1 );
106 $extracted = $processor->getExtractedInfo();
107 $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
108 }
109
110 /**
111 * @covers ExtensionProcessor::extractConfig
112 */
113 public function testExtractConfig() {
114 $processor = new ExtensionProcessor;
115 $info = [
116 'config' => [
117 'Bar' => 'somevalue',
118 'Foo' => 10,
119 '@IGNORED' => 'yes',
120 ],
121 ] + self::$default;
122 $info2 = [
123 'config' => [
124 '_prefix' => 'eg',
125 'Bar' => 'somevalue'
126 ],
127 'name' => 'FooBar2',
128 ];
129 $processor->extractInfo( $this->dir, $info, 1 );
130 $processor->extractInfo( $this->dir, $info2, 1 );
131 $extracted = $processor->getExtractedInfo();
132 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
133 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
134 $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
135 // Custom prefix:
136 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
137 }
138
139 public static function provideExtractExtensionMessagesFiles() {
140 $dir = __DIR__ . '/FooBar/';
141 return [
142 [
143 [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
144 [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
145 ],
146 [
147 [
148 'ExtensionMessagesFiles' => [
149 'FooBarAlias' => 'FooBar.alias.php',
150 'FooBarMagic' => 'FooBar.magic.i18n.php',
151 ],
152 ],
153 [
154 'wgExtensionMessagesFiles' => [
155 'FooBarAlias' => $dir . 'FooBar.alias.php',
156 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
157 ],
158 ],
159 ],
160 ];
161 }
162
163 /**
164 * @covers ExtensionProcessor::extractExtensionMessagesFiles
165 * @dataProvider provideExtractExtensionMessagesFiles
166 */
167 public function testExtractExtensionMessagesFiles( $input, $expected ) {
168 $processor = new ExtensionProcessor();
169 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
170 $out = $processor->getExtractedInfo();
171 foreach ( $expected as $key => $value ) {
172 $this->assertEquals( $value, $out['globals'][$key] );
173 }
174 }
175
176 public static function provideExtractMessagesDirs() {
177 $dir = __DIR__ . '/FooBar/';
178 return [
179 [
180 [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
181 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
182 ],
183 [
184 [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
185 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
186 ],
187 ];
188 }
189
190 /**
191 * @covers ExtensionProcessor::extractMessagesDirs
192 * @dataProvider provideExtractMessagesDirs
193 */
194 public function testExtractMessagesDirs( $input, $expected ) {
195 $processor = new ExtensionProcessor();
196 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
197 $out = $processor->getExtractedInfo();
198 foreach ( $expected as $key => $value ) {
199 $this->assertEquals( $value, $out['globals'][$key] );
200 }
201 }
202
203 /**
204 * @covers ExtensionProcessor::extractCredits
205 */
206 public function testExtractCredits() {
207 $processor = new ExtensionProcessor();
208 $processor->extractInfo( $this->dir, self::$default, 1 );
209 $this->setExpectedException( 'Exception' );
210 $processor->extractInfo( $this->dir, self::$default, 1 );
211 }
212
213 /**
214 * @covers ExtensionProcessor::extractResourceLoaderModules
215 * @dataProvider provideExtractResourceLoaderModules
216 */
217 public function testExtractResourceLoaderModules( $input, $expected ) {
218 $processor = new ExtensionProcessor();
219 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
220 $out = $processor->getExtractedInfo();
221 foreach ( $expected as $key => $value ) {
222 $this->assertEquals( $value, $out['globals'][$key] );
223 }
224 }
225
226 public static function provideExtractResourceLoaderModules() {
227 $dir = __DIR__ . '/FooBar';
228 return [
229 // Generic module with localBasePath/remoteExtPath specified
230 [
231 // Input
232 [
233 'ResourceModules' => [
234 'test.foo' => [
235 'styles' => 'foobar.js',
236 'localBasePath' => '',
237 'remoteExtPath' => 'FooBar',
238 ],
239 ],
240 ],
241 // Expected
242 [
243 'wgResourceModules' => [
244 'test.foo' => [
245 'styles' => 'foobar.js',
246 'localBasePath' => $dir,
247 'remoteExtPath' => 'FooBar',
248 ],
249 ],
250 ],
251 ],
252 // ResourceFileModulePaths specified:
253 [
254 // Input
255 [
256 'ResourceFileModulePaths' => [
257 'localBasePath' => '',
258 'remoteExtPath' => 'FooBar',
259 ],
260 'ResourceModules' => [
261 // No paths
262 'test.foo' => [
263 'styles' => 'foo.js',
264 ],
265 // Different paths set
266 'test.bar' => [
267 'styles' => 'bar.js',
268 'localBasePath' => 'subdir',
269 'remoteExtPath' => 'FooBar/subdir',
270 ],
271 // Custom class with no paths set
272 'test.class' => [
273 'class' => 'FooBarModule',
274 'extra' => 'argument',
275 ],
276 // Custom class with a localBasePath
277 'test.class.with.path' => [
278 'class' => 'FooBarPathModule',
279 'extra' => 'argument',
280 'localBasePath' => '',
281 ]
282 ],
283 ],
284 // Expected
285 [
286 'wgResourceModules' => [
287 'test.foo' => [
288 'styles' => 'foo.js',
289 'localBasePath' => $dir,
290 'remoteExtPath' => 'FooBar',
291 ],
292 'test.bar' => [
293 'styles' => 'bar.js',
294 'localBasePath' => "$dir/subdir",
295 'remoteExtPath' => 'FooBar/subdir',
296 ],
297 'test.class' => [
298 'class' => 'FooBarModule',
299 'extra' => 'argument',
300 'localBasePath' => $dir,
301 'remoteExtPath' => 'FooBar',
302 ],
303 'test.class.with.path' => [
304 'class' => 'FooBarPathModule',
305 'extra' => 'argument',
306 'localBasePath' => $dir,
307 'remoteExtPath' => 'FooBar',
308 ]
309 ],
310 ],
311 ],
312 // ResourceModuleSkinStyles with file module paths
313 [
314 // Input
315 [
316 'ResourceFileModulePaths' => [
317 'localBasePath' => '',
318 'remoteSkinPath' => 'FooBar',
319 ],
320 'ResourceModuleSkinStyles' => [
321 'foobar' => [
322 'test.foo' => 'foo.css',
323 ]
324 ],
325 ],
326 // Expected
327 [
328 'wgResourceModuleSkinStyles' => [
329 'foobar' => [
330 'test.foo' => 'foo.css',
331 'localBasePath' => $dir,
332 'remoteSkinPath' => 'FooBar',
333 ],
334 ],
335 ],
336 ],
337 // ResourceModuleSkinStyles with file module paths and an override
338 [
339 // Input
340 [
341 'ResourceFileModulePaths' => [
342 'localBasePath' => '',
343 'remoteSkinPath' => 'FooBar',
344 ],
345 'ResourceModuleSkinStyles' => [
346 'foobar' => [
347 'test.foo' => 'foo.css',
348 'remoteSkinPath' => 'BarFoo'
349 ],
350 ],
351 ],
352 // Expected
353 [
354 'wgResourceModuleSkinStyles' => [
355 'foobar' => [
356 'test.foo' => 'foo.css',
357 'localBasePath' => $dir,
358 'remoteSkinPath' => 'BarFoo',
359 ],
360 ],
361 ],
362 ],
363 ];
364 }
365
366 public static function provideSetToGlobal() {
367 return [
368 [
369 [ 'wgAPIModules', 'wgAvailableRights' ],
370 [],
371 [
372 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
373 'AvailableRights' => [ 'foobar', 'unfoobar' ],
374 ],
375 [
376 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
377 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
378 ],
379 ],
380 [
381 [ 'wgAPIModules', 'wgAvailableRights' ],
382 [
383 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
384 'wgAvailableRights' => [ 'barbaz' ]
385 ],
386 [
387 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
388 'AvailableRights' => [ 'foobar', 'unfoobar' ],
389 ],
390 [
391 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
392 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
393 ],
394 ],
395 [
396 [ 'wgGroupPermissions' ],
397 [
398 'wgGroupPermissions' => [
399 'sysop' => [ 'delete' ]
400 ],
401 ],
402 [
403 'GroupPermissions' => [
404 'sysop' => [ 'undelete' ],
405 'user' => [ 'edit' ]
406 ],
407 ],
408 [
409 'wgGroupPermissions' => [
410 'sysop' => [ 'delete', 'undelete' ],
411 'user' => [ 'edit' ]
412 ],
413 ]
414 ]
415 ];
416 }
417 }
418
419 /**
420 * Allow overriding the default value of $this->globals
421 * so we can test merging
422 */
423 class MockExtensionProcessor extends ExtensionProcessor {
424 public function __construct( $globals = [] ) {
425 $this->globals = $globals + $this->globals;
426 }
427 }