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
19 class ResourcesTest
extends MediaWikiTestCase
{
22 * @dataProvider provideResourceFiles
24 public function testFileExistence( $filename, $module, $resource ) {
25 $this->assertFileExists( $filename,
26 "File '$resource' referenced by '$module' must exist."
31 * @dataProvider provideMediaStylesheets
33 public function testStyleMedia( $moduleName, $media, $filename, $css ) {
34 $cssText = CSSMin
::minify( $css->cssText
);
37 strpos( $cssText, '@media' ) === false,
38 'Stylesheets should not both specify "media" and contain @media'
42 public function testVersionHash() {
43 $data = self
::getAllModules();
44 foreach ( $data['modules'] as $moduleName => $module ) {
45 $version = $module->getVersionHash( $data['context'] );
46 $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
51 * Verify that nothing explicitly depends on raw modules (such as "query").
53 * Depending on them is unsupported as they are not registered client-side by the startup module.
55 * @todo Modules can dynamically choose dependencies based on context. This method does not
56 * test such dependencies. The same goes for testMissingDependencies() and
57 * testUnsatisfiableDependencies().
59 public function testIllegalDependencies() {
60 $data = self
::getAllModules();
63 foreach ( $data['modules'] as $moduleName => $module ) {
64 if ( $module->isRaw() ) {
65 $illegalDeps[] = $moduleName;
69 /** @var ResourceLoaderModule $module */
70 foreach ( $data['modules'] as $moduleName => $module ) {
71 foreach ( $illegalDeps as $illegalDep ) {
72 $this->assertNotContains(
74 $module->getDependencies( $data['context'] ),
75 "Module '$moduleName' must not depend on '$illegalDep'"
82 * Verify that all modules specified as dependencies of other modules actually exist.
84 public function testMissingDependencies() {
85 $data = self
::getAllModules();
86 $validDeps = array_keys( $data['modules'] );
88 /** @var ResourceLoaderModule $module */
89 foreach ( $data['modules'] as $moduleName => $module ) {
90 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
91 $this->assertContains(
94 "The module '$dep' required by '$moduleName' must exist"
101 * Verify that all specified messages actually exist.
103 public function testMissingMessages() {
104 $data = self
::getAllModules();
105 $lang = Language
::factory( 'en' );
107 /** @var ResourceLoaderModule $module */
108 foreach ( $data['modules'] as $moduleName => $module ) {
109 foreach ( $module->getMessages() as $msgKey ) {
111 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
112 "Message '$msgKey' required by '$moduleName' must exist"
119 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
120 * for the involved modules.
122 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
123 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
125 public function testUnsatisfiableDependencies() {
126 $data = self
::getAllModules();
128 /** @var ResourceLoaderModule $module */
129 foreach ( $data['modules'] as $moduleName => $module ) {
130 $moduleTargets = $module->getTargets();
131 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
132 if ( !isset( $data['modules'][$dep] ) ) {
133 // Missing dependencies reported by testMissingDependencies
136 $targets = $data['modules'][$dep]->getTargets();
137 foreach ( $moduleTargets as $moduleTarget ) {
138 $this->assertContains(
141 "The module '$moduleName' must not have target '$moduleTarget' "
142 . "because its dependency '$dep' does not have it"
150 * CSSMin::getLocalFileReferences should ignore url(...) expressions
151 * that have been commented out.
153 public function testCommentedLocalFileReferences() {
154 $basepath = __DIR__
. '/../data/css/';
155 $css = file_get_contents( $basepath . 'comments.css' );
156 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
157 $expected = [ $basepath . 'not-commented.gif' ];
161 'Url(...) expression in comment should be omitted.'
166 * Get all registered modules from ResouceLoader.
169 protected static function getAllModules() {
170 global $wgEnableJavaScriptTest;
172 // Test existance of test suite files as well
173 // (can't use setUp or setMwGlobals because providers are static)
174 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
175 $wgEnableJavaScriptTest = true;
177 // Get main ResourceLoader
178 $rl = MediaWikiServices
::getInstance()->getResourceLoader();
182 foreach ( $rl->getModuleNames() as $moduleName ) {
183 $modules[$moduleName] = $rl->getModule( $moduleName );
187 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
190 'modules' => $modules,
191 'resourceloader' => $rl,
192 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
197 * Get all stylesheet files from modules that are an instance of
198 * ResourceLoaderFileModule (or one of its subclasses).
200 public static function provideMediaStylesheets() {
201 $data = self
::getAllModules();
204 foreach ( $data['modules'] as $moduleName => $module ) {
205 if ( !$module instanceof ResourceLoaderFileModule
) {
209 $reflectedModule = new ReflectionObject( $module );
211 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
212 $getStyleFiles->setAccessible( true );
214 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
215 $readStyleFile->setAccessible( true );
217 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
219 $flip = $module->getFlip( $data['context'] );
221 foreach ( $styleFiles as $media => $files ) {
222 if ( $media && $media !== 'all' ) {
223 foreach ( $files as $file ) {
228 // XXX: Wrapped in an object to keep it out of PHPUnit output
230 'cssText' => $readStyleFile->invoke(
247 * Get all resource files from modules that are an instance of
248 * ResourceLoaderFileModule (or one of its subclasses).
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 $moduleProxy = TestingAccessWrapper
::newFromObject( $module );
280 foreach ( $filePathProps['lists'] as $propName ) {
281 $list = $moduleProxy->$propName;
282 foreach ( $list as $key => $value ) {
283 // 'scripts' are numeral arrays.
284 // 'styles' can be numeral or associative.
285 // In case of associative the key is the file path
286 // and the value is the 'media' attribute.
287 if ( is_int( $key ) ) {
295 foreach ( $filePathProps['nested-lists'] as $propName ) {
296 $lists = $moduleProxy->$propName;
297 foreach ( $lists as $list ) {
298 foreach ( $list as $key => $value ) {
299 // We need the same filter as for 'lists',
300 // due to 'skinStyles'.
301 if ( is_int( $key ) ) {
311 foreach ( $files as $file ) {
313 $moduleProxy->getLocalPath( $file ),
315 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
319 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
320 $moduleProxy->readStyleFiles(
321 $module->getStyleFiles( $data['context'] ),
322 $module->getFlip( $data['context'] ),
326 $missingLocalFileRefs = $moduleProxy->missingLocalFileRefs
;
328 foreach ( $missingLocalFileRefs as $file ) {