Merge "Fix edit link for messages in $wgForceUIMsgAsContentMsg"
[lhc/web/wiklou.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 */
16 class ResourcesTest extends MediaWikiTestCase {
17
18 /**
19 * @dataProvider provideResourceFiles
20 */
21 public function testFileExistence( $filename, $module, $resource ) {
22 $this->assertFileExists( $filename,
23 "File '$resource' referenced by '$module' must exist."
24 );
25 }
26
27 /**
28 * @dataProvider provideMediaStylesheets
29 */
30 public function testStyleMedia( $moduleName, $media, $filename, $css ) {
31 $cssText = CSSMin::minify( $css->cssText );
32
33 $this->assertTrue(
34 strpos( $cssText, '@media' ) === false,
35 'Stylesheets should not both specify "media" and contain @media'
36 );
37 }
38
39 public function testVersionHash() {
40 $data = self::getAllModules();
41 foreach ( $data['modules'] as $moduleName => $module ) {
42 $version = $module->getVersionHash( $data['context'] );
43 $this->assertEquals( 8, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
44 }
45 }
46
47 /**
48 * Verify that nothing explicitly depends on the 'jquery' and 'mediawiki' modules.
49 * They are always loaded, depending on them is unsupported and leads to unexpected behaviour.
50 */
51 public function testIllegalDependencies() {
52 $data = self::getAllModules();
53 $illegalDeps = array( 'jquery', 'mediawiki' );
54
55 /** @var ResourceLoaderModule $module */
56 foreach ( $data['modules'] as $moduleName => $module ) {
57 foreach ( $illegalDeps as $illegalDep ) {
58 $this->assertNotContains(
59 $illegalDep,
60 $module->getDependencies(),
61 "Module '$moduleName' must not depend on '$illegalDep'"
62 );
63 }
64 }
65 }
66
67 /**
68 * Verify that all modules specified as dependencies of other modules actually exist.
69 */
70 public function testMissingDependencies() {
71 $data = self::getAllModules();
72 $validDeps = array_keys( $data['modules'] );
73
74 /** @var ResourceLoaderModule $module */
75 foreach ( $data['modules'] as $moduleName => $module ) {
76 foreach ( $module->getDependencies() as $dep ) {
77 $this->assertContains(
78 $dep,
79 $validDeps,
80 "The module '$dep' required by '$moduleName' must exist"
81 );
82 }
83 }
84 }
85
86 /**
87 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
88 * for the involved modules.
89 *
90 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
91 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
92 */
93 public function testUnsatisfiableDependencies() {
94 $data = self::getAllModules();
95 $validDeps = array_keys( $data['modules'] );
96
97 /** @var ResourceLoaderModule $module */
98 foreach ( $data['modules'] as $moduleName => $module ) {
99 $moduleTargets = $module->getTargets();
100 foreach ( $module->getDependencies() as $dep ) {
101 if ( !isset( $data['modules'][$dep] ) ) {
102 // Missing dependencies reported by testMissingDependencies
103 continue;
104 }
105 $targets = $data['modules'][$dep]->getTargets();
106 foreach ( $moduleTargets as $moduleTarget ) {
107 $this->assertContains(
108 $moduleTarget,
109 $targets,
110 "The module '$moduleName' must not have target '$moduleTarget' "
111 . "because its dependency '$dep' does not have it"
112 );
113 }
114 }
115 }
116 }
117
118 /**
119 * Get all registered modules from ResouceLoader.
120 * @return array
121 */
122 protected static function getAllModules() {
123 global $wgEnableJavaScriptTest;
124
125 // Test existance of test suite files as well
126 // (can't use setUp or setMwGlobals because providers are static)
127 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
128 $wgEnableJavaScriptTest = true;
129
130 // Initialize ResourceLoader
131 $rl = new ResourceLoader();
132
133 $modules = array();
134
135 foreach ( $rl->getModuleNames() as $moduleName ) {
136 $modules[$moduleName] = $rl->getModule( $moduleName );
137 }
138
139 // Restore settings
140 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
141
142 return array(
143 'modules' => $modules,
144 'resourceloader' => $rl,
145 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
146 );
147 }
148
149 /**
150 * Get all stylesheet files from modules that are an instance of
151 * ResourceLoaderFileModule (or one of its subclasses).
152 */
153 public static function provideMediaStylesheets() {
154 $data = self::getAllModules();
155 $cases = array();
156
157 foreach ( $data['modules'] as $moduleName => $module ) {
158 if ( !$module instanceof ResourceLoaderFileModule ) {
159 continue;
160 }
161
162 $reflectedModule = new ReflectionObject( $module );
163
164 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
165 $getStyleFiles->setAccessible( true );
166
167 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
168 $readStyleFile->setAccessible( true );
169
170 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
171
172 $flip = $module->getFlip( $data['context'] );
173
174 foreach ( $styleFiles as $media => $files ) {
175 if ( $media && $media !== 'all' ) {
176 foreach ( $files as $file ) {
177 $cases[] = array(
178 $moduleName,
179 $media,
180 $file,
181 // XXX: Wrapped in an object to keep it out of PHPUnit output
182 (object)array( 'cssText' => $readStyleFile->invoke( $module, $file, $flip ) ),
183 );
184 }
185 }
186 }
187 }
188
189 return $cases;
190 }
191
192 /**
193 * Get all resource files from modules that are an instance of
194 * ResourceLoaderFileModule (or one of its subclasses).
195 *
196 * Since the raw data is stored in protected properties, we have to
197 * overrride this through ReflectionObject methods.
198 */
199 public static function provideResourceFiles() {
200 $data = self::getAllModules();
201 $cases = array();
202
203 // See also ResourceLoaderFileModule::__construct
204 $filePathProps = array(
205 // Lists of file paths
206 'lists' => array(
207 'scripts',
208 'debugScripts',
209 'loaderScripts',
210 'styles',
211 ),
212
213 // Collated lists of file paths
214 'nested-lists' => array(
215 'languageScripts',
216 'skinScripts',
217 'skinStyles',
218 ),
219 );
220
221 foreach ( $data['modules'] as $moduleName => $module ) {
222 if ( !$module instanceof ResourceLoaderFileModule ) {
223 continue;
224 }
225
226 $reflectedModule = new ReflectionObject( $module );
227
228 $files = array();
229
230 foreach ( $filePathProps['lists'] as $propName ) {
231 $property = $reflectedModule->getProperty( $propName );
232 $property->setAccessible( true );
233 $list = $property->getValue( $module );
234 foreach ( $list as $key => $value ) {
235 // 'scripts' are numeral arrays.
236 // 'styles' can be numeral or associative.
237 // In case of associative the key is the file path
238 // and the value is the 'media' attribute.
239 if ( is_int( $key ) ) {
240 $files[] = $value;
241 } else {
242 $files[] = $key;
243 }
244 }
245 }
246
247 foreach ( $filePathProps['nested-lists'] as $propName ) {
248 $property = $reflectedModule->getProperty( $propName );
249 $property->setAccessible( true );
250 $lists = $property->getValue( $module );
251 foreach ( $lists as $list ) {
252 foreach ( $list as $key => $value ) {
253 // We need the same filter as for 'lists',
254 // due to 'skinStyles'.
255 if ( is_int( $key ) ) {
256 $files[] = $value;
257 } else {
258 $files[] = $key;
259 }
260 }
261 }
262 }
263
264 // Get method for resolving the paths to full paths
265 $method = $reflectedModule->getMethod( 'getLocalPath' );
266 $method->setAccessible( true );
267
268 // Populate cases
269 foreach ( $files as $file ) {
270 $cases[] = array(
271 $method->invoke( $module, $file ),
272 $moduleName,
273 ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file ),
274 );
275 }
276 }
277
278 return $cases;
279 }
280 }