wiki.techinc.nl/tests/phpunit/structure/ResourcesTest.php
Timo Tijhof 245bdafd9e resourceloader: Add wikimedia/minify package and remove local copies
- JavaScriptMinifier: The public interface for this internal class is
  ResourceLoader::filter which, except for one caller (minify.php)
  is indeed consistently used already, no other callers need replacing.

- CSSMin: Idem for minification, however, this class has some other
  method as well, which have a number of internal users, but none
  outside core. These have all been replaced in this commit.

- Remove pear/net_url2 as own dependency as this was only used by CSSMin
  and recently added there. This is now a dependency of wikimedia/minify
  instead. This was not part of the public API and no longer mentioned
  here in composer.json or RELEASE-NOTES. (It remains pinned in
  mediawiki-vendor, however.)

Bug: T273247
Depends-On: Iadff8c2112d5e53bd994ab4882006e8c644a2379
Depends-On: Id3c6dcc0b952a9efe34b3a9ed88b716101a51f87
Depends-On: I589be910f57289fd908b22db87241b0e52da60d4
Change-Id: I8d71fc64aeecdb31db218b02e361ae9bb0d19b48
2021-02-24 21:03:02 +00:00

310 lines
8.8 KiB
PHP

<?php
use MediaWiki\MediaWikiServices;
use Wikimedia\Minify\CSSMin;
use Wikimedia\TestingAccessWrapper;
/**
* Sanity checks for making sure registered resources are sane.
*
* @author Antoine Musso
* @author Niklas Laxström
* @author Santhosh Thottingal
* @copyright © 2012, Antoine Musso
* @copyright © 2012, Niklas Laxström
* @copyright © 2012, Santhosh Thottingal
*/
class ResourcesTest extends MediaWikiIntegrationTestCase {
/**
* @dataProvider provideMediaStylesheets
*/
public function testStyleMedia( $moduleName, $media, $filename, $css ) {
$cssText = CSSMin::minify( $css->cssText );
$this->assertTrue(
strpos( $cssText, '@media' ) === false,
'Stylesheets should not both specify "media" and contain @media'
);
}
/**
* Verify that all modules specified as dependencies of other modules actually
* exist and are not illegal.
*
* @todo Modules can dynamically choose dependencies based on context. This method
* does not find all such variations. The same applies to testUnsatisfiableDependencies().
*/
public function testValidDependencies() {
$data = self::getAllModules();
$knownDeps = array_keys( $data['modules'] );
$illegalDeps = [ 'startup' ];
// Avoid an assert for each module to keep the test fast.
// Instead, perform a single assertion against everything at once.
// When all is good, actual/expected are both empty arrays.
// When we find issues, add the violations to 'actual' and add an empty
// key to 'expected'. These keys in expected are because the PHPUnit diff
// (as of 6.5) only goes one level deep.
$actualUnknown = [];
$expectedUnknown = [];
$actualIllegal = [];
$expectedIllegal = [];
/** @var ResourceLoaderModule $module */
foreach ( $data['modules'] as $moduleName => $module ) {
foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
if ( !in_array( $dep, $knownDeps, true ) ) {
$actualUnknown[$moduleName][] = $dep;
$expectedUnknown[$moduleName] = [];
}
if ( in_array( $dep, $illegalDeps, true ) ) {
$actualIllegal[$moduleName][] = $dep;
$expectedIllegal[$moduleName] = [];
}
}
}
$this->assertEquals( $expectedUnknown, $actualUnknown, 'Dependencies that do not exist' );
$this->assertEquals( $expectedIllegal, $actualIllegal, 'Dependencies that are not legal' );
}
/**
* Verify that all specified messages actually exist.
*/
public function testMissingMessages() {
$data = self::getAllModules();
$lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
/** @var ResourceLoaderModule $module */
foreach ( $data['modules'] as $moduleName => $module ) {
foreach ( $module->getMessages() as $msgKey ) {
$this->assertTrue(
wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
"Message '$msgKey' required by '$moduleName' must exist"
);
}
}
}
/**
* Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
* for the involved modules.
*
* Example: A depends on B. A has targets: mobile, desktop. B has targets: desktop. Therefore the
* dependency is sometimes unsatisfiable: it's impossible to load module A on mobile.
*/
public function testUnsatisfiableDependencies() {
$data = self::getAllModules();
/** @var ResourceLoaderModule $module */
foreach ( $data['modules'] as $moduleName => $module ) {
$moduleTargets = $module->getTargets();
foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
if ( !isset( $data['modules'][$dep] ) ) {
// Missing dependencies reported by testMissingDependencies
continue;
}
$targets = $data['modules'][$dep]->getTargets();
foreach ( $moduleTargets as $moduleTarget ) {
$this->assertContains(
$moduleTarget,
$targets,
"The module '$moduleName' must not have target '$moduleTarget' "
. "because its dependency '$dep' does not have it"
);
}
}
}
}
/**
* CSSMin::getLocalFileReferences should ignore url(...) expressions
* that have been commented out.
*/
public function testCommentedLocalFileReferences() {
$basepath = __DIR__ . '/../data/css/';
$css = file_get_contents( $basepath . 'comments.css' );
$files = CSSMin::getLocalFileReferences( $css, $basepath );
$expected = [ $basepath . 'not-commented.gif' ];
$this->assertSame(
$expected,
$files,
'Url(...) expression in comment should be omitted.'
);
}
/**
* Get all registered modules from ResouceLoader.
* @return array
*/
protected static function getAllModules() {
global $wgEnableJavaScriptTest;
// Test existance of test suite files as well
// (can't use setUp or setMwGlobals because providers are static)
$org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
$wgEnableJavaScriptTest = true;
// Get main ResourceLoader
$rl = MediaWikiServices::getInstance()->getResourceLoader();
$modules = [];
foreach ( $rl->getModuleNames() as $moduleName ) {
$modules[$moduleName] = $rl->getModule( $moduleName );
}
// Restore settings
$wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
return [
'modules' => $modules,
'resourceloader' => $rl,
'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
];
}
/**
* Get all stylesheet files from modules that are an instance of
* ResourceLoaderFileModule (or one of its subclasses).
*/
public static function provideMediaStylesheets() {
$data = self::getAllModules();
$context = $data['context'];
foreach ( $data['modules'] as $moduleName => $module ) {
if ( !$module instanceof ResourceLoaderFileModule ) {
continue;
}
$moduleProxy = TestingAccessWrapper::newFromObject( $module );
$styleFiles = $moduleProxy->getStyleFiles( $context );
foreach ( $styleFiles as $media => $files ) {
if ( $media && $media !== 'all' ) {
foreach ( $files as $file ) {
yield [
$moduleName,
$media,
$file,
// XXX: Wrapped in an object to keep it out of PHPUnit output
(object)[
'cssText' => $moduleProxy->readStyleFile( $file, $context )
],
];
}
}
}
}
}
/**
* Check all resource files from ResourceLoaderFileModule modules.
*/
public function testResourceFiles() {
$data = self::getAllModules();
// See also ResourceLoaderFileModule::__construct
$filePathProps = [
// Lists of file paths
'lists' => [
'scripts',
'debugScripts',
'styles',
],
// Collated lists of file paths
'nested-lists' => [
'languageScripts',
'skinScripts',
'skinStyles',
],
];
foreach ( $data['modules'] as $moduleName => $module ) {
if ( !$module instanceof ResourceLoaderFileModule ) {
continue;
}
$moduleProxy = TestingAccessWrapper::newFromObject( $module );
$files = [];
foreach ( $filePathProps['lists'] as $propName ) {
$list = $moduleProxy->$propName;
foreach ( $list as $key => $value ) {
// 'scripts' are numeral arrays.
// 'styles' can be numeral or associative.
// In case of associative the key is the file path
// and the value is the 'media' attribute.
if ( is_int( $key ) ) {
$files[] = $value;
} else {
$files[] = $key;
}
}
}
foreach ( $filePathProps['nested-lists'] as $propName ) {
$lists = $moduleProxy->$propName;
foreach ( $lists as $list ) {
foreach ( $list as $key => $value ) {
// We need the same filter as for 'lists',
// due to 'skinStyles'.
if ( is_int( $key ) ) {
$files[] = $value;
} else {
$files[] = $key;
}
}
}
}
foreach ( $files as $file ) {
$relativePath = ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file );
$this->assertFileExists(
$moduleProxy->getLocalPath( $file ),
"File '$relativePath' referenced by '$moduleName' must exist."
);
}
// To populate missingLocalFileRefs. Not sure how sane this is inside this test...
$moduleProxy->readStyleFiles(
$module->getStyleFiles( $data['context'] ),
$data['context']
);
$missingLocalFileRefs = $moduleProxy->missingLocalFileRefs;
foreach ( $missingLocalFileRefs as $file ) {
$this->assertFileExists(
$file,
"File '$file' referenced by '$moduleName' must exist."
);
}
}
}
/**
* Check all image files from ResourceLoaderImageModule modules.
*/
public function testImageFiles() {
$data = self::getAllModules();
foreach ( $data['modules'] as $moduleName => $module ) {
if ( !$module instanceof ResourceLoaderImageModule ) {
continue;
}
$imagesFiles = $module->getImages( $data['context'] );
foreach ( $imagesFiles as $file ) {
$relativePath = $file->getName();
$this->assertFileExists(
$file->getPath( $data['context'] ),
"File '$relativePath' referenced by '$moduleName' must exist."
);
}
}
}
}