summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/structure/ResourcesTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/structure/ResourcesTest.php')
-rw-r--r--www/wiki/tests/phpunit/structure/ResourcesTest.php349
1 files changed, 349 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/structure/ResourcesTest.php b/www/wiki/tests/phpunit/structure/ResourcesTest.php
new file mode 100644
index 00000000..62ddaceb
--- /dev/null
+++ b/www/wiki/tests/phpunit/structure/ResourcesTest.php
@@ -0,0 +1,349 @@
+<?php
+/**
+ * Sanity checks for making sure registered resources are sane.
+ *
+ * @file
+ * @author Antoine Musso
+ * @author Niklas Laxström
+ * @author Santhosh Thottingal
+ * @author Timo Tijhof
+ * @copyright © 2012, Antoine Musso
+ * @copyright © 2012, Niklas Laxström
+ * @copyright © 2012, Santhosh Thottingal
+ * @copyright © 2012, Timo Tijhof
+ */
+class ResourcesTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideResourceFiles
+ */
+ public function testFileExistence( $filename, $module, $resource ) {
+ $this->assertFileExists( $filename,
+ "File '$resource' referenced by '$module' must exist."
+ );
+ }
+
+ /**
+ * @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'
+ );
+ }
+
+ public function testVersionHash() {
+ $data = self::getAllModules();
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ $version = $module->getVersionHash( $data['context'] );
+ $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
+ }
+ }
+
+ /**
+ * Verify that nothing explicitly depends on base modules, or other raw modules.
+ *
+ * Depending on them is unsupported as they are not registered client-side by the startup module.
+ *
+ * TODO Modules can dynamically choose dependencies based on context. This method does not
+ * test such dependencies. The same goes for testMissingDependencies() and
+ * testUnsatisfiableDependencies().
+ */
+ public function testIllegalDependencies() {
+ $data = self::getAllModules();
+
+ $illegalDeps = ResourceLoaderStartUpModule::getStartupModules();
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ if ( $module->isRaw() ) {
+ $illegalDeps[] = $moduleName;
+ }
+ }
+ $illegalDeps = array_unique( $illegalDeps );
+
+ /** @var ResourceLoaderModule $module */
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ foreach ( $illegalDeps as $illegalDep ) {
+ $this->assertNotContains(
+ $illegalDep,
+ $module->getDependencies( $data['context'] ),
+ "Module '$moduleName' must not depend on '$illegalDep'"
+ );
+ }
+ }
+ }
+
+ /**
+ * Verify that all modules specified as dependencies of other modules actually exist.
+ */
+ public function testMissingDependencies() {
+ $data = self::getAllModules();
+ $validDeps = array_keys( $data['modules'] );
+
+ /** @var ResourceLoaderModule $module */
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ foreach ( $module->getDependencies( $data['context'] ) as $dep ) {
+ $this->assertContains(
+ $dep,
+ $validDeps,
+ "The module '$dep' required by '$moduleName' must exist"
+ );
+ }
+ }
+ }
+
+ /**
+ * Verify that all specified messages actually exist.
+ */
+ public function testMissingMessages() {
+ $data = self::getAllModules();
+ $lang = Language::factory( '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->assertArrayEquals(
+ $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;
+
+ // Initialize ResourceLoader
+ $rl = new ResourceLoader();
+
+ $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();
+ $cases = [];
+
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ if ( !$module instanceof ResourceLoaderFileModule ) {
+ continue;
+ }
+
+ $reflectedModule = new ReflectionObject( $module );
+
+ $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
+ $getStyleFiles->setAccessible( true );
+
+ $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
+ $readStyleFile->setAccessible( true );
+
+ $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
+
+ $flip = $module->getFlip( $data['context'] );
+
+ foreach ( $styleFiles as $media => $files ) {
+ if ( $media && $media !== 'all' ) {
+ foreach ( $files as $file ) {
+ $cases[] = [
+ $moduleName,
+ $media,
+ $file,
+ // XXX: Wrapped in an object to keep it out of PHPUnit output
+ (object)[
+ 'cssText' => $readStyleFile->invoke(
+ $module,
+ $file,
+ $flip,
+ $data['context']
+ )
+ ],
+ ];
+ }
+ }
+ }
+ }
+
+ return $cases;
+ }
+
+ /**
+ * Get all resource files from modules that are an instance of
+ * ResourceLoaderFileModule (or one of its subclasses).
+ *
+ * Since the raw data is stored in protected properties, we have to
+ * overrride this through ReflectionObject methods.
+ */
+ public static function provideResourceFiles() {
+ $data = self::getAllModules();
+ $cases = [];
+
+ // 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;
+ }
+
+ $reflectedModule = new ReflectionObject( $module );
+
+ $files = [];
+
+ foreach ( $filePathProps['lists'] as $propName ) {
+ $property = $reflectedModule->getProperty( $propName );
+ $property->setAccessible( true );
+ $list = $property->getValue( $module );
+ 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 ) {
+ $property = $reflectedModule->getProperty( $propName );
+ $property->setAccessible( true );
+ $lists = $property->getValue( $module );
+ 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;
+ }
+ }
+ }
+ }
+
+ // Get method for resolving the paths to full paths
+ $method = $reflectedModule->getMethod( 'getLocalPath' );
+ $method->setAccessible( true );
+
+ // Populate cases
+ foreach ( $files as $file ) {
+ $cases[] = [
+ $method->invoke( $module, $file ),
+ $moduleName,
+ ( $file instanceof ResourceLoaderFilePath ? $file->getPath() : $file ),
+ ];
+ }
+
+ // To populate missingLocalFileRefs. Not sure how sane this is inside this test...
+ $module->readStyleFiles(
+ $module->getStyleFiles( $data['context'] ),
+ $module->getFlip( $data['context'] ),
+ $data['context']
+ );
+
+ $property = $reflectedModule->getProperty( 'missingLocalFileRefs' );
+ $property->setAccessible( true );
+ $missingLocalFileRefs = $property->getValue( $module );
+
+ foreach ( $missingLocalFileRefs as $file ) {
+ $cases[] = [
+ $file,
+ $moduleName,
+ $file,
+ ];
+ }
+ }
+
+ return $cases;
+ }
+}