3 use MediaWiki\MediaWikiServices
;
4 use Wikimedia\TestingAccessWrapper
;
7 * Sanity checks for making sure registered resources are sane.
9 * @author Antoine Musso
10 * @author Niklas Laxström
11 * @author Santhosh Thottingal
13 * @copyright © 2012, Antoine Musso
14 * @copyright © 2012, Niklas Laxström
15 * @copyright © 2012, Santhosh Thottingal
16 * @copyright © 2012, Timo Tijhof
18 class ResourcesTest
extends MediaWikiTestCase
{
21 * @dataProvider provideResourceFiles
23 public function testFileExistence( $filename, $module, $resource ) {
24 $this->assertFileExists( $filename,
25 "File '$resource' referenced by '$module' must exist."
30 * @dataProvider provideMediaStylesheets
32 public function testStyleMedia( $moduleName, $media, $filename, $css ) {
33 $cssText = CSSMin
::minify( $css->cssText
);
36 strpos( $cssText, '@media' ) === false,
37 'Stylesheets should not both specify "media" and contain @media'
41 public function testVersionHash() {
42 $data = self
::getAllModules();
43 foreach ( $data['modules'] as $moduleName => $module ) {
44 $version = $module->getVersionHash( $data['context'] );
45 $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
50 * Verify that nothing explicitly depends on raw modules (such as "query").
52 * Depending on them is unsupported as they are not registered client-side by the startup module.
54 * @todo Modules can dynamically choose dependencies based on context. This method does not
55 * test such dependencies. The same goes for testMissingDependencies() and
56 * testUnsatisfiableDependencies().
58 public function testIllegalDependencies() {
59 $data = self
::getAllModules();
62 foreach ( $data['modules'] as $moduleName => $module ) {
63 if ( $module->isRaw() ) {
64 $illegalDeps[] = $moduleName;
68 /** @var ResourceLoaderModule $module */
69 foreach ( $data['modules'] as $moduleName => $module ) {
70 foreach ( $illegalDeps as $illegalDep ) {
71 $this->assertNotContains(
73 $module->getDependencies( $data['context'] ),
74 "Module '$moduleName' must not depend on '$illegalDep'"
81 * Verify that all modules specified as dependencies of other modules actually exist.
83 public function testMissingDependencies() {
84 $data = self
::getAllModules();
85 $validDeps = array_keys( $data['modules'] );
87 /** @var ResourceLoaderModule $module */
88 foreach ( $data['modules'] as $moduleName => $module ) {
89 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
90 $this->assertContains(
93 "The module '$dep' required by '$moduleName' must exist"
100 * Verify that all specified messages actually exist.
102 public function testMissingMessages() {
103 $data = self
::getAllModules();
104 $lang = Language
::factory( 'en' );
106 /** @var ResourceLoaderModule $module */
107 foreach ( $data['modules'] as $moduleName => $module ) {
108 foreach ( $module->getMessages() as $msgKey ) {
110 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
111 "Message '$msgKey' required by '$moduleName' must exist"
118 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
119 * for the involved modules.
121 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
122 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
124 public function testUnsatisfiableDependencies() {
125 $data = self
::getAllModules();
127 /** @var ResourceLoaderModule $module */
128 foreach ( $data['modules'] as $moduleName => $module ) {
129 $moduleTargets = $module->getTargets();
130 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
131 if ( !isset( $data['modules'][$dep] ) ) {
132 // Missing dependencies reported by testMissingDependencies
135 $targets = $data['modules'][$dep]->getTargets();
136 foreach ( $moduleTargets as $moduleTarget ) {
137 $this->assertContains(
140 "The module '$moduleName' must not have target '$moduleTarget' "
141 . "because its dependency '$dep' does not have it"
149 * CSSMin::getLocalFileReferences should ignore url(...) expressions
150 * that have been commented out.
152 public function testCommentedLocalFileReferences() {
153 $basepath = __DIR__
. '/../data/css/';
154 $css = file_get_contents( $basepath . 'comments.css' );
155 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
156 $expected = [ $basepath . 'not-commented.gif' ];
160 'Url(...) expression in comment should be omitted.'
165 * Get all registered modules from ResouceLoader.
168 protected static function getAllModules() {
169 global $wgEnableJavaScriptTest;
171 // Test existance of test suite files as well
172 // (can't use setUp or setMwGlobals because providers are static)
173 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
174 $wgEnableJavaScriptTest = true;
176 // Get main ResourceLoader
177 $rl = MediaWikiServices
::getInstance()->getResourceLoader();
181 foreach ( $rl->getModuleNames() as $moduleName ) {
182 $modules[$moduleName] = $rl->getModule( $moduleName );
186 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
189 'modules' => $modules,
190 'resourceloader' => $rl,
191 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
196 * Get all stylesheet files from modules that are an instance of
197 * ResourceLoaderFileModule (or one of its subclasses).
199 public static function provideMediaStylesheets() {
200 $data = self
::getAllModules();
203 foreach ( $data['modules'] as $moduleName => $module ) {
204 if ( !$module instanceof ResourceLoaderFileModule
) {
208 $reflectedModule = new ReflectionObject( $module );
210 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
211 $getStyleFiles->setAccessible( true );
213 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
214 $readStyleFile->setAccessible( true );
216 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
218 $flip = $module->getFlip( $data['context'] );
220 foreach ( $styleFiles as $media => $files ) {
221 if ( $media && $media !== 'all' ) {
222 foreach ( $files as $file ) {
227 // XXX: Wrapped in an object to keep it out of PHPUnit output
229 'cssText' => $readStyleFile->invoke(
246 * Get all resource files from modules that are an instance of
247 * ResourceLoaderFileModule (or one of its subclasses).
249 public static function provideResourceFiles() {
250 $data = self
::getAllModules();
253 // See also ResourceLoaderFileModule::__construct
255 // Lists of file paths
262 // Collated lists of file paths
270 foreach ( $data['modules'] as $moduleName => $module ) {
271 if ( !$module instanceof ResourceLoaderFileModule
) {
275 $moduleProxy = TestingAccessWrapper
::newFromObject( $module );
279 foreach ( $filePathProps['lists'] as $propName ) {
280 $list = $moduleProxy->$propName;
281 foreach ( $list as $key => $value ) {
282 // 'scripts' are numeral arrays.
283 // 'styles' can be numeral or associative.
284 // In case of associative the key is the file path
285 // and the value is the 'media' attribute.
286 if ( is_int( $key ) ) {
294 foreach ( $filePathProps['nested-lists'] as $propName ) {
295 $lists = $moduleProxy->$propName;
296 foreach ( $lists as $list ) {
297 foreach ( $list as $key => $value ) {
298 // We need the same filter as for 'lists',
299 // due to 'skinStyles'.
300 if ( is_int( $key ) ) {
310 foreach ( $files as $file ) {
312 $moduleProxy->getLocalPath( $file ),
314 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
318 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
319 $moduleProxy->readStyleFiles(
320 $module->getStyleFiles( $data['context'] ),
321 $module->getFlip( $data['context'] ),
325 $missingLocalFileRefs = $moduleProxy->missingLocalFileRefs
;
327 foreach ( $missingLocalFileRefs as $file ) {