Developers can use this to test their local version of Codex with MediaWiki by pointing $wgCodexDevelopmentDir to their local clone of the Codex repo, e.g. $wgCodexDevelopmentDir = '/home/yourname/git/codex'; Setting $wgCodexDevelopmentDir affects where the following things come from: - Codex JS/CSS files for the full library - Codex JS/CSS files for code-split chunks, and the manifest.json file that points to them - Icons retrieved by CodexModule::getIcons() - CSS-only icons imported in Less - Design tokens imported in Less Other changes in this patch: - Add CodexModule::makeFilePath() to centralize the repeated path concatenation. This makes it easier to switch out the regular path for the dev mode path. - Replace all uses of $IP (which is deprecated) and MW_INSTALL_PATH in CodexModule with the BaseDirectory config setting. - Make CodexModule::getIcons() reset its static cache if the path to the icons file changes. Without this, it's impossible to make the unit tests pass. - Move the i18n messages code from the CodexModule constructor to getMessages(). It can't be in the constructor because makeFilePath() doesn't work there (it fails because the Config object hasn't been set up yet). - Add a 'mediawiki.skin.codex' import path so that we can stop hard-coding the path to the Codex mixins file. Without this, we can't make the Codex mixins come from the right place in development mode. - Consider $wgCodexDevelopmentDir in setting the cache key for compiled Less code, since changing this setting can change the output of Less compilation (by changing design tokens, icons or mixins). - Add unit tests for (the non-dev mode behavior of) CodexModule::getIcons() and the i18n message key handling. Bug: T314507 Change-Id: I11c6a81a1ba34fe10f4b1c98bf76f0db40c1ce98
935 lines
24 KiB
PHP
935 lines
24 KiB
PHP
<?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\Tests\Unit\DummyServicesTrait;
|
|
use RuntimeException;
|
|
use SkinFactory;
|
|
|
|
/**
|
|
* @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' ],
|
|
'scripts' => [ 'script-comment.js' ],
|
|
] );
|
|
$module->setName( 'testing' );
|
|
$module->setConfig( $ctx->getResourceLoader()->getConfig() );
|
|
|
|
$this->assertEquals(
|
|
[
|
|
'https://example.org/w/something/script-comment.js'
|
|
],
|
|
$module->getScriptURLsForDebug( $ctx ),
|
|
'script urls'
|
|
);
|
|
$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',
|
|
'dir' => 'ltr',
|
|
] );
|
|
$contextRtl = $this->getResourceLoaderContext( [
|
|
'lang' => 'he',
|
|
'dir' => 'rtl',
|
|
] );
|
|
|
|
// 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', 'dir' => 'ltr' ] );
|
|
$this->assertEquals(
|
|
[ 'all' => ".example { text-align: left; }\n" ],
|
|
$plain->getStyles( $context ),
|
|
'Unchanged styles in LTR mode'
|
|
);
|
|
$context = $this->getResourceLoaderContext( [ 'lang' => 'he', 'dir' => 'rtl' ] );
|
|
$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' );
|
|
}
|
|
}
|