3 class ExtensionProcessorTest
extends MediaWikiTestCase
{
5 private $dir, $dirname;
7 public function setUp() {
9 $this->dir
= __DIR__
. '/FooBar/extension.json';
10 $this->dirname
= dirname( $this->dir
);
14 * 'name' is absolutely required
18 public static $default = [
23 * @covers ExtensionProcessor::extractInfo
25 public function testExtractInfo() {
26 // Test that attributes that begin with @ are ignored
27 $processor = new ExtensionProcessor();
28 $processor->extractInfo( $this->dir
, self
::$default +
[
29 '@metadata' => [ 'foobarbaz' ],
30 'AnAttribute' => [ 'omg' ],
31 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
34 $extracted = $processor->getExtractedInfo();
35 $attributes = $extracted['attributes'];
36 $this->assertArrayHasKey( 'AnAttribute', $attributes );
37 $this->assertArrayNotHasKey( '@metadata', $attributes );
38 $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
41 public static function provideRegisterHooks() {
42 $merge = [ ExtensionRegistry
::MERGE_STRATEGY
=> 'array_merge_recursive' ];
45 // Content in extension.json
46 // Expected value of $wgHooks
54 // No current hooks, adding one for "FooBaz" in string format
57 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
58 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
60 // Hook for "FooBaz", adding another one
62 [ 'FooBaz' => [ 'PriorCallback' ] ],
63 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
64 [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] +
$merge,
66 // No current hooks, adding one for "FooBaz" in verbose array format
69 [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self
::$default,
70 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
72 // Hook for "BarBaz", adding one for "FooBaz"
74 [ 'BarBaz' => [ 'BarBazCallback' ] ],
75 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
77 'BarBaz' => [ 'BarBazCallback' ],
78 'FooBaz' => [ 'FooBazCallback' ],
81 // Callbacks for FooBaz wrapped in an array
84 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self
::$default,
86 'FooBaz' => [ 'Callback1' ],
89 // Multiple callbacks for FooBaz hook
92 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self
::$default,
94 'FooBaz' => [ 'Callback1', 'Callback2' ],
101 * @covers ExtensionProcessor::extractHooks
102 * @dataProvider provideRegisterHooks
104 public function testRegisterHooks( $pre, $info, $expected ) {
105 $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
106 $processor->extractInfo( $this->dir
, $info, 1 );
107 $extracted = $processor->getExtractedInfo();
108 $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
112 * @covers ExtensionProcessor::extractConfig1
114 public function testExtractConfig1() {
115 $processor = new ExtensionProcessor
;
118 'Bar' => 'somevalue',
130 $processor->extractInfo( $this->dir
, $info, 1 );
131 $processor->extractInfo( $this->dir
, $info2, 1 );
132 $extracted = $processor->getExtractedInfo();
133 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
134 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
135 $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
137 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
141 * @covers ExtensionProcessor::extractConfig2
143 public function testExtractConfig2() {
144 $processor = new ExtensionProcessor
;
147 'Bar' => [ 'value' => 'somevalue' ],
148 'Foo' => [ 'value' => 10 ],
149 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
154 'Bar' => [ 'value' => 'somevalue' ],
156 'config_prefix' => 'eg',
159 $processor->extractInfo( $this->dir
, $info, 2 );
160 $processor->extractInfo( $this->dir
, $info2, 2 );
161 $extracted = $processor->getExtractedInfo();
162 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
163 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
164 $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
166 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
169 public static function provideExtractExtensionMessagesFiles() {
170 $dir = __DIR__
. '/FooBar/';
173 [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
174 [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
178 'ExtensionMessagesFiles' => [
179 'FooBarAlias' => 'FooBar.alias.php',
180 'FooBarMagic' => 'FooBar.magic.i18n.php',
184 'wgExtensionMessagesFiles' => [
185 'FooBarAlias' => $dir . 'FooBar.alias.php',
186 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
194 * @covers ExtensionProcessor::extractExtensionMessagesFiles
195 * @dataProvider provideExtractExtensionMessagesFiles
197 public function testExtractExtensionMessagesFiles( $input, $expected ) {
198 $processor = new ExtensionProcessor();
199 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
200 $out = $processor->getExtractedInfo();
201 foreach ( $expected as $key => $value ) {
202 $this->assertEquals( $value, $out['globals'][$key] );
206 public static function provideExtractMessagesDirs() {
207 $dir = __DIR__
. '/FooBar/';
210 [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
211 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
214 [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
215 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
221 * @covers ExtensionProcessor::extractMessagesDirs
222 * @dataProvider provideExtractMessagesDirs
224 public function testExtractMessagesDirs( $input, $expected ) {
225 $processor = new ExtensionProcessor();
226 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
227 $out = $processor->getExtractedInfo();
228 foreach ( $expected as $key => $value ) {
229 $this->assertEquals( $value, $out['globals'][$key] );
234 * @covers ExtensionProcessor::extractCredits
236 public function testExtractCredits() {
237 $processor = new ExtensionProcessor();
238 $processor->extractInfo( $this->dir
, self
::$default, 1 );
239 $this->setExpectedException( 'Exception' );
240 $processor->extractInfo( $this->dir
, self
::$default, 1 );
244 * @covers ExtensionProcessor::extractResourceLoaderModules
245 * @dataProvider provideExtractResourceLoaderModules
247 public function testExtractResourceLoaderModules( $input, $expected ) {
248 $processor = new ExtensionProcessor();
249 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
250 $out = $processor->getExtractedInfo();
251 foreach ( $expected as $key => $value ) {
252 $this->assertEquals( $value, $out['globals'][$key] );
256 public static function provideExtractResourceLoaderModules() {
257 $dir = __DIR__
. '/FooBar';
259 // Generic module with localBasePath/remoteExtPath specified
263 'ResourceModules' => [
265 'styles' => 'foobar.js',
266 'localBasePath' => '',
267 'remoteExtPath' => 'FooBar',
273 'wgResourceModules' => [
275 'styles' => 'foobar.js',
276 'localBasePath' => $dir,
277 'remoteExtPath' => 'FooBar',
282 // ResourceFileModulePaths specified:
286 'ResourceFileModulePaths' => [
287 'localBasePath' => '',
288 'remoteExtPath' => 'FooBar',
290 'ResourceModules' => [
293 'styles' => 'foo.js',
295 // Different paths set
297 'styles' => 'bar.js',
298 'localBasePath' => 'subdir',
299 'remoteExtPath' => 'FooBar/subdir',
301 // Custom class with no paths set
303 'class' => 'FooBarModule',
304 'extra' => 'argument',
306 // Custom class with a localBasePath
307 'test.class.with.path' => [
308 'class' => 'FooBarPathModule',
309 'extra' => 'argument',
310 'localBasePath' => '',
316 'wgResourceModules' => [
318 'styles' => 'foo.js',
319 'localBasePath' => $dir,
320 'remoteExtPath' => 'FooBar',
323 'styles' => 'bar.js',
324 'localBasePath' => "$dir/subdir",
325 'remoteExtPath' => 'FooBar/subdir',
328 'class' => 'FooBarModule',
329 'extra' => 'argument',
330 'localBasePath' => $dir,
331 'remoteExtPath' => 'FooBar',
333 'test.class.with.path' => [
334 'class' => 'FooBarPathModule',
335 'extra' => 'argument',
336 'localBasePath' => $dir,
337 'remoteExtPath' => 'FooBar',
342 // ResourceModuleSkinStyles with file module paths
346 'ResourceFileModulePaths' => [
347 'localBasePath' => '',
348 'remoteSkinPath' => 'FooBar',
350 'ResourceModuleSkinStyles' => [
352 'test.foo' => 'foo.css',
358 'wgResourceModuleSkinStyles' => [
360 'test.foo' => 'foo.css',
361 'localBasePath' => $dir,
362 'remoteSkinPath' => 'FooBar',
367 // ResourceModuleSkinStyles with file module paths and an override
371 'ResourceFileModulePaths' => [
372 'localBasePath' => '',
373 'remoteSkinPath' => 'FooBar',
375 'ResourceModuleSkinStyles' => [
377 'test.foo' => 'foo.css',
378 'remoteSkinPath' => 'BarFoo'
384 'wgResourceModuleSkinStyles' => [
386 'test.foo' => 'foo.css',
387 'localBasePath' => $dir,
388 'remoteSkinPath' => 'BarFoo',
396 public static function provideSetToGlobal() {
399 [ 'wgAPIModules', 'wgAvailableRights' ],
402 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
403 'AvailableRights' => [ 'foobar', 'unfoobar' ],
406 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
407 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
411 [ 'wgAPIModules', 'wgAvailableRights' ],
413 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
414 'wgAvailableRights' => [ 'barbaz' ]
417 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
418 'AvailableRights' => [ 'foobar', 'unfoobar' ],
421 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
422 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
426 [ 'wgGroupPermissions' ],
428 'wgGroupPermissions' => [
429 'sysop' => [ 'delete' ]
433 'GroupPermissions' => [
434 'sysop' => [ 'undelete' ],
439 'wgGroupPermissions' => [
440 'sysop' => [ 'delete', 'undelete' ],
448 public function testGlobalSettingsDocumentedInSchema() {
450 $globalSettings = TestingAccessWrapper
::newFromClass(
451 ExtensionProcessor
::class )->globalSettings
;
453 $version = ExtensionRegistry
::MANIFEST_VERSION
;
454 $schema = FormatJson
::decode(
455 file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
459 foreach ( $globalSettings as $global ) {
460 if ( !isset( $schema['properties'][$global] ) ) {
461 $missing[] = $global;
465 $this->assertEquals( [], $missing,
466 "The following global settings are not documented in docs/extension.schema.json" );
471 * Allow overriding the default value of $this->globals
472 * so we can test merging
474 class MockExtensionProcessor
extends ExtensionProcessor
{
475 public function __construct( $globals = [] ) {
476 $this->globals
= $globals +
$this->globals
;