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
12 * @copyright © 2012, Antoine Musso
13 * @copyright © 2012, Niklas Laxström
14 * @copyright © 2012, Santhosh Thottingal
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'
40 * Verify that all modules specified as dependencies of other modules actually
41 * exist and are not illegal.
43 * @todo Modules can dynamically choose dependencies based on context. This method
44 * does not find all such variations. The same applies to testUnsatisfiableDependencies().
46 public function testValidDependencies() {
47 $data = self
::getAllModules();
48 $knownDeps = array_keys( $data['modules'] );
49 $illegalDeps = [ 'startup' ];
51 // Avoid an assert for each module to keep the test fast.
52 // Instead, perform a single assertion against everything at once.
53 // When all is good, actual/expected are both empty arrays.
54 // When we find issues, add the violations to 'actual' and add an empty
55 // key to 'expected'. These keys in expected are because the PHPUnit diff
56 // (as of 6.5) only goes one level deep.
58 $expectedUnknown = [];
60 $expectedIllegal = [];
62 /** @var ResourceLoaderModule $module */
63 foreach ( $data['modules'] as $moduleName => $module ) {
64 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
65 if ( !in_array( $dep, $knownDeps, true ) ) {
66 $actualUnknown[$moduleName][] = $dep;
67 $expectedUnknown[$moduleName] = [];
69 if ( in_array( $dep, $illegalDeps, true ) ) {
70 $actualIllegal[$moduleName][] = $dep;
71 $expectedIllegal[$moduleName] = [];
75 $this->assertEquals( $expectedUnknown, $actualUnknown, 'Dependencies that do not exist' );
76 $this->assertEquals( $expectedIllegal, $actualIllegal, 'Dependencies that are not legal' );
80 * Verify that all specified messages actually exist.
82 public function testMissingMessages() {
83 $data = self
::getAllModules();
84 $lang = Language
::factory( 'en' );
86 /** @var ResourceLoaderModule $module */
87 foreach ( $data['modules'] as $moduleName => $module ) {
88 foreach ( $module->getMessages() as $msgKey ) {
90 wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
91 "Message '$msgKey' required by '$moduleName' must exist"
98 * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
99 * for the involved modules.
101 * Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
102 * dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
104 public function testUnsatisfiableDependencies() {
105 $data = self
::getAllModules();
107 /** @var ResourceLoaderModule $module */
108 foreach ( $data['modules'] as $moduleName => $module ) {
109 $moduleTargets = $module->getTargets();
110 foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
111 if ( !isset( $data['modules'][$dep] ) ) {
112 // Missing dependencies reported by testMissingDependencies
115 $targets = $data['modules'][$dep]->getTargets();
116 foreach ( $moduleTargets as $moduleTarget ) {
117 $this->assertContains(
120 "The module '$moduleName' must not have target '$moduleTarget' "
121 . "because its dependency '$dep' does not have it"
129 * CSSMin::getLocalFileReferences should ignore url(...) expressions
130 * that have been commented out.
132 public function testCommentedLocalFileReferences() {
133 $basepath = __DIR__
. '/../data/css/';
134 $css = file_get_contents( $basepath . 'comments.css' );
135 $files = CSSMin
::getLocalFileReferences( $css, $basepath );
136 $expected = [ $basepath . 'not-commented.gif' ];
140 'Url(...) expression in comment should be omitted.'
145 * Get all registered modules from ResouceLoader.
148 protected static function getAllModules() {
149 global $wgEnableJavaScriptTest;
151 // Test existance of test suite files as well
152 // (can't use setUp or setMwGlobals because providers are static)
153 $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
154 $wgEnableJavaScriptTest = true;
156 // Get main ResourceLoader
157 $rl = MediaWikiServices
::getInstance()->getResourceLoader();
161 foreach ( $rl->getModuleNames() as $moduleName ) {
162 $modules[$moduleName] = $rl->getModule( $moduleName );
166 $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
169 'modules' => $modules,
170 'resourceloader' => $rl,
171 'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
176 * Get all stylesheet files from modules that are an instance of
177 * ResourceLoaderFileModule (or one of its subclasses).
179 public static function provideMediaStylesheets() {
180 $data = self
::getAllModules();
183 foreach ( $data['modules'] as $moduleName => $module ) {
184 if ( !$module instanceof ResourceLoaderFileModule
) {
188 $reflectedModule = new ReflectionObject( $module );
190 $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
191 $getStyleFiles->setAccessible( true );
193 $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
194 $readStyleFile->setAccessible( true );
196 $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
198 $flip = $module->getFlip( $data['context'] );
200 foreach ( $styleFiles as $media => $files ) {
201 if ( $media && $media !== 'all' ) {
202 foreach ( $files as $file ) {
207 // XXX: Wrapped in an object to keep it out of PHPUnit output
209 'cssText' => $readStyleFile->invoke(
226 * Get all resource files from modules that are an instance of
227 * ResourceLoaderFileModule (or one of its subclasses).
229 public static function provideResourceFiles() {
230 $data = self
::getAllModules();
233 // See also ResourceLoaderFileModule::__construct
235 // Lists of file paths
242 // Collated lists of file paths
250 foreach ( $data['modules'] as $moduleName => $module ) {
251 if ( !$module instanceof ResourceLoaderFileModule
) {
255 $moduleProxy = TestingAccessWrapper
::newFromObject( $module );
259 foreach ( $filePathProps['lists'] as $propName ) {
260 $list = $moduleProxy->$propName;
261 foreach ( $list as $key => $value ) {
262 // 'scripts' are numeral arrays.
263 // 'styles' can be numeral or associative.
264 // In case of associative the key is the file path
265 // and the value is the 'media' attribute.
266 if ( is_int( $key ) ) {
274 foreach ( $filePathProps['nested-lists'] as $propName ) {
275 $lists = $moduleProxy->$propName;
276 foreach ( $lists as $list ) {
277 foreach ( $list as $key => $value ) {
278 // We need the same filter as for 'lists',
279 // due to 'skinStyles'.
280 if ( is_int( $key ) ) {
290 foreach ( $files as $file ) {
292 $moduleProxy->getLocalPath( $file ),
294 ( $file instanceof ResourceLoaderFilePath ?
$file->getPath() : $file ),
298 // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
299 $moduleProxy->readStyleFiles(
300 $module->getStyleFiles( $data['context'] ),
301 $module->getFlip( $data['context'] ),
305 $missingLocalFileRefs = $moduleProxy->missingLocalFileRefs
;
307 foreach ( $missingLocalFileRefs as $file ) {