<?php
declare( strict_types = 1 );
namespace Wikibase\Client\Tests\Unit\DataAccess\ParserFunctions;
use MediaWiki\Languages\LanguageConverterFactory;
use MediaWiki\Languages\LanguageFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Parser\Parser;
use MediaWiki\Parser\ParserOptions;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use ValueFormatters\FormatterOptions;
use ValueFormatters\ValueFormatter;
use Wikibase\Client\DataAccess\DataAccessSnakFormatterFactory;
use Wikibase\Client\DataAccess\ParserFunctions\LanguageAwareRenderer;
use Wikibase\Client\DataAccess\ParserFunctions\StatementGroupRendererFactory;
use Wikibase\Client\DataAccess\ParserFunctions\VariantsAwareRenderer;
use Wikibase\Client\DataAccess\SnaksFinder;
use Wikibase\Client\Usage\EntityUsageFactory;
use Wikibase\Client\Usage\UsageAccumulatorFactory;
use Wikibase\Client\Usage\UsageDeduplicator;
use Wikibase\DataModel\Entity\BasicEntityIdParser;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityIdValue;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\ItemIdParser;
use Wikibase\DataModel\Entity\NumericPropertyId;
use Wikibase\DataModel\Services\Lookup\EntityLookup;
use Wikibase\DataModel\Services\Lookup\EntityRedirectTargetLookup;
use Wikibase\DataModel\Services\Lookup\InMemoryDataTypeLookup;
use Wikibase\DataModel\Services\Lookup\RestrictedEntityLookupFactory;
use Wikibase\DataModel\Services\Term\PropertyLabelResolver;
use Wikibase\DataModel\Snak\PropertyValueSnak;
use Wikibase\DataModel\Statement\StatementListProvider;
use Wikibase\Lib\Formatters\OutputFormatSnakFormatterFactory;
use Wikibase\Lib\Formatters\SnakFormatter;
use Wikibase\Lib\LanguageFallbackChainFactory;
use Wikibase\Lib\Store\FallbackLabelDescriptionLookup;
use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory;
/**
* @covers \Wikibase\Client\DataAccess\ParserFunctions\StatementGroupRendererFactory
*
* @group Wikibase
* @group WikibaseClient
* @group WikibaseDataAccess
*
* @license GPL-2.0-or-later
* @author Katie Filbert < aude.wiki@gmail.com >
*/
class StatementGroupRendererFactoryTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider wikitextTypeProvider
*/
public function testNewRendererFromParser_forWikitextType( string $type ): void {
$parser = $this->getParser( 'zh', 'es', true );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser, $type );
$this->assertInstanceOf( LanguageAwareRenderer::class, $renderer );
}
public static function wikitextTypeProvider(): iterable {
return [
[ DataAccessSnakFormatterFactory::TYPE_ESCAPED_PLAINTEXT ],
[ DataAccessSnakFormatterFactory::TYPE_RICH_WIKITEXT ],
];
}
public function testNewRenderer_contentConversionDisabled(): void {
$parser = $this->getParser( 'zh', 'es', false, true );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser );
$this->assertInstanceOf( LanguageAwareRenderer::class, $renderer );
}
public function testNewRenderer_titleConversionDisabled(): void {
$parser = $this->getParser( 'zh', 'es', false, false, true );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser );
$this->assertInstanceOf( VariantsAwareRenderer::class, $renderer );
}
/**
* @dataProvider newRenderer_forParserFormatProvider
*/
public function testNewRenderer_forParserFormat( string $languageCode, int $format ): void {
$parser = $this->getParser( $languageCode, 'es', false, false, false, $format );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser );
$this->assertInstanceOf( LanguageAwareRenderer::class, $renderer );
}
public static function newRenderer_forParserFormatProvider(): array {
return [
[ 'ku', Parser::OT_PLAIN ],
[ 'zh', Parser::OT_WIKI ],
[ 'zh', Parser::OT_PREPROCESS ],
];
}
public function testNewRenderer_forNonVariantLanguage(): void {
$parser = $this->getParser( 'en', 'es', true );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser );
$this->assertInstanceOf( LanguageAwareRenderer::class, $renderer );
}
public function testNewRender_forVariantLanguage(): void {
$parser = $this->getParser( 'zh' );
$rendererFactory = $this->getStatementGroupRendererFactory();
$renderer = $rendererFactory->newRendererFromParser( $parser );
$this->assertInstanceOf( VariantsAwareRenderer::class, $renderer );
}
/**
* @dataProvider provideWikitextTypes
*/
public function testRenderOutput( string $wikitextType, string $expectedWikitext, bool $titleUsageExpected ): void {
$wikitext = $this->getStatementGroupRendererFactory()
->newRendererFromParser( $this->getParser(), $wikitextType )
->render( new ItemId( 'Q1' ), 'P1' );
$this->assertSame( $expectedWikitext, $wikitext );
}
/**
* @dataProvider provideWikitextTypes
*/
public function testTitleUsageTracking( string $wikitextType, string $expectedWikitext, bool $titleUsageExpected ): void {
$parser = $this->getParser();
$usageAccumulator = $this->newUsageAccumulatorFactory()->newFromParser( $parser );
$this->getStatementGroupRendererFactory()
->newRendererFromParser( $parser, $wikitextType )
->render( new ItemId( 'Q1' ), 'P1' );
$usages = $usageAccumulator->getUsages();
$this->assertSame( $titleUsageExpected, array_key_exists( 'Q7#T', $usages ) );
}
public static function provideWikitextTypes(): array {
return [
[ DataAccessSnakFormatterFactory::TYPE_ESCAPED_PLAINTEXT, 'Kittens!', false ],
[ DataAccessSnakFormatterFactory::TYPE_RICH_WIKITEXT, '<span><span>Kittens!</span></span>', true ],
];
}
/**
* @dataProvider allowDataAccessInUserLanguageProvider
*/
public function testNewRenderer_usageTracking( bool $allowDataAccessInUserLanguage ): void {
$parser = $this->getParser( 'en', 'es', true );
$rendererFactory = $this->getStatementGroupRendererFactory( $allowDataAccessInUserLanguage );
$renderer = $rendererFactory->newRendererFromParser( $parser, DataAccessSnakFormatterFactory::TYPE_RICH_WIKITEXT );
$usageAccumulator = $this->newUsageAccumulatorFactory()->newFromParser( $parser );
$renderer->render( new ItemId( 'Q1' ), 'P1' );
$usages = $usageAccumulator->getUsages();
if ( $allowDataAccessInUserLanguage ) {
$this->assertArrayHasKey( 'Q7#L.es', $usages );
} else {
$this->assertArrayHasKey( 'Q7#L.en', $usages );
}
}
/**
* @dataProvider allowDataAccessInUserLanguageProvider
*/
public function testNewRendererFromParser_languageOption( bool $allowDataAccessInUserLanguage ): void {
$labelResolver = $this->createMock( PropertyLabelResolver::class );
$formatterFactory = $this->createMock( OutputFormatSnakFormatterFactory::class );
$formatterFactory->expects( $this->once() )
->method( 'getSnakFormatter' )
->willReturnCallback(
function( $format, FormatterOptions $options ) use ( $allowDataAccessInUserLanguage ) {
$this->assertSame(
$allowDataAccessInUserLanguage ? 'es' : 'de',
$options->getOption( ValueFormatter::OPT_LANG )
);
return $this->createMock( SnakFormatter::class );
}
);
$factory = new StatementGroupRendererFactory(
$labelResolver,
new SnaksFinder(),
$this->getRestrictedEntityLookupFactory(),
new DataAccessSnakFormatterFactory(
$this->getLanguageFallbackChainFactory(),
$formatterFactory,
new InMemoryDataTypeLookup(),
new ItemIdParser(),
$this->getFallbackLabelDescriptionLookupFactory()
),
$this->newUsageAccumulatorFactory(),
$this->createMock( LanguageConverterFactory::class ),
$this->createMock( LanguageFactory::class ),
$allowDataAccessInUserLanguage
);
$factory->newRendererFromParser( $this->getParser( 'de', 'es' ) );
}
public static function allowDataAccessInUserLanguageProvider(): array {
return [
[ true ],
[ false ],
];
}
private function getStatementGroupRendererFactory( bool $allowDataAccessInUserLanguage = false ): StatementGroupRendererFactory {
$labelResolver = $this->createMock( PropertyLabelResolver::class );
return new StatementGroupRendererFactory(
$labelResolver,
$this->getSnaksFinder(),
$this->getRestrictedEntityLookupFactory(),
new DataAccessSnakFormatterFactory(
$this->getLanguageFallbackChainFactory(),
$this->getSnakFormatterFactory(),
new InMemoryDataTypeLookup(),
new ItemIdParser(),
$this->getFallbackLabelDescriptionLookupFactory()
),
$this->newUsageAccumulatorFactory(),
MediaWikiServices::getInstance()->getLanguageConverterFactory(),
MediaWikiServices::getInstance()->getLanguageFactory(),
$allowDataAccessInUserLanguage
);
}
private function newUsageAccumulatorFactory(): UsageAccumulatorFactory {
return new UsageAccumulatorFactory(
new EntityUsageFactory( new BasicEntityIdParser() ),
new UsageDeduplicator( [] ),
$this->createStub( EntityRedirectTargetLookup::class )
);
}
private function getSnaksFinder(): SnaksFinder {
$snakListFinder = $this->createMock( SnaksFinder::class );
$snakListFinder->method( 'findSnaks' )
->willReturnCallback( function(
StatementListProvider $statementListProvider,
NumericPropertyId $propertyId,
?array $acceptableRanks
) {
return [
new PropertyValueSnak( $propertyId, new EntityIdValue( new ItemId( 'Q7' ) ) ),
];
} );
return $snakListFinder;
}
private function getLanguageFallbackChainFactory(): LanguageFallbackChainFactory {
return new LanguageFallbackChainFactory();
}
private function getSnakFormatterFactory(): OutputFormatSnakFormatterFactory {
$snakFormatter = $this->createMock( SnakFormatter::class );
$snakFormatter->method( 'formatSnak' )
->willReturn( 'Kittens!' );
$snakFormatterFactory = $this->createMock( OutputFormatSnakFormatterFactory::class );
$snakFormatterFactory->method( 'getSnakFormatter' )
->willReturn( $snakFormatter );
return $snakFormatterFactory;
}
private function getRestrictedEntityLookupFactory(): RestrictedEntityLookupFactory {
$entityLookup = $this->createMock( EntityLookup::class );
$entityLookup->method( 'getEntity' )
->willReturnCallback( function ( EntityId $id ) {
return new Item( $id );
} );
$entityLookup->method( 'hasEntity' )
->willReturn( true );
return new RestrictedEntityLookupFactory( $entityLookup, 200 );
}
private function getParser(
string $languageCode = 'en',
string $userLanguageCode = 'es',
bool $interfaceMessage = false,
bool $disableContentConversion = false,
bool $disableTitleConversion = false,
int $outputType = Parser::OT_HTML
): Parser {
$parserOptions = $this->getParserOptions(
$languageCode,
$userLanguageCode,
$interfaceMessage,
$disableContentConversion,
$disableTitleConversion
);
$parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setTitle( Title::makeTitle( NS_MAIN, 'Cat' ) );
$parser->startExternalParse( null, $parserOptions, $outputType );
return $parser;
}
private function getParserOptions( string $languageCode, string $userLanguageCode, bool $interfaceMessage,
bool $disableContentConversion, bool $disableTitleConversion
): ParserOptions {
$languageFactory = MediaWikiServices::getInstance()->getLanguageFactory();
$language = $languageFactory->getLanguage( $languageCode );
$userLanguage = $languageFactory->getLanguage( $userLanguageCode );
$parserOptions = new ParserOptions( User::newFromId( 0 ), $userLanguage );
$parserOptions->setTargetLanguage( $language );
$parserOptions->setInterfaceMessage( $interfaceMessage );
$parserOptions->disableContentConversion( $disableContentConversion );
$parserOptions->disableTitleConversion( $disableTitleConversion );
return $parserOptions;
}
private function getFallbackLabelDescriptionLookupFactory(): FallbackLabelDescriptionLookupFactory {
$languageFallbackLabelDescriptionLookupFactory = $this->createMock( FallbackLabelDescriptionLookupFactory::class );
$languageFallbackLabelDescriptionLookupFactory->method( 'newLabelDescriptionLookup' )
->willReturn( $this->createMock( FallbackLabelDescriptionLookup::class ) );
return $languageFallbackLabelDescriptionLookupFactory;
}
}