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