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
15 class ResourcesTest
extends MediaWikiTestCase
{
18 * @dataProvider provideResourceFiles
20 public function testFileExistence( $filename, $module, $resource ) {
21 $this->assertFileExists( $filename,
22 "File '$resource' referenced by '$module' must exist."
27 * @dataProvider provideMediaStylesheets
29 public function testStyleMedia( $moduleName, $media, $filename, $css ) {
30 $cssText = CSSMin
::minify( $css->cssText
);
33 strpos( $cssText, '@media' ) === false,
34 'Stylesheets should not both specify "media" and contain @media'
38 public function testVersionHash() {
39 $data = self
::getAllModules();
40 foreach ( $data['modules'] as $moduleName => $module ) {
41 $version = $module->getVersionHash( $data['context'] );
42 $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
47 * Verify that nothing explicitly depends on base modules, or other raw modules.
49 * Depending on them is unsupported as they are not registered client-side by the startup module.
51 * TODO Modules can dynamically choose dependencies based on context. This method does not
52 * test such dependencies. The same goes for testMissingDependencies() and
53 * testUnsatisfiableDependencies().
55 public function testIllegalDependencies() {
56 $data = self
::getAllModules();
58 $illegalDeps = ResourceLoaderStartUpModule
::getStartupModules();
59 foreach ( $data['modules'] as $moduleName => $module ) {
60 if ( $module->isRaw() ) {
61 $illegalDeps[] = $moduleName;
64 $illegalDeps = array_unique( $illegalDeps );
66 /** @var ResourceLoaderModule $module */
67 foreach ( $data['modules'] as $moduleName => $module ) {
68 foreach ( $illegalDeps as $illegalDep ) {
69 $this->assertNotContains(
71 $module->getDependencies( $data['context'] ),
72 "Module '$moduleName' must not depend on '$illegalDep'"
79 * Verify that all modules specified as dependencies of other modules actually exist.
81 public function testMissingDependencies() {
82 $data = self
::getAllModules();
83 $validDeps = array_keys( $data['modules'] );
85 /** @var ResourceLoaderModule $module */
86 foreach ( $data['modules'] as $moduleName => $module ) {
87 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
88 $this->assertContains(
91 "The module '$dep' required by '$moduleName' must exist"
98 * Verify that all specified messages actually exist.
100 public function testMissingMessages() {
101 $data = self
::getAllModules();
102 $lang = Language
::factory( 'en' );
104 /** @var ResourceLoaderModule $module */
105 foreach ( $data['modules'] as $moduleName => $module ) {
106 foreach ( $module->getMessages() as $msgKey ) {
108 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
109 "Message '$msgKey' required by '$moduleName' must exist"
116 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
117 * for the involved modules.
119 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
120 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
122 public function testUnsatisfiableDependencies() {
123 $data = self
::getAllModules();
125 /** @var ResourceLoaderModule $module */
126 foreach ( $data['modules'] as $moduleName => $module ) {
127 $moduleTargets = $module->getTargets();
128 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
129 if ( !isset( $data['modules'][$dep] ) ) {
130 // Missing dependencies reported by testMissingDependencies
133 $targets = $data['modules'][$dep]->getTargets();
134 foreach ( $moduleTargets as $moduleTarget ) {
135 $this->assertContains(
138 "The module '$moduleName' must not have target '$moduleTarget' "
139 . "because its dependency '$dep' does not have it"
147 * CSSMin::getLocalFileReferences should ignore url(...) expressions
148 * that have been commented out.
150 public function testCommentedLocalFileReferences() {
151 $basepath = __DIR__
. '/../data/css/';
152 $css = file_get_contents( $basepath . 'comments.css' );
153 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
154 $expected = [ $basepath . 'not-commented.gif' ];
155 $this->assertArrayEquals(
158 'Url(...) expression in comment should be omitted.'
163 * Get all registered modules from ResouceLoader.
166 protected static function getAllModules() {
167 global $wgEnableJavaScriptTest;
169 // Test existance of test suite files as well
170 // (can't use setUp or setMwGlobals because providers are static)
171 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
172 $wgEnableJavaScriptTest = true;
174 // Initialize ResourceLoader
175 $rl = new ResourceLoader();
179 foreach ( $rl->getModuleNames() as $moduleName ) {
180 $modules[$moduleName] = $rl->getModule( $moduleName );
184 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
187 'modules' => $modules,
188 'resourceloader' => $rl,
189 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
194 * Get all stylesheet files from modules that are an instance of
195 * ResourceLoaderFileModule (or one of its subclasses).
197 public static function provideMediaStylesheets() {
198 $data = self
::getAllModules();
201 foreach ( $data['modules'] as $moduleName => $module ) {
202 if ( !$module instanceof ResourceLoaderFileModule
) {
206 $reflectedModule = new ReflectionObject( $module );
208 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
209 $getStyleFiles->setAccessible( true );
211 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
212 $readStyleFile->setAccessible( true );
214 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
216 $flip = $module->getFlip( $data['context'] );
218 foreach ( $styleFiles as $media => $files ) {
219 if ( $media && $media !== 'all' ) {
220 foreach ( $files as $file ) {
225 // XXX: Wrapped in an object to keep it out of PHPUnit output
227 'cssText' => $readStyleFile->invoke(
244 * Get all resource files from modules that are an instance of
245 * ResourceLoaderFileModule (or one of its subclasses).
247 * Since the raw data is stored in protected properties, we have to
248 * overrride this through ReflectionObject methods.
250 public static function provideResourceFiles() {
251 $data = self
::getAllModules();
254 // See also ResourceLoaderFileModule::__construct
256 // Lists of file paths
263 // Collated lists of file paths
271 foreach ( $data['modules'] as $moduleName => $module ) {
272 if ( !$module instanceof ResourceLoaderFileModule
) {
276 $reflectedModule = new ReflectionObject( $module );
280 foreach ( $filePathProps['lists'] as $propName ) {
281 $property = $reflectedModule->getProperty( $propName );
282 $property->setAccessible( true );
283 $list = $property->getValue( $module );
284 foreach ( $list as $key => $value ) {
285 // 'scripts' are numeral arrays.
286 // 'styles' can be numeral or associative.
287 // In case of associative the key is the file path
288 // and the value is the 'media' attribute.
289 if ( is_int( $key ) ) {
297 foreach ( $filePathProps['nested-lists'] as $propName ) {
298 $property = $reflectedModule->getProperty( $propName );
299 $property->setAccessible( true );
300 $lists = $property->getValue( $module );
301 foreach ( $lists as $list ) {
302 foreach ( $list as $key => $value ) {
303 // We need the same filter as for 'lists',
304 // due to 'skinStyles'.
305 if ( is_int( $key ) ) {
314 // Get method for resolving the paths to full paths
315 $method = $reflectedModule->getMethod( 'getLocalPath' );
316 $method->setAccessible( true );
319 foreach ( $files as $file ) {
321 $method->invoke( $module, $file ),
323 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
327 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
328 $module->readStyleFiles(
329 $module->getStyleFiles( $data['context'] ),
330 $module->getFlip( $data['context'] ),
334 $property = $reflectedModule->getProperty( 'missingLocalFileRefs' );
335 $property->setAccessible( true );
336 $missingLocalFileRefs = $property->getValue( $module );
338 foreach ( $missingLocalFileRefs as $file ) {