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