wiki.techinc.nl/tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php
jdlrobson 4a287f9217 ResourceLoaderSkinModule defines the order of feature styles
Previously, the order of styles outputted by this module was
arbitrary, based on the order of the feature files passed in.

This was not how it was intended to work - the ResourceLoaderSkinModule
should be aware of the correct ordering.

In the case of normalize for example - it should always be the first
file output.

The implementation is adapted to check for valid features once in
the constructor and to also consider a DEFAULT_FEATURES value has been
set.

We  use the FEATURE_FILES constant to determine
the order of CSS output.

This is all documented in the class.

Bug: T269618
Change-Id: Iecbf11b5f09882e55d694651210d6d132d3cd412
2020-12-15 02:11:22 +00:00

493 lines
12 KiB
PHP

<?php
use Wikimedia\TestingAccessWrapper;
/**
* @group ResourceLoader
*/
class ResourceLoaderSkinModuleTest extends MediaWikiIntegrationTestCase {
public static function provideGetAvailableLogos() {
return [
[
[
'Logos' => [],
'Logo' => '/logo.png',
],
[
'1x' => '/logo.png',
]
],
[
[
'Logos' => [
'svg' => '/logo.svg',
'2x' => 'logo-2x.png'
],
'Logo' => '/logo.png',
],
[
'svg' => '/logo.svg',
'2x' => 'logo-2x.png',
'1x' => '/logo.png',
]
],
[
[
'Logos' => [
'wordmark' => '/logo-wordmark.png',
'1x' => '/logo.png',
'svg' => '/logo.svg',
'2x' => 'logo-2x.png'
],
],
[
'wordmark' => '/logo-wordmark.png',
'1x' => '/logo.png',
'svg' => '/logo.svg',
'2x' => 'logo-2x.png',
]
]
];
}
public static function provideGetStyles() {
// phpcs:disable Generic.Files.LineLength
return [
[
'parent' => [],
'logo' => '/logo.png',
'expected' => [
'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
],
],
[
'parent' => [
'screen' => '.example {}',
],
'logo' => '/logo.png',
'expected' => [
'screen' => [ '.example {}' ],
'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
],
],
[
'parent' => [],
'logo' => [
'1x' => '/logo.png',
'1.5x' => '/logo@1.5x.png',
'2x' => '/logo@2x.png',
],
'expected' => [
'all' => [ <<<CSS
.mw-wiki-logo { background-image: url(/logo.png); }
CSS
],
'(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx), (min-resolution: 144dpi)' => [ <<<CSS
.mw-wiki-logo { background-image: url(/logo@1.5x.png);background-size: 135px auto; }
CSS
],
'(-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi)' => [ <<<CSS
.mw-wiki-logo { background-image: url(/logo@2x.png);background-size: 135px auto; }
CSS
],
],
],
[
'parent' => [],
'logo' => [
'1x' => '/logo.png',
'svg' => '/logo.svg',
],
'expected' => [
'all' => [ <<<CSS
.mw-wiki-logo { background-image: url(/logo.png); }
CSS
, <<<CSS
.mw-wiki-logo { background-image: -webkit-linear-gradient(transparent, transparent), url(/logo.svg); background-image: linear-gradient(transparent, transparent), url(/logo.svg);background-size: 135px auto; }
CSS
],
],
],
];
// phpcs:enable
}
/**
* @dataProvider provideGetStyles
* @covers ResourceLoaderSkinModule
*/
public function testGetStyles( $parent, $logo, $expected ) {
$module = $this->getMockBuilder( ResourceLoaderSkinModule::class )
->setMethods( [ 'readStyleFiles', 'getConfig', 'getLogoData' ] )
->disableOriginalConstructor()
->getMock();
$module->expects( $this->once() )->method( 'readStyleFiles' )
->willReturn( $parent );
$module->expects( $this->any() )->method( 'getConfig' )
->willReturn( new HashConfig( [
'UseNewMediaStructure' => true,
] ) );
$module->expects( $this->once() )->method( 'getLogoData' )
->willReturn( $logo );
$ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()->getMock();
$module->__construct();
$this->assertEquals(
$expected,
$module->getStyles( $ctx )
);
}
/**
* @dataProvider provideGetAvailableLogos
* @covers ResourceLoaderSkinModule::getAvailableLogos
*/
public function testGetAvailableLogos( $config, $expected ) {
$logos = ResourceLoaderSkinModule::getAvailableLogos( new HashConfig( $config ) );
$this->assertSame( $logos, $expected );
}
/**
* @covers ResourceLoaderSkinModule::getAvailableLogos
*/
public function testGetAvailableLogosRuntimeException() {
$this->expectException( \RuntimeException::class );
ResourceLoaderSkinModule::getAvailableLogos( new HashConfig( [
'Logo' => false,
'Logos' => false,
'LogoHD' => false,
] ) );
}
/**
* @covers ResourceLoaderSkinModule::isKnownEmpty
*/
public function testIsKnownEmpty() {
$module = $this->getMockBuilder( ResourceLoaderSkinModule::class )
->disableOriginalConstructor()->setMethods( null )->getMock();
$ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()->getMock();
$this->assertFalse( $module->isKnownEmpty( $ctx ) );
}
/**
* @dataProvider provideGetLogoData
* @covers ResourceLoaderSkinModule::getLogoData
*/
public function testGetLogoData( $config, $expected, $baseDir = null ) {
if ( $baseDir ) {
$this->setMwGlobals( 'IP', $baseDir );
}
// Allow testing of protected method
$module = TestingAccessWrapper::newFromObject( new ResourceLoaderSkinModule() );
$this->assertEquals(
$expected,
$module->getLogoData( new HashConfig( $config ) )
);
}
public function provideGetLogoData() {
return [
'wordmark' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
'wordmark' => '/img/wordmark.png',
],
],
'expected' => '/img/default.png',
],
'simple' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
],
],
'expected' => '/img/default.png',
],
'default and 2x' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
'2x' => '/img/two-x.png',
],
],
'expected' => [
'1x' => '/img/default.png',
'2x' => '/img/two-x.png',
],
],
'default and all HiDPIs' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
'1.5x' => '/img/one-point-five.png',
'2x' => '/img/two-x.png',
],
],
'expected' => [
'1x' => '/img/default.png',
'1.5x' => '/img/one-point-five.png',
'2x' => '/img/two-x.png',
],
],
'default and SVG' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
'svg' => '/img/vector.svg',
],
],
'expected' => [
'1x' => '/img/default.png',
'svg' => '/img/vector.svg',
],
],
'everything' => [
'config' => [
'ResourceBasePath' => '/w',
'Logos' => [
'1x' => '/img/default.png',
'1.5x' => '/img/one-point-five.png',
'2x' => '/img/two-x.png',
'svg' => '/img/vector.svg',
],
],
'expected' => [
'1x' => '/img/default.png',
'svg' => '/img/vector.svg',
],
],
'versioned url' => [
'config' => [
'ResourceBasePath' => '/w',
'UploadPath' => '/w/images',
'Logos' => [
'1x' => '/w/test.jpg',
],
],
'expected' => '/w/test.jpg?edcf2',
'baseDir' => dirname( dirname( __DIR__ ) ) . '/data/media',
],
];
}
/**
* @dataProvider providePreloadLinks
* @covers ResourceLoaderSkinModule::getPreloadLinks
* @covers ResourceLoaderSkinModule::getLogoPreloadlinks
* @covers ResourceLoaderSkinModule::getLogoData
*/
public function testPreloadLinkHeaders( $config, $result ) {
$this->setMwGlobals( $config );
$ctx = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()->getMock();
$module = new ResourceLoaderSkinModule();
$this->assertEquals( [ $result ], $module->getHeaders( $ctx ) );
}
public function providePreloadLinks() {
return [
[
[
'wgResourceBasePath' => '/w',
'wgLogo' => false,
'wgLogoHD' => false,
'wgLogos' => [
'1x' => '/img/default.png',
'1.5x' => '/img/one-point-five.png',
'2x' => '/img/two-x.png',
],
],
'Link: </img/default.png>;rel=preload;as=image;media=' .
'not all and (min-resolution: 1.5dppx),' .
'</img/one-point-five.png>;rel=preload;as=image;media=' .
'(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
'</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
],
[
[
'wgResourceBasePath' => '/w',
'wgLogo' => false,
'wgLogoHD' => false,
'wgLogos' => [
'1x' => '/img/default.png',
],
],
'Link: </img/default.png>;rel=preload;as=image'
],
[
[
'wgResourceBasePath' => '/w',
'wgLogo' => false,
'wgLogoHD' => false,
'wgLogos' => [
'1x' => '/img/default.png',
'2x' => '/img/two-x.png',
],
],
'Link: </img/default.png>;rel=preload;as=image;media=' .
'not all and (min-resolution: 2dppx),' .
'</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
],
[
[
'wgResourceBasePath' => '/w',
'wgLogo' => false,
'wgLogoHD' => false,
'wgLogos' => [
'1x' => '/img/default.png',
'svg' => '/img/vector.svg',
],
],
'Link: </img/vector.svg>;rel=preload;as=image'
],
[
[
'wgResourceBasePath' => '/w',
'wgLogo' => false,
'wgLogoHD' => false,
'wgLogos' => [
'1x' => '/w/test.jpg',
],
'wgUploadPath' => '/w/images',
'IP' => dirname( dirname( __DIR__ ) ) . '/data/media',
],
'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
],
];
}
/**
* @covers ResourceLoaderSkinModule::getPreloadLinks
*/
public function testNoPreloadLogos() {
$module = new ResourceLoaderSkinModule( [ 'features' => [ 'logo' => false ] ] );
$context =
$this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()
->getMock();
$preloadLinks = $module->getPreloadLinks( $context );
$this->assertArrayEquals( [], $preloadLinks );
}
/**
* @covers ResourceLoaderSkinModule::getPreloadLinks
*/
public function testPreloadLogos() {
$module = new ResourceLoaderSkinModule();
$context = $this->getMockBuilder( ResourceLoaderContext::class )
->disableOriginalConstructor()->getMock();
$preloadLinks = $module->getPreloadLinks( $context );
$this->assertNotSameSize( [], $preloadLinks );
}
/**
* Covers ResourceLoaderSkinModule::FEATURE_FILES, but not annotatable.
*
* @dataProvider provideFeatureFiles
* @coversNothing
*
* @param string $file
*/
public function testFeatureFilesExist( string $file ) : void {
$this->assertFileExists( $file );
}
public function provideFeatureFiles() : Generator {
global $IP;
$featureFiles = ( new ReflectionClass( ResourceLoaderSkinModule::class ) )
->getConstant( 'FEATURE_FILES' );
foreach ( $featureFiles as $feature => $files ) {
foreach ( $files as $media => $stylesheets ) {
foreach ( $stylesheets as $stylesheet ) {
yield "$feature: $media: $stylesheet" => [ "$IP/$stylesheet" ];
}
}
}
}
/**
* @covers ResourceLoaderSkinModule::getStyleFiles
*/
public function testGetStyleFilesFeatureStylesOrder() : void {
$ctx = $this->createMock( ResourceLoaderContext::class );
$module = new ResourceLoaderSkinModule(
[
// The ordering should be controlled by ResourceLoaderSkinModule
// `normalize` will be outputted before `elements` despite the ordering
'features' => [ 'elements', 'normalize' ],
'styles' => [
'test.styles/styles.css' => [
'media' => 'screen'
]
]
]
);
$moduleFeaturesObject = new ResourceLoaderSkinModule(
[
// The ordering should be controlled by ResourceLoaderSkinModule
// `normalize` will be outputted before `elements` despite the ordering
'features' => [
'elements' => true,
'normalize' => true,
'toc' => false,
],
'styles' => [
'test.styles/styles.css' => [
'media' => 'screen'
]
]
]
);
list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
ResourceLoaderFileModule::extractBasePaths();
$featureFiles = ( new ReflectionClass( ResourceLoaderSkinModule::class ) )
->getConstant( 'FEATURE_FILES' );
$expected = [
'all' => [
new ResourceLoaderFilePath(
$featureFiles['normalize']['all'][0],
$defaultLocalBasePath,
$defaultRemoteBasePath
),
],
'screen' => [
new ResourceLoaderFilePath(
$featureFiles['elements']['screen'][0],
$defaultLocalBasePath,
$defaultRemoteBasePath
),
'test.styles/styles.css'
]
];
$this->assertEquals(
$expected,
$moduleFeaturesObject->getStyleFiles( $ctx )
);
$this->assertEquals(
$expected,
$module->getStyleFiles( $ctx )
);
}
}