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