3 use Wikimedia\TestingAccessWrapper
;
5 class ExtensionProcessorTest
extends MediaWikiTestCase
{
7 private $dir, $dirname;
9 public function setUp() {
11 $this->dir
= __DIR__
. '/FooBar/extension.json';
12 $this->dirname
= dirname( $this->dir
);
16 * 'name' is absolutely required
20 public static $default = [
25 * @covers ExtensionProcessor::extractInfo
27 public function testExtractInfo() {
28 // Test that attributes that begin with @ are ignored
29 $processor = new ExtensionProcessor();
30 $processor->extractInfo( $this->dir
, self
::$default +
[
31 '@metadata' => [ 'foobarbaz' ],
32 'AnAttribute' => [ 'omg' ],
33 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
36 $extracted = $processor->getExtractedInfo();
37 $attributes = $extracted['attributes'];
38 $this->assertArrayHasKey( 'AnAttribute', $attributes );
39 $this->assertArrayNotHasKey( '@metadata', $attributes );
40 $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
44 * @covers ExtensionProcessor::extractInfo
46 public function testExtractInfo_namespaces() {
47 // Test that namespace IDs can be overwritten
48 if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
49 define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
52 $processor = new ExtensionProcessor();
53 $processor->extractInfo( $this->dir
, self
::$default +
[
57 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
59 'content' => 'TestModel'
61 [ // Test_X will use ID 123456 not 334400
63 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
65 'content' => 'TestModel'
70 $extracted = $processor->getExtractedInfo();
72 $this->assertArrayHasKey(
73 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
76 $this->assertArrayNotHasKey(
77 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
82 $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
86 $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
87 $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
88 $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
89 $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
91 $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
92 $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
95 public static function provideRegisterHooks() {
96 $merge = [ ExtensionRegistry
::MERGE_STRATEGY
=> 'array_merge_recursive' ];
99 // Content in extension.json
100 // Expected value of $wgHooks
108 // No current hooks, adding one for "FooBaz" in string format
111 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
112 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
114 // Hook for "FooBaz", adding another one
116 [ 'FooBaz' => [ 'PriorCallback' ] ],
117 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
118 [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] +
$merge,
120 // No current hooks, adding one for "FooBaz" in verbose array format
123 [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self
::$default,
124 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
126 // Hook for "BarBaz", adding one for "FooBaz"
128 [ 'BarBaz' => [ 'BarBazCallback' ] ],
129 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
131 'BarBaz' => [ 'BarBazCallback' ],
132 'FooBaz' => [ 'FooBazCallback' ],
135 // Callbacks for FooBaz wrapped in an array
138 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self
::$default,
140 'FooBaz' => [ 'Callback1' ],
143 // Multiple callbacks for FooBaz hook
146 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self
::$default,
148 'FooBaz' => [ 'Callback1', 'Callback2' ],
155 * @covers ExtensionProcessor::extractHooks
156 * @dataProvider provideRegisterHooks
158 public function testRegisterHooks( $pre, $info, $expected ) {
159 $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
160 $processor->extractInfo( $this->dir
, $info, 1 );
161 $extracted = $processor->getExtractedInfo();
162 $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
166 * @covers ExtensionProcessor::extractConfig1
168 public function testExtractConfig1() {
169 $processor = new ExtensionProcessor
;
172 'Bar' => 'somevalue',
184 $processor->extractInfo( $this->dir
, $info, 1 );
185 $processor->extractInfo( $this->dir
, $info2, 1 );
186 $extracted = $processor->getExtractedInfo();
187 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
188 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
189 $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
191 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
195 * @covers ExtensionProcessor::extractConfig2
197 public function testExtractConfig2() {
198 $processor = new ExtensionProcessor
;
201 'Bar' => [ 'value' => 'somevalue' ],
202 'Foo' => [ 'value' => 10 ],
203 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
208 'Bar' => [ 'value' => 'somevalue' ],
210 'config_prefix' => 'eg',
213 $processor->extractInfo( $this->dir
, $info, 2 );
214 $processor->extractInfo( $this->dir
, $info2, 2 );
215 $extracted = $processor->getExtractedInfo();
216 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
217 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
218 $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
220 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
223 public static function provideExtractExtensionMessagesFiles() {
224 $dir = __DIR__
. '/FooBar/';
227 [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
228 [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
232 'ExtensionMessagesFiles' => [
233 'FooBarAlias' => 'FooBar.alias.php',
234 'FooBarMagic' => 'FooBar.magic.i18n.php',
238 'wgExtensionMessagesFiles' => [
239 'FooBarAlias' => $dir . 'FooBar.alias.php',
240 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
248 * @covers ExtensionProcessor::extractExtensionMessagesFiles
249 * @dataProvider provideExtractExtensionMessagesFiles
251 public function testExtractExtensionMessagesFiles( $input, $expected ) {
252 $processor = new ExtensionProcessor();
253 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
254 $out = $processor->getExtractedInfo();
255 foreach ( $expected as $key => $value ) {
256 $this->assertEquals( $value, $out['globals'][$key] );
260 public static function provideExtractMessagesDirs() {
261 $dir = __DIR__
. '/FooBar/';
264 [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
265 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
268 [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
269 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
275 * @covers ExtensionProcessor::extractMessagesDirs
276 * @dataProvider provideExtractMessagesDirs
278 public function testExtractMessagesDirs( $input, $expected ) {
279 $processor = new ExtensionProcessor();
280 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
281 $out = $processor->getExtractedInfo();
282 foreach ( $expected as $key => $value ) {
283 $this->assertEquals( $value, $out['globals'][$key] );
288 * @covers ExtensionProcessor::extractCredits
290 public function testExtractCredits() {
291 $processor = new ExtensionProcessor();
292 $processor->extractInfo( $this->dir
, self
::$default, 1 );
293 $this->setExpectedException( 'Exception' );
294 $processor->extractInfo( $this->dir
, self
::$default, 1 );
298 * @covers ExtensionProcessor::extractResourceLoaderModules
299 * @dataProvider provideExtractResourceLoaderModules
301 public function testExtractResourceLoaderModules( $input, $expected ) {
302 $processor = new ExtensionProcessor();
303 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
304 $out = $processor->getExtractedInfo();
305 foreach ( $expected as $key => $value ) {
306 $this->assertEquals( $value, $out['globals'][$key] );
310 public static function provideExtractResourceLoaderModules() {
311 $dir = __DIR__
. '/FooBar';
313 // Generic module with localBasePath/remoteExtPath specified
317 'ResourceModules' => [
319 'styles' => 'foobar.js',
320 'localBasePath' => '',
321 'remoteExtPath' => 'FooBar',
327 'wgResourceModules' => [
329 'styles' => 'foobar.js',
330 'localBasePath' => $dir,
331 'remoteExtPath' => 'FooBar',
336 // ResourceFileModulePaths specified:
340 'ResourceFileModulePaths' => [
341 'localBasePath' => '',
342 'remoteExtPath' => 'FooBar',
344 'ResourceModules' => [
347 'styles' => 'foo.js',
349 // Different paths set
351 'styles' => 'bar.js',
352 'localBasePath' => 'subdir',
353 'remoteExtPath' => 'FooBar/subdir',
355 // Custom class with no paths set
357 'class' => 'FooBarModule',
358 'extra' => 'argument',
360 // Custom class with a localBasePath
361 'test.class.with.path' => [
362 'class' => 'FooBarPathModule',
363 'extra' => 'argument',
364 'localBasePath' => '',
370 'wgResourceModules' => [
372 'styles' => 'foo.js',
373 'localBasePath' => $dir,
374 'remoteExtPath' => 'FooBar',
377 'styles' => 'bar.js',
378 'localBasePath' => "$dir/subdir",
379 'remoteExtPath' => 'FooBar/subdir',
382 'class' => 'FooBarModule',
383 'extra' => 'argument',
384 'localBasePath' => $dir,
385 'remoteExtPath' => 'FooBar',
387 'test.class.with.path' => [
388 'class' => 'FooBarPathModule',
389 'extra' => 'argument',
390 'localBasePath' => $dir,
391 'remoteExtPath' => 'FooBar',
396 // ResourceModuleSkinStyles with file module paths
400 'ResourceFileModulePaths' => [
401 'localBasePath' => '',
402 'remoteSkinPath' => 'FooBar',
404 'ResourceModuleSkinStyles' => [
406 'test.foo' => 'foo.css',
412 'wgResourceModuleSkinStyles' => [
414 'test.foo' => 'foo.css',
415 'localBasePath' => $dir,
416 'remoteSkinPath' => 'FooBar',
421 // ResourceModuleSkinStyles with file module paths and an override
425 'ResourceFileModulePaths' => [
426 'localBasePath' => '',
427 'remoteSkinPath' => 'FooBar',
429 'ResourceModuleSkinStyles' => [
431 'test.foo' => 'foo.css',
432 'remoteSkinPath' => 'BarFoo'
438 'wgResourceModuleSkinStyles' => [
440 'test.foo' => 'foo.css',
441 'localBasePath' => $dir,
442 'remoteSkinPath' => 'BarFoo',
450 public static function provideSetToGlobal() {
453 [ 'wgAPIModules', 'wgAvailableRights' ],
456 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
457 'AvailableRights' => [ 'foobar', 'unfoobar' ],
460 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
461 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
465 [ 'wgAPIModules', 'wgAvailableRights' ],
467 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
468 'wgAvailableRights' => [ 'barbaz' ]
471 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
472 'AvailableRights' => [ 'foobar', 'unfoobar' ],
475 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
476 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
480 [ 'wgGroupPermissions' ],
482 'wgGroupPermissions' => [
483 'sysop' => [ 'delete' ]
487 'GroupPermissions' => [
488 'sysop' => [ 'undelete' ],
493 'wgGroupPermissions' => [
494 'sysop' => [ 'delete', 'undelete' ],
503 * Attributes under manifest_version 2
505 * @covers ExtensionProcessor::extractAttributes
506 * @covers ExtensionProcessor::getExtractedInfo
508 public function testExtractAttributes() {
509 $processor = new ExtensionProcessor();
510 // Load FooBar extension
511 $processor->extractInfo( $this->dir
, [ 'name' => 'FooBar' ], 2 );
512 $processor->extractInfo(
534 $info = $processor->getExtractedInfo();
535 $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
536 $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
537 $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
541 * Attributes under manifest_version 1
543 * @covers ExtensionProcessor::extractInfo
545 public function testAttributes1() {
546 $processor = new ExtensionProcessor();
547 $processor->extractInfo(
554 'FizzBuzzMorePlugins' => [
561 $info = $processor->getExtractedInfo();
562 $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
563 $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
564 $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
565 $this->assertSame( [ 'ext.baz.fizzbuzz' ], $info['attributes']['FizzBuzzMorePlugins'] );
568 public function testGlobalSettingsDocumentedInSchema() {
570 $globalSettings = TestingAccessWrapper
::newFromClass(
571 ExtensionProcessor
::class )->globalSettings
;
573 $version = ExtensionRegistry
::MANIFEST_VERSION
;
574 $schema = FormatJson
::decode(
575 file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
579 foreach ( $globalSettings as $global ) {
580 if ( !isset( $schema['properties'][$global] ) ) {
581 $missing[] = $global;
585 $this->assertEquals( [], $missing,
586 "The following global settings are not documented in docs/extension.schema.json" );
591 * Allow overriding the default value of $this->globals
592 * so we can test merging
594 class MockExtensionProcessor
extends ExtensionProcessor
{
595 public function __construct( $globals = [] ) {
596 $this->globals
= $globals +
$this->globals
;