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 );
43 public static function provideRegisterHooks() {
44 $merge = [ ExtensionRegistry
::MERGE_STRATEGY
=> 'array_merge_recursive' ];
47 // Content in extension.json
48 // Expected value of $wgHooks
56 // No current hooks, adding one for "FooBaz" in string format
59 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
60 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
62 // Hook for "FooBaz", adding another one
64 [ 'FooBaz' => [ 'PriorCallback' ] ],
65 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
66 [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] +
$merge,
68 // No current hooks, adding one for "FooBaz" in verbose array format
71 [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self
::$default,
72 [ 'FooBaz' => [ 'FooBazCallback' ] ] +
$merge,
74 // Hook for "BarBaz", adding one for "FooBaz"
76 [ 'BarBaz' => [ 'BarBazCallback' ] ],
77 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self
::$default,
79 'BarBaz' => [ 'BarBazCallback' ],
80 'FooBaz' => [ 'FooBazCallback' ],
83 // Callbacks for FooBaz wrapped in an array
86 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self
::$default,
88 'FooBaz' => [ 'Callback1' ],
91 // Multiple callbacks for FooBaz hook
94 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self
::$default,
96 'FooBaz' => [ 'Callback1', 'Callback2' ],
103 * @covers ExtensionProcessor::extractHooks
104 * @dataProvider provideRegisterHooks
106 public function testRegisterHooks( $pre, $info, $expected ) {
107 $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
108 $processor->extractInfo( $this->dir
, $info, 1 );
109 $extracted = $processor->getExtractedInfo();
110 $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
114 * @covers ExtensionProcessor::extractConfig1
116 public function testExtractConfig1() {
117 $processor = new ExtensionProcessor
;
120 'Bar' => 'somevalue',
132 $processor->extractInfo( $this->dir
, $info, 1 );
133 $processor->extractInfo( $this->dir
, $info2, 1 );
134 $extracted = $processor->getExtractedInfo();
135 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
136 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
137 $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
139 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
143 * @covers ExtensionProcessor::extractConfig2
145 public function testExtractConfig2() {
146 $processor = new ExtensionProcessor
;
149 'Bar' => [ 'value' => 'somevalue' ],
150 'Foo' => [ 'value' => 10 ],
151 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
156 'Bar' => [ 'value' => 'somevalue' ],
158 'config_prefix' => 'eg',
161 $processor->extractInfo( $this->dir
, $info, 2 );
162 $processor->extractInfo( $this->dir
, $info2, 2 );
163 $extracted = $processor->getExtractedInfo();
164 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
165 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
166 $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
168 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
171 public static function provideExtractExtensionMessagesFiles() {
172 $dir = __DIR__
. '/FooBar/';
175 [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
176 [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
180 'ExtensionMessagesFiles' => [
181 'FooBarAlias' => 'FooBar.alias.php',
182 'FooBarMagic' => 'FooBar.magic.i18n.php',
186 'wgExtensionMessagesFiles' => [
187 'FooBarAlias' => $dir . 'FooBar.alias.php',
188 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
196 * @covers ExtensionProcessor::extractExtensionMessagesFiles
197 * @dataProvider provideExtractExtensionMessagesFiles
199 public function testExtractExtensionMessagesFiles( $input, $expected ) {
200 $processor = new ExtensionProcessor();
201 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
202 $out = $processor->getExtractedInfo();
203 foreach ( $expected as $key => $value ) {
204 $this->assertEquals( $value, $out['globals'][$key] );
208 public static function provideExtractMessagesDirs() {
209 $dir = __DIR__
. '/FooBar/';
212 [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
213 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
216 [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
217 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
223 * @covers ExtensionProcessor::extractMessagesDirs
224 * @dataProvider provideExtractMessagesDirs
226 public function testExtractMessagesDirs( $input, $expected ) {
227 $processor = new ExtensionProcessor();
228 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
229 $out = $processor->getExtractedInfo();
230 foreach ( $expected as $key => $value ) {
231 $this->assertEquals( $value, $out['globals'][$key] );
236 * @covers ExtensionProcessor::extractCredits
238 public function testExtractCredits() {
239 $processor = new ExtensionProcessor();
240 $processor->extractInfo( $this->dir
, self
::$default, 1 );
241 $this->setExpectedException( 'Exception' );
242 $processor->extractInfo( $this->dir
, self
::$default, 1 );
246 * @covers ExtensionProcessor::extractResourceLoaderModules
247 * @dataProvider provideExtractResourceLoaderModules
249 public function testExtractResourceLoaderModules( $input, $expected ) {
250 $processor = new ExtensionProcessor();
251 $processor->extractInfo( $this->dir
, $input + self
::$default, 1 );
252 $out = $processor->getExtractedInfo();
253 foreach ( $expected as $key => $value ) {
254 $this->assertEquals( $value, $out['globals'][$key] );
258 public static function provideExtractResourceLoaderModules() {
259 $dir = __DIR__
. '/FooBar';
261 // Generic module with localBasePath/remoteExtPath specified
265 'ResourceModules' => [
267 'styles' => 'foobar.js',
268 'localBasePath' => '',
269 'remoteExtPath' => 'FooBar',
275 'wgResourceModules' => [
277 'styles' => 'foobar.js',
278 'localBasePath' => $dir,
279 'remoteExtPath' => 'FooBar',
284 // ResourceFileModulePaths specified:
288 'ResourceFileModulePaths' => [
289 'localBasePath' => '',
290 'remoteExtPath' => 'FooBar',
292 'ResourceModules' => [
295 'styles' => 'foo.js',
297 // Different paths set
299 'styles' => 'bar.js',
300 'localBasePath' => 'subdir',
301 'remoteExtPath' => 'FooBar/subdir',
303 // Custom class with no paths set
305 'class' => 'FooBarModule',
306 'extra' => 'argument',
308 // Custom class with a localBasePath
309 'test.class.with.path' => [
310 'class' => 'FooBarPathModule',
311 'extra' => 'argument',
312 'localBasePath' => '',
318 'wgResourceModules' => [
320 'styles' => 'foo.js',
321 'localBasePath' => $dir,
322 'remoteExtPath' => 'FooBar',
325 'styles' => 'bar.js',
326 'localBasePath' => "$dir/subdir",
327 'remoteExtPath' => 'FooBar/subdir',
330 'class' => 'FooBarModule',
331 'extra' => 'argument',
332 'localBasePath' => $dir,
333 'remoteExtPath' => 'FooBar',
335 'test.class.with.path' => [
336 'class' => 'FooBarPathModule',
337 'extra' => 'argument',
338 'localBasePath' => $dir,
339 'remoteExtPath' => 'FooBar',
344 // ResourceModuleSkinStyles with file module paths
348 'ResourceFileModulePaths' => [
349 'localBasePath' => '',
350 'remoteSkinPath' => 'FooBar',
352 'ResourceModuleSkinStyles' => [
354 'test.foo' => 'foo.css',
360 'wgResourceModuleSkinStyles' => [
362 'test.foo' => 'foo.css',
363 'localBasePath' => $dir,
364 'remoteSkinPath' => 'FooBar',
369 // ResourceModuleSkinStyles with file module paths and an override
373 'ResourceFileModulePaths' => [
374 'localBasePath' => '',
375 'remoteSkinPath' => 'FooBar',
377 'ResourceModuleSkinStyles' => [
379 'test.foo' => 'foo.css',
380 'remoteSkinPath' => 'BarFoo'
386 'wgResourceModuleSkinStyles' => [
388 'test.foo' => 'foo.css',
389 'localBasePath' => $dir,
390 'remoteSkinPath' => 'BarFoo',
398 public static function provideSetToGlobal() {
401 [ 'wgAPIModules', 'wgAvailableRights' ],
404 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
405 'AvailableRights' => [ 'foobar', 'unfoobar' ],
408 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
409 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
413 [ 'wgAPIModules', 'wgAvailableRights' ],
415 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
416 'wgAvailableRights' => [ 'barbaz' ]
419 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
420 'AvailableRights' => [ 'foobar', 'unfoobar' ],
423 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
424 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
428 [ 'wgGroupPermissions' ],
430 'wgGroupPermissions' => [
431 'sysop' => [ 'delete' ]
435 'GroupPermissions' => [
436 'sysop' => [ 'undelete' ],
441 'wgGroupPermissions' => [
442 'sysop' => [ 'delete', 'undelete' ],
450 public function testGlobalSettingsDocumentedInSchema() {
452 $globalSettings = TestingAccessWrapper
::newFromClass(
453 ExtensionProcessor
::class )->globalSettings
;
455 $version = ExtensionRegistry
::MANIFEST_VERSION
;
456 $schema = FormatJson
::decode(
457 file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
461 foreach ( $globalSettings as $global ) {
462 if ( !isset( $schema['properties'][$global] ) ) {
463 $missing[] = $global;
467 $this->assertEquals( [], $missing,
468 "The following global settings are not documented in docs/extension.schema.json" );
473 * Allow overriding the default value of $this->globals
474 * so we can test merging
476 class MockExtensionProcessor
extends ExtensionProcessor
{
477 public function __construct( $globals = [] ) {
478 $this->globals
= $globals +
$this->globals
;