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