wiki.techinc.nl/tests/phpunit/unit/parser/Parsoid/LanguageVariantConverterUnitTest.php
daniel 4ad9c9b035 variant transform: allow input content-language to be a variant
When submitting HTML to transform/html/to/html, the language specified
by the input's content-language header should be allowed to be the
source variant.

It should also be possible to just specify the source variant, and
derive the base language from that rather than the content-language
header or the page language.

Change-Id: I703c112358a921a8b0c9e63b70fd820ae3ea16fc
2022-11-02 01:30:36 -04:00

429 lines
12 KiB
PHP

<?php
namespace MediaWiki\Parser\Parsoid;
use InvalidArgumentException;
use Language;
use MediaWiki\Languages\LanguageFactory;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Parser\Parsoid\Config\PageConfig;
use MediaWiki\Parser\Parsoid\Config\PageConfigFactory;
use MediaWiki\Parser\Parsoid\Config\SiteConfig;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Title;
use TitleFactory;
use Wikimedia\Parsoid\Core\PageBundle;
use Wikimedia\Parsoid\Parsoid;
/**
* @covers MediaWiki\Parser\Parsoid\LanguageVariantConverter
*/
class LanguageVariantConverterUnitTest extends MediaWikiUnitTestCase {
/** @dataProvider provideSetConfig */
public function testSetConfig( bool $shouldPageConfigFactoryBeUsed ) {
// Decide what should be called and what should not be
$shouldParsoidBeUsed = true;
$isLanguageConversionEnabled = true;
// Set expected language codes
$pageBundleLanguageCode = 'zh';
$titleLanguageCode = 'zh-hans';
$targetLanguageCode = 'zh-hans';
$sourceLanguageCode = null;
// Create mocks
$parsoidSettings = [];
$pageConfigMock = $this->getPageConfigMock();
$pageBundleMock = $this->getPageBundleMock( $pageBundleLanguageCode );
$languageVariantConverter = $this->getLanguageVariantConverter(
$shouldParsoidBeUsed,
$shouldPageConfigFactoryBeUsed,
$isLanguageConversionEnabled,
$pageBundleLanguageCode,
null,
$titleLanguageCode,
$targetLanguageCode,
$sourceLanguageCode,
$parsoidSettings
);
if ( !$shouldPageConfigFactoryBeUsed ) {
$languageVariantConverter->setPageConfig( $pageConfigMock );
}
$languageVariantConverter->convertPageBundleVariant( $pageBundleMock, $targetLanguageCode );
}
public function provideSetConfig() {
yield 'PageConfigFactory should not be used if PageConfig is set' => [ false ];
yield 'PageConfigFactory should be used if PageConfig is not set' => [ true ];
}
/** @dataProvider provideSourceLanguage */
public function testSourceLanguage(
?string $pageBundleLanguageCode,
?string $titleLanguageCode,
?string $contentLanguageOverride,
?string $sourceLanguageCode,
?string $expectedSourceCode
) {
// Decide what should be called and what should not be
$shouldParsoidBeUsed = true;
$shouldPageConfigFactoryBeUsed = true;
$isLanguageConversionEnabled = true;
// Set expected language codes
$titleLanguageCode = $titleLanguageCode ?? 'en';
$targetLanguageCode = 'en-us';
$parsoidSettings = [];
// Create mocks
if ( $pageBundleLanguageCode ) {
$pageBundleMock = $this->getPageBundleMock( $pageBundleLanguageCode );
} else {
$pageBundleMock = $this->getPageBundleMockWithoutLanguage();
}
$languageVariantConverter = $this->getLanguageVariantConverter(
$shouldParsoidBeUsed,
$shouldPageConfigFactoryBeUsed,
$isLanguageConversionEnabled,
$pageBundleLanguageCode,
$contentLanguageOverride,
$titleLanguageCode,
$targetLanguageCode,
$expectedSourceCode,
$parsoidSettings
);
$languageVariantConverter->convertPageBundleVariant( $pageBundleMock, $targetLanguageCode, $sourceLanguageCode );
}
public function provideSourceLanguage() {
yield 'content-language in PageBundle' => [
'sr-el', // PageBundle content-language
null, // Title PageLanguage
null, // PageLanguage override
'sr-ec', // explicit source
'sr-ec' // expected source
];
yield 'content-language but no source language' => [
'en', // PageBundle content-language
null, // Title PageLanguage
null, // PageLanguage override
null, // explicit source
null // expected source
];
yield 'content-language is variant' => [
'en-ca', // PageBundle content-language
null, // Title PageLanguage
null, // PageLanguage override
null, // explicit source
'en-ca' // expected source
];
yield 'Source variant is given' => [
null, // PageBundle content-language
null, // Title PageLanguage
null, // PageLanguage override
'en-ca', // explicit source
'en-ca' // expected source
];
yield 'Source variant is a base language' => [
null, // PageBundle content-language
null, // Title PageLanguage
null, // PageLanguage override
'en', // explicit source
null // expected source
];
yield 'Page language override is variant' => [
null, // PageBundle content-language
null, // PageBundle content-language
'en-ca', // PageLanguage override
'en-ca', // explicit source
'en-ca' // expected source
];
}
/** @dataProvider provideSiteConfiguration */
public function testSiteConfiguration(
bool $isLanguageConversionEnabled,
bool $shouldParsoidBeUsed,
bool $shouldPageConfigFactoryBeUsed
) {
// Set expected language codes
$pageBundleLanguageCode = 'zh';
$titleLanguageCode = 'zh-hans';
$targetLanguageCode = 'zh-hans';
$sourceLanguageCode = null;
// Create mocks
$parsoidSettings = [];
if ( !$isLanguageConversionEnabled ) {
$this->expectException( InvalidArgumentException::class );
$this->expectExceptionMessage( 'LanguageConversion is not supported' );
}
$pageBundleMock = $this->getPageBundleMock( $pageBundleLanguageCode );
$languageVariantConverter = $this->getLanguageVariantConverter(
$shouldParsoidBeUsed,
$shouldPageConfigFactoryBeUsed,
$isLanguageConversionEnabled,
$pageBundleLanguageCode,
null,
$titleLanguageCode,
$targetLanguageCode,
$sourceLanguageCode,
$parsoidSettings
);
$languageVariantConverter->convertPageBundleVariant( $pageBundleMock, $targetLanguageCode );
}
public function provideSiteConfiguration() {
$isLanguageConversionEnabled = false;
$shouldParsoidBeUsed = false;
$shouldPageConfigFactoryBeUsed = false;
yield 'If language conversion is disabled, parsoid and page config factory should not be used' =>
[ $isLanguageConversionEnabled, $shouldParsoidBeUsed, $shouldPageConfigFactoryBeUsed ];
$isLanguageConversionEnabled = true;
$shouldParsoidBeUsed = true;
$shouldPageConfigFactoryBeUsed = true;
yield 'If language conversion is enabled, parsoid and page config factory should be used' =>
[ $isLanguageConversionEnabled, $shouldParsoidBeUsed, $shouldPageConfigFactoryBeUsed ];
}
/**
* @param bool $shouldParsoidBeUsed
* @param bool $shouldPageConfigFactoryBeUsed
* @param bool $isLanguageConversionEnabled
* @param string|null $pageBundleLanguageCode
* @param string|null $contentLanguageOverride
* @param string $titleLanguageCode
* @param string $targetLanguageCode
* @param string|null $sourceLanguageCode
* @param array $parsoidSettings
*
* @return LanguageVariantConverter
*/
private function getLanguageVariantConverter(
bool $shouldParsoidBeUsed,
bool $shouldPageConfigFactoryBeUsed,
bool $isLanguageConversionEnabled,
?string $pageBundleLanguageCode,
?string $contentLanguageOverride,
string $titleLanguageCode,
string $targetLanguageCode,
?string $sourceLanguageCode,
array $parsoidSettings
): LanguageVariantConverter {
// If Content language is set, use language from there,
// If PageBundle language code is set, use that
// Else, fallback to title page language
$pageLanguageCode = $contentLanguageOverride ?? $pageBundleLanguageCode ?? $titleLanguageCode;
// The page language code should not be a variant
$pageLanguageCode = preg_replace( '/-.*$/', '', $pageLanguageCode );
$shouldSiteConfigBeUsed = true;
$parsoidSettings = [];
$pageIdentityValue = new PageIdentityValue( 1, NS_MAIN, 'hello_world', PageIdentity::LOCAL );
// Create the necessary mocks
$pageConfigMock = $this->getPageConfigMock();
$pageConfigFactoryMock = $this->getPageConfigFactoryMock(
$shouldPageConfigFactoryBeUsed,
// Expected arguments to PageConfigFactory mock
[ $pageIdentityValue, null, null, null, $pageLanguageCode, $parsoidSettings ],
$pageConfigMock
);
$pageBundleMock = $this->getPageBundleMock( $pageBundleLanguageCode );
$siteConfigMock = $this->getSiteConfigMock(
$shouldSiteConfigBeUsed, $pageLanguageCode, $isLanguageConversionEnabled
);
$titleFactoryMock = $this->getTitleFactoryMock( $pageIdentityValue, $titleLanguageCode );
$languageFactoryMock = $this->getLanguageFactoryMock();
$parsoidMock = $this->getParsoidMock(
$shouldParsoidBeUsed,
[
$pageConfigMock,
'variant',
$pageBundleMock,
[ 'variant' => [ 'source' => $sourceLanguageCode, 'target' => $targetLanguageCode ] ]
]
);
$languageVariantConverter = new LanguageVariantConverter(
$pageIdentityValue,
$pageConfigFactoryMock,
$parsoidMock,
$parsoidSettings,
$siteConfigMock,
$titleFactoryMock,
$languageFactoryMock
);
if ( $contentLanguageOverride ) {
$languageVariantConverter->setPageLanguageOverride( $contentLanguageOverride );
}
return $languageVariantConverter;
}
// Mock methods follow
/**
* @param bool $shouldBeCalled
* @param array $arguments
* @param PageConfig $pageConfig
*
* @return MockObject|PageConfigFactory
*/
private function getPageConfigFactoryMock( bool $shouldBeCalled, array $arguments, PageConfig $pageConfig ) {
$mock = $this->createMock( PageConfigFactory::class );
if ( $shouldBeCalled ) {
$mock->expects( $this->once() )
->method( 'create' )
->with( ...$arguments )
->willReturn( $pageConfig );
} else {
$mock->expects( $this->never() )
->method( 'create' );
}
return $mock;
}
/**
* @param bool $shouldBeCalled
* @param array $arguments
*
* @return MockObject|Parsoid
*/
private function getParsoidMock( bool $shouldBeCalled, array $arguments ) {
$mock = $this->createMock( Parsoid::class );
if ( $shouldBeCalled ) {
$mock->expects( $this->once() )
->method( 'pb2pb' )
->with( ...$arguments );
} else {
$mock->expects( $this->never() )
->method( 'pb2pb' );
}
return $mock;
}
/**
* @param bool $shouldBeCalled
* @param string $baseLanguageCode
* @param bool $isLanguageConversionEnabled
*
* @return MockObject|SiteConfig
*/
private function getSiteConfigMock(
bool $shouldBeCalled,
string $baseLanguageCode,
bool $isLanguageConversionEnabled
) {
$mock = $this->createMock( SiteConfig::class );
if ( $shouldBeCalled ) {
$mock->expects( $this->once() )
->method( 'langConverterEnabledForLanguage' )
->with( $baseLanguageCode )
->willReturn( $isLanguageConversionEnabled );
} else {
$mock->expects( $this->never() )
->method( 'langConverterEnabledForLanguage' );
}
return $mock;
}
/**
* @param PageIdentity $pageIdentity
* @param string $languageCode
*
* @return MockObject|TitleFactory
*/
private function getTitleFactoryMock( PageIdentity $pageIdentity, string $languageCode ) {
$languageMock = $this->getLanguageMock( $languageCode );
$titleMock = $this->createMock( Title::class );
$titleMock->method( 'getPageLanguage' )
->willReturn( $languageMock );
$mock = $this->createMock( TitleFactory::class );
$mock->expects( $this->once() )
->method( 'castFromPageIdentity' )
->willReturn( $titleMock )
->with( $pageIdentity );
return $mock;
}
/**
* @return MockObject|LanguageFactory
*/
private function getLanguageFactoryMock() {
$mock = $this->createMock( LanguageFactory::class );
$mock->method( 'getLanguage' )
->willReturnCallback( function ( $code ) {
return $this->getLanguageMock( $code );
} );
$mock->method( 'getParentLanguage' )
->willReturnCallback( function ( $code ) {
$code = preg_replace( '/-.*$/', '', $code );
return $this->getLanguageMock( $code );
} );
return $mock;
}
/**
* @return MockObject|PageBundle
*/
private function getPageBundleMockWithoutLanguage() {
return $this->getPageBundleMock( null );
}
/**
* @param string|null $languageCode
*
* @return MockObject|PageBundle
*/
private function getPageBundleMock( ?string $languageCode ) {
$mock = $this->createMock( PageBundle::class );
$mock->headers = [
'content-language' => $languageCode
];
return $mock;
}
/**
* @return MockObject|PageConfig
*/
private function getPageConfigMock() {
$mock = $this->createNoOpMock( PageConfig::class, [ 'setVariant' ] );
return $mock;
}
/**
* @param string $languageCode
*
* @return MockObject|Language
*/
private function getLanguageMock( $languageCode ): Language {
$languageMock = $this->createMock( Language::class );
$languageMock->method( 'getCode' )
->willReturn( $languageCode );
return $languageMock;
}
}