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
429 lines
12 KiB
PHP
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;
|
|
}
|
|
}
|