wiki.techinc.nl/tests/phpunit/includes/parser/ParserOutputTest.php

1324 lines
45 KiB
PHP
Raw Normal View History

<?php
use MediaWiki\MainConfigNames;
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Parser\ParserOutputStringSets;
use MediaWiki\Tests\Parser\ParserCacheSerializationTestCases;
use MediaWiki\Title\Title;
use MediaWiki\Title\TitleValue;
use MediaWiki\Utils\MWTimestamp;
use Wikimedia\Bcp47Code\Bcp47CodeValue;
Generate/set/get TOCData/SectionMetadata objects instead of arrays * ParserOutput::setSections()/::getSections() are expected to be deprecated. Uses in extensions and skins will need to be migrated in follow up patches once the new interface has stabilized. * In the skins code, the metadata is converted back to an array. Downstream skin TOC consumers will need to be migrated as well before we can remove the toLegacy() conversion. * Fixed SerializationTestTrait's validation method - Not sure if this is overkill but should handle all future complex objects we might stuff into the ParserCache. * This patch emits a backward-compatible Sections property in order to avoid changing the parser cache serialization format. T327439 has been filed to eventually use the JsonCodec support for object serialization, but for this initial patch it makes sense to avoid the need for a concurrent ParserCache format migration by using a backward-compatible serialization. * TOCData is nullable because the intent is that ParserOutput::setTOCData() is MW_MERGE_STRATEGY_WRITE_ONCE; that is, only the top-level fragment composing a page will set the TOCData. This will be enforced in the future via wfDeprecated() (T327429), but again our first patch is as backward-compatible as possible. Bug: T296025 Depends-On: I1b267d23cf49d147c5379b914531303744481b68 Co-Authored-By: C. Scott Ananian <cananian@wikimedia.org> Co-Authored-By: Subramanya Sastry <ssastry@wikimedia.org> Change-Id: I8329864535f0b1dd5f9163868a08d6cb1ffcb78f
2022-09-01 23:07:29 +00:00
use Wikimedia\Parsoid\Core\SectionMetadata;
use Wikimedia\Parsoid\Core\TOCData;
use Wikimedia\TestingAccessWrapper;
use Wikimedia\Tests\SerializationTestTrait;
/**
* @covers \MediaWiki\Parser\ParserOutput
* @covers CacheTime
* @group Database
* ^--- trigger DB shadowing because we are using Title magic
*/
class ParserOutputTest extends MediaWikiLangTestCase {
use SerializationTestTrait;
protected function setUp(): void {
parent::setUp();
MWTimestamp::setFakeTime( ParserCacheSerializationTestCases::FAKE_TIME );
$this->overrideConfigValue(
MainConfigNames::ParserCacheExpireTime,
ParserCacheSerializationTestCases::FAKE_CACHE_EXPIRY
);
// Serialization tests still use these methods.
$this->hideDeprecated( 'MediaWiki\Parser\ParserOutput::setTOCHTML' );
$this->hideDeprecated( 'MediaWiki\Parser\ParserOutput::getTOCHTML' );
}
/**
* Overrides SerializationTestTrait::getClassToTest
* @return string
*/
protected function getClassToTest(): string {
return ParserOutput::class;
}
/**
* Overrides SerializationTestTrait::getSerializedDataPath
* @return string
*/
protected function getSerializedDataPath(): string {
return __DIR__ . '/../../data/ParserCache';
}
/**
* Overrides SerializationTestTrait::getTestInstancesAndAssertions
* @return array
*/
protected function getTestInstancesAndAssertions(): array {
return ParserCacheSerializationTestCases::getParserOutputTestCases();
}
/**
* Overrides SerializationTestTrait::getSupportedSerializationFormats
* @return array
*/
protected function getSupportedSerializationFormats(): array {
return ParserCacheSerializationTestCases::getSupportedSerializationFormats(
$this->getClassToTest() );
}
public static function provideIsLinkInternal() {
return [
// Different domains
[ false, 'http://example.org', 'http://mediawiki.org' ],
// Same domains
[ true, 'http://example.org', 'http://example.org' ],
[ true, 'https://example.org', 'https://example.org' ],
[ true, '//example.org', '//example.org' ],
// Same domain different cases
[ true, 'http://example.org', 'http://EXAMPLE.ORG' ],
// Paths, queries, and fragments are not relevant
[ true, 'http://example.org', 'http://example.org/wiki/Main_Page' ],
[ true, 'http://example.org', 'http://example.org?my=query' ],
[ true, 'http://example.org', 'http://example.org#its-a-fragment' ],
// Different protocols
[ false, 'http://example.org', 'https://example.org' ],
[ false, 'https://example.org', 'http://example.org' ],
// Protocol relative servers always match http and https links
[ true, '//example.org', 'http://example.org' ],
[ true, '//example.org', 'https://example.org' ],
// But they don't match strange things like this
[ false, '//example.org', 'irc://example.org' ],
];
}
/**
* Test to make sure ParserOutput::isLinkInternal behaves properly
* @dataProvider provideIsLinkInternal
* @covers \MediaWiki\Parser\ParserOutput::isLinkInternal
*/
public function testIsLinkInternal( $shouldMatch, $server, $url ) {
$this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::appendJsConfigVar
* @covers \MediaWiki\Parser\ParserOutput::setJsConfigVar
* @covers \MediaWiki\Parser\ParserOutput::getJsConfigVars
*/
public function testJsConfigVars() {
$po = new ParserOutput();
$po->setJsConfigVar( 'a', '1' );
$po->appendJsConfigVar( 'b', 'a' );
$po->appendJsConfigVar( 'b', '0' );
$this->assertEqualsCanonicalizing( [
'a' => 1,
'b' => [ 'a' => true, '0' => true ],
], $po->getJsConfigVars() );
$po->setJsConfigVar( 'c', '2' );
$po->appendJsConfigVar( 'b', 'b' );
$po->appendJsConfigVar( 'b', '1' );
$this->assertEqualsCanonicalizing( [
'a' => 1,
'b' => [ 'a' => true, 'b' => true, '0' => true, '1' => true ],
'c' => 2,
], $po->getJsConfigVars() );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::appendExtensionData
* @covers \MediaWiki\Parser\ParserOutput::setExtensionData
* @covers \MediaWiki\Parser\ParserOutput::getExtensionData
*/
public function testExtensionData() {
$po = new ParserOutput();
$po->setExtensionData( "one", "Foo" );
$po->appendExtensionData( "three", "abc" );
$this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
$this->assertNull( $po->getExtensionData( "spam" ) );
$po->setExtensionData( "two", "Bar" );
$this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
$this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
// Note that overwriting extension data (as this test case
// does) is deprecated and will eventually throw an
// exception. However, at the moment it is still worth testing
// this case to ensure backward compatibility. (T300981)
$po->setExtensionData( "one", null );
$this->assertNull( $po->getExtensionData( "one" ) );
$this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
$this->assertEqualsCanonicalizing( [
'abc' => true,
], $po->getExtensionData( "three" ) );
$po->appendExtensionData( "three", "xyz" );
$this->assertEqualsCanonicalizing( [
'abc' => true,
'xyz' => true,
], $po->getExtensionData( "three" ) );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::setPageProperty
* @covers \MediaWiki\Parser\ParserOutput::getPageProperty
* @covers \MediaWiki\Parser\ParserOutput::unsetPageProperty
* @covers \MediaWiki\Parser\ParserOutput::getPageProperties
*/
public function testProperties() {
$po = new ParserOutput();
$po->setPageProperty( 'foo', 'val' );
$properties = $po->getPageProperties();
$this->assertSame( 'val', $po->getPageProperty( 'foo' ) );
$this->assertSame( 'val', $properties['foo'] );
$po->setPageProperty( 'foo', 'second val' );
$properties = $po->getPageProperties();
$this->assertSame( 'second val', $po->getPageProperty( 'foo' ) );
$this->assertSame( 'second val', $properties['foo'] );
$po->unsetPageProperty( 'foo' );
$properties = $po->getPageProperties();
Change return value of ParserOutput::getPageProperty() when property is missing The old ParserOutput::getProperty() method returned `false` when a property was missing. This requires callers to use the `?:` syntax to supply default values, which then causes any falsey value to be treated as missing. So, for example, setting the defaultsort to '0' will cause the default sort to be ignored. Modern php convention is to use `null` for missing values, and the `??` syntax is a better/more restrictive alternative to `?:`. We renamed `ParserOutput::getProperty()` to `::getPageProperty()` in 1.38 (Ie963eea5aa0f0e984ced7c4dfa0fd65d57313cfa/T287216) but kept the return value convention. Before this actually makes it into a 1.38 release, take the opportunity to fix the return value for the new `ParserOutput::getPageProperty()` method to return `null` when the property is missing. We need to do some temporary workarounds to the places we'd already swapped over to use the new `::getPageProperty()` method to allow them to handle either `false` or `null` as a return value; we'll clean that up once this is merged. Code search: https://codesearch.wmcloud.org/deployed/?q=-%3EgetPageProperty%5C%28|T301915&i=nope&files=&excludeFiles=&repos= Bug: T301915 Depends-On: I3f11ce604970e47b41fc1c123792df8c3045626f Depends-On: Ie7533f49fe4cad01ebfda29760d23c61e9867b10 Depends-On: Ic5c09f5caa4c897bc553c614fbae9cee159566a2 Depends-On: I0278b2eafd90e77e4fee41c45a1165fb79ddf47e Depends-On: I383abb6b7dc5e96c0061af13957609f6e31a1065 Depends-On: I79f9f4078e415284af29b15047bafd1c823d7f5b Depends-On: I02276c48c49f5d2d241a69eb0a6cdf439b572d8b Depends-On: I71628661b4539a4e35ae32846e719f92bcf782e0 Depends-On: I7e215cb43de0ce150a6bcc00f92481dcdcfed383 Change-Id: Iaa25c390118d2db2b6578cdd558f2defd5351d15
2022-02-16 22:03:26 +00:00
$this->assertSame( null, $po->getPageProperty( 'foo' ) );
$this->assertArrayNotHasKey( 'foo', $properties );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::setLanguage
* @covers \MediaWiki\Parser\ParserOutput::getLanguage
*/
public function testLanguage() {
$po = new ParserOutput();
$langFr = new Bcp47CodeValue( 'fr' );
$langCrhCyrl = new Bcp47CodeValue( 'crh-cyrl' );
// Fallback to null
$this->assertSame( null, $po->getLanguage() );
// Simple case
$po->setLanguage( $langFr );
$this->assertSame( $langFr->toBcp47Code(), $po->getLanguage()->toBcp47Code() );
// Language with a variant
$po->setLanguage( $langCrhCyrl );
$this->assertSame( $langCrhCyrl->toBcp47Code(), $po->getLanguage()->toBcp47Code() );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::getWrapperDivClass
* @covers \MediaWiki\Parser\ParserOutput::addWrapperDivClass
* @covers \MediaWiki\Parser\ParserOutput::clearWrapperDivClass
* @covers \MediaWiki\Parser\ParserOutput::getText
*/
public function testWrapperDivClass() {
$po = new ParserOutput();
$po->setText( 'Kittens' );
$this->assertStringContainsString( 'Kittens', $po->getText() );
$this->assertStringNotContainsString( '<div', $po->getText() );
$this->assertSame( 'Kittens', $po->getRawText() );
$po->addWrapperDivClass( 'foo' );
$text = $po->getText();
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringContainsString( 'class="mw-content-ltr foo"', $text );
$po->addWrapperDivClass( 'bar' );
$text = $po->getText();
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringContainsString( 'class="mw-content-ltr foo bar"', $text );
$po->addWrapperDivClass( 'bar' ); // second time does nothing, no "foo bar bar".
$text = $po->getText( [ 'unwrap' => true ] );
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringNotContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringNotContainsString( 'class="', $text );
$text = $po->getText( [ 'wrapperDivClass' => '' ] );
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringNotContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringNotContainsString( 'class="', $text );
$text = $po->getText( [ 'wrapperDivClass' => 'xyzzy' ] );
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringContainsString( 'class="mw-content-ltr xyzzy"', $text );
$this->assertStringNotContainsString( 'foo bar', $text );
$text = $po->getRawText();
$this->assertSame( 'Kittens', $text );
$po->clearWrapperDivClass();
$text = $po->getText();
$this->assertStringContainsString( 'Kittens', $text );
$this->assertStringNotContainsString( '<div', $text );
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
$this->assertStringNotContainsString( 'class="', $text );
}
/**
* This test aims at being replaced by its version in DefaultOutputTransformTest when ParserOutput::getText
* gets deprecated.
* @covers \MediaWiki\Parser\ParserOutput::getText
* @dataProvider provideGetText
* @param array $options Options to getText()
* @param string $text Parser text
* @param string $expect Expected output
*/
public function testGetText( $options, $text, $expect ) {
// Avoid other skins affecting the section edit links
$this->overrideConfigValue( MainConfigNames::DefaultSkin, 'fallback' );
RequestContext::resetMain();
$this->overrideConfigValues( [
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::ScriptPath => '/w',
MainConfigNames::Script => '/w/index.php',
] );
$po = new ParserOutput( $text );
self::initSections( $po );
$actual = $po->getText( $options );
$this->assertSame( $expect, $actual );
}
private static function initSections( ParserOutput $po ): void {
$po->setTOCData( new TOCData(
SectionMetadata::fromLegacy( [
'index' => "1",
'level' => 1,
'toclevel' => 1,
'number' => "1",
'line' => "Section 1",
'anchor' => "Section_1"
] ),
SectionMetadata::fromLegacy( [
'index' => "2",
'level' => 1,
'toclevel' => 1,
'number' => "2",
'line' => "Section 2",
'anchor' => "Section_2"
] ),
SectionMetadata::fromLegacy( [
'index' => "3",
'level' => 2,
'toclevel' => 2,
'number' => "2.1",
'line' => "Section 2.1",
'anchor' => "Section_2.1"
] ),
SectionMetadata::fromLegacy( [
'index' => "4",
'level' => 1,
'toclevel' => 1,
'number' => "3",
'line' => "Section 3",
'anchor' => "Section_3"
] ),
) );
}
public static function provideGetText() {
$text = <<<EOF
<p>Test document.
</p>
<meta property="mw:PageProp/toc" />
<h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
<p>One
</p>
<h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
<p>Two
</p>
<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><mw:editsection page="Talk:User:Bug_T261347" section="3">Section 2.1</mw:editsection></h3>
<p>Two point one
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
<p>Three
</p>
EOF;
$dedupText = <<<EOF
<p>This is a test document.</p>
<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
<style data-mw-deduplicate="duplicate1">.Same-attribute-different-content {}</style>
<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
<style>.Duplicate1 {}</style>
EOF;
return [
'No options' => [
[], $text, <<<EOF
<p>Test document.
</p>
<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading"><input type="checkbox" role="button" id="toctogglecheckbox" class="toctogglecheckbox" style="display:none" /><div class="toctitle" lang="en" dir="ltr"><h2 id="mw-toc-heading">Contents</h2><span class="toctogglespan"><label class="toctogglelabel" for="toctogglecheckbox"></label></span></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
<ul>
<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
</ul>
</div>
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>One
</p>
<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Two
</p>
<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
<p>Two point one
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
</p>
EOF
],
'Disable section edit links' => [
[ 'enableSectionEditLinks' => false ], $text, <<<EOF
<p>Test document.
</p>
<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading"><input type="checkbox" role="button" id="toctogglecheckbox" class="toctogglecheckbox" style="display:none" /><div class="toctitle" lang="en" dir="ltr"><h2 id="mw-toc-heading">Contents</h2><span class="toctogglespan"><label class="toctogglelabel" for="toctogglecheckbox"></label></span></div>
<ul>
<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
<ul>
<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
</ul>
</div>
<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
<p>One
</p>
<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
<p>Two
</p>
<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
<p>Two point one
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
</p>
EOF
],
'Disable TOC, but wrap' => [
[ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText == Skin::wrapHTML == Skin::wrapHTML no longer has to perform any guessing of the ParserOutput language. Nor does it have to special wiki pages vs special pages in this regard. Yay, code removal. == ImagePage == On URLs like /wiki/File:Example.jpg, the main output handler is ImagePage::view. This calls the parent Article::view to handle most of its output. Article::view obtains the ParserOptions, and then fetches ParserOutput, and then adds `<div class=mw-parser-output>` and its metadata to OutputPage. Before this change, ImagePage::view was creating a wrapper based on "predicting" what language the ParserOutput will contain. It couldn't call the new OutputPage::getContentLanguage or some equivalent as Article::view wouldn't have populated that yet. This leaky abstraction is fixed by this change as now the `<div>` from ParserOutput no longer comes with a "please wrap it properly" contract that Article subclasses couldn't possibly implement correctly (it coudln't wrap it after the fact because Article::view writes to OutputPage directly). RECENT (T310445): A special case was recently added for file pages about translated SVGs. For those, we decide which language to use for the "fullMedia" thumb atop the page. This was recently changed as part of T310445 from a hardcoded $wgLanguageCode (site content lang) to new problematic Title::getPageViewLanguage, which tries to guestimate the page language of the rendered ParserOutput and then gets the preferred variant for the current user. The motivation for this was to support language variants but used Title::getPageViewLanguage as a kitchen sink to achieve that minor side-effect. The only part of this now-deprecated method that we actually need is LanguageConverter::getPreferredVariant(). Test plan: Covered by ImagePageTest. == Skin mainpage-title == RECENT (T331095, T298715): A special case was added to Skin::getTemplateData that powers the mainpage-title interface message feature. This is empty by default, but when created via MediaWiki:mainpage-title allows interface admins to replace the H1 with a custom and localised page heading. A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was applied here to support language variants. Replace with the same fix as for ImagePage. Revert back to Message::inContentLanguage() but refactor to inLanguage() via MediaWikiServices::getContentLanguage so that LanguageConverter::getPreferredVariant can be applied. == EditPage == This was doing similar "predicting" of the ParserOutput language to create an empty preview placeholder for use by preview.js. Now that ApiParse (via ParserOutput::getText) returns a usable element without any secret "you magically know the right class, lang, and dir" contract, this placeholder is no longer needed. Test Plan: * EditPage: Default preview 1. index.php?title=Main_Page&action=edit 2. Show preview 3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> * EditPage: JS preview 1. Preferences > Editing > Show preview without reload 2. index.php?title=Main_Page&action=edit 3. Show preview 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 5. Type something and 'Show preview' again 6. Assert old element gone, new text is shown, and new element attributes are the same as the above. == McrUndoAction == Same as EditPage basically, but without the JS preview use case. == DifferenceEngine == Test: 1. Open /w/index.php?title=Main_Page&diff=0 (this shows the latest diff, can do manually by viewing /wiki/Main_Page, click "View history", click "Compare selected revisions") 2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> 3. Open /w/index.php?title=Main_Page&diff=0&action=render 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> == Special:ExpandTemplates == Test: 1. /wiki/Special:ExpandTemplates 2. Write "Hello". 3. "OK" 4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr> Bug: T341244 Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1 Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105 Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
<div class="mw-content-ltr mw-parser-output" lang="en" dir="ltr"><p>Test document.
</p>
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>One
</p>
<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Two
</p>
<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
<p>Two point one
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
</p></div>
EOF
],
'Style deduplication' => [
[], $dedupText, <<<EOF
<p>This is a test document.</p>
<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1">
<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1">
<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate2">
<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1">
<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
<style>.Duplicate1 {}</style>
EOF
],
'Style deduplication disabled' => [
[ 'deduplicateStyles' => false ], $dedupText, $dedupText
],
];
// phpcs:enable
}
/**
* @covers \MediaWiki\Parser\ParserOutput::hasText
*/
public function testHasText() {
$po = new ParserOutput( '' );
$this->assertTrue( $po->hasText() );
$po = new ParserOutput( null );
$this->assertFalse( $po->hasText() );
$po = new ParserOutput();
$this->assertFalse( $po->hasText() );
$po = new ParserOutput( '' );
$this->assertTrue( $po->hasText() );
$po = new ParserOutput( null );
$po->setText( '' );
$this->assertTrue( $po->hasText() );
$po = new ParserOutput( 'foo' );
$po->setText( null );
$this->assertFalse( $po->hasText() );
}
/**
* This test aims at being replaced by its version in DefaultOutputTransformTest when ParserOutput::getText
* gets deprecated.
* @covers \MediaWiki\Parser\ParserOutput::getText
*/
public function testGetText_failsIfNoText() {
$po = new ParserOutput( null );
$this->expectException( LogicException::class );
$po->getText();
}
public static function provideGetText_absoluteURLs() {
yield 'empty' => [
'text' => '',
'expectedText' => '',
];
yield 'no-links' => [
'text' => '<p>test</p>',
'expectedText' => '<p>test</p>',
];
yield 'simple link' => [
'text' => '<a href="/wiki/Test">test</a>',
'expectedText' => '<a href="//TEST_SERVER/wiki/Test">test</a>',
];
yield 'already absolute, relative' => [
'text' => '<a href="//TEST_SERVER/wiki/Test">test</a>',
'expectedText' => '<a href="//TEST_SERVER/wiki/Test">test</a>',
];
yield 'already absolute, https' => [
'text' => '<a href="https://TEST_SERVER/wiki/Test">test</a>',
'expectedText' => '<a href="https://TEST_SERVER/wiki/Test">test</a>',
];
yield 'external' => [
'text' => '<a href="https://en.wikipedia.org/wiki/Test">test</a>',
'expectedText' => '<a href="https://en.wikipedia.org/wiki/Test">test</a>',
];
}
/**
* This test aims at being replaced by its version in DefaultOutputTransformTest when ParserOutput::getText
* gets deprecated.
* @dataProvider provideGetText_absoluteURLs
*/
public function testGetText_absoluteURLs( string $text, string $expectedText ) {
$this->overrideConfigValue( MainConfigNames::Server, '//TEST_SERVER' );
$parserOutput = new ParserOutput( $text );
$this->assertSame( $expectedText, $parserOutput->getText( [ 'absoluteURLs' => true ] ) );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::getRawText
*/
public function testGetRawText_failsIfNoText() {
$po = new ParserOutput( null );
$this->expectException( LogicException::class );
$po->getRawText();
}
public static function provideMergeHtmlMetaDataFrom() {
// title text ------------
$a = new ParserOutput();
$a->setTitleText( 'X' );
$b = new ParserOutput();
yield 'only left title text' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
$a = new ParserOutput();
$b = new ParserOutput();
$b->setTitleText( 'Y' );
yield 'only right title text' => [ $a, $b, [ 'getTitleText' => 'Y' ] ];
$a = new ParserOutput();
$a->setTitleText( 'X' );
$b = new ParserOutput();
$b->setTitleText( 'Y' );
yield 'left title text wins' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
// index policy ------------
$a = new ParserOutput();
$a->setIndexPolicy( 'index' );
$b = new ParserOutput();
yield 'only left index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
$a = new ParserOutput();
$b = new ParserOutput();
$b->setIndexPolicy( 'index' );
yield 'only right index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
$a = new ParserOutput();
$a->setIndexPolicy( 'noindex' );
$b = new ParserOutput();
$b->setIndexPolicy( 'index' );
yield 'left noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
$a = new ParserOutput();
$a->setIndexPolicy( 'index' );
$b = new ParserOutput();
$b->setIndexPolicy( 'noindex' );
yield 'right noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
$crhCyrl = new Bcp47CodeValue( 'crh-cyrl' );
$a = new ParserOutput();
$a->setLanguage( $crhCyrl );
$b = new ParserOutput();
yield 'only left language' => [ $a, $b, [ 'getLanguage' => $crhCyrl ] ];
$a = new ParserOutput();
$b = new ParserOutput();
$b->setLanguage( $crhCyrl );
yield 'only right language' => [ $a, $b, [ 'getLanguage' => $crhCyrl ] ];
// head items and friends ------------
$a = new ParserOutput();
$a->addHeadItem( '<foo1>' );
$a->addHeadItem( '<bar1>', 'bar' );
$a->addModules( [ 'test-module-a' ] );
$a->addModuleStyles( [ 'test-module-styles-a' ] );
$a->setJsConfigVar( 'test-config-var-a', 'a' );
$a->appendJsConfigVar( 'test-config-var-c', 'abc' );
$a->appendJsConfigVar( 'test-config-var-c', 'def' );
$a->addExtraCSPStyleSrc( 'css.com' );
$a->addExtraCSPStyleSrc( 'css2.com' );
$a->addExtraCSPScriptSrc( 'js.com' );
$a->addExtraCSPDefaultSrc( 'img.com' );
$b = new ParserOutput();
$b->setIndexPolicy( 'noindex' );
$b->addHeadItem( '<foo2>' );
$b->addHeadItem( '<bar2>', 'bar' );
$b->addModules( [ 'test-module-b' ] );
$b->addModuleStyles( [ 'test-module-styles-b' ] );
$b->setJsConfigVar( 'test-config-var-b', 'b' );
$b->setJsConfigVar( 'test-config-var-a', 'X' );
$a->appendJsConfigVar( 'test-config-var-c', 'xyz' );
$a->appendJsConfigVar( 'test-config-var-c', 'def' );
$b->addExtraCSPStyleSrc( 'https://css.ca' );
$b->addExtraCSPScriptSrc( 'jscript.com' );
$b->addExtraCSPScriptSrc( 'vbscript.com' );
$b->addExtraCSPDefaultSrc( 'img.com/foo.jpg' );
// Note that overwriting test-config-var-a during the merge
// (as this test case does) is deprecated and will eventually
// throw an exception. However, at the moment it is still worth
// testing this case to ensure backward compatibility. (T300307)
yield 'head items and friends' => [ $a, $b, [
'getHeadItems' => [
'<foo1>',
'<foo2>',
'bar' => '<bar2>', // overwritten
],
'getModules' => [
'test-module-a',
'test-module-b',
],
'getModuleStyles' => [
'test-module-styles-a',
'test-module-styles-b',
],
'getJsConfigVars' => [
'test-config-var-a' => 'X', // overwritten
'test-config-var-b' => 'b',
'test-config-var-c' => [ // merged safely
'abc' => true, 'def' => true, 'xyz' => true,
],
],
'getExtraCSPStyleSrcs' => [
'css.com',
'css2.com',
'https://css.ca'
],
'getExtraCSPScriptSrcs' => [
'js.com',
'jscript.com',
'vbscript.com'
],
'getExtraCSPDefaultSrcs' => [
'img.com',
'img.com/foo.jpg'
]
] ];
// TOC ------------
$a = new ParserOutput( '' );
$a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
$b = new ParserOutput( '' );
$b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
yield 'concat TOC' => [ $a, $b, [
'getTOCHTML' => '',
'getSections' => [
Generate/set/get TOCData/SectionMetadata objects instead of arrays * ParserOutput::setSections()/::getSections() are expected to be deprecated. Uses in extensions and skins will need to be migrated in follow up patches once the new interface has stabilized. * In the skins code, the metadata is converted back to an array. Downstream skin TOC consumers will need to be migrated as well before we can remove the toLegacy() conversion. * Fixed SerializationTestTrait's validation method - Not sure if this is overkill but should handle all future complex objects we might stuff into the ParserCache. * This patch emits a backward-compatible Sections property in order to avoid changing the parser cache serialization format. T327439 has been filed to eventually use the JsonCodec support for object serialization, but for this initial patch it makes sense to avoid the need for a concurrent ParserCache format migration by using a backward-compatible serialization. * TOCData is nullable because the intent is that ParserOutput::setTOCData() is MW_MERGE_STRATEGY_WRITE_ONCE; that is, only the top-level fragment composing a page will set the TOCData. This will be enforced in the future via wfDeprecated() (T327429), but again our first patch is as backward-compatible as possible. Bug: T296025 Depends-On: I1b267d23cf49d147c5379b914531303744481b68 Co-Authored-By: C. Scott Ananian <cananian@wikimedia.org> Co-Authored-By: Subramanya Sastry <ssastry@wikimedia.org> Change-Id: I8329864535f0b1dd5f9163868a08d6cb1ffcb78f
2022-09-01 23:07:29 +00:00
SectionMetadata::fromLegacy( [ 'fromtitle' => 'A1' ] )->toLegacy(),
SectionMetadata::fromLegacy( [ 'fromtitle' => 'A2' ] )->toLegacy(),
SectionMetadata::fromLegacy( [ 'fromtitle' => 'B1' ] )->toLegacy(),
SectionMetadata::fromLegacy( [ 'fromtitle' => 'B2' ] )->toLegacy()
],
] ];
// Skin Control ------------
$a = new ParserOutput();
$a->setNewSection( true );
$a->setHideNewSection( true );
$a->setNoGallery( true );
$a->addWrapperDivClass( 'foo' );
$a->setIndicator( 'foo', 'Foo!' );
$a->setIndicator( 'bar', 'Bar!' );
$a->setExtensionData( 'foo', 'Foo!' );
$a->setExtensionData( 'bar', 'Bar!' );
$a->appendExtensionData( 'bat', 'abc' );
$b = new ParserOutput();
$b->setNoGallery( true );
$b->setEnableOOUI( true );
$b->setPreventClickjacking( true );
$a->addWrapperDivClass( 'bar' );
$b->setIndicator( 'zoo', 'Zoo!' );
$b->setIndicator( 'bar', 'Barrr!' );
$b->setExtensionData( 'zoo', 'Zoo!' );
$b->setExtensionData( 'bar', 'Barrr!' );
$b->appendExtensionData( 'bat', 'xyz' );
// Note that overwriting extension data during the merge
// (as this test case does for 'bar') is deprecated and will eventually
// throw an exception. However, at the moment it is still worth
// testing this case to ensure backward compatibility. (T300981)
yield 'skin control flags' => [ $a, $b, [
'getNewSection' => true,
'getHideNewSection' => true,
'getNoGallery' => true,
'getEnableOOUI' => true,
'getPreventClickjacking' => true,
'getIndicators' => [
'foo' => 'Foo!',
'bar' => 'Barrr!', // overwritten
'zoo' => 'Zoo!',
],
'getWrapperDivClass' => 'foo bar',
'$mExtensionData' => [
'foo' => 'Foo!',
'bar' => 'Barrr!', // overwritten
'zoo' => 'Zoo!',
// internal strategy key is exposed here because we're looking
// at the raw property value, not using getExtensionData()
'bat' => [ 'abc' => true, 'xyz' => true, '_mw-strategy' => 'union' ],
],
] ];
}
/**
* @dataProvider provideMergeHtmlMetaDataFrom
* @covers \MediaWiki\Parser\ParserOutput::mergeHtmlMetaDataFrom
*
* @param ParserOutput $a
* @param ParserOutput $b
* @param array $expected
*/
public function testMergeHtmlMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
$a->mergeHtmlMetaDataFrom( $b );
$this->assertFieldValues( $a, $expected );
// test twice, to make sure the operation is idempotent (except for the TOC, see below)
$a->mergeHtmlMetaDataFrom( $b );
// XXX: TOC joining should get smarter. Can we make it idempotent as well?
unset( $expected['getTOCHTML'] );
unset( $expected['getSections'] );
$this->assertFieldValues( $a, $expected );
}
private function assertFieldValues( ParserOutput $po, $expected ) {
$po = TestingAccessWrapper::newFromObject( $po );
foreach ( $expected as $method => $value ) {
$canonicalize = false;
if ( $method[0] === '$' ) {
$field = substr( $method, 1 );
$actual = $po->__get( $field );
} else {
$actual = $po->__call( $method, [] );
}
if ( $method === 'getJsConfigVars' ) {
$canonicalize = true;
}
if ( $canonicalize ) {
// order of entries isn't significant
$this->assertEqualsCanonicalizing( $value, $actual, $method );
} else {
$this->assertEquals( $value, $actual, $method );
}
}
}
/**
* @covers \MediaWiki\Parser\ParserOutput::addLink
* @covers \MediaWiki\Parser\ParserOutput::getLinks
*/
public function testAddLink() {
$a = new ParserOutput();
$a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
$a->addLink( new TitleValue( NS_TALK, 'Kittens' ), 16 );
$a->addLink( new TitleValue( NS_MAIN, 'Goats_786827346' ) );
$expected = [
NS_MAIN => [ 'Kittens' => 6, 'Goats_786827346' => 0 ],
NS_TALK => [ 'Kittens' => 16 ]
];
$this->assertSame( $expected, $a->getLinks() );
}
public static function provideMergeTrackingMetaDataFrom() {
// links ------------
$a = new ParserOutput();
$a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
$a->addLink( new TitleValue( NS_TALK, 'Kittens' ), 16 );
$a->addLink( new TitleValue( NS_MAIN, 'Goats' ), 7 );
$a->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Goats' ), 107, 1107 );
$a->addLanguageLink( new TitleValue( NS_MAIN, 'de' ) );
$a->addLanguageLink( new TitleValue( NS_MAIN, 'ru' ) );
$a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens DE', '', 'de' ) );
$a->addInterwikiLink( new TitleValue( NS_MAIN, 'Kittens RU', '', 'ru' ) );
$a->addExternalLink( 'https://kittens.wikimedia.test' );
$a->addExternalLink( 'https://goats.wikimedia.test' );
$a->addCategory( 'Foo', 'X' );
$a->addImage( new TitleValue( NS_FILE, 'Billy.jpg' ), '20180101000013', 'DEAD' );
$b = new ParserOutput();
$b->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
$b->addLink( Title::makeTitle( NS_TALK, 'Goats' ), 17 );
$b->addLink( new TitleValue( NS_MAIN, 'Dragons' ), 8 );
$b->addLink( new TitleValue( NS_FILE, 'Dragons.jpg' ), 28 );
$b->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Dragons' ), 108, 1108 );
$a->addTemplate( new TitleValue( NS_MAIN, 'Dragons' ), 118, 1118 );
$b->addLanguageLink( new TitleValue( NS_MAIN, 'fr' ) );
$b->addLanguageLink( new TitleValue( NS_MAIN, 'ru' ) );
$b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens FR', '', 'fr' ) );
$b->addInterwikiLink( new TitleValue( NS_MAIN, 'Dragons RU', '', 'ru' ) );
$b->addExternalLink( 'https://dragons.wikimedia.test' );
$b->addExternalLink( 'https://goats.wikimedia.test' );
$b->addCategory( 'Bar', 'Y' );
$b->addImage( new TitleValue( NS_FILE, 'Puff.jpg' ), '20180101000017', 'BEEF' );
yield 'all kinds of links' => [ $a, $b, [
'getLinks' => [
NS_MAIN => [
'Kittens' => 6,
'Goats' => 7,
'Dragons' => 8,
],
NS_TALK => [
'Kittens' => 16,
'Goats' => 17,
],
NS_FILE => [
'Dragons.jpg' => 28,
],
],
'getTemplates' => [
NS_MAIN => [
'Dragons' => 118,
],
NS_TEMPLATE => [
'Dragons' => 108,
'Goats' => 107,
],
],
'getTemplateIds' => [
NS_MAIN => [
'Dragons' => 1118,
],
NS_TEMPLATE => [
'Dragons' => 1108,
'Goats' => 1107,
],
],
'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
'getInterwikiLinks' => [
'de' => [ 'Kittens_DE' => 1 ],
'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
'fr' => [ 'Kittens_FR' => 1 ],
],
'getCategoryMap' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
'getFileSearchOptions' => [
'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
],
'getExternalLinks' => [
'https://dragons.wikimedia.test' => 1,
'https://kittens.wikimedia.test' => 1,
'https://goats.wikimedia.test' => 1,
]
] ];
// properties ------------
$a = new ParserOutput();
$a->setPageProperty( 'foo', 'Foo!' );
$a->setPageProperty( 'bar', 'Bar!' );
$a->setExtensionData( 'foo', 'Foo!' );
$a->setExtensionData( 'bar', 'Bar!' );
$a->appendExtensionData( 'bat', 'abc' );
$b = new ParserOutput();
$b->setPageProperty( 'zoo', 'Zoo!' );
$b->setPageProperty( 'bar', 'Barrr!' );
$b->setExtensionData( 'zoo', 'Zoo!' );
$b->setExtensionData( 'bar', 'Barrr!' );
$b->appendExtensionData( 'bat', 'xyz' );
// Note that overwriting extension data during the merge
// (as this test case does for 'bar') is deprecated and will eventually
// throw an exception. However, at the moment it is still worth
// testing this case to ensure backward compatibility. (T300981)
yield 'properties' => [ $a, $b, [
'getPageProperties' => [
'foo' => 'Foo!',
'bar' => 'Barrr!', // overwritten
'zoo' => 'Zoo!',
],
'$mExtensionData' => [
'foo' => 'Foo!',
'bar' => 'Barrr!', // overwritten
'zoo' => 'Zoo!',
// internal strategy key is exposed here because we're looking
// at the raw property value, not using getExtensionData()
'bat' => [ 'abc' => true, 'xyz' => true, '_mw-strategy' => 'union' ],
],
] ];
}
/**
* @dataProvider provideMergeTrackingMetaDataFrom
* @covers \MediaWiki\Parser\ParserOutput::mergeTrackingMetaDataFrom
*
* @param ParserOutput $a
* @param ParserOutput $b
* @param array $expected
*/
public function testMergeTrackingMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
$a->mergeTrackingMetaDataFrom( $b );
$this->assertFieldValues( $a, $expected );
// test twice, to make sure the operation is idempotent
$a->mergeTrackingMetaDataFrom( $b );
$this->assertFieldValues( $a, $expected );
}
public function provideMergeInternalMetaDataFrom() {
// flags & co
$a = new ParserOutput();
$a->addWarningMsg( 'duplicate-args-warning', 'A', 'B', 'C' );
$a->addWarningMsg( 'template-loop-warning', 'D' );
$a->setOutputFlag( 'foo' );
$a->setOutputFlag( 'bar' );
$a->recordOption( 'Foo' );
$a->recordOption( 'Bar' );
$b = new ParserOutput();
$b->addWarningMsg( 'template-equals-warning' );
$b->addWarningMsg( 'template-loop-warning', 'D' );
$b->setOutputFlag( 'zoo' );
$b->setOutputFlag( 'bar' );
$b->recordOption( 'Zoo' );
$b->recordOption( 'Bar' );
yield 'flags' => [ $a, $b, [
'getWarnings' => [
wfMessage( 'duplicate-args-warning', 'A', 'B', 'C' )->text(),
wfMessage( 'template-loop-warning', 'D' )->text(),
wfMessage( 'template-equals-warning' )->text(),
],
'$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
] ];
// timestamp ------------
$a = new ParserOutput();
$a->setTimestamp( '20180101000011' );
$b = new ParserOutput();
yield 'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
$a = new ParserOutput();
$b = new ParserOutput();
$b->setTimestamp( '20180101000011' );
yield 'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
$a = new ParserOutput();
$a->setTimestamp( '20180101000011' );
$b = new ParserOutput();
$b->setTimestamp( '20180101000001' );
yield 'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
$a = new ParserOutput();
$a->setTimestamp( '20180101000001' );
$b = new ParserOutput();
$b->setTimestamp( '20180101000011' );
yield 'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
// speculative rev id ------------
$a = new ParserOutput();
$a->setSpeculativeRevIdUsed( 9 );
$b = new ParserOutput();
yield 'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
$a = new ParserOutput();
$b = new ParserOutput();
$b->setSpeculativeRevIdUsed( 9 );
yield 'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
$a = new ParserOutput();
$a->setSpeculativeRevIdUsed( 9 );
$b = new ParserOutput();
$b->setSpeculativeRevIdUsed( 9 );
yield 'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
// limit report (recursive max) ------------
$a = new ParserOutput();
$a->setLimitReportData( 'naive1', 7 );
$a->setLimitReportData( 'naive2', 27 );
$a->setLimitReportData( 'limitreport-simple1', 7 );
$a->setLimitReportData( 'limitreport-simple2', 27 );
$a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
$a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
$a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
$a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
$a->setLimitReportData( 'limitreport-only-a', 13 );
$b = new ParserOutput();
$b->setLimitReportData( 'naive1', 17 );
$b->setLimitReportData( 'naive2', 17 );
$b->setLimitReportData( 'limitreport-simple1', 17 );
$b->setLimitReportData( 'limitreport-simple2', 17 );
$b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
$b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
$b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
$b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
$b->setLimitReportData( 'limitreport-only-b', 23 );
// first write wins
yield 'limit report' => [ $a, $b, [
'getLimitReportData' => [
'naive1' => 7,
'naive2' => 27,
'limitreport-simple1' => 7,
'limitreport-simple2' => 27,
'limitreport-pair1' => [ 7, 9 ],
'limitreport-pair2' => [ 27, 29 ],
'limitreport-more1' => [ 7, 9, 1 ],
'limitreport-more2' => [ 27, 29, 21 ],
'limitreport-only-a' => 13,
],
'getLimitReportJSData' => [
'naive1' => 7,
'naive2' => 27,
'limitreport' => [
'simple1' => 7,
'simple2' => 27,
'pair1' => [ 'value' => 7, 'limit' => 9 ],
'pair2' => [ 'value' => 27, 'limit' => 29 ],
'more1' => [ 7, 9, 1 ],
'more2' => [ 27, 29, 21 ],
'only-a' => 13,
],
],
] ];
}
/**
* @dataProvider provideMergeInternalMetaDataFrom
* @covers \MediaWiki\Parser\ParserOutput::mergeInternalMetaDataFrom
*
* @param ParserOutput $a
* @param ParserOutput $b
* @param array $expected
*/
public function testMergeInternalMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
$a->mergeInternalMetaDataFrom( $b );
$this->assertFieldValues( $a, $expected );
// test twice, to make sure the operation is idempotent
$a->mergeInternalMetaDataFrom( $b );
$this->assertFieldValues( $a, $expected );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::mergeInternalMetaDataFrom
* @covers \MediaWiki\Parser\ParserOutput::getTimes
* @covers \MediaWiki\Parser\ParserOutput::resetParseStartTime
*/
public function testMergeInternalMetaDataFrom_parseStartTime() {
/** @var object $a */
$a = new ParserOutput();
$a = TestingAccessWrapper::newFromObject( $a );
$a->resetParseStartTime();
$aClocks = $a->mParseStartTime;
$b = new ParserOutput();
$a->mergeInternalMetaDataFrom( $b );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$this->assertSame( $aClocks[$clock], $timestamp, $clock );
}
// try again, with times in $b also set, and later than $a's
usleep( 1234 );
/** @var object $b */
$b = new ParserOutput();
$b = TestingAccessWrapper::newFromObject( $b );
$b->resetParseStartTime();
$bClocks = $b->mParseStartTime;
$a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$this->assertSame( $aClocks[$clock], $timestamp, $clock );
$this->assertLessThanOrEqual( $bClocks[$clock], $timestamp, $clock );
}
// try again, with $a's times being later
usleep( 1234 );
$a->resetParseStartTime();
$aClocks = $a->mParseStartTime;
$a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$this->assertSame( $bClocks[$clock], $timestamp, $clock );
$this->assertLessThanOrEqual( $aClocks[$clock], $timestamp, $clock );
}
// try again, with no times in $a set
$a = new ParserOutput();
$a = TestingAccessWrapper::newFromObject( $a );
$a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$this->assertSame( $bClocks[$clock], $timestamp, $clock );
}
}
/**
* @covers \MediaWiki\Parser\ParserOutput::mergeInternalMetaDataFrom
* @covers \MediaWiki\Parser\ParserOutput::getTimes
* @covers \MediaWiki\Parser\ParserOutput::resetParseStartTime
* @covers \MediaWiki\Parser\ParserOutput::recordTimeProfile
* @covers \MediaWiki\Parser\ParserOutput::getTimeProfile
*/
public function testMergeInternalMetaDataFrom_timeProfile() {
/** @var object $a */
$a = new ParserOutput();
$a = TestingAccessWrapper::newFromObject( $a );
$a->resetParseStartTime();
usleep( 1234 );
$a->recordTimeProfile();
$aClocks = $a->mTimeProfile;
// make sure a second call to recordTimeProfile has no effect
usleep( 1234 );
$a->recordTimeProfile();
foreach ( $aClocks as $clock => $duration ) {
$this->assertNotNull( $duration );
$this->assertGreaterThan( 0, $duration );
$this->assertSame( $aClocks[$clock], $a->getTimeProfile( $clock ) );
}
$b = new ParserOutput();
$a->mergeInternalMetaDataFrom( $b );
$mergedClocks = $a->mTimeProfile;
foreach ( $mergedClocks as $clock => $duration ) {
$this->assertSame( $aClocks[$clock], $duration, $clock );
}
// try again, with times in $b also set, and later than $a's
$b->resetParseStartTime();
usleep( 1234 );
$b->recordTimeProfile();
$b = TestingAccessWrapper::newFromObject( $b );
$bClocks = $b->mTimeProfile;
$a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mTimeProfile;
foreach ( $mergedClocks as $clock => $duration ) {
$this->assertGreaterThanOrEqual( $aClocks[$clock], $duration, $clock );
$this->assertGreaterThanOrEqual( $bClocks[$clock], $duration, $clock );
}
}
/**
* @covers \MediaWiki\Parser\ParserOutput::getCacheTime
* @covers \MediaWiki\Parser\ParserOutput::setCacheTime
*/
public function testGetCacheTime() {
$clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
MWTimestamp::setFakeTime( static function () use ( &$clock ) {
return $clock++;
} );
$po = new ParserOutput();
$time = $po->getCacheTime();
// Use current (fake) time per default. Ignore the last digit.
// Subsequent calls must yield the exact same timestamp as the first.
$this->assertStringStartsWith( '2010010100000', $time );
$this->assertSame( $time, $po->getCacheTime() );
// After setting, the getter must return the time that was set.
$time = '20110606112233';
$po->setCacheTime( $time );
$this->assertSame( $time, $po->getCacheTime() );
}
/**
* @covers \MediaWiki\Parser\ParserOutput::addExtraCSPScriptSrc
* @covers \MediaWiki\Parser\ParserOutput::addExtraCSPDefaultSrc
* @covers \MediaWiki\Parser\ParserOutput::addExtraCSPStyleSrc
* @covers \MediaWiki\Parser\ParserOutput::getExtraCSPScriptSrcs
* @covers \MediaWiki\Parser\ParserOutput::getExtraCSPDefaultSrcs
* @covers \MediaWiki\Parser\ParserOutput::getExtraCSPStyleSrcs
*/
public function testCSPSources() {
$po = new ParserOutput;
$this->assertEquals( [], $po->getExtraCSPScriptSrcs(), 'empty Script' );
$this->assertEquals( [], $po->getExtraCSPStyleSrcs(), 'empty Style' );
$this->assertEquals( [], $po->getExtraCSPDefaultSrcs(), 'empty Default' );
$po->addExtraCSPScriptSrc( 'foo.com' );
$po->addExtraCSPScriptSrc( 'bar.com' );
$po->addExtraCSPDefaultSrc( 'baz.com' );
$po->addExtraCSPStyleSrc( 'fred.com' );
$po->addExtraCSPStyleSrc( 'xyzzy.com' );
$this->assertEquals( [ 'foo.com', 'bar.com' ], $po->getExtraCSPScriptSrcs(), 'Script' );
$this->assertEquals( [ 'baz.com' ], $po->getExtraCSPDefaultSrcs(), 'Default' );
$this->assertEquals( [ 'fred.com', 'xyzzy.com' ], $po->getExtraCSPStyleSrcs(), 'Style' );
}
public function testOutputStrings() {
$po = new ParserOutput;
$this->assertEquals( [], $po->getOutputStrings( ParserOutputStringSets::MODULE ) );
$this->assertEquals( [], $po->getOutputStrings( ParserOutputStringSets::MODULE_STYLE ) );
$this->assertEquals( [], $po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC ) );
$this->assertEquals( [], $po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_STYLE_SRC ) );
$this->assertEquals( [], $po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC ) );
$this->assertEquals( [], $po->getModules() );
$this->assertEquals( [], $po->getModuleStyles() );
$this->assertEquals( [], $po->getExtraCSPScriptSrcs() );
$this->assertEquals( [], $po->getExtraCSPStyleSrcs() );
$this->assertEquals( [], $po->getExtraCSPDefaultSrcs() );
$po->appendOutputStrings( ParserOutputStringSets::MODULE, [ 'a' ] );
$po->appendOutputStrings( ParserOutputStringSets::MODULE_STYLE, [ 'b' ] );
$po->appendOutputStrings( ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC, [ 'foo.com', 'bar.com' ] );
$po->appendOutputStrings( ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC, [ 'baz.com' ] );
$po->appendOutputStrings( ParserOutputStringSets::EXTRA_CSP_STYLE_SRC, [ 'fred.com' ] );
$po->appendOutputStrings( ParserOutputStringSets::EXTRA_CSP_STYLE_SRC, [ 'xyzzy.com' ] );
$this->assertEquals( [ 'a' ], $po->getOutputStrings( ParserOutputStringSets::MODULE ) );
$this->assertEquals( [ 'b' ], $po->getOutputStrings( ParserOutputStringSets::MODULE_STYLE ) );
$this->assertEquals( [ 'foo.com', 'bar.com' ],
$po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC ) );
$this->assertEquals( [ 'baz.com' ],
$po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC ) );
$this->assertEquals( [ 'fred.com', 'xyzzy.com' ],
$po->getOutputStrings( ParserOutputStringSets::EXTRA_CSP_STYLE_SRC ) );
$this->assertEquals( [ 'a' ], $po->getModules() );
$this->assertEquals( [ 'b' ], $po->getModuleStyles() );
$this->assertEquals( [ 'foo.com', 'bar.com' ], $po->getExtraCSPScriptSrcs() );
$this->assertEquals( [ 'baz.com' ], $po->getExtraCSPDefaultSrcs() );
$this->assertEquals( [ 'fred.com', 'xyzzy.com' ], $po->getExtraCSPStyleSrcs() );
}
}