]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - tests/phpunit/structure/ResourcesTest.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / tests / phpunit / structure / ResourcesTest.php
1 <?php
2 /**
3  * Sanity checks for making sure registered resources are sane.
4  *
5  * @file
6  * @author Antoine Musso
7  * @author Niklas Laxström
8  * @author Santhosh Thottingal
9  * @author Timo Tijhof
10  * @copyright © 2012, Antoine Musso
11  * @copyright © 2012, Niklas Laxström
12  * @copyright © 2012, Santhosh Thottingal
13  * @copyright © 2012, Timo Tijhof
14  */
15 class ResourcesTest extends MediaWikiTestCase {
16
17         /**
18          * @dataProvider provideResourceFiles
19          */
20         public function testFileExistence( $filename, $module, $resource ) {
21                 $this->assertFileExists( $filename,
22                         "File '$resource' referenced by '$module' must exist."
23                 );
24         }
25
26         /**
27          * @dataProvider provideMediaStylesheets
28          */
29         public function testStyleMedia( $moduleName, $media, $filename, $css ) {
30                 $cssText = CSSMin::minify( $css->cssText );
31
32                 $this->assertTrue(
33                         strpos( $cssText, '@media' ) === false,
34                         'Stylesheets should not both specify "media" and contain @media'
35                 );
36         }
37
38         public function testVersionHash() {
39                 $data = self::getAllModules();
40                 foreach ( $data['modules'] as $moduleName => $module ) {
41                         $version = $module->getVersionHash( $data['context'] );
42                         $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
43                 }
44         }
45
46         /**
47          * Verify that nothing explicitly depends on the 'jquery' and 'mediawiki' modules.
48          * They are always loaded, depending on them is unsupported and leads to unexpected behaviour.
49          * TODO Modules can dynamically choose dependencies based on context. This method does not
50          * test such dependencies. The same goes for testMissingDependencies() and
51          * testUnsatisfiableDependencies().
52          */
53         public function testIllegalDependencies() {
54                 $data = self::getAllModules();
55                 $illegalDeps = [ 'jquery', 'mediawiki' ];
56
57                 /** @var ResourceLoaderModule $module */
58                 foreach ( $data['modules'] as $moduleName => $module ) {
59                         foreach ( $illegalDeps as $illegalDep ) {
60                                 $this->assertNotContains(
61                                         $illegalDep,
62                                         $module->getDependencies( $data['context'] ),
63                                         "Module '$moduleName' must not depend on '$illegalDep'"
64                                 );
65                         }
66                 }
67         }
68
69         /**
70          * Verify that all modules specified as dependencies of other modules actually exist.
71          */
72         public function testMissingDependencies() {
73                 $data = self::getAllModules();
74                 $validDeps = array_keys( $data['modules'] );
75
76                 /** @var ResourceLoaderModule $module */
77                 foreach ( $data['modules'] as $moduleName => $module ) {
78                         foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
79                                 $this->assertContains(
80                                         $dep,
81                                         $validDeps,
82                                         "The module '$dep' required by '$moduleName' must exist"
83                                 );
84                         }
85                 }
86         }
87
88         /**
89          * Verify that all specified messages actually exist.
90          */
91         public function testMissingMessages() {
92                 $data = self::getAllModules();
93                 $lang = Language::factory( 'en' );
94
95                 /** @var ResourceLoaderModule $module */
96                 foreach ( $data['modules'] as $moduleName => $module ) {
97                         foreach ( $module->getMessages() as $msgKey ) {
98                                 $this->assertTrue(
99                                         wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
100                                         "Message '$msgKey' required by '$moduleName' must exist"
101                                 );
102                         }
103                 }
104         }
105
106         /**
107          * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
108          * for the involved modules.
109          *
110          * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
111          * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
112          */
113         public function testUnsatisfiableDependencies() {
114                 $data = self::getAllModules();
115
116                 /** @var ResourceLoaderModule $module */
117                 foreach ( $data['modules'] as $moduleName => $module ) {
118                         $moduleTargets = $module->getTargets();
119                         foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
120                                 if ( !isset( $data['modules'][$dep] ) ) {
121                                         // Missing dependencies reported by testMissingDependencies
122                                         continue;
123                                 }
124                                 $targets = $data['modules'][$dep]->getTargets();
125                                 foreach ( $moduleTargets as $moduleTarget ) {
126                                         $this->assertContains(
127                                                 $moduleTarget,
128                                                 $targets,
129                                                 "The module '$moduleName' must not have target '$moduleTarget' "
130                                                         . "because its dependency '$dep' does not have it"
131                                         );
132                                 }
133                         }
134                 }
135         }
136
137         /**
138          * CSSMin::getLocalFileReferences should ignore url(...) expressions
139          * that have been commented out.
140          */
141         public function testCommentedLocalFileReferences() {
142                 $basepath = __DIR__ . '/../data/css/';
143                 $css = file_get_contents( $basepath . 'comments.css' );
144                 $files = CSSMin::getLocalFileReferences( $css, $basepath );
145                 $expected = [ $basepath . 'not-commented.gif' ];
146                 $this->assertArrayEquals(
147                         $expected,
148                         $files,
149                         'Url(...) expression in comment should be omitted.'
150                 );
151         }
152
153         /**
154          * Get all registered modules from ResouceLoader.
155          * @return array
156          */
157         protected static function getAllModules() {
158                 global $wgEnableJavaScriptTest;
159
160                 // Test existance of test suite files as well
161                 // (can't use setUp or setMwGlobals because providers are static)
162                 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
163                 $wgEnableJavaScriptTest = true;
164
165                 // Initialize ResourceLoader
166                 $rl = new ResourceLoader();
167
168                 $modules = [];
169
170                 foreach ( $rl->getModuleNames() as $moduleName ) {
171                         $modules[$moduleName] = $rl->getModule( $moduleName );
172                 }
173
174                 // Restore settings
175                 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
176
177                 return [
178                         'modules' => $modules,
179                         'resourceloader' => $rl,
180                         'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
181                 ];
182         }
183
184         /**
185          * Get all stylesheet files from modules that are an instance of
186          * ResourceLoaderFileModule (or one of its subclasses).
187          */
188         public static function provideMediaStylesheets() {
189                 $data = self::getAllModules();
190                 $cases = [];
191
192                 foreach ( $data['modules'] as $moduleName => $module ) {
193                         if ( !$module instanceof ResourceLoaderFileModule ) {
194                                 continue;
195                         }
196
197                         $reflectedModule = new ReflectionObject( $module );
198
199                         $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
200                         $getStyleFiles->setAccessible( true );
201
202                         $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
203                         $readStyleFile->setAccessible( true );
204
205                         $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
206
207                         $flip = $module->getFlip( $data['context'] );
208
209                         foreach ( $styleFiles as $media => $files ) {
210                                 if ( $media && $media !== 'all' ) {
211                                         foreach ( $files as $file ) {
212                                                 $cases[] = [
213                                                         $moduleName,
214                                                         $media,
215                                                         $file,
216                                                         // XXX: Wrapped in an object to keep it out of PHPUnit output
217                                                         (object)[
218                                                                 'cssText' => $readStyleFile->invoke(
219                                                                         $module,
220                                                                         $file,
221                                                                         $flip,
222                                                                         $data['context']
223                                                                 )
224                                                         ],
225                                                 ];
226                                         }
227                                 }
228                         }
229                 }
230
231                 return $cases;
232         }
233
234         /**
235          * Get all resource files from modules that are an instance of
236          * ResourceLoaderFileModule (or one of its subclasses).
237          *
238          * Since the raw data is stored in protected properties, we have to
239          * overrride this through ReflectionObject methods.
240          */
241         public static function provideResourceFiles() {
242                 $data = self::getAllModules();
243                 $cases = [];
244
245                 // See also ResourceLoaderFileModule::__construct
246                 $filePathProps = [
247                         // Lists of file paths
248                         'lists' => [
249                                 'scripts',
250                                 'debugScripts',
251                                 'styles',
252                         ],
253
254                         // Collated lists of file paths
255                         'nested-lists' => [
256                                 'languageScripts',
257                                 'skinScripts',
258                                 'skinStyles',
259                         ],
260                 ];
261
262                 foreach ( $data['modules'] as $moduleName => $module ) {
263                         if ( !$module instanceof ResourceLoaderFileModule ) {
264                                 continue;
265                         }
266
267                         $reflectedModule = new ReflectionObject( $module );
268
269                         $files = [];
270
271                         foreach ( $filePathProps['lists'] as $propName ) {
272                                 $property = $reflectedModule->getProperty( $propName );
273                                 $property->setAccessible( true );
274                                 $list = $property->getValue( $module );
275                                 foreach ( $list as $key => $value ) {
276                                         // 'scripts' are numeral arrays.
277                                         // 'styles' can be numeral or associative.
278                                         // In case of associative the key is the file path
279                                         // and the value is the 'media' attribute.
280                                         if ( is_int( $key ) ) {
281                                                 $files[] = $value;
282                                         } else {
283                                                 $files[] = $key;
284                                         }
285                                 }
286                         }
287
288                         foreach ( $filePathProps['nested-lists'] as $propName ) {
289                                 $property = $reflectedModule->getProperty( $propName );
290                                 $property->setAccessible( true );
291                                 $lists = $property->getValue( $module );
292                                 foreach ( $lists as $list ) {
293                                         foreach ( $list as $key => $value ) {
294                                                 // We need the same filter as for 'lists',
295                                                 // due to 'skinStyles'.
296                                                 if ( is_int( $key ) ) {
297                                                         $files[] = $value;
298                                                 } else {
299                                                         $files[] = $key;
300                                                 }
301                                         }
302                                 }
303                         }
304
305                         // Get method for resolving the paths to full paths
306                         $method = $reflectedModule->getMethod( 'getLocalPath' );
307                         $method->setAccessible( true );
308
309                         // Populate cases
310                         foreach ( $files as $file ) {
311                                 $cases[] = [
312                                         $method->invoke( $module, $file ),
313                                         $moduleName,
314                                         ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file ),
315                                 ];
316                         }
317
318                         // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
319                         $module->readStyleFiles(
320                                 $module->getStyleFiles( $data['context'] ),
321                                 $module->getFlip( $data['context'] ),
322                                 $data['context']
323                         );
324
325                         $property = $reflectedModule->getProperty( 'missingLocalFileRefs' );
326                         $property->setAccessible( true );
327                         $missingLocalFileRefs = $property->getValue( $module );
328
329                         foreach ( $missingLocalFileRefs as $file ) {
330                                 $cases[] = [
331                                         $file,
332                                         $moduleName,
333                                         $file,
334                                 ];
335                         }
336                 }
337
338                 return $cases;
339         }
340 }