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