3 * Sanity checks for making sure registered resources are sane.
6 * @author Antoine Musso
7 * @author Niklas Laxström
8 * @author Santhosh Thottingal
10 * @copyright © 2012, Antoine Musso
11 * @copyright © 2012, Niklas Laxström
12 * @copyright © 2012, Santhosh Thottingal
13 * @copyright © 2012, Timo Tijhof
16 class ResourcesTest
extends MediaWikiTestCase
{
19 * @dataProvider provideResourceFiles
21 public function testFileExistence( $filename, $module, $resource ) {
22 $this->assertFileExists( $filename,
23 "File '$resource' referenced by '$module' must exist."
28 * @dataProvider provideMediaStylesheets
30 public function testStyleMedia( $moduleName, $media, $filename, $css ) {
31 $cssText = CSSMin
::minify( $css->cssText
);
34 strpos( $cssText, '@media' ) === false,
35 'Stylesheets should not both specify "media" and contain @media'
39 public function testVersionHash() {
40 $data = self
::getAllModules();
41 foreach ( $data['modules'] as $moduleName => $module ) {
42 $version = $module->getVersionHash( $data['context'] );
43 $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
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 * TODO Modules can dynamically choose dependencies based on context. This method does not
51 * test such dependencies. The same goes for testMissingDependencies() and
52 * testUnsatisfiableDependencies().
54 public function testIllegalDependencies() {
55 $data = self
::getAllModules();
56 $illegalDeps = [ 'jquery', 'mediawiki' ];
58 /** @var ResourceLoaderModule $module */
59 foreach ( $data['modules'] as $moduleName => $module ) {
60 foreach ( $illegalDeps as $illegalDep ) {
61 $this->assertNotContains(
63 $module->getDependencies( $data['context'] ),
64 "Module '$moduleName' must not depend on '$illegalDep'"
71 * Verify that all modules specified as dependencies of other modules actually exist.
73 public function testMissingDependencies() {
74 $data = self
::getAllModules();
75 $validDeps = array_keys( $data['modules'] );
77 /** @var ResourceLoaderModule $module */
78 foreach ( $data['modules'] as $moduleName => $module ) {
79 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
80 $this->assertContains(
83 "The module '$dep' required by '$moduleName' must exist"
90 * Verify that all specified messages actually exist.
92 public function testMissingMessages() {
93 $data = self
::getAllModules();
94 $lang = Language
::factory( 'en' );
96 /** @var ResourceLoaderModule $module */
97 foreach ( $data['modules'] as $moduleName => $module ) {
98 foreach ( $module->getMessages() as $msgKey ) {
100 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
101 "Message '$msgKey' required by '$moduleName' must exist"
108 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
109 * for the involved modules.
111 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
112 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
114 public function testUnsatisfiableDependencies() {
115 $data = self
::getAllModules();
117 /** @var ResourceLoaderModule $module */
118 foreach ( $data['modules'] as $moduleName => $module ) {
119 $moduleTargets = $module->getTargets();
120 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
121 if ( !isset( $data['modules'][$dep] ) ) {
122 // Missing dependencies reported by testMissingDependencies
125 $targets = $data['modules'][$dep]->getTargets();
126 foreach ( $moduleTargets as $moduleTarget ) {
127 $this->assertContains(
130 "The module '$moduleName' must not have target '$moduleTarget' "
131 . "because its dependency '$dep' does not have it"
139 * CSSMin::getLocalFileReferences should ignore url(...) expressions
140 * that have been commented out.
142 public function testCommentedLocalFileReferences() {
143 $basepath = __DIR__
. '/../data/css/';
144 $css = file_get_contents( $basepath . 'comments.css' );
145 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
146 $expected = [ $basepath . 'not-commented.gif' ];
147 $this->assertArrayEquals(
150 'Url(...) expression in comment should be omitted.'
155 * Get all registered modules from ResouceLoader.
158 protected static function getAllModules() {
159 global $wgEnableJavaScriptTest;
161 // Test existance of test suite files as well
162 // (can't use setUp or setMwGlobals because providers are static)
163 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
164 $wgEnableJavaScriptTest = true;
166 // Initialize ResourceLoader
167 $rl = new ResourceLoader();
171 foreach ( $rl->getModuleNames() as $moduleName ) {
172 $modules[$moduleName] = $rl->getModule( $moduleName );
176 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
179 'modules' => $modules,
180 'resourceloader' => $rl,
181 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
186 * Get all stylesheet files from modules that are an instance of
187 * ResourceLoaderFileModule (or one of its subclasses).
189 public static function provideMediaStylesheets() {
190 $data = self
::getAllModules();
193 foreach ( $data['modules'] as $moduleName => $module ) {
194 if ( !$module instanceof ResourceLoaderFileModule
) {
198 $reflectedModule = new ReflectionObject( $module );
200 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
201 $getStyleFiles->setAccessible( true );
203 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
204 $readStyleFile->setAccessible( true );
206 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
208 $flip = $module->getFlip( $data['context'] );
210 foreach ( $styleFiles as $media => $files ) {
211 if ( $media && $media !== 'all' ) {
212 foreach ( $files as $file ) {
217 // XXX: Wrapped in an object to keep it out of PHPUnit output
219 'cssText' => $readStyleFile->invoke(
236 * Get all resource files from modules that are an instance of
237 * ResourceLoaderFileModule (or one of its subclasses).
239 * Since the raw data is stored in protected properties, we have to
240 * overrride this through ReflectionObject methods.
242 public static function provideResourceFiles() {
243 $data = self
::getAllModules();
246 // See also ResourceLoaderFileModule::__construct
248 // Lists of file paths
255 // Collated lists of file paths
263 foreach ( $data['modules'] as $moduleName => $module ) {
264 if ( !$module instanceof ResourceLoaderFileModule
) {
268 $reflectedModule = new ReflectionObject( $module );
272 foreach ( $filePathProps['lists'] as $propName ) {
273 $property = $reflectedModule->getProperty( $propName );
274 $property->setAccessible( true );
275 $list = $property->getValue( $module );
276 foreach ( $list as $key => $value ) {
277 // 'scripts' are numeral arrays.
278 // 'styles' can be numeral or associative.
279 // In case of associative the key is the file path
280 // and the value is the 'media' attribute.
281 if ( is_int( $key ) ) {
289 foreach ( $filePathProps['nested-lists'] as $propName ) {
290 $property = $reflectedModule->getProperty( $propName );
291 $property->setAccessible( true );
292 $lists = $property->getValue( $module );
293 foreach ( $lists as $list ) {
294 foreach ( $list as $key => $value ) {
295 // We need the same filter as for 'lists',
296 // due to 'skinStyles'.
297 if ( is_int( $key ) ) {
306 // Get method for resolving the paths to full paths
307 $method = $reflectedModule->getMethod( 'getLocalPath' );
308 $method->setAccessible( true );
311 foreach ( $files as $file ) {
313 $method->invoke( $module, $file ),
315 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
319 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
320 $module->readStyleFiles(
321 $module->getStyleFiles( $data['context'] ),
322 $module->getFlip( $data['context'] ),
326 $property = $reflectedModule->getProperty( 'missingLocalFileRefs' );
327 $property->setAccessible( true );
328 $missingLocalFileRefs = $property->getValue( $module );
330 foreach ( $missingLocalFileRefs as $file ) {