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 base modules, or other raw modules.
50 * Depending on them is unsupported as they are not registered client-side by the startup module.
52 * TODO Modules can dynamically choose dependencies based on context. This method does not
53 * test such dependencies. The same goes for testMissingDependencies() and
54 * testUnsatisfiableDependencies().
56 public function testIllegalDependencies() {
57 $data = self
::getAllModules();
59 $illegalDeps = ResourceLoaderStartUpModule
::getStartupModules();
60 foreach ( $data['modules'] as $moduleName => $module ) {
61 if ( $module->isRaw() ) {
62 $illegalDeps[] = $moduleName;
65 $illegalDeps = array_unique( $illegalDeps );
67 /** @var ResourceLoaderModule $module */
68 foreach ( $data['modules'] as $moduleName => $module ) {
69 foreach ( $illegalDeps as $illegalDep ) {
70 $this->assertNotContains(
72 $module->getDependencies( $data['context'] ),
73 "Module '$moduleName' must not depend on '$illegalDep'"
80 * Verify that all modules specified as dependencies of other modules actually exist.
82 public function testMissingDependencies() {
83 $data = self
::getAllModules();
84 $validDeps = array_keys( $data['modules'] );
86 /** @var ResourceLoaderModule $module */
87 foreach ( $data['modules'] as $moduleName => $module ) {
88 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
89 $this->assertContains(
92 "The module '$dep' required by '$moduleName' must exist"
99 * Verify that all specified messages actually exist.
101 public function testMissingMessages() {
102 $data = self
::getAllModules();
103 $lang = Language
::factory( 'en' );
105 /** @var ResourceLoaderModule $module */
106 foreach ( $data['modules'] as $moduleName => $module ) {
107 foreach ( $module->getMessages() as $msgKey ) {
109 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
110 "Message '$msgKey' required by '$moduleName' must exist"
117 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
118 * for the involved modules.
120 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
121 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
123 public function testUnsatisfiableDependencies() {
124 $data = self
::getAllModules();
126 /** @var ResourceLoaderModule $module */
127 foreach ( $data['modules'] as $moduleName => $module ) {
128 $moduleTargets = $module->getTargets();
129 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
130 if ( !isset( $data['modules'][$dep] ) ) {
131 // Missing dependencies reported by testMissingDependencies
134 $targets = $data['modules'][$dep]->getTargets();
135 foreach ( $moduleTargets as $moduleTarget ) {
136 $this->assertContains(
139 "The module '$moduleName' must not have target '$moduleTarget' "
140 . "because its dependency '$dep' does not have it"
148 * CSSMin::getLocalFileReferences should ignore url(...) expressions
149 * that have been commented out.
151 public function testCommentedLocalFileReferences() {
152 $basepath = __DIR__
. '/../data/css/';
153 $css = file_get_contents( $basepath . 'comments.css' );
154 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
155 $expected = [ $basepath . 'not-commented.gif' ];
156 $this->assertArrayEquals(
159 'Url(...) expression in comment should be omitted.'
164 * Get all registered modules from ResouceLoader.
167 protected static function getAllModules() {
168 global $wgEnableJavaScriptTest;
170 // Test existance of test suite files as well
171 // (can't use setUp or setMwGlobals because providers are static)
172 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
173 $wgEnableJavaScriptTest = true;
175 // Initialize ResourceLoader
176 $rl = new ResourceLoader();
180 foreach ( $rl->getModuleNames() as $moduleName ) {
181 $modules[$moduleName] = $rl->getModule( $moduleName );
185 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
188 'modules' => $modules,
189 'resourceloader' => $rl,
190 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
195 * Get all stylesheet files from modules that are an instance of
196 * ResourceLoaderFileModule (or one of its subclasses).
198 public static function provideMediaStylesheets() {
199 $data = self
::getAllModules();
202 foreach ( $data['modules'] as $moduleName => $module ) {
203 if ( !$module instanceof ResourceLoaderFileModule
) {
207 $reflectedModule = new ReflectionObject( $module );
209 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
210 $getStyleFiles->setAccessible( true );
212 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
213 $readStyleFile->setAccessible( true );
215 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
217 $flip = $module->getFlip( $data['context'] );
219 foreach ( $styleFiles as $media => $files ) {
220 if ( $media && $media !== 'all' ) {
221 foreach ( $files as $file ) {
226 // XXX: Wrapped in an object to keep it out of PHPUnit output
228 'cssText' => $readStyleFile->invoke(
245 * Get all resource files from modules that are an instance of
246 * ResourceLoaderFileModule (or one of its subclasses).
248 * Since the raw data is stored in protected properties, we have to
249 * overrride this through ReflectionObject methods.
251 public static function provideResourceFiles() {
252 $data = self
::getAllModules();
255 // See also ResourceLoaderFileModule::__construct
257 // Lists of file paths
264 // Collated lists of file paths
272 foreach ( $data['modules'] as $moduleName => $module ) {
273 if ( !$module instanceof ResourceLoaderFileModule
) {
277 $reflectedModule = new ReflectionObject( $module );
281 foreach ( $filePathProps['lists'] as $propName ) {
282 $property = $reflectedModule->getProperty( $propName );
283 $property->setAccessible( true );
284 $list = $property->getValue( $module );
285 foreach ( $list as $key => $value ) {
286 // 'scripts' are numeral arrays.
287 // 'styles' can be numeral or associative.
288 // In case of associative the key is the file path
289 // and the value is the 'media' attribute.
290 if ( is_int( $key ) ) {
298 foreach ( $filePathProps['nested-lists'] as $propName ) {
299 $property = $reflectedModule->getProperty( $propName );
300 $property->setAccessible( true );
301 $lists = $property->getValue( $module );
302 foreach ( $lists as $list ) {
303 foreach ( $list as $key => $value ) {
304 // We need the same filter as for 'lists',
305 // due to 'skinStyles'.
306 if ( is_int( $key ) ) {
315 // Get method for resolving the paths to full paths
316 $method = $reflectedModule->getMethod( 'getLocalPath' );
317 $method->setAccessible( true );
320 foreach ( $files as $file ) {
322 $method->invoke( $module, $file ),
324 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
328 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
329 $module->readStyleFiles(
330 $module->getStyleFiles( $data['context'] ),
331 $module->getFlip( $data['context'] ),
335 $property = $reflectedModule->getProperty( 'missingLocalFileRefs' );
336 $property->setAccessible( true );
337 $missingLocalFileRefs = $property->getValue( $module );
339 foreach ( $missingLocalFileRefs as $file ) {