[], MainConfigNames::AllowExternalImages => false, MainConfigNames::AllowExternalImagesFrom => '', MainConfigNames::Server => 'localhost', MainConfigNames::ArticlePath => false, MainConfigNames::InterwikiMagic => true, MainConfigNames::ExtraInterlanguageLinkPrefixes => [], MainConfigNames::InterlanguageLinkCodeMap => [], MainConfigNames::LocalInterwikis => [], MainConfigNames::LanguageCode => 'qqq', MainConfigNames::DisableLangConversion => false, MainConfigNames::NamespaceAliases => [], MainConfigNames::UrlProtocols => [ 'http://' ], MainConfigNames::Script => false, MainConfigNames::ScriptPath => '/wiki', MainConfigNames::LoadScript => false, MainConfigNames::LocalTZoffset => null, MainConfigNames::ThumbLimits => [ 4242 ], MainConfigNames::MaxTemplateDepth => 42, MainConfigNames::LegalTitleChars => 'abc', MainConfigNames::NoFollowLinks => true, MainConfigNames::NoFollowNsExceptions => [ 5 ], MainConfigNames::NoFollowDomainExceptions => [ 'www.mediawiki.org' ], MainConfigNames::ExternalLinkTarget => false, MainConfigNames::EnableMagicLinks => [ 'ISBN' => true, 'PMID' => true, 'RFC' => true, ], ]; private StatsFactory $statsFactory; protected function setUp(): void { parent::setUp(); $this->statsFactory = StatsFactory::newNull(); } private function createMockOrOverride( string $class, array $overrides ) { return $overrides[$class] ?? $this->createNoOpMock( $class ); } /** * TODO it might save code to have this helper always return a * TestingAccessWrapper? * * @param array $configOverrides Configuration options overriding default ServiceOptions config defined in * DEFAULT_CONFIG above. * @param array $parsoidSettings * @param array $serviceOverrides * * @return SiteConfig */ private function createSiteConfig( array $configOverrides = [], array $parsoidSettings = [], array $serviceOverrides = [] ): SiteConfig { return new SiteConfig( new ServiceOptions( SiteConfig::CONSTRUCTOR_OPTIONS, array_replace( self::DEFAULT_CONFIG, $configOverrides ) ), $parsoidSettings, $this->createSimpleObjectFactory(), $this->createMockOrOverride( Language::class, $serviceOverrides ), new NullStatsdDataFactory(), $this->statsFactory, $this->createMockOrOverride( MagicWordFactory::class, $serviceOverrides ), $this->createMockOrOverride( NamespaceInfo::class, $serviceOverrides ), $this->createMockOrOverride( SpecialPageFactory::class, $serviceOverrides ), $this->createMockOrOverride( InterwikiLookup::class, $serviceOverrides ), $this->createMockOrOverride( UserOptionsLookup::class, $serviceOverrides ), $this->createMockOrOverride( LanguageFactory::class, $serviceOverrides ), $this->createMockOrOverride( LanguageConverterFactory::class, $serviceOverrides ), $this->createMockOrOverride( LanguageNameUtils::class, $serviceOverrides ), $this->createMockOrOverride( UrlUtils::class, $serviceOverrides ), $this->createMockOrOverride( IContentHandlerFactory::class, $serviceOverrides ), [], $this->createMockOrOverride( ParserFactory::class, $serviceOverrides ), new HashConfig( $configOverrides ), false ); } public static function provideConfigParameterPassed(): iterable { yield 'galleryOptions' => [ [ MainConfigNames::GalleryOptions => [ 'blabla' ] ], 'galleryOptions', [ 'blabla' ] ]; yield 'allowedExternalImagePrefixes, false' => [ [ MainConfigNames::AllowExternalImages => true ], 'allowedExternalImagePrefixes', [ '' ] ]; yield 'allowedExternalImagePrefixes, true' => [ [ MainConfigNames::AllowExternalImages => false, MainConfigNames::AllowExternalImagesFrom => [ 'blabla' ] ], 'allowedExternalImagePrefixes', [ 'blabla' ] ]; yield 'interwikiMagic' => [ [ MainConfigNames::InterwikiMagic => true ], 'interwikiMagic', true ]; yield 'script' => [ [ MainConfigNames::Script => 'blabla' ], 'script', 'blabla' ]; yield 'scriptpath' => [ [ MainConfigNames::ScriptPath => 'blabla' ], 'scriptpath', 'blabla' ]; yield 'server' => [ [ MainConfigNames::Server => 'blabla' ], 'server', 'blabla' ]; yield 'timezoneOffset' => [ [ MainConfigNames::LocalTZoffset => 42 ], 'timezoneOffset', 42 ]; yield 'getMaxTemplateDepth' => [ [ MainConfigNames::MaxTemplateDepth => 42 ], 'getMaxTemplateDepth', 42 ]; /* $wgLegalTitleChars can't be tested with this mechanism. yield 'legalTitleChars' => [ [ MainConfigNames::LegalTitleChars => 'blabla' ], 'legalTitleChars', 'blabla' ]; */ yield 'getProtocols' => [ [ MainConfigNames::UrlProtocols => [ 'blabla' ] ], 'getProtocols', [ 'blabla' ] ]; yield 'getNoFollowConfig' => [ [], 'getNoFollowConfig', [ 'nofollow' => true, 'nsexceptions' => [ 5 ], 'domainexceptions' => [ 'www.mediawiki.org' ] ] ]; yield 'getExternalLinkTargetEmpty' => [ [], 'getExternalLinkTarget', false ]; yield 'getExternalLinkTargetString' => [ [ MainConfigNames::ExternalLinkTarget => "_blank" ], 'getExternalLinkTarget', "_blank" ]; } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::galleryOptions * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::allowedExternalImagePrefixes * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::interwikiMagic * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::script * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::scriptpath * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::server * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::timezoneOffset * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getMaxTemplateDepth * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::legalTitleChars * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getProtocols * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getNoFollowConfig * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getExternalLinkTarget * @dataProvider provideConfigParameterPassed * @param array $settings * @param string $method * @param mixed $expectedValue */ public function testConfigParametersPassed( array $settings, string $method, $expectedValue ) { $config = $this->createSiteConfig( $settings ); $config = TestingAccessWrapper::newFromObject( $config ); $this->assertSame( $expectedValue, $config->$method() ); } public static function provideParsoidSettingPassed() { yield 'linterEnabled' => [ [ 'linting' => true ], 'linterEnabled', true ]; } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::linterEnabled() * @dataProvider provideParsoidSettingPassed * @param array $settings * @param string $method * @param mixed $expectedValue */ public function testParsoidSettingPassed( array $settings, string $method, $expectedValue ) { $config = $this->createSiteConfig( [], $settings ); $config = TestingAccessWrapper::newFromObject( $config ); $this->assertSame( $expectedValue, $config->$method() ); } public static function provideServiceMethodProxied() { yield 'canonicalNamespaceId' => [ NamespaceInfo::class, 'getCanonicalIndex', [ 'blabla_arg' ], 42, 'canonicalNamespaceId', 42 ]; yield 'namespaceId' => [ Language::class, 'getNsIndex', [ 'blabla_arg' ], 42, 'namespaceId', 42 ]; yield 'namespaceName, NS_MAIN' => [ Language::class, 'getFormattedNsText', [ NS_MAIN ], '', 'namespaceName', '' ]; yield 'namespaceName, NS_USER, null' => [ Language::class, 'getFormattedNsText', [ NS_USER ], '', 'namespaceName', null ]; yield 'namespaceName, NS_USER' => [ Language::class, 'getFormattedNsText', [ NS_USER ], 'User', 'namespaceName', 'User' ]; yield 'namespaceHasSubpages' => [ NamespaceInfo::class, 'hasSubpages', [ 42 ], true, 'namespaceHasSubpages', true ]; yield 'namespaceCase, first letter' => [ NamespaceInfo::class, 'isCapitalized', [ 42 ], true, 'namespaceCase', 'first-letter' ]; yield 'namespaceCase, case sensitive' => [ NamespaceInfo::class, 'isCapitalized', [ 42 ], false, 'namespaceCase', 'case-sensitive' ]; yield 'namespaceIsTalk' => [ NamespaceInfo::class, 'isTalk', [ 42 ], true, 'namespaceIsTalk', true ]; yield 'ucfirst' => [ Language::class, 'ucfirst', [ 'bla' ], 'Bla', 'ucfirst', 'Bla' ]; yield 'linkTrail' => [ Language::class, 'linkTrail', [], 'blabla', 'linkTrail', 'blabla' ]; yield 'langBcp47' => [ Language::class, null, [], null, 'langBcp47', '' ]; yield 'rtl' => [ Language::class, 'isRTL', [], true, 'rtl', true ]; yield 'getVariableIDs' => [ MagicWordFactory::class, 'getVariableIDs', [], [ 'blabla' ], 'getVariableIDs', [ 'blabla' ] ]; yield 'getFunctionSynonyms' => [ [ ParserFactory::class, 'getMainInstance' ], [ Parser::class, 'getFunctionSynonyms' ], [], [ 0 => [ 'blabla' ], 1 => [ 'blabla' ] ], 'getFunctionSynonyms', [ 0 => [ 'blabla' ], 1 => [ 'blabla' ] ] ]; yield 'getMagicWords' => [ Language::class, 'getMagicWords', [], [ 'blabla' ], 'getMagicWords', [ 'blabla' ] ]; yield 'getNonNativeExtensionTags' => [ [ ParserFactory::class, 'getMainInstance' ], [ Parser::class, 'getTags' ], [], [ 'blabla' ], 'getNonNativeExtensionTags', [ 'blabla' => true ] ]; } /** * @dataProvider provideServiceMethodProxied * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::canonicalNamespaceId * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::namespaceId * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::namespaceName * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::namespaceHasSubpages * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::namespaceCase * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::namespaceIsTalk * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::ucfirst * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::linkTrail * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::langBcp47 * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::rtl * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::widthOption * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getVariableIDs * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getFunctionSynonyms * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getMagicWords * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getNonNativeExtensionTags * @param string|array $serviceClassSpec * @param ?string|array $serviceMethodSpec * @param array $arguments * @param mixed $returnValue * @param string $method * @param mixed $expectedValue */ public function testServiceMethodProxied( $serviceClassSpec, $serviceMethodSpec, array $arguments, $returnValue, string $method, $expectedValue ) { $serviceClass = is_array( $serviceClassSpec ) ? $serviceClassSpec[0] : $serviceClassSpec; $serviceMock = $this->createMock( $serviceClass ); if ( $serviceMethodSpec ) { if ( is_array( $serviceMethodSpec ) ) { // Service mock is a factory, create a object and let the factory return that object $mock = $this->createMock( $serviceMethodSpec[0] ); $serviceMock->method( $serviceClassSpec[1] )->willReturn( $mock ); $serviceMethod = $serviceMethodSpec[1]; } else { // No factory, use the service mock directly $mock = $serviceMock; $serviceMethod = $serviceMethodSpec; } // Let the mock return the expected arguments $mock ->expects( $this->once() ) ->method( $serviceMethod ) ->with( ...$arguments ) ->willReturn( $returnValue ); } $config = $this->createSiteConfig( [], [], [ $serviceClass => $serviceMock ] ); $config = TestingAccessWrapper::newFromObject( $config ); if ( $expectedValue === '' ) { $expectedValue = $serviceMock; } $this->assertSame( $expectedValue, $config->$method( ...$arguments ) ); } public static function provideArticlePath_exception() { yield 'No $1' => [ '/test/test' ]; yield 'Wrong path' => [ 'test\\test/$1' ]; } /** * @dataProvider provideArticlePath_exception * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::determineArticlePath * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::baseURI * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::relativeLinkPrefix * @param string $articlePath */ public function testArticlePath_exception( string $articlePath ) { $this->expectException( UnexpectedValueException::class ); $config = $this->createSiteConfig( [ MainConfigNames::ArticlePath => $articlePath ], [], [ UrlUtils::class => $this->createMock( UrlUtils::class ) ] ); $config->baseURI(); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::determineArticlePath * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::baseURI * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::relativeLinkPrefix */ public function testArticlePath_nopath() { $config = $this->createSiteConfig( [ MainConfigNames::ArticlePath => '$1', MainConfigNames::Server => 'https://localhost' ], [], [ UrlUtils::class => new UrlUtils() ] ); $this->assertSame( 'https://localhost/', $config->baseURI() ); $this->assertSame( './', $config->relativeLinkPrefix() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::determineArticlePath * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::baseURI * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::relativeLinkPrefix */ public function testArticlePath() { $config = $this->createSiteConfig( [ MainConfigNames::ArticlePath => '/wiki/$1', MainConfigNames::Server => 'https://localhost' ], [], [ UrlUtils::class => new UrlUtils() ] ); $this->assertSame( './', $config->relativeLinkPrefix() ); $this->assertSame( 'https://localhost/wiki/', $config->baseURI() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::mwaToRegex * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::redirectRegexp */ public function testRedirectRegexp() { $langMock = $this->createMock( Language::class ); $magicWordFactoryMock = $this->createMock( MagicWordFactory::class ); $magicWordFactoryMock ->method( 'newArray' ) ->willReturn( new MagicWordArray( [ 'blabla_case_sen', 'blabla_case_insen' ], $magicWordFactoryMock ) ); $magicWordFactoryMock ->method( 'get' ) ->willReturnOnConsecutiveCalls( new MagicWord( 'blabla_id', [ 'blabla_synonym1' ], true, $langMock ), new MagicWord( 'blabla_id', [ 'blabla_synonym2' ], false, $langMock ) ); $config = $this->createSiteConfig( [], [], [ MagicWordFactory::class => $magicWordFactoryMock ] ); $this->assertSame( '@(?i:blabla_synonym2)|blabla_synonym1@Su', $config->redirectRegexp() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::categoryRegexp */ public function testCategoryRegexp() { $nsInfoMock = $this->createMock( NamespaceInfo::class ); $nsInfoMock ->method( 'getCanonicalName' ) ->willReturn( 'Bla bla' ); $langMock = $this->createMock( Language::class ); $langMock ->method( 'getNamespaceAliases' ) ->willReturn( [ 'Bla_alias' => NS_CATEGORY, 'Ignored' => NS_MAIN ] ); $config = $this->createSiteConfig( [], [], [ NamespaceInfo::class => $nsInfoMock, Language::class => $langMock ] ); $this->assertSame( '@(?i:Bla[ _]bla|Bla[ _]alias)@', $config->categoryRegexp() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::bswRegexp */ public function testBswRegexp() { $langMock = $this->createMock( Language::class ); $magicWordFactoryMock = $this->createMock( MagicWordFactory::class ); $magicWordFactoryMock ->method( 'getDoubleUnderscoreArray' ) ->willReturn( new MagicWordArray( [ 'blabla' ], $magicWordFactoryMock ) ); $magicWordFactoryMock ->method( 'get' ) ->willReturn( new MagicWord( 'blabla_id', [ 'blabla_synonym' ], true, $langMock ) ); $config = $this->createSiteConfig( [], [], [ MagicWordFactory::class => $magicWordFactoryMock ] ); $this->assertSame( '@(?i:(?!))|blabla_synonym@Su', $config->bswRegexp() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::specialPageLocalName */ public function testSpecialPageLocalName() { $specialPageFactoryMock = $this->createMock( SpecialPageFactory::class ); $specialPageFactoryMock ->method( 'resolveAlias' ) ->with( 'blabla_alias' ) ->willReturn( [ 'resolved_page', 'resolved_subpage' ] ); $specialPageFactoryMock ->method( 'getLocalNameFor' ) ->with( 'resolved_page', 'resolved_subpage' ) ->willReturn( 'blabla' ); $config = $this->createSiteConfig( [], [], [ SpecialPageFactory::class => $specialPageFactoryMock ] ); $this->assertSame( 'blabla', $config->specialPageLocalName( 'blabla_alias' ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::interwikiMap */ public function testInterwikiMap() { $interwikiMock = $this->createMock( InterwikiLookup::class ); $interwikiMock ->method( 'getAllPrefixes' ) ->willReturn( [ [ 'iw_prefix' => 'ru', 'iw_url' => '//test/', 'iw_local' => 1 ] ] ); $langNameUtilsMock = $this->createMock( LanguageNameUtils::class ); $langNameUtilsMock ->method( 'getLanguageNames' ) ->willReturn( [ 'ru' => 'Russian' ] ); $messageCacheMock = $this->createMock( MessageCache::class ); $messageCacheMock ->method( 'get' ) ->willReturn( false ); $urlUtilsMock = $this->createMock( UrlUtils::class ); $urlUtilsMock ->method( 'expand' ) ->with( '//test/', 1 ) ->willReturn( 'http://test/' ); $config = $this->createSiteConfig( [ MainConfigNames::ExtraInterlanguageLinkPrefixes => [ 'ru' ], MainConfigNames::LocalInterwikis => [ 'ru' ], ], [], [ InterwikiLookup::class => $interwikiMock, LanguageNameUtils::class => $langNameUtilsMock, MessageCache::class => $messageCacheMock, UrlUtils::class => $urlUtilsMock ] ); $this->assertSame( [ 'ru' => [ 'prefix' => 'ru', 'url' => 'http://test/$1', 'protorel' => true, 'local' => true, 'language' => true, 'bcp47' => 'ru', 'localinterwiki' => true, 'extralanglink' => true, 'code' => 'ru', ] ], $config->interwikiMap() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::iwp */ public function testIwp() { $config = $this->createSiteConfig(); $this->assertNotNull( $config->iwp() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::linkPrefixRegex */ public function testLinkPrefixRegex_disabled() { $langMock = $this->createMock( Language::class ); $langMock ->method( 'linkPrefixExtension' ) ->willReturn( false ); $config = $this->createSiteConfig( [], [], [ Language::class => $langMock ] ); $this->assertNull( $config->linkPrefixRegex() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::linkPrefixRegex */ public function testLinkPrefixRegex() { $langMock = $this->createMock( Language::class ); $langMock ->method( 'linkPrefixExtension' ) ->willReturn( true ); $langMock ->method( 'linkPrefixCharset' ) ->willReturn( 'blabla' ); $config = $this->createSiteConfig( [], [], [ Language::class => $langMock ] ); $this->assertStringContainsString( 'blabla', $config->linkPrefixRegex() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::mainpage */ public function testMainpage() { $this->markTestSkipped( 'Requires MessageCache; not a unit test' ); $this->assertSame( 'Main Page', $this->createSiteConfig()->mainpage() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::langConverterEnabledBcp47 */ public function testLangConverterEnabledBcp47_disabled() { $langConverterFactoryMock = $this->createMock( LanguageConverterFactory::class ); $langConverterFactoryMock ->method( 'isConversionDisabled' ) ->willReturn( true ); $config = $this->createSiteConfig( [], [], [ LanguageConverterFactory::class => $langConverterFactoryMock, ] ); $zh = new Bcp47CodeValue( 'zh' ); $this->assertFalse( $config->langConverterEnabledBcp47( $zh ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::langConverterEnabledBcp47 */ public function testLangConverterEnabledBcp47_invalidCode() { $langMock = $this->createMock( Language::class ); $langMock ->method( 'getCode' ) ->willReturn( 'bogus' ); $langConverterFactoryMock = $this->createMock( LanguageConverterFactory::class ); $langConverterFactoryMock ->method( 'isConversionDisabled' ) ->willReturn( false ); $langFactoryMock = $this->createMock( LanguageFactory::class ); $langFactoryMock ->method( 'getLanguage' ) ->with( $langMock ) ->willReturn( $langMock ); $config = $this->createSiteConfig( [], [], [ LanguageFactory::class => $langFactoryMock, LanguageConverterFactory::class => $langConverterFactoryMock, ] ); $this->assertFalse( $config->langConverterEnabledBcp47( $langMock ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::langConverterEnabledBcp47 */ public function testLangConverterEnabled_valid() { $langMock = $this->createMock( Language::class ); $langMock ->method( 'getCode' ) ->willReturn( 'zh' ); $langConverterMock = $this->createMock( ZhConverter::class ); $langConverterMock ->method( 'hasVariants' ) ->willReturn( true ); $langConverterFactoryMock = $this->createMock( LanguageConverterFactory::class ); $langConverterFactoryMock ->method( 'getLanguageConverter' ) ->with( $langMock ) ->willReturn( $langConverterMock ); $langConverterFactoryMock ->method( 'isConversionDisabled' ) ->willReturn( false ); $langFactoryMock = $this->createMock( LanguageFactory::class ); $langFactoryMock ->method( 'getLanguage' ) ->with( $langMock ) ->willReturn( $langMock ); $config = $this->createSiteConfig( [], [], [ LanguageFactory::class => $langFactoryMock, LanguageConverterFactory::class => $langConverterFactoryMock ] ); $this->assertTrue( $config->langConverterEnabledBcp47( $langMock ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::variantsFor */ public function testVariantsFor_disabled() { $langFactoryMock = $this->createMock( LanguageFactory::class ); $langFactoryMock ->method( 'getLanguage' ) ->willReturnCallback( function ( $code ) { if ( !is_string( $code ) ) { $code = strtolower( $code->toBcp47Code() ); } $langMock = $this->createMock( Language::class ); $langMock->method( 'getCode' ) ->willReturn( $code ); return $langMock; } ); $converterMock = $this->createMock( ILanguageConverter::class ); $converterMock ->method( 'hasVariants' ) ->willReturn( true ); $converterMock ->method( 'getVariants' ) ->willReturn( [ 'zh-hans' ] ); $converterMock ->method( 'getVariantFallbacks' ) ->willReturn( 'zh-fallback' ); $langConverterFactoryMock = $this->createMock( LanguageConverterFactory::class ); $langConverterFactoryMock ->method( 'isConversionDisabled' ) ->willReturn( true ); $langConverterFactoryMock ->method( 'getLanguageConverter' ) ->willReturnCallback( function ( $l ) use ( $converterMock ) { if ( $l->getCode() === 'zh' ) { return $converterMock; } return $this->createMock( ILanguageConverter::class ); } ); $config = $this->createSiteConfig( [], [], [ LanguageFactory::class => $langFactoryMock, LanguageConverterFactory::class => $langConverterFactoryMock, ] ); $this->assertNull( $config->variantsFor( new Bcp47CodeValue( 'zh-Hans' ) ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::variantsFor */ public function testVariantsFor() { $langFactoryMock = $this->createMock( LanguageFactory::class ); $langFactoryMock ->method( 'getLanguage' ) ->willReturnCallback( function ( $code ) { if ( !is_string( $code ) ) { $code = strtolower( $code->toBcp47Code() ); } $langMock = $this->createMock( Language::class ); $langMock->method( 'getCode' ) ->willReturn( $code ); return $langMock; } ); $converterMock = $this->createMock( ILanguageConverter::class ); $converterMock ->method( 'hasVariants' ) ->willReturn( true ); $converterMock ->method( 'getVariants' ) ->willReturn( [ 'zh-hans' ] ); $converterMock ->method( 'getVariantFallbacks' ) ->willReturn( 'zh-fallback' ); $langConverterFactoryMock = $this->createMock( LanguageConverterFactory::class ); $langConverterFactoryMock ->method( 'isConversionDisabled' ) ->willReturn( false ); $langConverterFactoryMock ->method( 'getLanguageConverter' ) ->willReturnCallback( function ( $l ) use ( $converterMock ) { if ( $l->getCode() === 'zh' ) { return $converterMock; } return $this->createMock( ILanguageConverter::class ); } ); $config = $this->createSiteConfig( [], [], [ LanguageFactory::class => $langFactoryMock, LanguageConverterFactory::class => $langConverterFactoryMock ] ); $variantsForZh = $config->variantsFor( new Bcp47CodeValue( 'zh-Hans' ) ); $this->assertIsArray( $variantsForZh ); $this->assertArrayHasKey( 'base', $variantsForZh ); $this->assertEquals( 'zh', $variantsForZh['base']->getCode() ); $this->assertArrayHasKey( 'fallbacks', $variantsForZh ); $fallbacks = $variantsForZh['fallbacks']; $this->assertIsArray( $fallbacks ); $this->assertCount( 1, $fallbacks ); $this->assertEquals( 'zh-fallback', $fallbacks[0]->getCode() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::widthOption */ public function testWithOption() { $optionsLookupMock = $this->createMock( UserOptionsLookup::class ); $optionsLookupMock ->method( 'getDefaultOption' ) ->with( 'thumbsize' ) ->willReturn( 'small' ); $config = $this->createSiteConfig( [ MainConfigNames::ThumbLimits => [ 'small' => 42 ] ], [], [ UserOptionsLookup::class => $optionsLookupMock ] ); $this->assertSame( 42, $config->widthOption() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getMagicWordMatcher */ public function testGetMagicWordMatcher() { $magicWordMock = $this->createMock( MagicWord::class ); $magicWordMock ->expects( $this->once() ) ->method( 'getRegexStartToEnd' ) ->willReturn( 'blabla' ); $magicWordFactoryMock = $this->createMock( MagicWordFactory::class ); $magicWordFactoryMock ->expects( $this->once() ) ->method( 'get' ) ->with( 'blabla_id' ) ->willReturn( $magicWordMock ); $config = $this->createSiteConfig( [], [], [ MagicWordFactory::class => $magicWordFactoryMock ] ); $this->assertSame( 'blabla', $config->getMagicWordMatcher( 'blabla_id' ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getParameterizedAliasMatcher */ public function testGetParameterizedAliasMatcher() { $langMock = $this->createMock( Language::class ); $magicWordFactoryMock = $this->createMock( MagicWordFactory::class ); $magicWordFactoryMock ->method( 'newArray' ) ->willReturn( new MagicWordArray( [ 'test' ], $magicWordFactoryMock ) ); $magicWordFactoryMock ->method( 'get' ) ->willReturn( new MagicWord( 'blabla_id', [ 'blabla_alias:$1' ], true, $langMock ) ); $config = $this->createSiteConfig( [], [], [ MagicWordFactory::class => $magicWordFactoryMock ] ); $matcher = $config->getParameterizedAliasMatcher( [ 'blabla' ] ); $this->assertSame( [ 'k' => 'test', 'v' => 'blabla' ], $matcher( 'blabla_alias:blabla' ) ); $this->assertNull( $matcher( 'Blabla_alias:blabla' ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getSpecialNSAliases */ public function testGetSpecialNSAliases() { $mockLang = $this->createMock( Language::class ); $mockLang ->method( 'getNsText' ) ->willReturn( 'Special_Special' ); $mockLang ->method( 'getNamespaceAliases' ) ->willReturn( [ 'From Language' => NS_SPECIAL, 'Whatever' => NS_MAIN ] ); $config = $this->createSiteConfig( [ MainConfigNames::NamespaceAliases => [ 'From Config' => NS_SPECIAL, 'Whatever' => NS_MAIN ] ], [], [ Language::class => $mockLang ] ); $config = TestingAccessWrapper::newFromObject( $config ); $this->assertSame( [ 'Special', 'Special[ _]Special', 'From[ _]Language', 'From[ _]Config' ], $config->getSpecialNSAliases() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getSpecialPageAliases */ public function testGetSpecialPageAliases() { $mockLang = $this->createMock( Language::class ); $mockLang ->method( 'getSpecialPageAliases' ) ->willReturn( [ 'Page1' => [ 'Alias1', 'Alias2' ] ] ); $config = $this->createSiteConfig( [], [], [ Language::class => $mockLang ] ); $config = TestingAccessWrapper::newFromObject( $config ); $this->assertSame( [ 'Page1', 'Alias1', 'Alias2' ], $config->getSpecialPageAliases( 'Page1' ) ); $this->assertSame( [ 'Page2' ], $config->getSpecialPageAliases( 'Page2' ) ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::getMWConfigValue */ public function testGetMWConfigValue() { $config = $this->createSiteConfig( [ 'CiteResponsiveReferences' => true, 'CiteResponsiveReferencesThreshold' => 10, ], [], [] ); $config = TestingAccessWrapper::newFromObject( $config ); $this->assertSame( true, $config->getMWConfigValue( 'CiteResponsiveReferences' ) ); $this->assertSame( 10, $config->getMWConfigValue( 'CiteResponsiveReferencesThreshold' ) ); $this->assertSame( null, $config->getMWConfigValue( 'CiteUnknownConfig' ) ); } public function provideMetricsData(): iterable { return [ [ "metric_name", [ [ "label1" => "value1", "label2" => "value2" ], [ "label1" => "value1", "label2" => "value3" ] ], [ [ "value1", "value2" ], [ "value1", "value3" ] ] ] ]; } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::incrementCounter * @dataProvider provideMetricsData */ public function testIncrementCounter( $name, $labels, $expectedValues ) { $config = $this->createSiteConfig(); $config->incrementCounter( $name, $labels[0] ); $config->incrementCounter( $name, $labels[1] ); $counter = $this->statsFactory->withComponent( "Parsoid" )->getCounter( $name ); $this->assertSame( 2, $counter->getSampleCount() ); $this->assertSame( $expectedValues[0], $counter->getSamples()[0]->getLabelValues() ); $this->assertSame( 1.0, $counter->getSamples()[0]->getValue() ); $this->assertSame( $expectedValues[1], $counter->getSamples()[1]->getLabelValues() ); $this->assertSame( 1.0, $counter->getSamples()[1]->getValue() ); } /** * @covers \MediaWiki\Parser\Parsoid\Config\SiteConfig::observeTiming * @dataProvider provideMetricsData */ public function testObserveTiming( $name, $labels, $expectedValues ) { $config = $this->createSiteConfig(); $config->observeTiming( $name, 1500.1, $labels[0] ); $config->observeTiming( $name, 2500.2, $labels[1] ); $counter = $this->statsFactory->withComponent( "Parsoid" )->getTiming( $name ); $this->assertSame( 2, $counter->getSampleCount() ); $this->assertSame( $expectedValues[0], $counter->getSamples()[0]->getLabelValues() ); $this->assertSame( 1500.1, $counter->getSamples()[0]->getValue() ); $this->assertSame( $expectedValues[1], $counter->getSamples()[1]->getLabelValues() ); $this->assertSame( 2500.2, $counter->getSamples()[1]->getValue() ); } }